using GostCryptography.Base;
using GostCryptography.Properties;
using GostCryptography.Reflection;
using System.Collections;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace GostCryptography.Xml
{
    sealed class GostSignedXmlImpl : SignedXml
    {
        public GostSignedXmlImpl()
        {
        }

        public GostSignedXmlImpl(XmlElement element) : base(element)
        {
        }

        public GostSignedXmlImpl(XmlDocument document) : base(document)
        {
        }

        public GetIdElementDelegate GetIdElementHandler { get; set; }

        private IEnumerator KeyInfoEnumerable
        {
            get => this.GetKeyInfoEnumerable();
            set => this.SetKeyInfoEnumerable(value);
        }

        private IEnumerator X509Enumerable
        {
            get => this.GetX509Enumerable();
            set => this.SetX509Enumerable(value);
        }

        private X509Certificate2Collection X509Collection
        {
            get => this.GetX509Collection();
            set => this.SetX509Collection(value);
        }

        [SecuritySafeCritical]
        public void ComputeSignatureGost()
        {
            var signingKey = SigningKey;

            if (signingKey == null)
            {
                ComputeSignatureBase();
            }
            else
            {
                if ((SignedInfo.SignatureMethod == null) && (signingKey is GostAsymmetricAlgorithm))
                {
                    SignedInfo.SignatureMethod = signingKey.SignatureAlgorithm;
                }

                ComputeSignatureBase();
            }
        }

        [SecurityCritical]
        private void ComputeSignatureBase()
        {
            ComputeSignature();
        }

        protected override AsymmetricAlgorithm GetPublicKey()
        {
            if (KeyInfo == null)
            {
                throw ExceptionUtility.CryptographicException(Resources.XmlKeyInfoRequired);
            }

            if (X509Enumerable != null)
            {
                var nextCertificatePublicKey = GetNextCertificatePublicKey();

                if (nextCertificatePublicKey != null)
                {
                    return nextCertificatePublicKey;
                }
            }

            if (KeyInfoEnumerable == null)
            {
                KeyInfoEnumerable = KeyInfo.GetEnumerator();
            }

            var keyInfoEnum = KeyInfoEnumerable;

            while (keyInfoEnum.MoveNext())
            {
                if (keyInfoEnum.Current is RSAKeyValue rsaKeyValue)
                {
                    return rsaKeyValue.Key;
                }

                if (keyInfoEnum.Current is DSAKeyValue dsaKeyValue)
                {
                    return dsaKeyValue.Key;
                }

                if (keyInfoEnum.Current is GostKeyValue gostKeyValue)
                {
                    return gostKeyValue.PublicKey;
                }

                if (keyInfoEnum.Current is KeyInfoX509Data keyInfoX509Data)
                {
                    X509Collection = CryptographyXmlUtils.BuildBagOfCertsVerification(keyInfoX509Data);

                    if (X509Collection.Count > 0)
                    {
                        X509Enumerable = X509Collection.GetEnumerator();

                        var nextCertificatePublicKey = GetNextCertificatePublicKey();

                        if (nextCertificatePublicKey != null)
                        {
                            return nextCertificatePublicKey;
                        }
                    }
                }
            }

            return null;
        }

        [SecuritySafeCritical]
        private AsymmetricAlgorithm GetNextCertificatePublicKey()
        {
            while (X509Enumerable.MoveNext())
            {
                if (X509Enumerable.Current is X509Certificate2 certificate)
                {
                    return certificate.GetPublicKeyAlgorithm();
                }
            }

            return null;
        }

        public override XmlElement GetIdElement(XmlDocument document, string idValue)
        {
            if (GetIdElementHandler != null)
            {
                return GetIdElementHandler(document, idValue);
            }

            return base.GetIdElement(document, idValue);
        }
    }
}
