/**
 * 
 */
package at.gv.egiz.pdfas.impl.api;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import at.gv.egiz.pdfas.api.PdfAs;
import at.gv.egiz.pdfas.api.analyze.AnalyzeParameters;
import at.gv.egiz.pdfas.api.analyze.AnalyzeResult;
import at.gv.egiz.pdfas.api.commons.Constants;
import at.gv.egiz.pdfas.api.commons.SignatureInformation;
import at.gv.egiz.pdfas.api.exceptions.PdfAsException;
import at.gv.egiz.pdfas.api.sign.SignParameters;
import at.gv.egiz.pdfas.api.sign.SignResult;
import at.gv.egiz.pdfas.api.verify.VerifyAfterAnalysisParameters;
import at.gv.egiz.pdfas.api.verify.VerifyParameters;
import at.gv.egiz.pdfas.api.verify.VerifyResult;
import at.gv.egiz.pdfas.api.verify.VerifyResults;
import at.gv.egiz.pdfas.commandline.CommandlineConnectorChooser;
import at.gv.egiz.pdfas.exceptions.ErrorCode;
import at.gv.egiz.pdfas.framework.config.SettingsHelper;
import at.gv.egiz.pdfas.framework.input.ExtractionStage;
import at.gv.egiz.pdfas.framework.signator.SignatorInformation;
import at.gv.egiz.pdfas.framework.vfilter.VerificationFilterParameters;
import at.gv.egiz.pdfas.impl.api.analyze.AnalyzeResultImpl;
import at.gv.egiz.pdfas.impl.api.commons.DataSinkAdapter;
import at.gv.egiz.pdfas.impl.api.commons.PdfDataSourceAdapter;
import at.gv.egiz.pdfas.impl.api.commons.SignatureInformationAdapter;
import at.gv.egiz.pdfas.impl.api.commons.SignatureProfileImpl;
import at.gv.egiz.pdfas.impl.api.commons.TextDataSourceAdapter;
import at.gv.egiz.pdfas.impl.api.sign.ActualSignaturePositionAdapter;
import at.gv.egiz.pdfas.impl.api.sign.SignResultImpl;
import at.gv.egiz.pdfas.impl.api.verify.VerifyResultAdapter;
import at.gv.egiz.pdfas.impl.api.verify.VerifyResultsImpl;
import at.gv.egiz.pdfas.impl.vfilter.VerificationFilterParametersImpl;
import at.gv.egiz.pdfas.utils.ConfigUtils;
import at.knowcenter.wag.egov.egiz.PdfAS;
import at.knowcenter.wag.egov.egiz.PdfASID;
import at.knowcenter.wag.egov.egiz.cfg.SettingsReader;
import at.knowcenter.wag.egov.egiz.exceptions.PDFDocumentException;
import at.knowcenter.wag.egov.egiz.exceptions.PresentableException;
import at.knowcenter.wag.egov.egiz.exceptions.SignatureException;
import at.knowcenter.wag.egov.egiz.framework.SignatorFactory;
import at.knowcenter.wag.egov.egiz.pdf.NoSignatureHolder;
import at.knowcenter.wag.egov.egiz.pdf.SignatureHolder;
import at.knowcenter.wag.egov.egiz.pdf.TablePos;
import at.knowcenter.wag.egov.egiz.sig.SignatureResponse;
import at.knowcenter.wag.egov.egiz.sig.SignatureTypeDefinition;
import at.knowcenter.wag.egov.egiz.sig.SignatureTypes;

/**
 * Implementation of the {@link PdfAs} interface.
 * 
 * @author wprinz
 */
public class PdfAsObject implements PdfAs
{

  /**
   * The log.
   */
  private static Log log = LogFactory.getLog(CheckHelper.class);
  
  /**
   * This constructor is for internal use only - use
   * {@link at.gv.egiz.pdfas.PdfAsFactory} instead.
   * 
   * @param workDirectory
   *          The work directory.
   * @throws PdfAsException
   *           Thrown, if the configuration cannot be processed.
   */
  public PdfAsObject(File workDirectory) throws PdfAsException
  {
     String path = workDirectory != null ? workDirectory.getPath() : null;
    SettingsReader.initialize(path, path);
    reloadConfig();
  }
  
  /**
   * This constructor is for internal use only - use
   * {@link at.gv.egiz.pdfas.PdfAsFactory} instead.
   * 
   * @throws PdfAsException
   *           Thrown, if the configuration cannot be processed.
   */
  public PdfAsObject() throws PdfAsException
  {
     this(null);
  }
  
  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#reloadConfig()
   */
  public void reloadConfig() throws PdfAsException
  {
     ConfigUtils.initializeLogger();
    SettingsReader.createInstance();
    SignatureTypes.createInstance();
  }

  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#getProfileInformation()
   */
  public List getProfileInformation() throws PdfAsException
  {
     log.debug("Collecting profile information.");
    final String MOA_SIGN_KEY_IDENTIFIER_KEY = "moa.sign.KeyIdentifier";

    SettingsReader settings = SettingsReader.getInstance();
    final String defaultMoaKeyIdentifiert = settings.getSetting(MOA_SIGN_KEY_IDENTIFIER_KEY, null);

    SignatureTypes types = SignatureTypes.getInstance();
    List profiles = types.getSignatureTypeDefinitions();

    List profileInformation = new ArrayList(profiles.size());

    Iterator it = profiles.iterator();
    while (it.hasNext())
    {
      SignatureTypeDefinition profile = (SignatureTypeDefinition) it.next();

      final String profileId = profile.getType();
      log.debug("Processing profile \"" + profileId + "\".");
      final String moaKeyIdentifier = settings.getSetting("sig_obj." + profileId + "." + MOA_SIGN_KEY_IDENTIFIER_KEY, defaultMoaKeyIdentifiert);

      // modified by tknall
      SignatureProfileImpl signatureProfile = new SignatureProfileImpl(profileId, moaKeyIdentifier);

      // start - added by tknall
      
      // signature entries relevant to the search algorithm
      Properties signatureEntries = new Properties();
      
      // search for table entries
      String parentPropertyKey = "sig_obj." + profileId + ".table";
      log.debug("Looking for subkeys of \"" + parentPropertyKey + "\".");
      Vector keysVector = settings.getSettingKeys(parentPropertyKey);
      if (keysVector != null) {
         Iterator keyIt = keysVector.iterator();
         while (keyIt.hasNext()) {
            String subKey = (String) keyIt.next();
            if (subKey != null && subKey.length() > 0) {
               String fullKey = parentPropertyKey + "." + subKey;
               String value = settings.getValueFromKey(fullKey);
               int lastIndex = fullKey.lastIndexOf(".");
               if (lastIndex != -1) {
                  String endsWith = fullKey.substring(lastIndex + 1);
                  if (value != null && value.length() > 0) {
                     if (NumberUtils.isDigits(endsWith)) {
                        signatureEntries.setProperty(fullKey, value);
                     } else {
                        log.debug("Ignoring table entry \"" + fullKey + "\" because it does not end with a digit. Therefore it is not relevant for the seach algorithm.");
                     }
                  } else {
                     log.warn("Problem detected with key \"" + fullKey + "\". The value is empty.");
                  }
               }
            }
         }
      }
      
      // search for table entries
      parentPropertyKey = "sig_obj." + profileId + ".key";
      log.debug("Looking for subkeys of \"" + parentPropertyKey + "\".");
      keysVector = settings.getSettingKeys(parentPropertyKey);
      if (keysVector != null) {
         Iterator keyIt = keysVector.iterator();
         while (keyIt.hasNext()) {
            String subKey = (String) keyIt.next();
            if (subKey != null && subKey.length() > 0) {
               String fullKey = parentPropertyKey + "." + subKey;
               String value = settings.getValueFromKey(fullKey);
               if (value != null && value.length() > 0) {
                  signatureEntries.setProperty(fullKey, value);
               } else {
                  log.warn("Problem detected with key \"" + fullKey + "\". The value is empty.");
               }
            }
         }
      }
      
      // set properties
      signatureProfile.setSignatureBlockEntries(signatureEntries);

      // stop - added by tknall
      
      profileInformation.add(signatureProfile);
    }

    return profileInformation;
  }

  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#sign(at.gv.egiz.pdfas.api.sign.SignParameters)
   */
  public SignResult sign(SignParameters signParameters) throws PdfAsException
  {
    CheckHelper.checkSignParameters(signParameters);
    
    signParameters.setDocument(PdfAS.applyStrictMode(signParameters.getDocument()));
    
    if (signParameters.getSignatureProfileId() == null)
    {
      SettingsReader settings = SettingsReader.getInstance();
      String defaultProfile = settings.getValueFromKey(SignatureTypes.DEFAULT_TYPE);
      signParameters.setSignatureProfileId(defaultProfile);
    }

    PdfASID signatorId = null;
    if (signParameters.getSignatureType().equals(Constants.SIGNATURE_TYPE_BINARY))
    {
      signatorId = SignatorFactory.MOST_RECENT_BINARY_SIGNATOR_ID;
    }
    if (signParameters.getSignatureType().equals(Constants.SIGNATURE_TYPE_TEXTUAL))
    {
      signatorId = SignatorFactory.MOST_RECENT_TEXTUAL_SIGNATOR_ID;
    }
    if (signParameters.getSignatureType().equals(Constants.SIGNATURE_TYPE_DETACHEDTEXTUAL))
    {
      signatorId = SignatorFactory.MOST_RECENT_DETACHEDTEXT_SIGNATOR_ID;
    }

    TablePos pos = PosHelper.formTablePos(signParameters.getSignaturePositioning());

    String connectorId = CommandlineConnectorChooser.chooseCommandlineConnectorForSign(signParameters.getSignatureDevice());
 
    SignatorInformation si = PdfAS.signCommandline(
          new PdfDataSourceAdapter(signParameters.getDocument()),
          new DataSinkAdapter(signParameters.getOutput()),
          signatorId,
          connectorId,
          signParameters.getSignatureProfileId(),
          signParameters.getSignatureKeyIdentifier(),
          pos,
          signParameters.getTimeStamperImpl()
    );

    return new SignResultImpl(signParameters.getOutput(), si.getSignSignatureObject().getX509Certificate(),
          new ActualSignaturePositionAdapter(si.getActualTablePos()), si.getNonTextualObjects());
  }

  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#verify(at.gv.egiz.pdfas.api.verify.VerifyParameters)
   */
  public VerifyResults verify(VerifyParameters verifyParameters) throws PdfAsException
  {
    CheckHelper.checkVerifyParameters(verifyParameters);

    AnalyzeParameters ap = new AnalyzeParameters();
    fillAnalyzeParametersWithVerifyParameters(ap, verifyParameters);
    AnalyzeResult analyzeResult = analyze(ap);

    if (verifyParameters.getSignatureToVerify() != Constants.VERIFY_ALL)
    {
      if (verifyParameters.getSignatureToVerify() >= analyzeResult.getSignatures().size())
      {
        throw new SignatureException(312, "The selected signature to be verified doesn't exist. " + verifyParameters.getSignatureToVerify());
      }

      Object stv = analyzeResult.getSignatures().get(verifyParameters.getSignatureToVerify());
      List selectedSignature = new ArrayList(1);
      selectedSignature.add(stv);
      analyzeResult = new AnalyzeResultImpl(selectedSignature);
    }

    VerifyAfterAnalysisParameters vaap = new VerifyAfterAnalysisParameters();
    vaap.setAnalyzeResult(analyzeResult);
    fillVerifyAfterAnalysisParametersWithVerifyParameters(vaap, verifyParameters);
    VerifyResults res = verify(vaap);
    
    if (verifyParameters.isReturnNonTextObjects()) {
       res.setNonTextualObjects(PdfAS.extractNonTextualObjects(new PdfDataSourceAdapter(verifyParameters.getDocument())));    
    }
    
    return res;
    
  }
 

  /**
   * Copies all adequate parameters from the {@link VerifyParameters} to the
   * {@link AnalyzeParameters}.
   * 
   * @param ap
   *          The {@link AnalyzeParameters}.
   * @param vp
   *          The {@link VerifyParameters}.
   */
  protected void fillAnalyzeParametersWithVerifyParameters(AnalyzeParameters ap, VerifyParameters vp)
  {
    ap.setDocument(vp.getDocument());
    ap.setVerifyMode(vp.getVerifyMode());
  }

  /**
   * Copies all adequate parameters from the {@link VerifyParameters} to the
   * {@link VerifyAfterAnalysisParameters}.
   * 
   * @param vaap
   *          The {@link VerifyAfterAnalysisParameters}.
   * @param vp
   *          The {@link VerifyParameters}.
   */
  protected void fillVerifyAfterAnalysisParametersWithVerifyParameters(VerifyAfterAnalysisParameters vaap, VerifyParameters vp)
  {
    vaap.setSignatureDevice(vp.getSignatureDevice());
    vaap.setVerificationTime(vp.getVerificationTime());
    vaap.setReturnHashInputData(vp.isReturnHashInputData());
  }

  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#analyze(at.gv.egiz.pdfas.api.analyze.AnalyzeParameters)
   */
  public AnalyzeResult analyze(AnalyzeParameters analyzeParameters) throws PdfAsException
  {
    CheckHelper.checkAnalyzeParameters(analyzeParameters);

    VerificationFilterParameters parametersConfig = SettingsHelper.readVerificationFilterParametersFromSettings();
    boolean binaryOnly = parametersConfig.extractBinarySignaturesOnly();
    if (analyzeParameters.getVerifyMode().equals(Constants.VERIFY_MODE_BINARY_ONLY))
    {
      binaryOnly = true;
    }
    boolean assumeOnlySB = parametersConfig.assumeOnlySignatureUpdateBlocks();
    if (analyzeParameters.getVerifyMode().equals(Constants.VERIFY_MODE_SEMI_CONSERVATIVE))
    {
      assumeOnlySB = true;
    }
    if (analyzeParameters.getVerifyMode().equals(Constants.VERIFY_MODE_FULL_CONSERVATIVE))
    {
      assumeOnlySB = false;
    }
    VerificationFilterParameters parameters = new VerificationFilterParametersImpl(binaryOnly, assumeOnlySB, parametersConfig.scanForOldSignatures());
    
    at.gv.egiz.pdfas.framework.input.DataSource inputDataSource = null;
    if (analyzeParameters.getDocument().getMimeType().equals("application/pdf"))
    {
      inputDataSource = new PdfDataSourceAdapter(analyzeParameters.getDocument());
    }
    else
    {
      try
      {
        inputDataSource = new TextDataSourceAdapter(analyzeParameters.getDocument());
      }
      catch (UnsupportedEncodingException e)
      {
        throw new PresentableException(ErrorCode.DOCUMENT_CANNOT_BE_READ, "The characterEncoding is not supported." + analyzeParameters.getDocument().getCharacterEncoding(), e);
      }
    }
    assert inputDataSource != null;
    
    ExtractionStage es = new ExtractionStage();
    List signature_holders = es.extractSignatureHolders(inputDataSource, parameters);

//    List sigInfs = new ArrayList(signature_holders.size());
    List sigInfs = new ArrayList();
    List noSigs = new ArrayList();
    Iterator it = signature_holders.iterator();
    while (it.hasNext())
    {
      SignatureHolder sh = (SignatureHolder)it.next();
      
      if(sh instanceof NoSignatureHolder) {
        noSigs.add(sh);
      } else {
      
        SignatureInformation si = new SignatureInformationAdapter(sh);
        sigInfs.add(si);
      }
    }
    
    return new AnalyzeResultImpl(sigInfs, noSigs);
  }

  /**
   * @see at.gv.egiz.pdfas.api.PdfAs#verify(at.gv.egiz.pdfas.api.verify.VerifyAfterAnalysisParameters)
   */
  public VerifyResults verify(VerifyAfterAnalysisParameters verifyAfterAnalysisParameters) throws PdfAsException
  {
    CheckHelper.checkVerifyAfterAnalysisParameters(verifyAfterAnalysisParameters);

    List signatures = verifyAfterAnalysisParameters.getAnalyzeResult().getSignatures();
    
    // added by tknall
    if (signatures == null || signatures.isEmpty()) {
       throw new PDFDocumentException(ErrorCode.DOCUMENT_NOT_SIGNED, "PDF document not signed."); //$NON-NLS-1$
    }
    
    List signature_holders = new ArrayList(signatures.size());
    Iterator it = signatures.iterator();
    while (it.hasNext())
    {
      SignatureInformation si = (SignatureInformation) it.next();
      SignatureHolder sh = (SignatureHolder) si.getInternalSignatureInformation();
      signature_holders.add(sh);
    }
    assert signature_holders.size() == signatures.size();
    
    List results = PdfAS.verifySignatureHolders(signature_holders, verifyAfterAnalysisParameters.getSignatureDevice(), verifyAfterAnalysisParameters.isReturnHashInputData(), verifyAfterAnalysisParameters.getVerificationTime());
    
    List vrs = new ArrayList(results.size());
    
    assert signature_holders.size() == results.size() : "Not all signatures were verified.";

    for (int i = 0; i < signature_holders.size(); i++)
    {
      SignatureResponse response = (SignatureResponse) results.get(i);
      SignatureHolder holder = (SignatureHolder) signature_holders.get(i);

      VerifyResult vr = new VerifyResultAdapter(response, holder, verifyAfterAnalysisParameters.getVerificationTime());
      vrs.add(vr);
    }

    return new VerifyResultsImpl(vrs);
  }

}