package org.tmatesoft.svn.core.internal.wc2.ng;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLock;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNDate;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.ISVNWCNodeHandler;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCNodeReposInfo;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.SVNWCSchedule;
import org.tmatesoft.svn.core.internal.wc17.SVNWCContext.ScheduleInternalInfo;
import org.tmatesoft.svn.core.internal.wc17.SVNWCUtils;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbKind;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.SVNWCDbStatus;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbBaseInfo;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbBaseInfo.BaseInfoField;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbInfo;
import org.tmatesoft.svn.core.internal.wc17.db.ISVNWCDb.WCDbInfo.InfoField;
import org.tmatesoft.svn.core.internal.wc17.db.SVNWCDb;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.AdditionInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.DeletionInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.MovedInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.NodeOriginInfo;
import org.tmatesoft.svn.core.internal.wc17.db.StructureFields.PristineInfo;
import org.tmatesoft.svn.core.internal.wc17.db.SvnWcDbShared;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.wc.SVNConflictDescription;
import org.tmatesoft.svn.core.wc.SVNTreeConflictDescription;
import org.tmatesoft.svn.core.wc2.SvnChecksum;
import org.tmatesoft.svn.core.wc2.SvnGetInfo;
import org.tmatesoft.svn.core.wc2.SvnInfo;
import org.tmatesoft.svn.core.wc2.SvnSchedule;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.tmatesoft.svn.core.wc2.SvnWorkingCopyInfo;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnNgGetInfo extends SvnNgOperationRunner<SvnInfo, SvnGetInfo> implements ISVNWCNodeHandler {
    
    private boolean hasRootTreeConflict;
    private boolean isFirstInfo;
    private Map<File, SVNTreeConflictDescription> treeConflicts;
    
    public void reset(SvnWcGeneration wcGeneration) {
        super.reset(wcGeneration);
        
        hasRootTreeConflict = false;
        isFirstInfo = false;
        treeConflicts = null;
    }
    
    @Override
    protected SvnInfo run(SVNWCContext context) throws SVNException {
        hasRootTreeConflict = false;
        isFirstInfo = true;
        getTreeConflicts().clear();
        
        if (getOperation().isFetchActualOnly()) {
            SVNTreeConflictDescription treeConflict = context.getDb().opReadTreeConflict(getFirstTarget());
            if (treeConflict != null) {
                hasRootTreeConflict = true;
                getTreeConflicts().put(getFirstTarget(), treeConflict);
            }
        }
        
        try {
            context.nodeWalkChildren(getFirstTarget(), this, getOperation().isFetchExcluded(), getOperation().getDepth(), getOperation().getApplicableChangelists());
        } catch (SVNException e) {
            if (!(e.getErrorMessage().getErrorCode() == SVNErrorCode.WC_PATH_NOT_FOUND && hasRootTreeConflict)) {
                throw e;
            }
        }
        SVNWCNodeReposInfo reposInfo = null; 
        if (!getTreeConflicts().isEmpty()) {
            reposInfo = getWcContext().getNodeReposInfo(getFirstTarget());
            if (reposInfo.reposRootUrl == null) {
                reposInfo = null;
            }
        }
        for (File target: getTreeConflicts().keySet()) {
            SVNTreeConflictDescription treeConflict = getTreeConflicts().get(target);
            if (isDepthIncludes(getFirstTarget(), getOperation().getDepth(), target, treeConflict.getNodeKind())) {
                SvnInfo unversionedInfo = buildUnversionedInfo(target);
                Collection<SVNConflictDescription> conflicts = new ArrayList<SVNConflictDescription>(1);
                conflicts.add(treeConflict);
                unversionedInfo.getWcInfo().setConflicts(conflicts);
                if (reposInfo != null) {
                    unversionedInfo.setRepositoryRootURL(reposInfo.reposRootUrl);
                    unversionedInfo.setRepositoryUuid(reposInfo.reposUuid);
                }
                getOperation().receive(SvnTarget.fromFile(target), unversionedInfo);
            }
        }
        return getOperation().first();
    }

    public void nodeFound(File localAbspath, SVNWCDbKind kind) throws SVNException {
        SvnInfo info = buildInfo(localAbspath, kind);
        if (info == null && isFirstInfo) {
            SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.WC_PATH_NOT_FOUND, "The node ''{0}'' was not found", localAbspath), 
                    SVNLogType.WC);
        }
        isFirstInfo = false;
        if (info != null) {
            getOperation().receive(SvnTarget.fromFile(localAbspath), info);
        }
        if (getOperation().isFetchActualOnly() && kind == SVNWCDbKind.Dir) {
            Map<String,SVNTreeConflictDescription> treeConflicts = getWcContext().getDb().opReadAllTreeConflicts(localAbspath);
            for (String name : treeConflicts.keySet()) {
                getTreeConflicts().put(SVNFileUtil.createFilePath(localAbspath, name), treeConflicts.get(name));
            }
        }
        getTreeConflicts().remove(localAbspath);
    }
    
    private SvnInfo buildUnversionedInfo(File localAbspath) throws SVNException {
        SvnWorkingCopyInfo wcInfo = new SvnWorkingCopyInfo();
        wcInfo.setPath(localAbspath);
        SvnInfo info = new SvnInfo();
        info.setWcInfo(wcInfo);
        info.setRevision(SVNWCContext.INVALID_REVNUM);
        info.setLastChangedRevision(SVNWCContext.INVALID_REVNUM);
        info.setSize(ISVNWCDb.INVALID_FILESIZE);
        info.setLastChangedDate(SVNDate.NULL);
        info.setKind(SVNNodeKind.NONE);
        
        wcInfo.setDepth(SVNDepth.UNKNOWN);
        wcInfo.setRecordedSize(ISVNWCDb.INVALID_FILESIZE);
        wcInfo.setCopyFromRevision(SVNWCContext.INVALID_REVNUM);
        wcInfo.setSchedule(SvnSchedule.NORMAL);
        
        return info;
    }
    
    private SvnInfo buildInfo(File localAbspath, SVNWCDbKind kind) throws SVNException {
        SvnInfo info = new SvnInfo();
        SvnWorkingCopyInfo wcInfo = new SvnWorkingCopyInfo();
        wcInfo.setPath(localAbspath);
        info.setWcInfo(wcInfo);
        info.setKind(kind.toNodeKind());
        
        wcInfo.setCopyFromRevision(SVNWCContext.INVALID_REVNUM);
        WCDbInfo readInfo = getWcContext().getDb().readInfo(localAbspath, 
                InfoField.status, InfoField.kind, InfoField.revision, InfoField.reposRelPath, InfoField.reposRootUrl,
                InfoField.reposUuid,
                InfoField.changedRev, InfoField.changedDate, InfoField.changedAuthor, 
                InfoField.depth, InfoField.checksum,
                InfoField.originalReposRelpath, InfoField.originalRootUrl, InfoField.originalUuid, InfoField.originalRevision,
                InfoField.lock, InfoField.translatedSize, InfoField.lastModTime, InfoField.changelist,
                InfoField.conflicted, InfoField.opRoot, InfoField.haveBase, InfoField.movedHere, InfoField.movedTo);
        
        info.setRevision(readInfo.revision);
        info.setRepositoryRootURL(readInfo.reposRootUrl);
        info.setRepositoryUuid(readInfo.reposUuid);
        info.setLastChangedDate(readInfo.changedDate);
        info.setLastChangedAuthor(readInfo.changedAuthor);
        info.setLastChangedRevision(readInfo.changedRev);
        
        wcInfo.setDepth(readInfo.depth);
        wcInfo.setChecksum(readInfo.checksum);
        wcInfo.setRecordedSize(readInfo.translatedSize);
        wcInfo.setRecordedTime(readInfo.lastModTime);
        wcInfo.setChangelist(readInfo.changelist);
        wcInfo.setMovedTo(readInfo.movedToAbsPath);
        if (readInfo.opRoot && readInfo.movedHere) {
            final Structure<MovedInfo> movedInfo = SvnWcDbShared.scanMoved((SVNWCDb) getWcContext().getDb(), localAbspath);
            wcInfo.setMovedFrom(movedInfo.<File>get(MovedInfo.movedFromAbsPath));
        }
        
        File reposRelPath = readInfo.reposRelPath;
        
        if (readInfo.originalRootUrl != null) {
            info.setRepositoryRootURL(readInfo.originalRootUrl);
            info.setRepositoryUuid(readInfo.originalUuid);
        }
        
        if (readInfo.status == SVNWCDbStatus.Added) {
            if (readInfo.originalReposRelpath != null) {
                info.setRevision(readInfo.originalRevision);
                reposRelPath = readInfo.originalReposRelpath;
                
                if (readInfo.opRoot) {
                    wcInfo.setCopyFromUrl(SVNWCUtils.join(info.getRepositoryRootUrl(), readInfo.originalReposRelpath));
                    wcInfo.setCopyFromRevision(readInfo.originalRevision);
                }
            } else if (readInfo.opRoot) {
                Structure<AdditionInfo> additionInfo = 
                        SvnWcDbShared.scanAddition((SVNWCDb) getWcContext().getDb(), localAbspath);
                info.setRepositoryRootURL(additionInfo.<SVNURL>get(AdditionInfo.reposRootUrl));
                info.setRepositoryUuid(additionInfo.<String>get(AdditionInfo.reposUuid));
                if (readInfo.haveBase) {
                    long baseRev = getWcContext().getDb().getBaseInfo(localAbspath, BaseInfoField.revision).revision;
                    info.setRevision(baseRev);
                }
                additionInfo.release();
            } else {
                Structure<NodeOriginInfo> nodeOrigin = getWcContext().getNodeOrigin(localAbspath, true);
                info.setRepositoryRootURL(nodeOrigin.<SVNURL>get(NodeOriginInfo.reposRootUrl));
                info.setRepositoryUuid(nodeOrigin.text(NodeOriginInfo.reposUuid));
                info.setRevision(nodeOrigin.lng(NodeOriginInfo.revision));
                nodeOrigin.release();
            }
            
            ScheduleInternalInfo scheduleInfo = getWcContext().getNodeScheduleInternal(localAbspath, true, false);
            wcInfo.setSchedule(toSchedule(scheduleInfo.schedule));
            info.setUrl(getWcContext().getNodeUrl(localAbspath));
            
        } else if (readInfo.status == SVNWCDbStatus.Deleted) {
            Structure<PristineInfo> pristineInfo = getWcContext().getDb().readPristineInfo(localAbspath);
            
            info.setLastChangedRevision(pristineInfo.lng(PristineInfo.changed_rev));
            info.setLastChangedDate(pristineInfo.<SVNDate>get(PristineInfo.changed_date));
            info.setLastChangedAuthor(pristineInfo.text(PristineInfo.changed_author));
            wcInfo.setDepth(pristineInfo.<SVNDepth>get(PristineInfo.depth));
            wcInfo.setChecksum(pristineInfo.<SvnChecksum>get(PristineInfo.checksum));
            
            pristineInfo.release();

            Structure<DeletionInfo> delInfo = SvnWcDbShared.scanDeletion((SVNWCDb) getWcContext().getDb(), localAbspath);
            File workDelAbsPath = delInfo.<File>get(DeletionInfo.workDelAbsPath);
            delInfo.release();
            if (workDelAbsPath != null) {
                File addedAbsPath = SVNFileUtil.getFileDir(workDelAbsPath);
                Structure<AdditionInfo> additionInfo = 
                        SvnWcDbShared.scanAddition((SVNWCDb) getWcContext().getDb(), addedAbsPath
                        );
                reposRelPath = additionInfo.<File>get(AdditionInfo.reposRelPath);
                info.setRepositoryRootURL(additionInfo.<SVNURL>get(AdditionInfo.reposRootUrl));
                info.setRepositoryUuid(additionInfo.<String>get(AdditionInfo.reposUuid));
                info.setRevision(additionInfo.lng(AdditionInfo.originalRevision));
                additionInfo.release();
                File p = SVNFileUtil.createFilePath(reposRelPath, SVNWCUtils.skipAncestor(addedAbsPath, localAbspath));
                
                info.setUrl(SVNWCUtils.join(info.getRepositoryRootUrl(), p));
            } else {
                WCDbBaseInfo baseInfo = getWcContext().getDb().getBaseInfo(localAbspath, BaseInfoField.revision, BaseInfoField.reposRelPath, BaseInfoField.reposRootUrl, BaseInfoField.reposUuid);
                reposRelPath = baseInfo.reposRelPath;
                info.setRevision(baseInfo.revision);
                info.setRepositoryRootURL(baseInfo.reposRootUrl);
                info.setRepositoryUuid(baseInfo.reposUuid);
                
                info.setUrl(SVNWCUtils.join(info.getRepositoryRootUrl(), reposRelPath));
            }
            wcInfo.setSchedule(SvnSchedule.DELETE);          
        } else if (readInfo.status == SVNWCDbStatus.NotPresent || readInfo.status == SVNWCDbStatus.ServerExcluded) {
            return null;
        } else {
            if (info.getRepositoryRootUrl() != null) {
                if (reposRelPath != null) {
                    info.setUrl(SVNWCUtils.join(info.getRepositoryRootUrl(), reposRelPath));
                } else {
                    info.setUrl(info.getRepositoryRootUrl());
                }
            }
            wcInfo.setSchedule(SvnSchedule.NORMAL);          
        }
        
        if (readInfo.status == SVNWCDbStatus.Excluded) {
            wcInfo.setDepth(SVNDepth.EXCLUDE);            
        }
        info.setSize(ISVNWCDb.INVALID_FILESIZE);
        wcInfo.setWcRoot(getWcContext().getDb().getWCRoot(localAbspath));
        
        if (readInfo.conflicted) {
            wcInfo.setConflicts(getWcContext().getDb().readConflicts(localAbspath));
        }
        if (readInfo.lock != null) {
            SVNLock lock = new SVNLock(null, readInfo.lock.token, readInfo.lock.owner, readInfo.lock.comment, readInfo.lock.date, null);
            info.setLock(lock);
        }
        return info;
    }

    private SvnSchedule toSchedule(SVNWCSchedule schedule) {
        if (schedule == SVNWCSchedule.add) {
            return SvnSchedule.ADD;
        } else if (schedule == SVNWCSchedule.delete) {
            return SvnSchedule.DELETE;
        } else if (schedule == SVNWCSchedule.normal) {
            return SvnSchedule.NORMAL;
        } else if (schedule == SVNWCSchedule.replace) {
            return SvnSchedule.REPLACE;
        }
        return null;
    }

    private boolean isDepthIncludes(File rootPath, SVNDepth depth, File childPath, SVNNodeKind childKind) {
        if (depth == SVNDepth.INFINITY) {
            return true;
        }
        File childParentPath = SVNFileUtil.getParentFile(childPath);
        if (depth == SVNDepth.IMMEDIATES) {
            return rootPath.equals(childParentPath);
        } else if (depth == SVNDepth.FILES) {
            return childKind == SVNNodeKind.FILE && rootPath.equals(childParentPath);
            
        }
        return rootPath.equals(childPath);
    }

    private Map<File, SVNTreeConflictDescription> getTreeConflicts() {
        if (treeConflicts == null) {
            treeConflicts = new HashMap<File, SVNTreeConflictDescription>();
        }
        return treeConflicts;
    }
}
