using System;
using System.Collections;
using System.Text;
using ComponentPro.Net;
using ComponentPro.Security.Certificates;

namespace Pop3Client.Security
{
    class CustomCertValidator : ICertificateValidator
    {
        private static readonly Hashtable acceptedCertificates = new Hashtable();

        private string GetCertProblem(CertificateValidationResult res, CertificateValidationStatus status, ref bool addToTrustedListForm)
        {
            switch (status)
            {
                case CertificateValidationStatus.TimeNotValid:
                    return "Server certificate has expired or is not valid yet.";
                    
                case CertificateValidationStatus.Revoked:
                    return "Server certificate has been revoked.";
                    
                case CertificateValidationStatus.UnknownCA:
                    return "Server certificate was issued by an unknown authority.";
                    
                case CertificateValidationStatus.RootNotTrusted:
                    addToTrustedListForm = true;
                    return "Server certificate was issued by an untrusted authority.";
                    
                case CertificateValidationStatus.IncompleteChain:
                    return "Server certificate does not chain up to a trusted root authority.";
                    
                case CertificateValidationStatus.Malformed:
                    return "Server certificate is malformed.";
                    
                case CertificateValidationStatus.CNNotMatch:
                    return "Server hostname does not match the certificate.";
                    
                case CertificateValidationStatus.UnknownError:
                    return string.Format("Error {0:x} encountered while validating server's certificate.", res.ErrorCode);
                    
                default:
                    return status.ToString();                    
            }
        }

        public TlsSslCertificateAcceptance Validate(CertificateValidatorRequestInfo info)
        {
            CertificateValidationResult res = info.CertificateChain.Check(info.ServerCommonName, 0);

            if (res.Valid)
                return TlsSslCertificateAcceptance.Acceptable;

            CertificateValidationStatus status = res.Status;

            CertificateValidationStatus[] values = (CertificateValidationStatus[])Enum.GetValues(typeof(CertificateValidationStatus));

            bool addToTrustedListForm = false;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < values.Length; i++)
            {
                if ((status & values[i]) == 0)
                    continue;

                status ^= values[i];

                sb.AppendFormat("{0}\r\n", GetCertProblem(res, values[i], ref addToTrustedListForm));
            }

            X509Certificate cert = info.CertificateChain[0];

            // If the certificate is already saved then return Accept.
            if (acceptedCertificates.Contains(Convert.ToBase64String(cert.GetHash())))
                return TlsSslCertificateAcceptance.Acceptable;

            CertValidator certForm = new CertValidator();
            certForm.Certificate = cert;
            certForm.ShowAddToTrustedList = addToTrustedListForm;

            certForm.ShowDialog();

            // Add certiticate of the issuer CA to the trusted list.
            if (certForm.AddToTrustedList)
            {
                CertificateStore trustedCaStore = new CertificateStore(CertificateStoreNameType.Root);
                X509Certificate rootCertificate = info.CertificateChain[info.CertificateChain.Count - 1];

                trustedCaStore.AddCertificate(rootCertificate);
            }

            if (certForm.Accepted)
            {
                // Save accepted certificate.
                acceptedCertificates.Add(Convert.ToBase64String(cert.GetHash()), cert);
                return TlsSslCertificateAcceptance.Acceptable;
            }

            if ((res.Status & CertificateValidationStatus.TimeNotValid) != 0)
                return TlsSslCertificateAcceptance.Expired;
            if ((res.Status & CertificateValidationStatus.Revoked) != 0)
                return TlsSslCertificateAcceptance.Revoked;
            if ((res.Status & (CertificateValidationStatus.UnknownCA | CertificateValidationStatus.RootNotTrusted | CertificateValidationStatus.IncompleteChain)) != 0)
                return TlsSslCertificateAcceptance.UnknownAuthority;
            if ((res.Status & (CertificateValidationStatus.Malformed | CertificateValidationStatus.UnknownError)) != 0)
                return TlsSslCertificateAcceptance.Other;

            return TlsSslCertificateAcceptance.Unknow;
        }
    }
}
