/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package COSE;

import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;
import java.util.ArrayList;
import java.util.List;

/**
 * The Signer class is used to implement the COSE_Signer object.
 * This provides the information dealing with a single signature for the SignMessage class.
 * <p>
 * Create a Signer object for adding a new signature to a message, existing signers will have a Signer object created for them when a SignMessage object is created by Message.DecodeFromBytes.
 * <p>
 * Examples of using this class can be found in
 * <br><a href="https://github.com/cose-wg/COSE-JAVA/wiki/Sign-Message-Example">Single Signer Example</a> an example of signing and verify a message with a single signature.
 * <br><a href="https://github.com/cose-wg/COSE-JAVA/wiki/Multi-Sign-Example">Multiple Signer Example</a> an example of signing and verifying a message which has multiple signatures.
 * @author jimsch
 */
public class Signer extends Attribute {
    protected byte[] rgbSignature;
    protected String contextString;
    OneKey cnKey;
    
    /**
     * Create a new signer object to add to a SignMessage
     */
    public Signer() {
        contextString = "Signature";
    }
    
    /**
     * Create a new signer object for a SignMessage and set the key to be used.
     * 
     * @param key key to use for signing.
     */
    public Signer(OneKey key) {
        contextString = "Signature";
        cnKey = key;
    }
        
    /**
     * Remove the key object from the signer
     * 
     * @since COSE 0.9.1
     */
    public void clearKey() {
        cnKey = null;
    }
    
    /**
     * Set a key object on a signer
     * 
     * @since COSE 0.9.1
     * @param keyIn key to be used for signing or verification
     * @throws CoseException - Invalid key passed in
     */
    public void setKey(OneKey keyIn) throws CoseException {
        setupKey(keyIn);
    }
    
    /**
     * Set the key on the object, if there is not a signature on this object then set
     * the algorithm and the key id from the key if they exist on the key and do not exist in the message.
     * 
     * @param key key to be used]
     */
    private void setupKey(OneKey key) throws CoseException {
        CBORObject cn2;
        CBORObject cn;
        
        cnKey = key;

        if (rgbSignature != null) return;
        
        cn = key.get(KeyKeys.Algorithm);
        if (cn != null) {
            cn2 = findAttribute(HeaderKeys.Algorithm);
            if (cn2 == null) addAttribute(HeaderKeys.Algorithm, cn, Attribute.PROTECTED);
        }
        
        cn = key.get(KeyKeys.KeyId);
        if (cn != null) {
            cn2 = findAttribute(HeaderKeys.KID);
            if (cn2 == null) addAttribute(HeaderKeys.KID, cn, Attribute.UNPROTECTED);
        }
    }
    
    /**
     * Internal function used in creating a Sign1Message object from a byte string.
     * 
     * @param obj COSE_Sign1 encoded object.
     * @throws CoseException Errors generated by the COSE module
     */
    protected void DecodeFromCBORObject(CBORObject obj) throws CoseException {
        if (obj.getType() != CBORType.Array) throw new CoseException("Invalid Signer structure");
        
        if (obj.size() != 3) throw new CoseException("Invalid Signer structure");
        
        if (obj.get(0).getType() == CBORType.ByteString) {
            rgbProtected = obj.get(0).GetByteString();
            if (rgbProtected.length == 0) {
                objProtected = CBORObject.NewMap();
            }
            else {
                objProtected = CBORObject.DecodeFromBytes(rgbProtected);
                if (objProtected.size() == 0) rgbProtected = new byte[0];
            }
        }
        else throw new CoseException("Invalid Signer structure");
        
        if (obj.get(1).getType() == CBORType.Map) {
            objUnprotected = obj.get(1);
        }
        else throw new CoseException("Invalid Signer structure");
        
        if (obj.get(2).getType() == CBORType.ByteString) rgbSignature = obj.get(2).GetByteString();
        else if (!obj.get(2).isNull()) throw new CoseException("Invalid Signer structure");

        CBORObject countersignature = this.findAttribute(HeaderKeys.CounterSignature, UNPROTECTED);
        if (countersignature != null) {
            if ((countersignature.getType() != CBORType.Array) ||
                    (countersignature.getValues().isEmpty())) {
                throw new CoseException("Invalid countersignature attribute");
            }
            
            if (countersignature.get(0).getType() == CBORType.Array) {
                for (CBORObject csObj : countersignature.getValues()) {
                    if (csObj.getType() != CBORType.Array) {
                        throw new CoseException("Invalid countersignature attribute");
                    }
                    
                    CounterSign cs = new CounterSign(csObj);
                    cs.setObject(this);
                    this.addCountersignature(cs);
                }
            }
            else {
                CounterSign cs = new CounterSign(countersignature);
                cs.setObject(this);
                this.addCountersignature(cs);
            }
        }
        
        countersignature = this.findAttribute(HeaderKeys.CounterSignature0, UNPROTECTED);
        if (countersignature != null) {
            if (countersignature.getType() != CBORType.ByteString) {
                throw new CoseException("Invalid Countersignature0 attribute");
            }
            
            CounterSign1 cs = new CounterSign1(countersignature.GetByteString());
            cs.setObject(this);
            this.counterSign1 = cs;
        }
    }    
    
    /**
     * Internal function used to create a serialization of a COSE_Sign1 message
     * 
     * @return CBOR object which can be encoded.
     * @throws CoseException Errors generated by the COSE module
     */

    protected CBORObject EncodeToCBORObject() throws CoseException {
        if (rgbSignature == null) throw new CoseException("Message not yet signed");
        if (rgbProtected == null) throw new CoseException("Internal Error");
        CBORObject obj = CBORObject.NewArray();
        
        obj.Add(rgbProtected);
        obj.Add(objUnprotected);
        obj.Add(rgbSignature);
        
        return obj;
    }

    public void sign(byte[] rgbBodyProtected, byte[] rgbContent) throws CoseException
    {
        if (rgbProtected == null) {
            if(objProtected.size() == 0) rgbProtected = new byte[0];
            else rgbProtected = objProtected.EncodeToBytes();
        }
        
        CBORObject obj = CBORObject.NewArray();
        obj.Add(contextString);
        obj.Add(rgbBodyProtected);
        obj.Add(rgbProtected);
        obj.Add(externalData);
        obj.Add(rgbContent);

        AlgorithmID alg = AlgorithmID.FromCBOR(findAttribute(HeaderKeys.Algorithm));
        
        rgbSignature = SignCommon.computeSignature(alg, obj.EncodeToBytes(), cnKey);   
        
        ProcessCounterSignatures();
    }
    
    public boolean validate(byte[] rgbBodyProtected, byte[] rgbContent) throws CoseException
    {
        CBORObject obj = CBORObject.NewArray();
        obj.Add(contextString);
        obj.Add(rgbBodyProtected);
        obj.Add(rgbProtected);
        obj.Add(externalData);
        obj.Add(rgbContent);

        AlgorithmID alg = AlgorithmID.FromCBOR(findAttribute(HeaderKeys.Algorithm));

        return SignCommon.validateSignature(alg, obj.EncodeToBytes(), rgbSignature, cnKey);        
    }

    
    
    List<CounterSign> counterSignList = new ArrayList<CounterSign>();
    CounterSign1 counterSign1;
    
    public void addCountersignature(CounterSign countersignature)
    {
        counterSignList.add(countersignature);
    }
    
    public List<CounterSign> getCountersignerList() {
        return counterSignList;
    }
    
    public CounterSign1 getCountersign1() {
        return counterSign1;
    }
    
    public void setCountersign1(CounterSign1 value) {
        counterSign1 = value;
    }
    
    protected void ProcessCounterSignatures() throws CoseException {
        if (!counterSignList.isEmpty()) {
            if (counterSignList.size() == 1) {
                counterSignList.get(0).sign(rgbProtected, rgbSignature);
                addAttribute(HeaderKeys.CounterSignature, counterSignList.get(0).EncodeToCBORObject(), Attribute.UNPROTECTED);
            }
            else {
                CBORObject list = CBORObject.NewArray();
                for (CounterSign sig : counterSignList) {
                    sig.sign(rgbProtected, rgbSignature);
                    list.Add(sig.EncodeToCBORObject());
                }
                addAttribute(HeaderKeys.CounterSignature, list, Attribute.UNPROTECTED);
            }
        }
        
        if (counterSign1 != null) {
            counterSign1.sign(rgbProtected, rgbSignature);
            addAttribute(HeaderKeys.CounterSignature0, counterSign1.EncodeToCBORObject(), Attribute.UNPROTECTED);
        }
    }
    
    public boolean validate(CounterSign1 countersignature) throws CoseException {
        return countersignature.validate(rgbProtected, rgbSignature);
    }
    
    public boolean validate(CounterSign countersignature) throws CoseException {
        return countersignature.validate(rgbProtected, rgbSignature);
    }
    
}
