(*
 * LuaScript
 * Extension for Elysion Framworks, can be used with other projects, too
 *
 * Thanks to the Valkyrie Engine guys
 * Valkyries TLua class is being used as a base for TLuaScript
 *
 *)

unit LuaScript;

{$IFDEF DARWIN}
  {$linkframework Lua}
{$ENDIF}

interface

uses
  SysUtils,
  lua;

type


  TLuaScript = class
  private
    FState: PLua_State;
  public
    constructor Create; virtual;
               procedure LoadFromFile(const FileName : string);
               //procedure LoadFromLuaState(vLuaState: PLua_State);
               //procedure LoadStream(IST : TInputInterfaceStream; const StreamName : string);
               procedure AddVar(const Name : string; const l : LongInt); Overload;
               procedure AddVar(const Name : string; const s : string); Overload;
               procedure GetVar(const Name : string; var l : LongInt); Overload;
               procedure GetVar(const Name : string; var s : string); Overload;
               function FunctionExists(const Name : string) : boolean;
               function Execute(const Name : string) : boolean; Overload;
               function Execute(const Name : string; const Number : LongInt) : boolean; Overload;
               function Execute(const Name: String; const Value: String): Boolean; Overload;
//               function Execute(const Name : string; Args : Array of Const) : boolean;
               function ExecuteCommand(const Command : string) : boolean; Overload;
               function ExecuteCommand(const Command : string; const Number : LongInt) : boolean; Overload;
               procedure Register(const Name : string; Proc : lua_CFunction);
               procedure RegisterConst(const Name : string; value : Integer);
               destructor Destroy; override;
               protected
               Ansi     : AnsiString;
               Error    : AnsiString;
               LuaState : PLua_State;
  end;
			 
			 
//@abstract(Reads a parameter from the string.)
//Str is the given string, Num is the number of the parameter and
//ParamChar is the format. If ParamChar is a single character then
//the function returns the Num-th substring divided by ParamChars.
//If ParamChar is two characters (ex. "()") then it returns the
//string between the characters. If ParamChar is three characters,
//then then the function returns the Num-th parameter of the
//string inside -- "(,)" = 3-ParamChar example.
//FUNCTION NEEDS TO BE REEDITED!
function Parameter(Str : string; Num : byte; ParamChar: string) : string;

implementation

function Parameter3(GStr : string; Num : byte; ParamChar: string) : string;
var pos : byte;
    dt  : byte;
    str : string;
begin
  pos := 0;
  dt  := 1;
  str := '';
  repeat Inc(pos) until (GStr[pos] = Paramchar[1]) or (pos >= Length(GStr));

  repeat
    Inc(pos);
    if GStr[pos] = ParamChar[2] then begin Inc(pos); Inc(dt); end;
    if GStr[pos] = ParamChar[3] then pos := 0;
    if (dt = Num) and (pos <> 0) then str := str + GStr[pos];
  until (pos >= Length(GStr)) or (dt > Num) or (pos = 0);
  Parameter3 := str;
end;

function Parameter2(GStr : string; ParamChar: string) : string;
var pos : byte;
    str : string;
begin
  pos := 0;
  str := '';
  repeat Inc(pos) until (GStr[pos] = Paramchar[1]) or (pos >= Length(GStr));

  if pos = Length(GStr) then Exit('');
  repeat
    Inc(pos);
    if GStr[pos] = ParamChar[2] then pos := 0;
    if (pos <> 0) then str := str + GStr[pos];
  until (pos >= Length(GStr)) or (pos = 0);
  Parameter2 := str;
end;

function Parameter1(s : string; nr : byte; ch : char) : string;
var pos : byte;
    dt  : byte;
    str : string;
begin
  pos := 0;
  dt  := 1;
  str := '';
  repeat
    Inc(pos);
    if s[pos] = ch then begin Inc(pos); Inc(dt); end;
    if dt = nr then str := str + s[pos];
  until (pos >= Length(s)) or (dt > nr);
  Parameter1 := str;
end;

function Parameter(Str : string; Num : byte; ParamChar: string) : string;
begin
  if Length(ParamChar) = 1 then Parameter := Parameter1(Str,Num,ParamChar[1]) else
  if Length(ParamChar) = 2 then Parameter := Parameter2(Str,ParamChar) else
  Parameter := Parameter3(Str,Num,ParamChar);
end;

constructor TLuaScript.Create;
begin
  LuaState := Lua_Open();
  LuaOpen_Base(LuaState);
end;

procedure TLuaScript.LoadFromFile(const FileName : string);
begin
  Ansi := Filename;
  LuaL_DoFile(LuaState, PChar(ExtractFilePath(ParamStr(0)) + Ansi));
end;

procedure TLuaScript.AddVar(const Name : string; const l : LongInt);
begin
  Ansi := Name;
  lua_pushnumber(LuaState,l);
  lua_setglobal(LuaState,PChar(Ansi));
end;

procedure TLuaScript.GetVar(const Name : string; var l : LongInt);
begin
  Ansi := Name;
  lua_getglobal(LuaState,PChar(Ansi));
  
  Case lua_Type(LuaState,-1) Of
        LUA_TNUMBER  : l := Round(lua_tonumber(LuaState,-1));
  End;
  
  //l := Round(lua_tonumber(LuaState,lua_gettop(LuaState)));
  lua_pop(LuaState,1);
end;

procedure TLuaScript.GetVar(const Name : string; var s : string);
begin
  Ansi := Name;
  lua_getglobal(LuaState,PChar(Ansi));
  
  Case lua_Type(LuaState,-1) Of
        LUA_TSTRING  : Ansi := lua_tostring(LuaState,-1);
  end;
 
  
  //Ansi := lua_tostring(LuaState,lua_gettop(LuaState));
  s := ansi;
  lua_pop(LuaState,1);
end;

procedure TLuaScript.AddVar(const Name : string; const s : String);
begin
  Ansi := s;
  lua_pushstring(LuaState,PChar(Ansi));
  Ansi := Name;
  lua_setglobal(LuaState,PChar(Ansi));
end;

{
procedure TLuaScript.LoadStream(IST : TInputInterfaceStream; const StreamName : string);
var Size : LongInt;
    Pos  : PByte;
begin
  Ansi := StreamName;
  Size := IST.ReadLong;
  Log('Loading LUA stream -- "'+StreamName+'" ('+IntToStr(Size)+'b)');
  Pos := IST.Position;
  lua_dobuffer(LuaState,PChar(Pos),Size,PChar(Ansi));
  IST.Position := Pos + Size;
end;
}

function TLuaScript.FunctionExists(const Name : string) : boolean;
begin
  Ansi := Name;
  lua_getglobal(LuaState, PChar(Ansi));
  FunctionExists := lua_type(LuaState,-1) = LUA_TFUNCTION;
  lua_pop(LuaState,1);
end;

function TLuaScript.Execute(const Name : string) : boolean;
begin
  Ansi := Name;
  lua_getglobal(LuaState, PChar(Ansi));
  if lua_pcall(LuaState, 0,0,0) <> 0 then
  begin
    Error := lua_tostring(LuaState,-1);
    //Log('ERROR/'+Name+': '+Error);
    lua_pop(LuaState,1);
    Exit(False);
  end;
  Exit(True);
end;

function TLuaScript.Execute(const Name : string; const Number : LongInt) : boolean;
begin
  Ansi := Name;
  lua_getglobal(LuaState, PChar(Ansi));
  lua_pushnumber(LuaState,Number);
  if lua_pcall(LuaState, 1,0,0) <> 0 then
  begin
    Error := lua_tostring(LuaState,-1);
    //Log('ERROR/'+Name+': '+Error);
    lua_pop(LuaState,1);
    Exit(False);
  end;
  Exit(True);
end;

function TLuaScript.Execute(const Name: String; const Value: String): Boolean;
begin
  Ansi := Name;
  lua_getglobal(LuaState, PChar(Ansi));
  lua_pushstring(LuaState, PChar(Value));
  if lua_pcall(LuaState, 1, 0, 0) <> 0 then
  begin
    Error := lua_tostring(LuaState, -1);
    lua_pop(LuaState, 1);
  end;
  Exit(true);
end;

function TLuaScript.ExecuteCommand(const Command : string; const Number : LongInt) : boolean;
var Args,Arg : string;
    Count    : Byte;
begin
  Ansi := Parameter(Command,1,'(');
  Args := Parameter(Command,1,'()');
  
  if Args = '' then Exit(Execute(Ansi,Number));
  
  lua_getglobal(LuaState, PChar(Ansi));
  lua_pushnumber(LuaState,Number);

  Count := 1;
  repeat
    ///////////////////// MAY BE OPTIMIZED USING SPLIT INSTEAD OF Parameter
    Arg := Parameter(Args,Count,',');
    
    ///////////////////// NO  ERROR CHECKING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    if Arg = '' then Break;
    Arg := Trim(Arg);
    case Arg[1] of
      '-','0'..'9' : lua_pushnumber(LuaState,StrToInt(Arg));
      'T','t'      : lua_pushboolean(LuaState,true);
      'F','f'      : lua_pushboolean(LuaState,false);
      ''''         : begin Ansi := Parameter(Arg,1,''''''); lua_pushstring(LuaState,PChar(Ansi)); end;
      '"'          : begin Ansi := Parameter(Arg,1,'""'); lua_pushstring(LuaState,PChar(Ansi)); end;
      else {Log('Invalid command arguments passed to ExecuteCommand : '+Command)};
    end;
    Inc(Count);
  until Arg = '';

  if lua_pcall(LuaState, Count,0,0) <> 0 then
  begin
    Error := lua_tostring(LuaState,-1);
    //Log('ERROR/'+Command+': '+Error);
    lua_pop(LuaState,1);
    Exit(False);
  end;
  Exit(True);
end;

function TLuaScript.ExecuteCommand(const Command : string) : boolean;
var Args,Arg : string;
    Count    : Byte;
begin
  Ansi := Parameter(Command,1,'(');
  Args := Parameter(Command,1,'()');

  if Args = '' then Exit(Execute(Ansi));

  lua_getglobal(LuaState, PChar(Ansi));

  Count := 1;
  repeat
    ///////////////////// MAY BE OPTIMIZED USING SPLIT INSTEAD OF Parameter
    Arg := Parameter(Args,Count,',');

    ///////////////////// NO  ERROR CHECKING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    if Arg = '' then Break;
    Arg := Trim(Arg);
    case Arg[1] of
      '-','0'..'9' : lua_pushnumber(LuaState,StrToInt(Arg));
      'T','t'      : lua_pushboolean(LuaState,true);
      'F','f'      : lua_pushboolean(LuaState,false);
      ''''         : begin Ansi := Parameter(Arg,1,''''''); lua_pushstring(LuaState,PChar(Ansi)); end;
      '"'          : begin Ansi := Parameter(Arg,1,'""'); lua_pushstring(LuaState,PChar(Ansi)); end;
      else {Log('Invalid command arguments passed to ExecuteCommand : '+Command)};
    end;
    Inc(Count);
  until Arg = '';

  if lua_pcall(LuaState, Count-1,0,0) <> 0 then
  begin
    Error := lua_tostring(LuaState,-1);
    //Log('ERROR/'+Command+': '+Error);
    lua_pop(LuaState,1);
    Exit(False);
  end;
  Exit(True);
end;


{function Execute(const Name : string; Args : Array of Const) : boolean;
var count : Byte;
begin
  if High(Args) < 0 then Exit(Execute(Name));
  Ansi := Name;
  lua_getglobal(LuaState, PChar(Ansi));

  for count := 0 to High(Args) do
  case
  lua_pushnumber(LuaState,Number);
  
  
  
  if lua_pcall(LuaState, High(Args)+1,0,0) <> 0 then
  begin
    Error := lua_tostring(LuaState,-1);
    Log('ERROR/'+Name+': '+Error);
    lua_pop(LuaState,1);
    Exit(False);
  end;
end;  }

procedure TLuaScript.Register(const Name : string; Proc : lua_CFunction);
begin
  Ansi := Name;
  lua_register(LuaState, PChar(Ansi), Proc);
end;

procedure TLuaScript.RegisterConst(const Name : string; value : Integer);
begin
  Ansi := Name;
  lua_pushnumber(LuaState,value);
  lua_setglobal(LuaState,PChar(Ansi));
end;

destructor TLuaScript.Destroy;
begin
  lua_close(LuaState);
  LuaState := nil;
  //Log('Lua closed.');
  inherited Destroy;
end;


end.

