package de.usd.cstchef.operations.signature;

import java.awt.event.ActionEvent;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import de.usd.cstchef.view.ui.FormatTextField;

public abstract class XmlSignature extends KeystoreOperation {

    private boolean multiSignature = false;
    private XMLSignatureFactory signatureFac;
    private HashMap<String, String> digestMethods = new HashMap<String,String>();
    private HashMap<String, String> signatureMethods = new HashMap<String,String>();    
    //"rsa-sha256", SignatureMethod.RSA_SHA256,
    //"rsa-sha512", SignatureMethod.RSA_SHA512
    private String[] availDigestMethods = new String[] {"sha1", "sha256", "sha512"};
    private String[] availSignatureMethods = new String[] {"rsa-sha1"};//, "rsa-sha256", "rsa-sha512"};

	protected JComboBox<String> signatureMethod;
	protected JComboBox<String> digestMethod;

	protected JButton addReferenceButton;
	protected JButton removeReferenceButton;
	protected ArrayList<FormatTextField> referenceFields = new ArrayList<FormatTextField>();
    
	protected JComboBox<String> includeKeyInfo;
    protected JCheckBox certificate;
    protected JCheckBox subject;
    protected JCheckBox issuer;
    protected JCheckBox serialIssuer;

	protected FormatTextField idIdentifier;

	public XmlSignature() {
		super();
		this.digestMethods.put("sha1", DigestMethod.SHA1);
		this.digestMethods.put("sha256", DigestMethod.SHA256);
		this.digestMethods.put("sha512", DigestMethod.SHA512);
		this.signatureMethods.put("rsa-sha1", SignatureMethod.RSA_SHA1);
        this.signatureFac = XMLSignatureFactory.getInstance("DOM");
        this.createMyUI();
	}

    protected ArrayList<Reference> getReferences() throws Exception {
      String digMethod = (String) digestMethod.getSelectedItem();
      ArrayList<Reference> referenceList = new ArrayList<Reference>();
      if( referenceFields != null && referenceFields.size() > 0)  {
        for( FormatTextField field : referenceFields ) {
          String referenceString = new String(field.getText());
          Reference ref = signatureFac.newReference("#" + referenceString, signatureFac.newDigestMethod(digestMethods.get(digMethod), null), Collections.singletonList (signatureFac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, null);
          referenceList.add(ref);
        }
      } else {
        Reference ref = signatureFac.newReference("", signatureFac.newDigestMethod(digestMethods.get(digMethod), null), Collections.singletonList (signatureFac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)), null, null);
        referenceList.add(ref);
      }
      return referenceList;
    }


    protected void validateIdAttributes(Document doc) throws Exception {
      String idAttribute = new String(idIdentifier.getText());
      if( idAttribute == null || idAttribute.isEmpty() ) {
        return;
      }
      XPathFactory xPathfactory = XPathFactory.newInstance();
      XPath xpath = xPathfactory.newXPath();
      XPathExpression expr = xpath.compile("descendant-or-self::*/@" + idAttribute);
      NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
      if(nodeList != null && nodeList.getLength() > 0) {
        for(int j = 0; j < nodeList.getLength(); j++) {
            Attr attr = (Attr)nodeList.item(j);
            ((Element)attr.getOwnerElement()).setIdAttributeNode(attr,true);
        }
      }
    }


    protected KeyInfo getKeyInfo() throws Exception {
      PrivateKeyEntry keyEntry = this.selectedEntry;
      String keyInfoChoice = (String) includeKeyInfo.getSelectedItem();
      if( Boolean.parseBoolean(keyInfoChoice) ) {
        X509Certificate cert = (X509Certificate)keyEntry.getCertificate();
        KeyInfoFactory keyInfoFac = signatureFac.getKeyInfoFactory();
        List<Object> x509Content = new ArrayList<Object>();
        if( this.subject.isSelected() ) {
          x509Content.add(cert.getSubjectX500Principal().getName());
        } 
        if( this.serialIssuer.isSelected() ) {
          x509Content.add(keyInfoFac.newX509IssuerSerial(cert.getIssuerX500Principal().getName(),cert.getSerialNumber()));
        }
        if( this.issuer.isSelected() ) {
          x509Content.add(cert.getIssuerX500Principal().getName());
        }
        if( this.certificate.isSelected() ) {
          x509Content.add(cert);
        }
        X509Data xd = keyInfoFac.newX509Data(x509Content);
        return keyInfoFac.newKeyInfo(Collections.singletonList(xd));
      }
      return (KeyInfo)null;
    }
    

    protected void createSignature(Document document) throws Exception {
      String signMethod = (String)signatureMethod.getSelectedItem();
      PrivateKeyEntry keyEntry = this.selectedEntry;

      if( this.multiSignature )
        this.validateIdAttributes(document);
      ArrayList<Reference> references = this.getReferences();
      SignedInfo signatureInfo = signatureFac.newSignedInfo(signatureFac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null), signatureFac.newSignatureMethod(signatureMethods.get(signMethod), null), references);
      KeyInfo keyInfo = this.getKeyInfo();
      XMLSignature signature = signatureFac.newXMLSignature(signatureInfo, keyInfo);

      DOMSignContext dsc = new DOMSignContext (keyEntry.getPrivateKey(), document.getDocumentElement()); 
      signature.sign(dsc);
    }


    protected void addIdSelectors() {
        this.multiSignature = true;

		this.idIdentifier = new FormatTextField();
		this.addUIElement("Identifier", this.idIdentifier);

		addReferenceButton = new JButton("Add Reference");
		addReferenceButton.addActionListener(this);
		this.addUIElement(null, addReferenceButton);
    }


	public void createMyUI() {
        super.createMyUI();

		this.includeKeyInfo = new JComboBox<>(new String[]{ "true", "false" });
		this.includeKeyInfo.addActionListener(this);
		this.addUIElement("IncludeKeyInfo", this.includeKeyInfo);
        
        this.certificate = new JCheckBox("Include Certificate");
        this.certificate.setSelected(false);
		this.certificate.addActionListener(this);
		this.addUIElement(null, this.certificate);

        this.subject = new JCheckBox("Include Subject");
        this.subject.setSelected(false);
		this.subject.addActionListener(this);
		this.addUIElement(null, this.subject);

        this.issuer = new JCheckBox("Include Issuer");
        this.issuer.setSelected(false);
		this.issuer.addActionListener(this);
		this.addUIElement(null, this.issuer);

        this.serialIssuer = new JCheckBox("Include Issuer");
        this.serialIssuer.setSelected(false);
		this.serialIssuer.addActionListener(this);
		this.addUIElement(null, this.serialIssuer);
    
		this.digestMethod = new JComboBox<String>(this.availDigestMethods);
		this.digestMethod.addActionListener(this);
		this.addUIElement("DigestMethod", this.digestMethod);

		this.signatureMethod = new JComboBox<String>(this.availSignatureMethods);
		this.signatureMethod.addActionListener(this);
		this.addUIElement("SignatureMethod", this.signatureMethod);
	}

	public void actionPerformed(ActionEvent arg0) {
        if( arg0.getSource() == addReferenceButton ) {
          FormatTextField tmpRef = new FormatTextField();
          this.addUIElement("Reference", tmpRef);
          referenceFields.add(tmpRef);
        } else if( arg0.getSource() == this.includeKeyInfo) {
          String keyInfoChoice = (String) includeKeyInfo.getSelectedItem();
          if(!Boolean.parseBoolean(keyInfoChoice) ) {
            this.certificate.setVisible(false);
            this.subject.setVisible(false);
            this.issuer.setVisible(false);
            this.serialIssuer.setVisible(false);
          } else {
            this.certificate.setVisible(true);
            this.subject.setVisible(true);
            this.issuer.setVisible(true);
            this.serialIssuer.setVisible(true);
          }
        }
        super.actionPerformed(arg0);
	}
}