// 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
{

	internal class LuaCoroLib
	{
		public const string LIB_NAME = "coroutine";

		public static int OpenLib( ILuaState lua )
		{
			NameFuncPair[] define = new NameFuncPair[]
			{
				new NameFuncPair( "create", 	CO_Create	),
				new NameFuncPair( "resume", 	CO_Resume	),
				new NameFuncPair( "running", 	CO_Running	),
				new NameFuncPair( "status", 	CO_Status	),
				new NameFuncPair( "wrap", 		CO_Wrap		),
				new NameFuncPair( "yield", 		CO_Yield	),
			};

			lua.L_NewLib( define );
			return 1;
		}

		private static int CO_Create( ILuaState lua )
		{
			lua.L_CheckType( 1, LuaType.LUA_TFUNCTION );
			ILuaState newLua = lua.NewThread();
			lua.PushValue( 1 ); // move function to top
			lua.XMove( newLua, 1 ); // move function from lua to newLua
			return 1;
		}

		private static int AuxResume( ILuaState lua, ILuaState co, int narg )
		{
			if(!co.CheckStack(narg)) {
				lua.PushString("too many arguments to resume");
				return -1; // error flag
			}
			if( co.Status == ThreadStatus.LUA_OK && co.GetTop() == 0 )
			{
				lua.PushString( "cannot resume dead coroutine" );
				return -1; // error flag
			}
			lua.XMove( co, narg );
			ThreadStatus status = co.Resume( lua, narg );
			if( status == ThreadStatus.LUA_OK || status == ThreadStatus.LUA_YIELD )
			{
				int nres = co.GetTop();
				if(!lua.CheckStack(nres+1)) {
					co.Pop(nres); // remove results anyway;
					lua.PushString("too many results to resume");
					return -1; // error flag
				}
				co.XMove( lua, nres ); // move yielded values
				return nres;
			}
			else
			{
				co.XMove( lua, 1 ); // move error message
				return -1;
			}
		}

		private static int CO_Resume( ILuaState lua )
		{
			ILuaState co = lua.ToThread( 1 );
			lua.L_ArgCheck( co != null, 1, "coroutine expected" );
			int r = AuxResume( lua, co, lua.GetTop() - 1 );
			if( r < 0 )
			{
				lua.PushBoolean( false );
				lua.Insert( -2 );
				return 2; // return false + error message
			}
			else
			{
				lua.PushBoolean( true );
				lua.Insert( -(r+1) );
				return r+1; // return true + `resume' returns
			}
		}

		private static int CO_Running( ILuaState lua )
		{
			bool isMain = lua.PushThread();
			lua.PushBoolean( isMain );
			return 2;
		}

		private static int CO_Status( ILuaState lua )
		{
			ILuaState co = lua.ToThread( 1 );
			lua.L_ArgCheck( co != null, 1, "coroutine expected" );
			if( (LuaState)lua == (LuaState)co )
				lua.PushString( "running" );
			else switch( co.Status )
			{
				case ThreadStatus.LUA_YIELD:
					lua.PushString( "suspended" );
					break;
				case ThreadStatus.LUA_OK:
				{
					LuaDebug ar = new LuaDebug();
					if( co.GetStack( 0, ar ) ) // does it have frames?
						lua.PushString( "normal" );
					else if( co.GetTop() == 0 )
						lua.PushString( "dead" );
					else
						lua.PushString( "suspended" );
					break;
				}
				default: // some error occurred
					lua.PushString( "dead" );
					break;
			}
			return 1;
		}

		private static int CO_AuxWrap( ILuaState lua )
		{
			ILuaState co = lua.ToThread( lua.UpvalueIndex(1) );
			int r = AuxResume( lua, co, lua.GetTop() );
			if( r < 0 )
			{
				if( lua.IsString( -1 ) ) // error object is a string?
				{
					lua.L_Where( 1 ); // add extra info
					lua.Insert( -2 );
					lua.Concat( 2 );
				}
				lua.Error();
			}
			return r;
		}

		private static int CO_Wrap( ILuaState lua )
		{
			CO_Create( lua );
			lua.PushCSharpClosure( CO_AuxWrap, 1 );
			return 1;
		}

		private static int CO_Yield( ILuaState lua )
		{
			return lua.Yield( lua.GetTop() );
		}

	}

}

