package org.alfresco.filesys.repo;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.EntryUtils;
import org.apache.poi.poifs.filesystem.FilteringDirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import java.io.File;
/**
 * Compares content for to see if content is equal.
 * 
 * Most mimetypes can simply be binary compared but for some mimetypes
 * there may be trivial differences so a binary compare is not sufficient.
 * 
 * In particular MS Project and MS Excel write to header fields without changing content. 
 * 
 * @author mrogers
 *
 */
public class CIFSContentComparator implements ContentComparator
{
    // TODO Externalize Map of mimetype to comparator
    private Map customComparators = new HashMap();
    
    private static final Log logger = LogFactory.getLog(CIFSContentComparator.class);
    
    /**
     * 
     */
    public void init()
    {   
        customComparators.put("application/vnd.ms-project", new MPPContentComparator());
        customComparators.put("application/vnd.ms-excel", new XLSContentComparator());
    }  
    @Override
    public boolean isContentEqual(ContentReader existingContent,
            File newFile)
    {
        String mimetype = existingContent.getMimetype();
        logger.debug("isContentEqual mimetype=" + mimetype);
        
        long newSize = newFile.length();
   
        ContentComparator custom = customComparators.get(mimetype);
        
        if(custom == null)
        {
            // No custom comparator - check length then do a binary diff
            if(existingContent.getSize() != newSize)
            {
                // Different size
                logger.debug("generic comparision, size is different - not equal");
                return false;
            }
            
            InputStream rightIs = null;
            InputStream leftIs = null;
            try
            {   
                rightIs = new BufferedInputStream(new FileInputStream(newFile));
                leftIs = existingContent.getContentInputStream();
                boolean retVal = EqualsHelper.binaryStreamEquals(leftIs, rightIs);
                rightIs = null;
                leftIs = null;
                
                if(logger.isDebugEnabled())
                {
                    logger.debug("generic comparision, binary content comparison equal=" + retVal);
                }
                return retVal;
            }
            catch (IOException e)
            {
                logger.debug("Unable to compare contents", e);
                return false;
            }
            finally
            {
                if(leftIs != null)
                {
                    try
                    {
                        leftIs.close();
                    } 
                    catch (IOException e)
                    {
                        // Do nothing this is cleanup code
                    }
                }
                if(rightIs != null)
                {
                    try
                    {
                        rightIs.close();
                    } 
                    catch (IOException e)
                    {
                        // Do nothing this is cleanup code
                    }
                }
            }
        }
        else
        {
            // there is a custom comparator for this mimetype
            return custom.isContentEqual(existingContent, newFile);
        }
    }
    // Comparator for MS Project
    private class MPPContentComparator implements ContentComparator
    {
     
        @Override
        public boolean isContentEqual(ContentReader existingContent,
                File newFile)
        {
            long newSize = newFile.length();
            
            if(logger.isDebugEnabled())
            {
                logger.debug("comparing two project files size:" + existingContent.getSize() + ", and " + newFile.length());
            }
           
            if(existingContent.getSize() != newSize)
            {
                logger.debug("project files are different size");
                // Different size
                return false;
            }
            
            /**
             * Use POI to compare the content of the MPP file, exluding certain properties
             */
            InputStream leftIs = null;
            try
            {  
                Collection excludes = new HashSet();
                excludes.add("Props");
                excludes.add("Props12");
                excludes.add("Props9");
                
                leftIs = existingContent.getContentInputStream();
                NPOIFSFileSystem fs2 = new NPOIFSFileSystem(leftIs);              
                NPOIFSFileSystem fs1 = new NPOIFSFileSystem(newFile);                
                
                DirectoryEntry de1 = fs1.getRoot();
                DirectoryEntry de2 = fs2.getRoot();
                
                FilteringDirectoryNode fs1Filtered = new FilteringDirectoryNode(de1, excludes);
                FilteringDirectoryNode fs2Filtered = new FilteringDirectoryNode(de2, excludes);
                
                boolean retVal = EntryUtils.areDirectoriesIdentical(fs1Filtered, fs2Filtered);
                if(logger.isDebugEnabled())
                {
                    logger.debug("returning equal="+ retVal);
                }
                
                return retVal;
            }
            catch (ContentIOException ce)
            {
                logger.debug("Unable to compare contents", ce);
                return false;
            }
            catch (IOException e)
            {
                logger.debug("Unable to compare contents", e);
                return false;
            }
            finally
            {
                if(leftIs != null)
                {
                    try
                    {
                        leftIs.close();
                    } 
                    catch (IOException e)
                    {
                       // Ignore
                    }
                }
            }
        }
    }
    
    // Comparator for MS Excel
    private class XLSContentComparator implements ContentComparator
    {
     
        @Override
        public boolean isContentEqual(ContentReader existingContent,
                File newFile)
        {
            long newSize = newFile.length();
            
            if(logger.isDebugEnabled())
            {
                logger.debug("comparing two excel files size:" + existingContent.getSize() + ", and " + newFile.length());
            }
           
            if(existingContent.getSize() != newSize)
            {
                logger.debug("excel files are different size");
                // Different size
                return false;
            }
            
            /**
             * Use POI to compare the content of the XLS file, exluding certain properties
             */
            File tpm1 = null;
            File tpm2 = null;
            InputStream leftIs = null;
            try 
            {  
                Collection excludes = new HashSet();
                
                tpm1 = TempFileProvider.createTempFile("CIFSContentComparator1", "xls");
                tpm2 = TempFileProvider.createTempFile("CIFSContentComparator2", "xls");
                
                HSSFWorkbook wb1 = new HSSFWorkbook(existingContent.getContentInputStream());
                HSSFWorkbook wb2 = new HSSFWorkbook(new FileInputStream(newFile));
                wb1.writeProtectWorkbook("", "CIFSContentComparator");
                wb2.writeProtectWorkbook("", "CIFSContentComparator");
                
                wb1.write(new FileOutputStream(tpm1));
                wb2.write(new FileOutputStream(tpm2));
                
                
                NPOIFSFileSystem fs2 = new NPOIFSFileSystem(tpm1);              
                NPOIFSFileSystem fs1 = new NPOIFSFileSystem(tpm2);                
                
                DirectoryEntry de1 = fs1.getRoot();
                DirectoryEntry de2 = fs2.getRoot();
                
                FilteringDirectoryNode fs1Filtered = new FilteringDirectoryNode(de1, excludes);
                FilteringDirectoryNode fs2Filtered = new FilteringDirectoryNode(de2, excludes);
                
                boolean retVal = EntryUtils.areDirectoriesIdentical(fs1Filtered, fs2Filtered);
                if(logger.isDebugEnabled())
                {
                    logger.debug("returning equal="+ retVal);
                }
                
                return retVal;
            }
            catch (ContentIOException ce)
            {
                logger.debug("Unable to compare contents", ce);
                return false;
            }
            catch (IOException e)
            {
                logger.debug("Unable to compare contents", e);
                return false;
            }
            finally
            {
            	if(tpm1 != null)
            	{
            		try 
            		{
            	        tpm1.delete();
            		}
            		catch (Exception e)
            		{
            			// ignore
            		}
            	}
            	if(tpm2 != null)
            	{
            		try 
            		{
            		    tpm2.delete();
        		    }
        		    catch (Exception e)
        		    {
        			    // ignore
        		    }
            	}
                if(leftIs != null)
                {
                    try
                    {
                        leftIs.close();
                    } 
                    catch (IOException e)
                    {
                       // Ignore
                    }
                }
            }
        }
    }
    
    
    
}