using System;

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Security;

namespace Org.BouncyCastle.Crypto.Signers
{
	/**
	 * EC-NR as described in IEEE 1363-2000
	 */
	public class ECNRSigner
		: IDsa
	{
		private bool			forSigning;
		private ECKeyParameters	key;
		private SecureRandom	random;

		public string AlgorithmName
		{
			get { return "ECNR"; }
		}

		public void Init(
			bool				forSigning,
			ICipherParameters	parameters)
		{
			this.forSigning = forSigning;

			if (forSigning)
			{
				if (parameters is ParametersWithRandom)
				{
					ParametersWithRandom rParam = (ParametersWithRandom) parameters;

					this.random = rParam.Random;
//					this.key = (ECPrivateKeyParameters) rParam.Parameters;
					parameters = rParam.Parameters;
				}
				else
				{
					this.random = new SecureRandom();
//					this.key = (ECPrivateKeyParameters) parameters;
				}

				if (!(parameters is ECPrivateKeyParameters))
					throw new InvalidKeyException("EC private key required for signing");

				this.key = (ECPrivateKeyParameters) parameters;
			}
			else
			{
				if (!(parameters is ECPublicKeyParameters))
					throw new InvalidKeyException("EC public key required for verification");

				this.key = (ECPublicKeyParameters) parameters;
			}
		}

		// Section 7.2.5 ECSP-NR, pg 34
		/**
		 * generate a signature for the given message using the key we were
		 * initialised with.  Generally, the order of the curve should be at
		 * least as long as the hash of the message of interest, and with
		 * ECNR it *must* be at least as long.
		 *
		 * @param digest  the digest to be signed.
		 * @exception DataLengthException if the digest is longer than the key allows
		 */
		public BigInteger[] GenerateSignature(
			byte[] message)
		{
			if (!this.forSigning)
			{
				// not properly initilaized... deal with it
				throw new InvalidOperationException("not initialised for signing");
			}

			BigInteger n = ((ECPrivateKeyParameters) this.key).Parameters.N;
			int nBitLength = n.BitLength;

			BigInteger e = new BigInteger(1, message);
			int eBitLength = e.BitLength;

			ECPrivateKeyParameters  privKey = (ECPrivateKeyParameters)key;

			if (eBitLength > nBitLength)
			{
				throw new DataLengthException("input too large for ECNR key.");
			}

			BigInteger r = null;
			BigInteger s = null;

			AsymmetricCipherKeyPair tempPair;
			do // generate r
			{
				// generate another, but very temporary, key pair using
				// the same EC parameters
				ECKeyPairGenerator keyGen = new ECKeyPairGenerator();

				keyGen.Init(new ECKeyGenerationParameters(privKey.Parameters, this.random));

				tempPair = keyGen.GenerateKeyPair();

				//    BigInteger Vx = tempPair.getPublic().getW().getAffineX();
				ECPublicKeyParameters V = (ECPublicKeyParameters) tempPair.Public; // get temp's public key
				BigInteger Vx = V.Q.X.ToBigInteger(); // get the point's x coordinate

				r = Vx.Add(e).Mod(n);
			}
			while (r.SignValue == 0);

			// generate s
			BigInteger x = privKey.D;                // private key value
			BigInteger u = ((ECPrivateKeyParameters) tempPair.Private).D; // temp's private key value
			s = u.Subtract(r.Multiply(x)).Mod(n);

			return new BigInteger[]{ r, s };
		}

		// Section 7.2.6 ECVP-NR, pg 35
		/**
		 * return true if the value r and s represent a signature for the
		 * message passed in. Generally, the order of the curve should be at
		 * least as long as the hash of the message of interest, and with
		 * ECNR, it *must* be at least as long.  But just in case the signer
		 * applied mod(n) to the longer digest, this implementation will
		 * apply mod(n) during verification.
		 *
		 * @param digest  the digest to be verified.
		 * @param r       the r value of the signature.
		 * @param s       the s value of the signature.
		 * @exception DataLengthException if the digest is longer than the key allows
		 */
		public bool VerifySignature(
			byte[]		message,
			BigInteger	r,
			BigInteger	s)
		{
			if (this.forSigning)
			{
				// not properly initilaized... deal with it
				throw new InvalidOperationException("not initialised for verifying");
			}

			ECPublicKeyParameters pubKey = (ECPublicKeyParameters)key;
			BigInteger n = pubKey.Parameters.N;
			int nBitLength = n.BitLength;

			BigInteger e = new BigInteger(1, message);
			int eBitLength = e.BitLength;

			if (eBitLength > nBitLength)
			{
				throw new DataLengthException("input too large for ECNR key.");
			}

			// r in the range [1,n-1]
			if (r.CompareTo(BigInteger.One) < 0 || r.CompareTo(n) >= 0)
			{
				return false;
			}

			// TODO So why is this different from the spec?
			// s in the range [0,n-1]           NB: ECNR spec says 0
			if (s.CompareTo(BigInteger.Zero) < 0 || s.CompareTo(n) >= 0)
			{
				return false;
			}

			// compute P = sG + rW

			ECPoint G = pubKey.Parameters.G;
			ECPoint W = pubKey.Q;
			// calculate P using Bouncy math
			ECPoint P = G.Multiply(s).Add(W.Multiply(r));

			BigInteger x = P.X.ToBigInteger();
			BigInteger t = r.Subtract(x).Mod(n);

			return t.Equals(e);
		}
	}
}
