using System;
using System.Collections;
using System.IO;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Cmp;
using Org.BouncyCastle.Asn1.Cms;
using Org.BouncyCastle.Asn1.Tsp;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Utilities.Date;

namespace Org.BouncyCastle.Tsp
{
    /**
     * Generator for RFC 3161 Time Stamp Responses.
     */
    public class TimeStampResponseGenerator
    {
        private PkiStatus status;

        private Asn1EncodableVector statusStrings;

        private int failInfo;
        private TimeStampTokenGenerator tokenGenerator;
        private IList acceptedAlgorithms;
        private IList acceptedPolicies;
        private IList acceptedExtensions;

        public TimeStampResponseGenerator(
            TimeStampTokenGenerator tokenGenerator,
            IList acceptedAlgorithms)
            : this(tokenGenerator, acceptedAlgorithms, null, null)
        {
        }

        public TimeStampResponseGenerator(
            TimeStampTokenGenerator tokenGenerator,
            IList acceptedAlgorithms,
            IList acceptedPolicy)
            : this(tokenGenerator, acceptedAlgorithms, acceptedPolicy, null)
        {
        }

        public TimeStampResponseGenerator(
            TimeStampTokenGenerator tokenGenerator,
            IList acceptedAlgorithms,
            IList acceptedPolicies,
            IList acceptedExtensions)
        {
            this.tokenGenerator = tokenGenerator;
            this.acceptedAlgorithms = acceptedAlgorithms;
            this.acceptedPolicies = acceptedPolicies;
            this.acceptedExtensions = acceptedExtensions;

            statusStrings = new Asn1EncodableVector();
        }

        private void AddStatusString(string statusString)
        {
            statusStrings.Add(new DerUtf8String(statusString));
        }

        private void SetFailInfoField(int field)
        {
            failInfo |= field;
        }

        private PkiStatusInfo GetPkiStatusInfo()
        {
            Asn1EncodableVector v = new Asn1EncodableVector(
                new DerInteger((int)status));

            if (statusStrings.Count > 0)
            {
                v.Add(new PkiFreeText(new DerSequence(statusStrings)));
            }

            if (failInfo != 0)
            {
                v.Add(new FailInfo(failInfo));
            }

            return new PkiStatusInfo(new DerSequence(v));
        }

        public TimeStampResponse Generate(
            TimeStampRequest request,
            BigInteger serialNumber,
            DateTime genTime)
        {
            return Generate(request, serialNumber, new DateTimeObject(genTime));
        }

        /**
         * Return an appropriate TimeStampResponse.
         * <p>
         * If genTime is null a timeNotAvailable error response will be returned.
         *
         * @param request the request this response is for.
         * @param serialNumber serial number for the response token.
         * @param genTime generation time for the response token.
         * @param provider provider to use for signature calculation.
         * @return
         * @throws NoSuchAlgorithmException
         * @throws NoSuchProviderException
         * @throws TSPException
         * </p>
         */
        public TimeStampResponse Generate(
            TimeStampRequest request,
            BigInteger serialNumber,
            DateTimeObject genTime)
        {
            TimeStampResp resp;

            try
            {
                if (genTime == null)
                    throw new TspValidationException("The time source is not available.",
                        PkiFailureInfo.TimeNotAvailable);

                request.Validate(acceptedAlgorithms, acceptedPolicies, acceptedExtensions);

                this.status = PkiStatus.Granted;
                this.AddStatusString("Operation Okay");

                PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo();

                ContentInfo tstTokenContentInfo;
                try
                {
                    TimeStampToken token = tokenGenerator.Generate(request, serialNumber, genTime.Value);
                    byte[] encoded = token.ToCmsSignedData().GetEncoded();

                    tstTokenContentInfo = ContentInfo.GetInstance(Asn1Object.FromByteArray(encoded));
                }
                catch (IOException e)
                {
                    throw new TspException("Timestamp token received cannot be converted to ContentInfo", e);
                }

                resp = new TimeStampResp(pkiStatusInfo, tstTokenContentInfo);
            }
            catch (TspValidationException e)
            {
                status = PkiStatus.Rejection;

                this.SetFailInfoField(e.FailureCode);
                this.AddStatusString(e.Message);

                PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo();

                resp = new TimeStampResp(pkiStatusInfo, null);
            }

            try
            {
                return new TimeStampResponse(resp);
            }
            catch (IOException e)
            {
                throw new TspException("created badly formatted response!", e);
            }
        }

        class FailInfo
            : DerBitString
        {
            internal FailInfo(
                int failInfoValue)
                : base(GetBytes(failInfoValue), GetPadBits(failInfoValue))
            {
            }
        }

        /**
         * Generate a TimeStampResponse with chosen status and FailInfoField.
         *
         * @param status the PKIStatus to set.
         * @param failInfoField the FailInfoField to set.
         * @param statusString an optional string describing the failure.
         * @return a TimeStampResponse with a failInfoField and optional statusString
         * @throws TSPException in case the response could not be created
         */
        public TimeStampResponse GenerateFailResponse(PkiStatus status, int failInfoField, string statusString)
        {
            this.status = status;

            this.SetFailInfoField(failInfoField);

            if (statusString != null)
            {
                this.AddStatusString(statusString);
            }

            PkiStatusInfo pkiStatusInfo = GetPkiStatusInfo();

            TimeStampResp resp = new TimeStampResp(pkiStatusInfo, null);

            try
            {
                return new TimeStampResponse(resp);
            }
            catch (IOException e)
            {
                throw new TspException("created badly formatted response!", e);
            }
        }
    }
}
