/**
 * Copyright (c) 2012, The National Archives <pronom@nationalarchives.gsi.gov.uk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 *  * Neither the name of the The National Archives nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package uk.gov.nationalarchives.droid.container;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.gov.nationalarchives.droid.core.SignatureParseException;
import uk.gov.nationalarchives.droid.core.interfaces.DroidCore;
import uk.gov.nationalarchives.droid.core.interfaces.IdentificationMethod;
import uk.gov.nationalarchives.droid.core.interfaces.IdentificationRequest;
import uk.gov.nationalarchives.droid.core.interfaces.IdentificationResultCollection;
import uk.gov.nationalarchives.droid.core.interfaces.IdentificationResultImpl;
import uk.gov.nationalarchives.droid.core.interfaces.archive.ArchiveFormatResolver;
import uk.gov.nationalarchives.droid.core.interfaces.archive.ContainerIdentifier;
import uk.gov.nationalarchives.droid.core.interfaces.archive.ContainerIdentifierFactory;
import uk.gov.nationalarchives.droid.core.interfaces.signature.ErrorCode;
import uk.gov.nationalarchives.droid.core.interfaces.signature.SignatureFileException;

/**
 * @author rflitcroft
 *
 */
//CHECKSTYLE:OFF - fan out complexity slightly too high.
public abstract class AbstractContainerIdentifier implements ContainerIdentifier {
//CHECKSTYLE:ON

    /**
     * 
     */
    private static final String ERROR_READING_SIGNATURE_FILE = "Error reading signature file";
    private ContainerSignatureSaxParser signatureFileParser;
    private ContainerIdentifierFactory containerIdentifierFactory;
    private String containerType;
    private ArchiveFormatResolver containerFormatResolver;
    private DroidCore droidCore;
    private String signatureFilePath;
    
    private ContainerIdentifierInit init;

    //private List<ContainerSignature> containerSignatures = new ArrayList<ContainerSignature>();
    private Map<Integer, List<FileFormatMapping>> formats = new HashMap<Integer, List<FileFormatMapping>>(); 
    //private List<String> uniqueFileEntries;
    
    private long maxBytesToScan = -1;
    private IdentifierEngine identifierEngine;
    
    /**
     * {@inheritDoc}
     * @throws IOException 
     */
    @Override
    public final IdentificationResultCollection submit(IdentificationRequest request) throws IOException {
        final ContainerSignatureMatchCollection matches = new ContainerSignatureMatchCollection(
            getContainerSignatures(),
            init.getUniqueFileEntries(),
            maxBytesToScan);

        process(request, matches);
        
        final IdentificationResultCollection results = new IdentificationResultCollection(request);
        for (final ContainerSignatureMatch match : matches.getContainerSignatureMatches()) {
            if (match.isMatch()) {
                List<FileFormatMapping> mappings = formats.get(match.getSignature().getId());
                for (final FileFormatMapping mapping : mappings) {
                    final IdentificationResultImpl result = new IdentificationResultImpl();
                    result.setMethod(IdentificationMethod.CONTAINER);
                    result.setRequestMetaData(request.getRequestMetaData());
                    result.setPuid(mapping.getPuid());
                    results.addResult(result);
                }
            }
        }
        
        return results;
    }
    
    /**
     * Subclasses should override this to process the container in its specific way.
     * @param request the identification request to process.
     * @param matches a Liost of container signature potential matches.
     * @throws IOException if the input stream could not be read
     */
    protected abstract void process(IdentificationRequest request, 
            ContainerSignatureMatchCollection matches) throws IOException;
    
    /**
     * Get the Identifier Engine.
     * 
     * @return The Identifier Engine
     */
    public IdentifierEngine getIdentifierEngine() {
        return identifierEngine;
    }
    
    /**
     * Set the Identifier Engine.
     * 
     * @param identifierEngine The Identifier Engine
     */
    public void setIdentifierEngine(final IdentifierEngine identifierEngine) {
        this.identifierEngine = identifierEngine;
    }
    
    /**
     * @param containerSignature the containerSignature to add.
     */
    public void addContainerSignature(ContainerSignature containerSignature) {
        init.addContainerSignature(containerSignature);
    }
    
    /**
     * @param formats the formats to set
     */
    public void setFormats(Map<Integer, List<FileFormatMapping>> formats) {
        this.formats = formats;
    }

    /**
     * @return all container signatures.
     */
    public List<ContainerSignature> getContainerSignatures() {
        return init.getContainerSignatures();
    }
    
    /**
     * @return the formats
     */
    protected Map<Integer, List<FileFormatMapping>> getFormats() {
        return formats;
    }
    
    /**
     * Initialises the Zip identifier using the XML parser configured.
     * @throws SignatureFileException if the Signature file could not be initialised
     */
    public void init() throws SignatureFileException {
        try {
            FileInputStream sigFile = new FileInputStream(signatureFilePath);
            ContainerSignatureDefinitions defs = signatureFileParser.parse(sigFile);
            
            init = new ContainerIdentifierInit();
            init.init(defs, containerType, formats, droidCore);
            
            /*
            Set<String> uniqueFileSet = new HashSet<String>();
            
            for (ContainerSignature sig : defs.getContainerSignatures()) {
                if (sig.getContainerType().equals(containerType)) {
                    addContainerSignature(sig);
                    uniqueFileSet.addAll(sig.getFiles().keySet());
                }
            }
            uniqueFileEntries = new ArrayList<String>(uniqueFileSet); 
            
            for (FileFormatMapping fmt : defs.getFormats()) {
                List<FileFormatMapping> mappings = formats.get(fmt.getSignatureId());
                if (mappings == null) {
                    mappings = new ArrayList<FileFormatMapping>();
                    formats.put(fmt.getSignatureId(), mappings);
                }
                mappings.add(fmt);
                droidCore.removeSignatureForPuid(fmt.getPuid());
            }
            */
            
            
            for (TriggerPuid triggerPuid : defs.getTiggerPuids()) {
                if (triggerPuid.getContainerType().equals(containerType)) {
                    containerIdentifierFactory.addContainerIdentifier(containerType, this);
                    final String puid = triggerPuid.getPuid();
                    containerFormatResolver.registerPuid(puid, containerType);
                }
            }
        } catch (IOException e) {
            throw new SignatureFileException(ERROR_READING_SIGNATURE_FILE, e, ErrorCode.FILE_NOT_FOUND);
        } catch (SignatureParseException e) {
            throw new SignatureFileException(ERROR_READING_SIGNATURE_FILE, e, ErrorCode.INVALID_SIGNATURE_FILE);
        }
    }
    
    /**
     * @param signatureFileParser the signatureFileParser to set
     */
    public void setSignatureFileParser(ContainerSignatureSaxParser signatureFileParser) {
        this.signatureFileParser = signatureFileParser;
    }
    
    /**
     * @param containerIdentifierFactory the containerIdentifierFactory to set
     */
    public void setContainerIdentifierFactory(ContainerIdentifierFactory containerIdentifierFactory) {
        this.containerIdentifierFactory = containerIdentifierFactory;
    }
    
    /**
     * @param containerType the containerType to set
     */
    public void setContainerType(String containerType) {
        this.containerType = containerType;
    }
    
    /**
     * @param containerFormatResolver the containerFormatResolver to set
     */
    public void setContainerFormatResolver(ArchiveFormatResolver containerFormatResolver) {
        this.containerFormatResolver = containerFormatResolver;
    }
    
    /**
     * @param droidCore the droidCore to set
     */
    public void setDroidCore(DroidCore droidCore) {
        this.droidCore = droidCore;
    }
    
    /**
     * @param signatureFilePath the signatureFilePath to set
     */
    public void setSignatureFilePath(String signatureFilePath) {
        this.signatureFilePath = signatureFilePath;
    }

    @Override
    public void setMaxBytesToScan(long maxBytesToScan) {
        this.maxBytesToScan = maxBytesToScan;
    }
}
