unit UCore;

interface

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

{$I switches.inc}

uses uPluginDefs,
     uCoreModule,
     UHooks,
     UServices,
     UModules;
{*********************
  TCore
  Class manages all CoreModules, teh StartUp, teh MainLoop and the shutdown process
  Also it does some Error Handling, and maybe sometime multithreaded Loading ;)
*********************}

type
  TModuleListItem = record
    Module:       TCoreModule; //Instance of the Modules Class
    Info:         TModuleInfo; //ModuleInfo returned by Modules Modulinfo Proc
    NeedsDeInit:  Boolean;     //True if Module was succesful inited
  end;
  
  TCore = class
    private
      //Some Hook Handles. See Plugin SDKs Hooks.txt for Infos
      hLoadingFinished: THandle;
      hMainLoop:        THandle;
      hTranslate:       THandle;
      hLoadTextures:    THandle;
      hExitQuery:       THandle;
      hExit:            THandle;
      hDebug:           THandle;
      hError:           THandle;
      sReportError:     THandle;
      sReportDebug:     THandle;
      sShowMessage:     THandle;
      sRetranslate:     THandle;
      sReloadTextures:  THandle;
      sGetModuleInfo:   THandle;
      sGetApplicationHandle: THandle;

      Modules:          Array [0..High(CORE_MODULES_TO_LOAD)] of TModuleListItem;

      //Cur + Last Executed Setting and Getting ;)
      iCurExecuted: Integer;
      iLastExecuted: Integer;

      Procedure SetCurExecuted(Value: Integer);

      //Function Get all Modules and Creates them
      Function GetModules: Boolean;

      //Loads Core and all Modules
      Function Load: Boolean;

      //Inits Core and all Modules
      Function Init: Boolean;

      //DeInits Core and all Modules
      Function DeInit: Boolean;

      //Load the Core
      Function LoadCore: Boolean;

      //Init the Core
      Function InitCore: Boolean;

      //DeInit the Core
      Function DeInitCore: Boolean;

      //Called one Time per Frame
      Function MainLoop: Boolean;

    public
      Hooks:            THookManager;   //Teh Hook Manager ;)
      Services:         TServiceManager;//The Service Manager

      Name:             String;         //Name of this Application
      Version:          LongWord;       //Version of this ". For Info Look PluginDefs Functions

      LastErrorReporter:String;         //Who Reported the Last Error String
      LastErrorString:  String;         //Last Error String reported

      property CurExecuted: Integer read iCurExecuted write SetCurExecuted;       //ID of Plugin or Module curently Executed
      property LastExecuted: Integer read iLastExecuted;

      //---------------
      //Main Methods to control the Core:
      //---------------
      Constructor Create(const cName: String; const cVersion: LongWord);

      //Starts Loading and Init Process. Then Runs MainLoop. DeInits on Shutdown
      Procedure Run;

      //Method for other Classes to get Pointer to a specific Module
      Function GetModulebyName(const Name: String): PCoreModule;

      //--------------
      // Hook and Service Procs:
      //--------------
      Function ShowMessage(wParam: TwParam; lParam: TlParam): integer; //Shows a Message (lParam: PChar Text, wParam: Symbol)
      Function ReportError(wParam: TwParam; lParam: TlParam): integer; //Shows a Message (wParam: Pchar(Message), lParam: PChar(Reportername))
      Function ReportDebug(wParam: TwParam; lParam: TlParam): integer; //Shows a Message (wParam: Pchar(Message), lParam: PChar(Reportername))
      Function Retranslate(wParam: TwParam; lParam: TlParam): integer; //Calls Translate hook
      Function ReloadTextures(wParam: TwParam; lParam: TlParam): integer; //Calls LoadTextures hook
      Function GetModuleInfo(wParam: TwParam; lParam: TlParam): integer; //If lParam = nil then get length of Moduleinfo Array. If lparam <> nil then write array of TModuleInfo to address at lparam
      Function GetApplicationHandle(wParam: TwParam; lParam: TlParam): integer; //Returns Application Handle
  end;

var
  Core: TCore; 

implementation

uses {$IFDEF win32}
       Windows,
      {$ENDIF}
      SysUtils;

//-------------
// Create - Creates Class + Hook and Service Manager
//-------------
Constructor TCore.Create(const cName: String; const cVersion: LongWord);
begin
  Name := cName;
  Version := cVersion;
  iLastExecuted := 0;
  iCurExecuted := 0;

  LastErrorReporter := '';
  LastErrorString   := '';

  Hooks := THookManager.Create(50);
  Services := TServiceManager.Create;
end;

//-------------
//Starts Loading and Init Process. Then Runs MainLoop. DeInits on Shutdown
//-------------
Procedure TCore.Run;
var
  noError: Boolean;
begin
  //Get Modules
  Try
    noError := GetModules;
  Except
    noError := False;
  end;

  //Loading
  if (noError) then
  begin
    Try
      noError := Load;
    Except
      noError := False;
    end;

    if (noError) then
    begin //Init
      Try
        noError := Init;
      Except
        noError := False;
      end;

      If noError then
      begin
        //Call Translate Hook
        noError := (Hooks.CallEventChain(hTranslate, 0, 0) = 0);

        If noError then
        begin //Calls LoadTextures Hook
          noError := (Hooks.CallEventChain(hLoadTextures, 0, 0) = 0);

          if noError then
          begin //Calls Loading Finished Hook
            noError := (Hooks.CallEventChain(hLoadingFinished, 0, 0) = 0);

            If noError then
            begin
              //Start MainLoop
              While noError do
              begin
                noError := MainLoop;
                // to-do : Call Display Draw here
              end;
            end
            else
            begin
              If (LastErrorString <> '') then
                Self.ShowMessage(CORE_SM_ERROR, PChar('Error calling LoadingFinished Hook: ' + LastErrorString))
              else
                Self.ShowMessage(CORE_SM_ERROR, PChar('Error calling LoadingFinished Hook'));
            end;
          end
          else
          begin
            If (LastErrorString <> '') then
              Self.ShowMessage(CORE_SM_ERROR, PChar('Error loading textures: ' + LastErrorString))
            else
              Self.ShowMessage(CORE_SM_ERROR, PChar('Error loading textures'));
          end;
        end
        else
        begin
          If (LastErrorString <> '') then
            Self.ShowMessage(CORE_SM_ERROR, PChar('Error translating: ' + LastErrorString))
          else
            Self.ShowMessage(CORE_SM_ERROR, PChar('Error translating'));
        end;
        
      end
      else
      begin
        If (LastErrorString <> '') then
          Self.ShowMessage(CORE_SM_ERROR, PChar('Error initing Modules: ' + LastErrorString))
        else
          Self.ShowMessage(CORE_SM_ERROR, PChar('Error initing Modules'));
      end;
    end
    else
    begin
      If (LastErrorString <> '') then
        Self.ShowMessage(CORE_SM_ERROR, PChar('Error loading Modules: ' + LastErrorString))
      else
        Self.ShowMessage(CORE_SM_ERROR, PChar('Error loading Modules'));
    end;
  end
  else
  begin
    If (LastErrorString <> '') then
      Self.ShowMessage(CORE_SM_ERROR, PChar('Error Getting Modules: ' + LastErrorString))
    else
      Self.ShowMessage(CORE_SM_ERROR, PChar('Error Getting Modules'));
  end;

  //DeInit
  DeInit;
end;

//-------------
//Called one Time per Frame
//-------------
Function TCore.MainLoop: Boolean;
begin
  Result := False;

end;

//-------------
//Function Get all Modules and Creates them
//-------------
Function TCore.GetModules: Boolean;
var
  I: Integer;
begin
  Result := False;
  try
    For I := 0 to high(Modules) do
    begin
      Modules[I].NeedsDeInit := False;
      Modules[I].Module := CORE_MODULES_TO_LOAD[I].Create;
      Modules[I].Module.Info(@Modules[I].Info);
    end;
    Result := True;
  except
    ReportError(Integer(PChar('Can''t get module #' + InttoStr(I) + ' "' + Modules[I].Info.Name + '"')), PChar('Core'));
  end;
end;

//-------------
//Loads Core and all Modules
//-------------
Function TCore.Load: Boolean;
var
  I: Integer;
begin
  Result := LoadCore;

  I := 0;
  While ((Result = True) AND (I <= High(CORE_MODULES_TO_LOAD))) do
  begin
    try
      Result := Modules[I].Module.Load;
    except
      Result := False;
      ReportError(Integer(PChar('Error loading module #' + InttoStr(I) + ' "' + Modules[I].Info.Name + '"')), PChar('Core'));
    end;

    Inc(I);
  end;
end;

//-------------
//Inits Core and all Modules
//-------------
Function TCore.Init: Boolean;
var
  I: Integer;
begin
  Result := InitCore;

  I := 0;
  While ((Result = True) AND (I <= High(CORE_MODULES_TO_LOAD))) do
  begin
    try
      Result := Modules[I].Module.Init;
    except
      Result := False;
      ReportError(Integer(PChar('Error initing module #' + InttoStr(I) + ' "' + Modules[I].Info.Name + '"')), PChar('Core'));
    end;

    Modules[I].NeedsDeInit := Result;
    Inc(I);
  end;
end;

//-------------
//DeInits Core and all Modules
//-------------
Function TCore.DeInit: Boolean;
var
  I: Integer;
label Continue;
begin
  I := High(CORE_MODULES_TO_LOAD);

  Continue:
  Try
    While (I >= 0) do
    begin
      If (Modules[I].NeedsDeInit) then
        Modules[I].Module.DeInit;

      Dec(I);
    end;
  Except


  end;
  If (I >= 0) then
    GoTo Continue;

  DeInitCore;
end;

//-------------
//Load the Core
//-------------
Function TCore.LoadCore: Boolean;
begin
  hLoadingFinished := Hooks.AddEvent('Core/LoadingFinished');
  hMainLoop        := Hooks.AddEvent('Core/MainLoop');
  hTranslate       := Hooks.AddEvent('Core/Translate');
  hLoadTextures    := Hooks.AddEvent('Core/LoadTextures');
  hExitQuery       := Hooks.AddEvent('Core/ExitQuery');
  hExit            := Hooks.AddEvent('Core/Exit');
  hDebug           := Hooks.AddEvent('Core/NewDebugInfo');
  hError           := Hooks.AddEvent('Core/NewError');
  
  sReportError     := Services.AddService('Core/ReportError', nil, Self.ReportError);
  sReportDebug     := Services.AddService('Core/ReportDebug', nil, Self.ReportDebug);
  sShowMessage     := Services.AddService('Core/ShowMessage', nil, Self.ShowMessage);
  sRetranslate     := Services.AddService('Core/Retranslate', nil, Self.Retranslate);
  sReloadTextures  := Services.AddService('Core/ReloadTextures', nil, Self.ReloadTextures);
  sGetModuleInfo   := Services.AddService('Core/GetModuleInfo', nil, Self.GetModuleInfo);
  sGetApplicationHandle := Services.AddService('Core/GetApplicationHandle', nil, Self.GetApplicationHandle);

  //A little Test
  Hooks.AddSubscriber('Core/NewError', HookTest);
end;

//-------------
//Init the Core
//-------------
Function TCore.InitCore: Boolean;
begin
  //Dont Init s.th. atm.
end;

//-------------
//DeInit the Core
//-------------
Function TCore.DeInitCore: Boolean;
begin


  // to-do : write TService-/HookManager.Free and call it here
end;

//-------------
//Method for other Classes to get Pointer to a specific Module
//-------------
Function TCore.GetModulebyName(const Name: String): PCoreModule;
var I: Integer;
begin
  Result := nil;
  For I := 0 to high(Modules) do
    If (Modules[I].Info.Name = Name) then
    begin
      Result := @Modules[I].Module;
      Break;
    end;
end;

//-------------
// Shows a MessageDialog (lParam: PChar Text, wParam: Symbol)
//-------------
Function TCore.ShowMessage(wParam: TwParam; lParam: TlParam): integer;
var Params: Cardinal; 
begin
  Result := -1;

  {$IFDEF Delphi}
  If (lParam<>nil) then
  begin
    Params := MB_OK;
    Case wParam of
      CORE_SM_ERROR: Params := Params or MB_ICONERROR;
      CORE_SM_WARNING: Params := Params or MB_ICONWARNING;
      CORE_SM_INFO: Params := Params or MB_ICONINFORMATION;
    end;

    //Anzeigen:
    Result := Messagebox(0, lParam, PChar(Name), Params);
  end;
  {$ENDIF}

  // to-do : write ShowMessage for other OSes
end;

//-------------
// Calls NewError HookChain (wParam: Pchar(Message), lParam: PChar(Reportername))
//-------------
Function TCore.ReportError(wParam: TwParam; lParam: TlParam): integer;
begin
  //Update LastErrorReporter and LastErrorString
  LastErrorReporter := String(PChar(lParam));
  LastErrorString   := String(PChar(Pointer(wParam)));
  
  Hooks.CallEventChain(hError, wParam, lParam);
end;

//-------------
// Calls NewDebugInfo HookChain (wParam: Pchar(Message), lParam: PChar(Reportername))
//-------------
Function TCore.ReportDebug(wParam: TwParam; lParam: TlParam): integer;
begin
  Hooks.CallEventChain(hDebug, wParam, lParam);
end;

//-------------
// Calls Translate hook
//-------------
Function TCore.Retranslate(wParam: TwParam; lParam: TlParam): integer;
begin
  Hooks.CallEventChain(hTranslate, 1, nil);
end;

//-------------
// Calls LoadTextures hook
//-------------
Function TCore.ReloadTextures(wParam: TwParam; lParam: TlParam): integer;
begin
  Hooks.CallEventChain(hLoadTextures, 1, nil);
end;

//-------------
// If lParam = nil then get length of Moduleinfo Array. If lparam <> nil then write array of TModuleInfo to address at lparam
//-------------
Function TCore.GetModuleInfo(wParam: TwParam; lParam: TlParam): integer;
begin
  if (Pointer(lParam) = nil) then
  begin
    Result := Length(Modules);
  end
  else
  begin
    Try
      For Result := 0 to High(Modules) do
      begin
        AModuleInfo(Pointer(lParam))[Result].Name := Modules[Result].Info.Name;
        AModuleInfo(Pointer(lParam))[Result].Version := Modules[Result].Info.Version;
        AModuleInfo(Pointer(lParam))[Result].Description := Modules[Result].Info.Description;
      end;
    Except
      Result := -1;
    end;
  end;
end;

//-------------
// Returns Application Handle
//-------------
Function TCore.GetApplicationHandle(wParam: TwParam; lParam: TlParam): integer;
begin
  Result := hInstance;
end;

//-------------
// Called when setting CurExecuted
//-------------
Procedure TCore.SetCurExecuted(Value: Integer);
begin
  //Set Last Executed
  iLastExecuted := iCurExecuted;

  //Set Cur Executed
  iCurExecuted := Value;
end;

end.