using System;
using System.Globalization;

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Nist;
using Org.BouncyCastle.Asn1.Sec;
using Org.BouncyCastle.Asn1.TeleTrust;
using Org.BouncyCastle.Asn1.X9;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;

namespace Org.BouncyCastle.Crypto.Generators
{
    public class ECKeyPairGenerator
		: IAsymmetricCipherKeyPairGenerator
    {
		private readonly string algorithm;

		private ECDomainParameters parameters;
		private DerObjectIdentifier publicKeyParamSet;
        private SecureRandom random;

		public ECKeyPairGenerator()
			: this("EC")
		{
		}

		public ECKeyPairGenerator(
			string algorithm)
		{
			if (algorithm == null)
				throw new ArgumentNullException("algorithm");

			this.algorithm = VerifyAlgorithmName(algorithm);
		}

		public void Init(
            KeyGenerationParameters parameters)
        {
			if (parameters is ECKeyGenerationParameters)
			{
				ECKeyGenerationParameters ecP = (ECKeyGenerationParameters) parameters;

				this.publicKeyParamSet = ecP.PublicKeyParamSet;
				this.parameters = ecP.DomainParameters;
			}
			else
			{
				DerObjectIdentifier oid;
				switch (parameters.Strength)
				{
					case 192:
						oid = X9ObjectIdentifiers.Prime192v1;
						break;
					case 224:
						oid = SecObjectIdentifiers.SecP224r1;
						break;
					case 239:
						oid = X9ObjectIdentifiers.Prime239v1;
						break;
					case 256:
						oid = X9ObjectIdentifiers.Prime256v1;
						break;
					case 384:
						oid = SecObjectIdentifiers.SecP384r1;
						break;
					case 521:
						oid = SecObjectIdentifiers.SecP521r1;
						break;
					default:
						throw new InvalidParameterException("unknown key size.");
				}

				X9ECParameters ecps = FindECCurveByOid(oid);

				this.parameters = new ECDomainParameters(
					ecps.Curve, ecps.G, ecps.N, ecps.H, ecps.GetSeed());
			}

			this.random = parameters.Random;
		}

		/**
         * Given the domain parameters this routine Generates an EC key
         * pair in accordance with X9.62 section 5.2.1 pages 26, 27.
         */
        public AsymmetricCipherKeyPair GenerateKeyPair()
        {
            BigInteger n = parameters.N;
            BigInteger d;

            do
            {
                d = new BigInteger(n.BitLength, random);
            }
            while (d.SignValue == 0 || (d.CompareTo(n) >= 0));

            ECPoint q = parameters.G.Multiply(d);

			if (publicKeyParamSet != null)
			{
				return new AsymmetricCipherKeyPair(
					new ECPublicKeyParameters(algorithm, q, publicKeyParamSet),
					new ECPrivateKeyParameters(algorithm, d, publicKeyParamSet));
			}

			return new AsymmetricCipherKeyPair(
				new ECPublicKeyParameters(algorithm, q, parameters),
				new ECPrivateKeyParameters(algorithm, d, parameters));
		}

		private string VerifyAlgorithmName(
			string algorithm)
		{
			string upper = algorithm.ToUpper(CultureInfo.InvariantCulture);

			switch (upper)
			{
				case "EC":
				case "ECDSA":
				case "ECDH":
				case "ECDHC":
				case "ECGOST3410":
				case "ECMQV":
					break;
				default:
					throw new ArgumentException("unrecognised algorithm: " + algorithm, "algorithm");
			}

			return upper;
		}

		internal static X9ECParameters FindECCurveByOid(DerObjectIdentifier oid)
		{
			// TODO ECGost3410NamedCurves support (returns ECDomainParameters though)

			X9ECParameters ecP = X962NamedCurves.GetByOid(oid);

			if (ecP == null)
			{
				ecP = SecNamedCurves.GetByOid(oid);

				if (ecP == null)
				{
					ecP = NistNamedCurves.GetByOid(oid);

					if (ecP == null)
					{
						ecP = TeleTrusTNamedCurves.GetByOid(oid);
					}
				}
			}

			return ecP;
		}

		internal static ECPublicKeyParameters GetCorrespondingPublicKey(
			ECPrivateKeyParameters privKey)
		{
			ECDomainParameters parameters = privKey.Parameters;
			ECPoint q = parameters.G.Multiply(privKey.D);

			if (privKey.PublicKeyParamSet != null)
			{
				return new ECPublicKeyParameters(privKey.AlgorithmName, q, privKey.PublicKeyParamSet);
			}

			return new ECPublicKeyParameters(privKey.AlgorithmName, q, parameters);
		}
	}
}
