using System;
using System.Collections;
using System.Diagnostics;

using Org.BouncyCastle.Asn1.X9;

using Org.BouncyCastle.Math.EC.Multiplier;

namespace Org.BouncyCastle.Math.EC
{
	/**
	 * base class for points on elliptic curves.
	 */
	public abstract class ECPoint
	{
		internal readonly ECCurve			curve;
		internal readonly ECFieldElement	x, y;
		internal readonly bool				withCompression;
		internal ECMultiplier				multiplier = null;
		internal PreCompInfo				preCompInfo = null;

		protected internal ECPoint(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y,
			bool			withCompression)
		{
			if (curve == null)
				throw new ArgumentNullException("curve");

			this.curve = curve;
			this.x = x;
			this.y = y;
			this.withCompression = withCompression;
		}

		public ECCurve Curve
		{
			get { return curve; }
		}

		public ECFieldElement X
		{
			get { return x; }
		}

		public ECFieldElement Y
		{
			get { return y; }
		}

		public bool IsInfinity
		{
			get { return x == null && y == null; }
		}

		public bool IsCompressed
		{
			get { return withCompression; }
		}

		public override bool Equals(
			object obj)
		{
			if (obj == this)
				return true;

			ECPoint o = obj as ECPoint;

			if (o == null)
				return false;

			if (this.IsInfinity)
				return o.IsInfinity;

			return x.Equals(o.x) && y.Equals(o.y);
		}

		public override int GetHashCode()
		{
			if (this.IsInfinity)
				return 0;

			return x.GetHashCode() ^ y.GetHashCode();
		}

//		/**
//		 * Mainly for testing. Explicitly set the <code>ECMultiplier</code>.
//		 * @param multiplier The <code>ECMultiplier</code> to be used to multiply
//		 * this <code>ECPoint</code>.
//		 */
//		internal void SetECMultiplier(
//			ECMultiplier multiplier)
//		{
//			this.multiplier = multiplier;
//		}

		/**
		 * Sets the <code>PreCompInfo</code>. Used by <code>ECMultiplier</code>s
		 * to save the precomputation for this <code>ECPoint</code> to store the
		 * precomputation result for use by subsequent multiplication.
		 * @param preCompInfo The values precomputed by the
		 * <code>ECMultiplier</code>.
		 */
		internal void SetPreCompInfo(
			PreCompInfo preCompInfo)
		{
			this.preCompInfo = preCompInfo;
		}

		public abstract byte[] GetEncoded();

		public abstract ECPoint Add(ECPoint b);
		public abstract ECPoint Subtract(ECPoint b);
		public abstract ECPoint Negate();
		public abstract ECPoint Twice();
		public abstract ECPoint Multiply(BigInteger b);

		/**
		* Sets the appropriate <code>ECMultiplier</code>, unless already set. 
		*/
		internal virtual void AssertECMultiplier()
		{
			if (this.multiplier == null)
			{
				lock (this)
				{
					if (this.multiplier == null)
					{
						this.multiplier = new FpNafMultiplier();
					}
				}
			}
		}
	}

	public abstract class ECPointBase
		: ECPoint
	{
		protected internal ECPointBase(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y,
			bool			withCompression)
			: base(curve, x, y, withCompression)
		{
		}

		protected internal abstract bool YTilde { get; }

		/**
		 * return the field element encoded with point compression. (S 4.3.6)
		 */
		public override byte[] GetEncoded()
		{
			if (this.IsInfinity)
				return new byte[1];

			// Note: some of the tests rely on calculating byte length from the field element
			// (since the test cases use mismatching fields for curve/elements)
			int byteLength = X9IntegerConverter.GetByteLength(x);
			byte[] X = X9IntegerConverter.IntegerToBytes(this.X.ToBigInteger(), byteLength);
			byte[] PO;

			if (withCompression)
			{
				PO = new byte[1 + X.Length];

				PO[0] = (byte)(YTilde ? 0x03 : 0x02);
			}
			else
			{
				byte[] Y = X9IntegerConverter.IntegerToBytes(this.Y.ToBigInteger(), byteLength);
				PO = new byte[1 + X.Length + Y.Length];

				PO[0] = 0x04;

				Y.CopyTo(PO, 1 + X.Length);
			}

			X.CopyTo(PO, 1);

			return PO;
		}

		/**
		 * Multiplies this <code>ECPoint</code> by the given number.
		 * @param k The multiplicator.
		 * @return <code>k * this</code>.
		 */
		public override ECPoint Multiply(
			BigInteger k)
		{
			if (k.SignValue < 0)
				throw new ArgumentException("The multiplicator cannot be negative", "k");

			if (this.IsInfinity)
				return this;

			if (k.SignValue == 0)
				return this.curve.Infinity;

			AssertECMultiplier();
			return this.multiplier.Multiply(this, k, preCompInfo);
		}
	}

	/**
	 * Elliptic curve points over Fp
	 */
	public class FpPoint
		: ECPointBase
	{
		/**
		 * Create a point which encodes with point compression.
		 *
		 * @param curve the curve to use
		 * @param x affine x co-ordinate
		 * @param y affine y co-ordinate
		 */
		public FpPoint(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y)
			: this(curve, x, y, false)
		{
		}

		/**
		 * Create a point that encodes with or without point compresion.
		 *
		 * @param curve the curve to use
		 * @param x affine x co-ordinate
		 * @param y affine y co-ordinate
		 * @param withCompression if true encode with point compression
		 */
		public FpPoint(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y,
			bool			withCompression)
			: base(curve, x, y, withCompression)
		{
			if ((x != null && y == null) || (x == null && y != null))
				throw new ArgumentException("Exactly one of the field elements is null");
		}

		protected internal override bool YTilde
		{
			get
			{
				return this.Y.ToBigInteger().TestBit(0);
			}
		}

		// B.3 pg 62
		public override ECPoint Add(
			ECPoint b)
		{
			if (this.IsInfinity)
				return b;

			if (b.IsInfinity)
				return this;

			// Check if b = this or b = -this
			if (this.x.Equals(b.x))
			{
				if (this.y.Equals(b.y))
				{
					// this = b, i.e. this must be doubled
					return this.Twice();
				}

				Debug.Assert(this.y.Equals(b.y.Negate()));

				// this = -b, i.e. the result is the point at infinity
				return this.curve.Infinity;
			}

			ECFieldElement gamma = b.y.Subtract(this.y).Divide(b.x.Subtract(this.x));

			ECFieldElement x3 = gamma.Square().Subtract(this.x).Subtract(b.x);
			ECFieldElement y3 = gamma.Multiply(this.x.Subtract(x3)).Subtract(this.y);

			return new FpPoint(curve, x3, y3);
		}

		// B.3 pg 62
		public override ECPoint Twice()
		{
			// Twice identity element (point at infinity) is identity
			if (this.IsInfinity)
				return this;

			// if y1 == 0, then (x1, y1) == (x1, -y1)
			// and hence this = -this and thus 2(x1, y1) == infinity
			if (this.y.ToBigInteger().SignValue == 0)
				return this.curve.Infinity;

			ECFieldElement TWO = this.curve.FromBigInteger(BigInteger.Two);
			ECFieldElement THREE = this.curve.FromBigInteger(BigInteger.Three);
			ECFieldElement gamma = this.x.Square().Multiply(THREE).Add(curve.a).Divide(y.Multiply(TWO));

			ECFieldElement x3 = gamma.Square().Subtract(this.x.Multiply(TWO));
			ECFieldElement y3 = gamma.Multiply(this.x.Subtract(x3)).Subtract(this.y);

			return new FpPoint(curve, x3, y3, this.withCompression);
		}

		// D.3.2 pg 102 (see Note:)
		public override ECPoint Subtract(
			ECPoint b)
		{
			if (b.IsInfinity)
				return this;

			// Add -b
			return Add(b.Negate());
		}

		public override ECPoint Negate()
		{
			return new FpPoint(this.curve, this.x, this.y.Negate(), this.withCompression);
		}

		/**
		 * Sets the default <code>ECMultiplier</code>, unless already set. 
		 */
		internal override void AssertECMultiplier()
		{
			if (this.multiplier == null)
			{
				lock (this)
				{
					if (this.multiplier == null)
					{
						this.multiplier = new WNafMultiplier();
					}
				}
			}
		}
	}

	/**
	 * Elliptic curve points over F2m
	 */
	public class F2mPoint
		: ECPointBase
	{
		/**
		 * @param curve base curve
		 * @param x x point
		 * @param y y point
		 */
		public F2mPoint(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y)
			:  this(curve, x, y, false)
		{
		}

		/**
		 * @param curve base curve
		 * @param x x point
		 * @param y y point
		 * @param withCompression true if encode with point compression.
		 */
		public F2mPoint(
			ECCurve			curve,
			ECFieldElement	x,
			ECFieldElement	y,
			bool			withCompression)
			: base(curve, x, y, withCompression)
		{
			if ((x != null && y == null) || (x == null && y != null))
			{
				throw new ArgumentException("Exactly one of the field elements is null");
			}

			if (x != null)
			{
				// Check if x and y are elements of the same field
				F2mFieldElement.CheckFieldElements(this.x, this.y);

				// Check if x and a are elements of the same field
				F2mFieldElement.CheckFieldElements(this.x, this.curve.A);
			}
		}

		/**
		 * Constructor for point at infinity
		 */
		[Obsolete("Use ECCurve.Infinity property")]
		public F2mPoint(
			ECCurve curve)
			: this(curve, null, null)
		{
		}

		protected internal override bool YTilde
		{
			get
			{
				// X9.62 4.2.2 and 4.3.6:
				// if x = 0 then ypTilde := 0, else ypTilde is the rightmost
				// bit of y * x^(-1)
				return this.X.ToBigInteger().SignValue != 0
					&& this.Y.Multiply(this.X.Invert()).ToBigInteger().TestBit(0);
			}
		}

		/**
		 * Check, if two <code>ECPoint</code>s can be added or subtracted.
		 * @param a The first <code>ECPoint</code> to check.
		 * @param b The second <code>ECPoint</code> to check.
		 * @throws IllegalArgumentException if <code>a</code> and <code>b</code>
		 * cannot be added.
		 */
		private static void CheckPoints(
			ECPoint	a,
			ECPoint	b)
		{
			// Check, if points are on the same curve
			if (!a.curve.Equals(b.curve))
				throw new ArgumentException("Only points on the same curve can be added or subtracted");

//			F2mFieldElement.CheckFieldElements(a.x, b.x);
		}

		/* (non-Javadoc)
		 * @see org.bouncycastle.math.ec.ECPoint#add(org.bouncycastle.math.ec.ECPoint)
		 */
		public override ECPoint Add(ECPoint b)
		{
			CheckPoints(this, b);
			return AddSimple((F2mPoint) b);
		}

		/**
		 * Adds another <code>ECPoints.F2m</code> to <code>this</code> without
		 * checking if both points are on the same curve. Used by multiplication
		 * algorithms, because there all points are a multiple of the same point
		 * and hence the checks can be omitted.
		 * @param b The other <code>ECPoints.F2m</code> to add to
		 * <code>this</code>.
		 * @return <code>this + b</code>
		 */
		internal F2mPoint AddSimple(F2mPoint b)
		{
			if (this.IsInfinity)
				return b;

			if (b.IsInfinity)
				return this;

			F2mFieldElement x2 = (F2mFieldElement) b.X;
			F2mFieldElement y2 = (F2mFieldElement) b.Y;

			// Check if b == this or b == -this
			if (this.x.Equals(x2))
			{
				// this == b, i.e. this must be doubled
				if (this.y.Equals(y2))
					return (F2mPoint) this.Twice();

				// this = -other, i.e. the result is the point at infinity
				return (F2mPoint) this.curve.Infinity;
			}

			ECFieldElement xSum = this.x.Add(x2);

			F2mFieldElement lambda
				= (F2mFieldElement)(this.y.Add(y2)).Divide(xSum);

			F2mFieldElement x3
				= (F2mFieldElement)lambda.Square().Add(lambda).Add(xSum).Add(this.curve.A);

			F2mFieldElement y3
				= (F2mFieldElement)lambda.Multiply(this.x.Add(x3)).Add(x3).Add(this.y);

			return new F2mPoint(curve, x3, y3, withCompression);
		}

		/* (non-Javadoc)
		 * @see org.bouncycastle.math.ec.ECPoint#subtract(org.bouncycastle.math.ec.ECPoint)
		 */
		public override ECPoint Subtract(
			ECPoint b)
		{
			CheckPoints(this, b);
			return SubtractSimple((F2mPoint) b);
		}

		/**
		 * Subtracts another <code>ECPoints.F2m</code> from <code>this</code>
		 * without checking if both points are on the same curve. Used by
		 * multiplication algorithms, because there all points are a multiple
		 * of the same point and hence the checks can be omitted.
		 * @param b The other <code>ECPoints.F2m</code> to subtract from
		 * <code>this</code>.
		 * @return <code>this - b</code>
		 */
		internal F2mPoint SubtractSimple(
			F2mPoint b)
		{
			if (b.IsInfinity)
				return this;

			// Add -b
			return AddSimple((F2mPoint) b.Negate());
		}

		/* (non-Javadoc)
		 * @see Org.BouncyCastle.Math.EC.ECPoint#twice()
		 */
		public override ECPoint Twice()
		{
			// Twice identity element (point at infinity) is identity
			if (this.IsInfinity)
				return this;

			// if x1 == 0, then (x1, y1) == (x1, x1 + y1)
			// and hence this = -this and thus 2(x1, y1) == infinity
			if (this.x.ToBigInteger().SignValue == 0)
				return this.curve.Infinity;

			F2mFieldElement lambda = (F2mFieldElement) this.x.Add(this.y.Divide(this.x));
			F2mFieldElement x2 = (F2mFieldElement)lambda.Square().Add(lambda).Add(this.curve.A);
			ECFieldElement ONE = this.curve.FromBigInteger(BigInteger.One);
			F2mFieldElement y2 = (F2mFieldElement)this.x.Square().Add(
				x2.Multiply(lambda.Add(ONE)));

			return new F2mPoint(this.curve, x2, y2, withCompression);
		}

		public override ECPoint Negate()
		{
			return new F2mPoint(curve, this.x, this.x.Add(this.y), withCompression);
		}

		/**
		 * Sets the appropriate <code>ECMultiplier</code>, unless already set. 
		 */
		internal override void AssertECMultiplier()
		{
			if (this.multiplier == null)
			{
				lock (this)
				{
					if (this.multiplier == null)
					{
						if (((F2mCurve) this.curve).IsKoblitz)
						{
							this.multiplier = new WTauNafMultiplier();
						}
						else
						{
							this.multiplier = new WNafMultiplier();
						}
					}
				}
			}
		}
	}
}
