package org.jboss.loom.utils.compar;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.util.CRCUtil;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.slf4j.LoggerFactory;

/**
 * Compares a list of hashes with actual files in a directory tree.
 * 
 * @author Ondrej Zizka, ozizka at redhat.com
 */
public class FileHashComparer {
    private static final org.slf4j.Logger log = LoggerFactory.getLogger( FileHashComparer.class );


    public static enum MatchResult { MATCH, MISMATCH, MISSING };
    
    
    /**
     *  Reads the hashes from file and compares each entry to the respective file in given base $dir.
     *  Hashes file format is:
     *       92ae740a ./bin/twiddle.bat
     */
    public static ComparisonResult compareHashesAndDir( File hashes, File dir, IOFileFilter filter ) throws FileNotFoundException, IOException{
        Map<Path, MatchResult> results = compareHashesAndDir( readHashes(hashes), dir, filter );
        return new ComparisonResult( dir ).setMatches( results ).setHashes( hashes );
    }
    
    public static ComparisonResult compareHashesAndDir( InputStream hashesIS, File dir, IOFileFilter filter ) throws FileNotFoundException, IOException{
        Map<Path, MatchResult> results = compareHashesAndDir( readHashes(hashesIS), dir, filter );
        return new ComparisonResult( dir ).setMatches( results );
    }
    
    private static Map<Path, MatchResult> compareHashesAndDir( Map<String, Long> hashes, File dir, IOFileFilter filter ) throws IOException {
        Map<Path, MatchResult> matches = new HashMap();
        
        // Iterate through hashes and compare with files.
        for( Map.Entry<String, Long> entry : hashes.entrySet() ) {
            String path = entry.getKey();
            
            // Apply the filter.
            if( ! filter.accept( new File(path) ) )
                continue;
            
            File file = new File(dir, path);
            Path pathNorm = file.toPath().normalize();
            
            if( ! file.exists() ){
                matches.put( pathNorm, MatchResult.MISSING );
                continue;
            }
            long hashReal = computeCrc32(file);
            Long hash     = entry.getValue();
            matches.put( pathNorm, hash == hashReal ? MatchResult.MATCH : MatchResult.MISMATCH );
        }
        return matches;
    }

    
    /**
     *  Reads a file format 
     *    92ae740a ./bin/twiddle.bat
     *  and returns a map of paths -> hashes.
     *  The paths are normalized, while kept relative. I.e. ./foo/../bar/a results in bar/a .
     */
    static Map<String, Long> readHashes( File file ) throws FileNotFoundException {
        return readHashes( new FileInputStream(file) );
    }
    
    static Map<String, Long> readHashes( InputStream hashesIS ) throws FileNotFoundException {
        
        Scanner sc = new Scanner( hashesIS );
        Map<String, Long> hashes = new HashMap();
        
        //  92ae740a ./bin/twiddle.bat
        while( sc.hasNextLine() ){
            try {
                long hash = sc.nextLong(16);
                String path = sc.nextLine();
                path = path.trim();
                path = Paths.get(path).normalize().toString();
                hashes.put( path, hash );
            }
            catch( NoSuchElementException ex ){
                log.warn("Failed parsing line: " + sc.nextLine(), ex);
                sc.nextLine();
            }
        }
        return hashes;
    }

    
    /**
     *  Computes CRC32 checksum of given file.
     *  Uses net.lingala.zip4j.util and wraps their exception.
     */
    public static long computeCrc32( File file ) throws IOException {
        try {
            return CRCUtil.computeFileCRC(file.getPath());
        } catch( ZipException ex ) {
            throw new IOException("Can't compute CRC32 of " + file.getPath() + ": " + ex.getMessage(), ex);
        }
    }
        
}// class

@@ -2144,24 +2144,42 @@
          if(
+ filter != null &&
  ! filter.ac

Allow filter = null