// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace UniLua
{
	using Math = System.Math;
	using Double = System.Double;
	using Random = System.Random;
	using BitConverter = System.BitConverter;

	internal class LuaMathLib
	{
		public const string LIB_NAME = "math";

		private const double RADIANS_PER_DEGREE = Math.PI / 180.0;

		private static Random RandObj;

		public static int OpenLib( ILuaState lua )
		{
			NameFuncPair[] define = new NameFuncPair[]
			{
				new NameFuncPair( "abs",   		Math_Abs ),
				new NameFuncPair( "acos",  		Math_Acos ),
				new NameFuncPair( "asin",  		Math_Asin ),
				new NameFuncPair( "atan2", 		Math_Atan2 ),
				new NameFuncPair( "atan",  		Math_Atan ),
				new NameFuncPair( "ceil",  		Math_Ceil ),
				new NameFuncPair( "cosh",  		Math_Cosh ),
				new NameFuncPair( "cos",   		Math_Cos ),
				new NameFuncPair( "deg",   		Math_Deg ),
				new NameFuncPair( "exp",   		Math_Exp ),
				new NameFuncPair( "floor", 		Math_Floor ),
				new NameFuncPair( "fmod",  		Math_Fmod ),
				new NameFuncPair( "frexp", 		Math_Frexp ),
				new NameFuncPair( "ldexp", 		Math_Ldexp ),
				new NameFuncPair( "log10", 		Math_Log10 ),
				new NameFuncPair( "log",   		Math_Log ),
				new NameFuncPair( "max",   		Math_Max ),
				new NameFuncPair( "min",   		Math_Min ),
				new NameFuncPair( "modf",  		Math_Modf ),
				new NameFuncPair( "pow",   		Math_Pow ),
				new NameFuncPair( "rad",   		Math_Rad ),
				new NameFuncPair( "random",     Math_Random ),
				new NameFuncPair( "randomseed", Math_RandomSeed ),
				new NameFuncPair( "sinh", 		Math_Sinh ),
				new NameFuncPair( "sin",   		Math_Sin ),
				new NameFuncPair( "sqrt",  		Math_Sqrt ),
				new NameFuncPair( "tanh",   	Math_Tanh ),
				new NameFuncPair( "tan",   		Math_Tan ),
			};

			lua.L_NewLib( define );

			lua.PushNumber( Math.PI );
			lua.SetField( -2, "pi" );

			lua.PushNumber( Double.MaxValue );
			lua.SetField( -2, "huge" );

			RandObj = new Random();

			return 1;
		}

		private static int Math_Abs( ILuaState lua )
		{
			lua.PushNumber( Math.Abs( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Acos( ILuaState lua )
		{
			lua.PushNumber( Math.Acos( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Asin( ILuaState lua )
		{
			lua.PushNumber( Math.Asin( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Atan2( ILuaState lua )
		{
			lua.PushNumber( Math.Atan2( lua.L_CheckNumber(1),
										lua.L_CheckNumber(2)));
			return 1;
		}

		private static int Math_Atan( ILuaState lua )
		{
			lua.PushNumber( Math.Atan( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Ceil( ILuaState lua )
		{
			lua.PushNumber( Math.Ceiling( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Cosh( ILuaState lua )
		{
			lua.PushNumber( Math.Cosh( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Cos( ILuaState lua )
		{
			lua.PushNumber( Math.Cos( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Deg( ILuaState lua )
		{
			lua.PushNumber( lua.L_CheckNumber(1) / RADIANS_PER_DEGREE );
			return 1;
		}

		private static int Math_Exp( ILuaState lua )
		{
			lua.PushNumber( Math.Exp( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Floor( ILuaState lua )
		{
			lua.PushNumber( Math.Floor( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Fmod( ILuaState lua )
		{
			lua.PushNumber( Math.IEEERemainder( lua.L_CheckNumber(1),
												lua.L_CheckNumber(2)));
			return 1;
		}

		private static int Math_Frexp( ILuaState lua )
		{
			double d = lua.L_CheckNumber(1);

			// Translate the double into sign, exponent and mantissa.
			long bits = BitConverter.DoubleToInt64Bits(d);
			// Note that the shift is sign-extended, hence the test against -1 not 1
			bool negative = (bits < 0);
			int exponent = (int) ((bits >> 52) & 0x7ffL);
			long mantissa = bits & 0xfffffffffffffL;

			// Subnormal numbers; exponent is effectively one higher,
			// but there's no extra normalisation bit in the mantissa
			if (exponent==0)
			{
				exponent++;
			}
			// Normal numbers; leave exponent as it is but add extra
			// bit to the front of the mantissa
			else
			{
				mantissa = mantissa | (1L<<52);
			}

			// Bias the exponent. It's actually biased by 1023, but we're
			// treating the mantissa as m.0 rather than 0.m, so we need
			// to subtract another 52 from it.
			exponent -= 1075;

			if (mantissa == 0) 
			{
				lua.PushNumber( 0.0 );
				lua.PushNumber( 0.0 );
				return 2;
			}

			/* Normalize */
			while((mantissa & 1) == 0) 
			{    /*  i.e., Mantissa is even */
				mantissa >>= 1;
				exponent++;
			}

			double m = (double)mantissa;
			double e = (double)exponent;
			while( m >= 1 )
			{
				m /= 2.0;
				e += 1.0;
			}

			if( negative ) m = -m;
			lua.PushNumber( m );
			lua.PushNumber( e );
			return 2;
		}

		private static int Math_Ldexp( ILuaState lua )
		{
			lua.PushNumber( lua.L_CheckNumber(1) * Math.Pow(2, lua.L_CheckNumber(2)) );
			return 1;
		}

		private static int Math_Log10( ILuaState lua )
		{
			lua.PushNumber( Math.Log10( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Log( ILuaState lua )
		{
			double x = lua.L_CheckNumber(1);
			double res;
			if( lua.IsNoneOrNil(2) )
				res = Math.Log(x);
			else
			{
				double logBase = lua.L_CheckNumber(2);
				if( logBase == 10.0 )
					res = Math.Log10(x);
				else
					res = Math.Log(x, logBase);
			}
			lua.PushNumber(res);
			return 1;
		}

		private static int Math_Max( ILuaState lua )
		{
			int n = lua.GetTop();
			double dmax = lua.L_CheckNumber(1);
			for( int i=2; i<=n; ++i )
			{
				double d = lua.L_CheckNumber(i);
				if( d > dmax )
					dmax = d;
			}
			lua.PushNumber(dmax);
			return 1;
		}

		private static int Math_Min( ILuaState lua )
		{
			int n = lua.GetTop();
			double dmin = lua.L_CheckNumber(1);
			for( int i=2; i<=n; ++i )
			{
				double d = lua.L_CheckNumber(i);
				if( d < dmin )
					dmin = d;
			}
			lua.PushNumber(dmin);
			return 1;
		}

		private static int Math_Modf( ILuaState lua )
		{
			double d = lua.L_CheckNumber(1);
			double c = Math.Ceiling(d);
			lua.PushNumber( c );
			lua.PushNumber( d-c );
			return 2;
		}

		private static int Math_Pow( ILuaState lua )
		{
			lua.PushNumber( Math.Pow( lua.L_CheckNumber(1),
									  lua.L_CheckNumber(2)));
			return 1;
		}

		private static int Math_Rad( ILuaState lua )
		{
			lua.PushNumber( lua.L_CheckNumber(1) * RADIANS_PER_DEGREE );
			return 1;
		}

		private static int Math_Random( ILuaState lua )
		{
			double r = RandObj.NextDouble();
			switch( lua.GetTop() )
			{
				case 0: // no argument
					lua.PushNumber( r );
					break;
				case 1:
				{
					double u = lua.L_CheckNumber(1);
					lua.L_ArgCheck( 1.0 <= u, 1, "interval is empty" );
					lua.PushNumber( Math.Floor(r*u) + 1.0 ); // int in [1, u]
					break;
				}
				case 2:
				{
					double l = lua.L_CheckNumber(1);
					double u = lua.L_CheckNumber(2);
					lua.L_ArgCheck( l <= u, 2, "interval is empty" );
					lua.PushNumber( Math.Floor(r*(u-l+1)) + l ); // int in [l, u]
					break;
				}
				default: return lua.L_Error( "wrong number of arguments" );
			}
			return 1;
		}

		private static int Math_RandomSeed( ILuaState lua )
		{
			RandObj = new Random( (int)lua.L_CheckUnsigned(1) );
			RandObj.Next();
			return 0;
		}

		private static int Math_Sinh( ILuaState lua )
		{
			lua.PushNumber( Math.Sinh( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Sin( ILuaState lua )
		{
			lua.PushNumber( Math.Sin( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Sqrt( ILuaState lua )
		{
			lua.PushNumber( Math.Sqrt( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Tanh( ILuaState lua )
		{
			lua.PushNumber( Math.Tanh( lua.L_CheckNumber(1) ) );
			return 1;
		}

		private static int Math_Tan( ILuaState lua )
		{
			lua.PushNumber( Math.Tan( lua.L_CheckNumber(1) ) );
			return 1;
		}

	}

}

