using GostCryptography.Asn1.Ber;
using GostCryptography.Properties;
using System;

namespace GostCryptography.Asn1.Gost.PublicKey
{
    public sealed class AlgorithmIdentifier : Asn1Type
    {
        public AlgorithmIdentifier()
        {
        }

        public AlgorithmIdentifier(Asn1ObjectIdentifier algorithm, Asn1OpenType parameters)
        {
            Algorithm = algorithm;
            Parameters = parameters;
        }
        
        public Asn1ObjectIdentifier Algorithm { get; private set; }

        public Asn1Type Parameters { get; private set; }

        public override void Decode(Asn1BerDecodeBuffer buffer, bool explicitTagging, int implicitLength)
        {
            var elemLength = explicitTagging ? MatchTag(buffer, Asn1Tag.Sequence) : implicitLength;

            Algorithm = null;
            Parameters = null;

            var context = new Asn1BerDecodeContext(buffer, elemLength);
            var parsedLen = new IntHolder();

            if (!context.MatchElemTag(0, 0, ObjectIdentifierTypeCode, parsedLen, false))
            {
                throw ExceptionUtility.CryptographicException(Resources.Asn1MissingRequiredException, buffer.ByteCount);
            }

            Algorithm = new Asn1ObjectIdentifier();
            Algorithm.Decode(buffer, true, parsedLen.Value);

            if (!context.Expired())
            {
                Parameters = new Asn1OpenType();
                Parameters.Decode(buffer, true, 0);
            }

            CheckAlg(true);
        }

        public override int Encode(Asn1BerEncodeBuffer buffer, bool explicitTagging)
        {
            var len = 0;
            CheckAlg(false);

            if (Parameters != null)
            {
                len += Parameters.Encode(buffer, true);
            }

            len += Algorithm.Encode(buffer, true);

            if (explicitTagging)
            {
                len += buffer.EncodeTagAndLength(Asn1Tag.Sequence, len);
            }

            return len;
        }

        private void CheckAlg(bool decode)
        {
            AlgorithmId algorithmId = null;

            foreach (var alg in PkiConstants.SupportedAlgorithms)
            {
                if (alg.Id.Equals(Algorithm))
                {
                    algorithmId = alg;
                    break;
                }
            }

            if ((algorithmId != null) && ((decode && (Parameters != null)) && (algorithmId.Type != null)))
            {
                try
                {
                    var buffer = new Asn1BerDecodeBuffer(((Asn1OpenType)Parameters).Value);
                    Parameters = (Asn1Type)Activator.CreateInstance(algorithmId.Type.GetType());
                    Parameters.Decode(buffer, true, 0);
                    buffer.InvokeEndElement("parameters", -1);
                }
                catch (Exception exception)
                {
                    Asn1Util.WriteStackTrace(exception, Console.Error);
                    throw ExceptionUtility.CryptographicException(Resources.Asn1TableConstraint);
                }
            }
        }
    }
}
