#region Copyright & License Information
/*
 * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
 * This file is part of OpenRA, which is free software. It is made
 * available to you under the terms of the GNU General Public License
 * as published by the Free Software Foundation. For more information,
 * see COPYING.
 */
#endregion

using System;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
using OpenRA.Scripting;
using OpenRA.Support;

namespace OpenRA
{
	/// <summary>
	/// 1d world distance - 1024 units = 1 cell.
	/// </summary>
	public struct WRange : IComparable, IComparable<WRange>, IEquatable<WRange>, IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding
	{
		public readonly int Range;

		public WRange(int r) { Range = r; }
		public static readonly WRange Zero = new WRange(0);
		public static WRange FromCells(int cells) { return new WRange(1024 * cells); }

		public static WRange operator +(WRange a, WRange b) { return new WRange(a.Range + b.Range); }
		public static WRange operator -(WRange a, WRange b) { return new WRange(a.Range - b.Range); }
		public static WRange operator -(WRange a) { return new WRange(-a.Range); }
		public static WRange operator /(WRange a, int b) { return new WRange(a.Range / b); }
		public static WRange operator *(WRange a, int b) { return new WRange(a.Range * b); }
		public static WRange operator *(int a, WRange b) { return new WRange(a * b.Range); }
		public static bool operator <(WRange a, WRange b) { return a.Range < b.Range; }
		public static bool operator >(WRange a, WRange b) { return a.Range > b.Range; }
		public static bool operator <=(WRange a, WRange b) { return a.Range <= b.Range; }
		public static bool operator >=(WRange a, WRange b) { return a.Range >= b.Range; }

		public static bool operator ==(WRange me, WRange other) { return me.Range == other.Range; }
		public static bool operator !=(WRange me, WRange other) { return !(me == other); }

		// Sampled a N-sample probability density function in the range [-1024..1024]
		// 1 sample produces a rectangular probability
		// 2 samples produces a triangular probability
		// ...
		// N samples approximates a true gaussian
		public static WRange FromPDF(MersenneTwister r, int samples)
		{
			return new WRange(Exts.MakeArray(samples, _ => r.Next(-1024, 1024))
				.Sum() / samples);
		}

		public static bool TryParse(string s, out WRange result)
		{
			s = s.ToLowerInvariant();
			var components = s.Split('c');
			var cell = 0;
			var subcell = 0;
			result = WRange.Zero;

			switch (components.Length)
			{
			case 2:
				if (!Exts.TryParseIntegerInvariant(components[0], out cell) ||
					!Exts.TryParseIntegerInvariant(components[1], out subcell))
						return false;
				break;
			case 1:
				if (!Exts.TryParseIntegerInvariant(components[0], out subcell))
					return false;
				break;
			default: return false;
			}

			// Propagate sign to fractional part
			if (cell < 0)
				subcell = -subcell;

			result = new WRange(1024 * cell + subcell);
			return true;
		}

		public override int GetHashCode() { return Range.GetHashCode(); }

		public bool Equals(WRange other) { return other == this; }
		public override bool Equals(object obj) { return obj is WRange && Equals((WRange)obj); }

		public int CompareTo(object obj)
		{
			if (!(obj is WRange))
				return 1;
			return Range.CompareTo(((WRange)obj).Range);
		}

		public int CompareTo(WRange other) { return Range.CompareTo(other.Range); }

		public override string ToString() { return Range.ToString(); }

		#region Scripting interface
		public LuaValue Add(LuaRuntime runtime, LuaValue left, LuaValue right)
		{
			WRange a;
			WRange b;
			if (!left.TryGetClrValue<WRange>(out a) || !right.TryGetClrValue<WRange>(out b))
				throw new LuaException("Attempted to call WRange.Add(WRange, WRange) with invalid arguments.");

			return new LuaCustomClrObject(a + b);
		}

		public LuaValue Subtract(LuaRuntime runtime, LuaValue left, LuaValue right)
		{
			WRange a;
			WRange b;
			if (!left.TryGetClrValue<WRange>(out a) || !right.TryGetClrValue<WRange>(out b))
				throw new LuaException("Attempted to call WRange.Subtract(WRange, WRange) with invalid arguments.");

			return new LuaCustomClrObject(a - b);
		}

		public LuaValue Equals(LuaRuntime runtime, LuaValue left, LuaValue right)
		{
			WRange a;
			WRange b;
			if (!left.TryGetClrValue<WRange>(out a) || !right.TryGetClrValue<WRange>(out b))
				throw new LuaException("Attempted to call WRange.Equals(WRange, WRange) with invalid arguments.");

			return a == b;
		}

		public LuaValue this[LuaRuntime runtime, LuaValue key]
		{
			get
			{
				switch (key.ToString())
				{
					case "Range": return Range;
					default: throw new LuaException("WPos does not define a member '{0}'".F(key));
				}
			}

			set
			{
				throw new LuaException("WRange is read-only. Use WRange.New to create a new value");
			}
		}
		#endregion
	}
}
