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

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Map;

import org.tmatesoft.svn.core.SVNAnnotationGenerator;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNProperty;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslatorInputStream;
import org.tmatesoft.svn.core.internal.wc17.SVNStatusEditor17;
import org.tmatesoft.svn.core.internal.wc17.db.Structure;
import org.tmatesoft.svn.core.internal.wc2.SvnRemoteOperationRunner;
import org.tmatesoft.svn.core.internal.wc2.SvnWcGeneration;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RepositoryInfo;
import org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.RevisionsPair;
import org.tmatesoft.svn.core.io.SVNFileRevision;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.ISVNAnnotateHandler;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatusType;
import org.tmatesoft.svn.core.wc2.*;
import org.tmatesoft.svn.util.SVNLogType;

public class SvnRemoteAnnotate extends SvnRemoteOperationRunner<SvnAnnotateItem, SvnAnnotate> implements ISVNAnnotateHandler { 
	
	@Override
    public boolean isApplicable(SvnAnnotate operation, SvnWcGeneration wcGeneration) throws SVNException {
        return (operation.hasRemoteTargets() || wcGeneration == SvnWcGeneration.V17);
    }
	
	@Override
    protected SvnAnnotateItem run() throws SVNException {
		if (getOperation().getEndRevision() == SVNRevision.UNDEFINED) {
			if (getOperation().hasRemoteTargets()) {
				getOperation().setEndRevision(SVNRevision.HEAD);
			} else {
				getOperation().setEndRevision(SVNRevision.WORKING);
			}
	    }
		
		if (getOperation().getStartRevision() == null || !getOperation().getStartRevision().isValid() ||
    			getOperation().getEndRevision() == null || !getOperation().getEndRevision().isValid()) {
    		SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION);
            SVNErrorManager.error(err, SVNLogType.WC);
        }
		
		Structure<RepositoryInfo> repositoryInfo = getRepositoryAccess().createRepositoryFor(
				getOperation().getFirstTarget(), getOperation().getEndRevision(), getOperation().getFirstTarget().getResolvedPegRevision(),null);
    	
    	SVNRepository repository = repositoryInfo.<SVNRepository>get(RepositoryInfo.repository);
		repositoryInfo.release();
		
		Structure<RevisionsPair> pair = 
        		getRepositoryAccess().getRevisionNumber(repository, getOperation().getFirstTarget(), getOperation().getStartRevision(), null);
        long startRev = pair.lng(RevisionsPair.revNumber);
        pair = getRepositoryAccess().getRevisionNumber(repository, getOperation().getFirstTarget(), getOperation().getEndRevision(), pair);
        long endRev = pair.lng(RevisionsPair.revNumber);
        pair.release();
        
        if (endRev < startRev) {
            SVNErrorManager.error(SVNErrorMessage.create(SVNErrorCode.CLIENT_BAD_REVISION, "Start revision must precede end revision"), SVNLogType.DEFAULT);
        }

        if (!getOperation().isIgnoreMimeType()) {
            SvnOperationFactory operationFactory = getOperation().getOperationFactory();
            ISVNEventHandler oldEventHandler = operationFactory.getEventHandler();
            operationFactory.setEventHandler(null);
            SvnGetProperties getProperties = operationFactory.createGetProperties();
            getProperties.setRevision(getOperation().getRevision());
            getProperties.setSingleTarget(getOperation().getFirstTarget());
            SVNProperties svnProperties = getProperties.run();
            operationFactory.setEventHandler(oldEventHandler);

            if (svnProperties != null) {
                String mimeTypeString = svnProperties.getStringValue(SVNProperty.MIME_TYPE);
                if (SVNProperty.isBinaryMimeType(mimeTypeString)) {
                    SVNErrorMessage errorMessage = SVNErrorMessage.create(SVNErrorCode.CLIENT_IS_BINARY_FILE, "Cannot calculate blame information for binary file ''{0}''", getOperation().getFirstTarget());
                    SVNErrorManager.error(errorMessage, SVNLogType.CLIENT);
                }
            }
        }
        
        String path;
        File tmpFile;
    	if (getOperation().hasRemoteTargets()){
    		tmpFile = SVNFileUtil.createTempDirectory("annotate");
    		path = repository.getLocation().toString();
    	} else {
    	    tmpFile = getWcContext().getDb().getWCRootTempDir(getOperation().getFirstTarget().getFile());
    	    tmpFile = SVNFileUtil.createUniqueFile(tmpFile, "annotate", ".tmp", false);
    	    SVNFileUtil.deleteAll(tmpFile, true);
    	    SVNFileUtil.ensureDirectoryExists(tmpFile);
            path = getOperation().getFirstTarget().getFile().getAbsolutePath();
    	}
    	
    	SVNAnnotationGenerator generator = new SVNAnnotationGenerator(path, tmpFile, startRev, 
    			getOperation().isIgnoreMimeType(), getOperation().isUseMergeHistory(), getOperation().getDiffOptions(), getOperation().getInputEncoding(), this, this);
    	
       try {
    	   	repository.getFileRevisions("", startRev > 0 ? startRev - 1 : startRev, endRev, getOperation().isUseMergeHistory(), generator);

    	   	if (getOperation().getEndRevision() == SVNRevision.WORKING) {
    	   	    File target = getOperation().getFirstTarget().getFile();
                SvnStatus status = SVNStatusEditor17.internalStatus(getWcContext(), target);
                if (status.getTextStatus() != SVNStatusType.STATUS_NORMAL) {
                    SVNProperties properties = getWcContext().getActualProps(target);
                    String keywords = properties.getStringValue(SVNProperty.KEYWORDS);
                    InputStream source = null;
                    OutputStream dst = null;
                    try {
                        source = SVNFileUtil.openFileForReading(target);
                        Map<?,?> keywordsMap = keywords != null ?  
                                SVNTranslator.computeKeywords(keywords, null, null, null, null, null, getOperation().getOptions()) : null;
                        source = new SVNTranslatorInputStream(source, new byte[] {'\n'}, false, keywordsMap, false);

                        SVNFileRevision localRevision = new SVNFileRevision(path, -1, new SVNProperties(), new SVNProperties());
                        generator.openRevision(localRevision);
                        generator.addFileBlame(source);
                        generator.closeRevision(null);
                        
                    } finally {
                        SVNFileUtil.closeFile(dst);
                        SVNFileUtil.closeFile(source);
                    }
                }
            }
  	
    	   	if (!generator.isLastRevisionReported()) {
                generator.reportAnnotations(this, getOperation().getInputEncoding());
            }
            
        } finally {
            generator.dispose();
            SVNFileUtil.deleteAll(tmpFile, !"text-base".equals(tmpFile.getName()), null);
        }
       
        return getOperation().first();
    }
    
    public void handleLine(Date date, long revision, String author, String line, Date mergedDate, 
            long mergedRevision, String mergedAuthor, String mergedPath, int lineNumber) throws SVNException {
    	getOperation().receive(getOperation().getFirstTarget(), 
    			new SvnAnnotateItem(date, revision, author, line, mergedDate, mergedRevision, mergedAuthor, mergedPath, lineNumber)
    			);
    }
    
    public boolean handleRevision(Date date, long revision, String author, File contents) throws SVNException{
    	SvnAnnotateItem item = new SvnAnnotateItem(date, revision, author, contents);
    	getOperation().receive(getOperation().getFirstTarget(), item);
    	return item.getReturnResult();
    }
    
    public void handleLine(Date date, long revision, String author, String line) throws SVNException {
    }
    
    public void handleEOF() {
    	try {
			getOperation().receive(getOperation().getFirstTarget(), new SvnAnnotateItem(true));
		} catch (SVNException e) {
			
		}
    }
    
}
