unit U_ScriptManager;

interface
uses
  Classes, Generics.Collections,
  U_LogEmitter,
  U_Script;

type
  TScriptManager = class(TLogEmitter)
  private
    FPath: string;
    FZBar: OleVariant; // keep a reference around for whenever we need it
    FRunScripts: TStringList; // all non-library scripts
    FLibScripts: TStringList; // all library scripts
    FLangEngines: TDictionary<string, OleVariant>; // the language-specific global engines

    procedure ClearScripts;

    function  GetScript(Index: integer): TScript;
    function  GetScriptByName(Name: string): TScript;
  public
    constructor Create(Path: string; ZBar: OleVariant);
    destructor  Destroy; override;

    procedure Refresh;

    function  Add(Filename: string): integer;
    function  Count: integer;

    function  GetEngine(Language: string; Independent: boolean): OleVariant;
    function  GetScriptForCommand(Command: string): TScript;

    property Path: string                         read FPath;
    property Script[Index: integer]: TScript      read GetScript; default;
    property ScriptByName[Name: string]: TScript  read GetScriptByName;

    property OnLog;
  end;

implementation

uses
  SysUtils,
  L_ScriptingUtils, ComObj, ObjComAuto;

{ ============================================================================ }
{ TScriptManager }

{ ---------------------------------------------------------------------------- }
constructor TScriptManager.Create(Path: string; ZBar: OleVariant);
begin
  FPath := Path;
  FZBar := ZBar;

  FRunScripts := TStringList.Create;
  FLibScripts := TStringList.Create;
  FLangEngines := TDictionary<string, OleVariant>.Create;
end;
{ ---------------------------------------------------------------------------- }
destructor TScriptManager.Destroy;
begin
  ClearScripts;

  FRunScripts.Free;
  FLibScripts.Free;
  FLangEngines.Free;

  inherited;
end;

{ ---------------------------------------------------------------------------- }
procedure TScriptManager.Refresh;
var
  Search: TSearchRec;
  RetVal: integer;
  Found: TStringList;
  i: Integer;
  Script: TScript;
begin
  ClearScripts;

  RetVal := FindFirst(IncludeTrailingPathDelimiter(FPath) + '*.*', faNormal, Search);
  if RetVal = 0 then begin
    Found := TStringList.Create;
    try
      try
        repeat
          if ScriptLanguageFromExtension(Search.Name) <> '' then begin
            Found.Add(Search.Name);
          end;
          RetVal := FindNext(Search);
        until RetVal <> 0;
      finally
        FindClose(Search);
      end;

      Found.CaseSensitive := False;
      Found.Sort;
      for i := 0 to Found.Count - 1 do begin
        WriteToLog(Format('Loading %s...', [Found[i]]));
        Script := TScript.Create(Self, IncludeTrailingPathDelimiter(FPath) + Found[i]);
        WriteToLog(Format('OK', [Found[i]]), llInfo, 1);
        if Script.IsLibrary then begin
          FLibScripts.AddObject(Found[i], Script);
        end else begin
          FRunScripts.AddObject(Found[i], Script);
        end;
        Script.Enabled := True;
      end;

    finally
      Found.Free;
    end;
  end;
end;


{ ---------------------------------------------------------------------------- }
procedure TScriptManager.ClearScripts;
var
  i: Integer;
begin
  for i := 0 to FRunScripts.Count - 1 do begin
    TScript(FRunScripts.Objects[i]).Free;
  end;
  FRunScripts.Clear;

  for i := 0 to FLibScripts.Count - 1 do begin
    TScript(FLibScripts.Objects[i]).Free;
  end;
  FLibScripts.Clear;

  // Release all the objects individually?
  FLangEngines.Clear;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.Count: integer;
begin
  Result := FLibScripts.Count + FRunScripts.Count;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.Add(Filename: string): integer;
begin
  if ScriptLanguageFromExtension(Filename) <> '' then begin
    Result := FRunScripts.AddObject(ExtractFileName(Filename), TScript.Create(Self, Filename));
  end else begin
    Result := -1;
  end;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.GetEngine(Language: string; Independent: boolean): OleVariant;
var
  Index: integer;
  Name: string;
  Script: TScript;
begin
  if not Independent then begin
    // check list of global objects, to see if we haven't got one for this
    //  language yet
    if FLangEngines.ContainsKey(LowerCase(Language)) then begin
      Result := FLangEngines[LowerCase(Language)];
      Exit;
    end;
  end;

  Result := CreateOLEObject('MSScriptControl.ScriptControl');
  Result.Language := Language;

  if Independent then begin
    // Add all library script objects
    for Index := 0 to FLibScripts.Count - 1 do begin
      Script := TScript(FLibScripts.Objects[Index]);
      Name := ExtractFileName(Script.&Public.Filename);
      Name := Copy(Name, Length(Name) - Length(ExtractFileExt(Name)));
      Name := Name + '_' + Script.Engine.Language;
      Result.AddObject(Name, Script.Engine.CodeObject, True);
    end;
  end else begin
    // Add a generic script environment
    Result.AddObject('ScriptEnvironment',
                     TObjectDispatch.Create(TScriptEnvironment.Create(nil)) as IDispatch,
                     True);

    // Remember this engine under its language name
    FLangEngines.Add(LowerCase(Language), Result);
  end;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.GetScriptForCommand(Command: string): TScript;
var
  i: Integer;
begin
  for i := 0 to FRunScripts.Count - 1 do begin
    Result := TScript(FRunScripts.Objects[i]);
    if Result.HandlesCommand(Command) then begin
      Exit;
    end;
  end;
  Result := nil;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.GetScript(Index: integer): TScript;
begin
  if Index < FLibScripts.Count then begin
    Result := TScript(FLibScripts.Objects[Index]);
  end else begin
    Result := TScript(FRunScripts.Objects[Index - FLibScripts.Count]);
  end;
end;

{ ---------------------------------------------------------------------------- }
function TScriptManager.GetScriptByName(Name: string): TScript;
var
  Index: integer;
begin
  Index := FRunScripts.IndexOf(Name);
  if Index >= 0 then begin
    Result := GetScript(Index);
  end else begin
    Result := nil;
  end;
end;

end.
