using System;
using System.Collections;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;

namespace Org.BouncyCastle.Cmp
{
    public class ProtectedPkiMessageBuilder
    {
        private PkiHeaderBuilder hdrBuilBuilder;
        private PkiBody body;
        private IList generalInfos = Platform.CreateArrayList();
        private IList extraCerts = Platform.CreateArrayList();

        public ProtectedPkiMessageBuilder(GeneralName sender, GeneralName recipient)
            : this(PkiHeader.CMP_2000, sender, recipient)
        {
        }

        public ProtectedPkiMessageBuilder(int pvno, GeneralName sender, GeneralName recipient)
        {
            hdrBuilBuilder = new PkiHeaderBuilder(pvno, sender, recipient);
        }

        public ProtectedPkiMessageBuilder SetTransactionId(byte[] tid)
        {
            hdrBuilBuilder.SetTransactionID(tid);
            return this;
        }

        public ProtectedPkiMessageBuilder SetFreeText(PkiFreeText freeText)
        {
            hdrBuilBuilder.SetFreeText(freeText);
            return this;
        }

        public ProtectedPkiMessageBuilder AddGeneralInfo(InfoTypeAndValue genInfo)
        {
            generalInfos.Add(genInfo);
            return this;
        }

        public ProtectedPkiMessageBuilder SetMessageTime(DerGeneralizedTime generalizedTime)
        {
            hdrBuilBuilder.SetMessageTime(generalizedTime);
            return this;
        }

        public ProtectedPkiMessageBuilder SetRecipKID(byte[] id)
        {
            hdrBuilBuilder.SetRecipKID(id);
            return this;
        }

        public ProtectedPkiMessageBuilder SetRecipNonce(byte[] nonce)
        {
            hdrBuilBuilder.SetRecipNonce(nonce);
            return this;
        }

        public ProtectedPkiMessageBuilder SetSenderKID(byte[] id)
        {
            hdrBuilBuilder.SetSenderKID(id);
            return this;
        }

        public ProtectedPkiMessageBuilder SetSenderNonce(byte[] nonce)
        {
            hdrBuilBuilder.SetSenderNonce(nonce);
            return this;
        }

        public ProtectedPkiMessageBuilder SetBody(PkiBody body)
        {
            this.body = body;
            return this;
        }

        public ProtectedPkiMessageBuilder AddCmpCertificate(X509Certificate certificate)
        {
            extraCerts.Add(certificate);
            return this;
        }

        public ProtectedPkiMessage Build(ISignatureFactory signatureFactory)
        {
            if (null == body)
                throw new InvalidOperationException("body must be set before building");

            IStreamCalculator calculator = signatureFactory.CreateCalculator();

            if (!(signatureFactory.AlgorithmDetails is AlgorithmIdentifier))
            {
                throw new ArgumentException("AlgorithmDetails is not AlgorithmIdentifier");
            }

            FinalizeHeader((AlgorithmIdentifier)signatureFactory.AlgorithmDetails);
            PkiHeader header = hdrBuilBuilder.Build();
            DerBitString protection = new DerBitString(CalculateSignature(calculator, header, body));
            return FinalizeMessage(header, protection);
        }

        public ProtectedPkiMessage Build(IMacFactory factory)
        {
            if (null == body)
                throw new InvalidOperationException("body must be set before building");

            IStreamCalculator calculator = factory.CreateCalculator();
            FinalizeHeader((AlgorithmIdentifier)factory.AlgorithmDetails);
            PkiHeader header = hdrBuilBuilder.Build();
            DerBitString protection = new DerBitString(CalculateSignature(calculator, header, body));
            return FinalizeMessage(header, protection);
        }

        private void FinalizeHeader(AlgorithmIdentifier algorithmIdentifier)
        {
            hdrBuilBuilder.SetProtectionAlg(algorithmIdentifier);
            if (generalInfos.Count > 0)
            {
                InfoTypeAndValue[] genInfos = new InfoTypeAndValue[generalInfos.Count];
                for (int t = 0; t < genInfos.Length; t++)
                {
                    genInfos[t] = (InfoTypeAndValue)generalInfos[t];
                }

                hdrBuilBuilder.SetGeneralInfo(genInfos);
            }
        }

        private ProtectedPkiMessage FinalizeMessage(PkiHeader header, DerBitString protection)
        {
            if (extraCerts.Count > 0)
            {
                CmpCertificate[] cmpCertificates = new CmpCertificate[extraCerts.Count];
                for (int i = 0; i < cmpCertificates.Length; i++)
                {
                    byte[] cert = ((X509Certificate)extraCerts[i]).GetEncoded();
                    cmpCertificates[i] = CmpCertificate.GetInstance((Asn1Sequence.FromByteArray(cert)));
                }

                return new ProtectedPkiMessage(new PkiMessage(header, body, protection, cmpCertificates));
            }

            return new ProtectedPkiMessage(new PkiMessage(header, body, protection));
        }

        private byte[] CalculateSignature(IStreamCalculator signer, PkiHeader header, PkiBody body)
        {
            Asn1EncodableVector avec = new Asn1EncodableVector();
            avec.Add(header);
            avec.Add(body);
            byte[] encoded = new DerSequence(avec).GetEncoded();
            signer.Stream.Write(encoded, 0, encoded.Length);
            object result = signer.GetResult();

            if (result is DefaultSignatureResult)
            {
                return ((DefaultSignatureResult)result).Collect();
            }
            else if (result is IBlockResult)
            {
                return ((IBlockResult)result).Collect();
            }
            else if (result is byte[])
            {
                return (byte[])result;
            }

            throw new InvalidOperationException("result is not byte[] or DefaultSignatureResult");
        }
    }
}
