unit uOptions;
{$INCLUDE 'Plugin.inc'}

interface

uses
  SysUtils, //Windows, Classes, Generics.Collections,
  Generics.Collections,
  {$INCLUDE 'DelphiFarUnits.inc'} ,
  uSystem, uStrings,
  uPluginConsts;

type
  TOptions = class(TFarConfig)
  public
    type
      TSavedPatternList = class;
      TSavedPatternInfo = class
      private
        fList: TSavedPatternList;
        fFileNamePattern: string;
        fStartingNumberAsString: string;
        function GetStartingNumber: integer;
        function GetStartingNumberLength: integer;     
        procedure SetStartingNumberAsString(const Value: string);
        function GetAsString: string;
        procedure SetAsString(const Value: string);
      protected
        procedure SetChanged;
        property List: TSavedPatternList read fList;
        property AsString: string read GetAsString write SetAsString;
      public
        constructor Create(List: TSavedPatternList; const FileNamePattern: string);
        property FileNamePattern: string read fFileNamePattern;
        property StartingNumberAsString: string read fStartingNumberAsString write SetStartingNumberAsString;
        property StartingNumber: integer read GetStartingNumber;
        property StartingNumberLength: integer read GetStartingNumberLength;
      end;
      TSavedPatternList = class
      protected
        type TInternalList = class(TObjectDictionary<string,TSavedPatternInfo>);
      private
        fOptions: TOptions;
        fList: TInternalList;
        function GetAsString: string;
        procedure SetAsString(const Value: string);
      protected
        procedure SetChanged;
        property Options: TOptions read fOptions;
        property List: TInternalList read fList;
        property AsString: string read GetAsString write SetAsString;
      public
        constructor Create(Options: TOptions);
        destructor Destroy; override;
        procedure Assign(Source: TSavedPatternList);
        procedure Clear;
        function Find(const FileNamePattern: string; out Info: TSavedPatternInfo): boolean;
        function Add(const FileNamePattern, StartingNumberAsString: string): TSavedPatternInfo;
        procedure Delete(const FileNamePattern: string);
      end;
  private
    fFileNamePattern: string;
    fStartingNumberAsString: string;
    fAllowRenameFileUnderCursor: boolean;
    fSavedPatterns: TSavedPatternList;
    fSavePatternSettings: boolean;
    function GetStartingNumberLength: integer;
    function GetStartingNumber: integer;
    procedure SetFileNamePattern(const Value: string);
    procedure SetStartingNumberAsString(const Value: string);
    procedure SetAllowRenameFileUnderCursor(const Value: boolean);
    procedure SetSavePatternSettings(const Value: boolean);
  protected
  public
    constructor Create;
    destructor Destroy; override;
    procedure Init;
    procedure Load;
    procedure Save(Force: boolean = False);
    procedure Assign(Options: TOptions);
    property FileNamePattern: string read fFileNamePattern write SetFileNamePattern;
    property StartingNumberAsString: string read fStartingNumberAsString write SetStartingNumberAsString;
    property StartingNumber: integer read GetStartingNumber;
    property StartingNumberLength: integer read GetStartingNumberLength;
    property AllowRenameFileUnderCursor: boolean read fAllowRenameFileUnderCursor write SetAllowRenameFileUnderCursor;
    property SavePatternSettings: boolean read fSavePatternSettings write SetSavePatternSettings;
    property SavedPatterns: TSavedPatternList read fSavedPatterns;
  end;

implementation

{ TDirSyncOptions }

procedure TOptions.Assign(Options: TOptions);
begin
  FileNamePattern := Options.FileNamePattern;
  StartingNumberAsString := Options.StartingNumberAsString;
  AllowRenameFileUnderCursor := Options.AllowRenameFileUnderCursor;
  SavePatternSettings := Options.SavePatternSettings;
  SavedPatterns.Assign(Options.SavedPatterns);
end;

constructor TOptions.Create;
begin
  inherited Create('NewName', False);
  fSavedPatterns := TSavedPatternList.Create(Self);
  Init;
end;

destructor TOptions.Destroy;
begin
  FreeAndNil(fSavedPatterns);
  inherited;
end;

function TOptions.GetStartingNumber: integer;
begin
  Result := StrToInt(StartingNumberAsString);
end;

function TOptions.GetStartingNumberLength: integer;
begin
  Result := Length(StartingNumberAsString);
end;

procedure TOptions.Init;
begin
  fFileNamePattern := 'file%#.%e';
  fStartingNumberAsString := '0';
  fAllowRenameFileUnderCursor := False;
  fSavePatternSettings := False;
  fSavedPatterns.Clear;
  SetChanged;
end;

procedure TOptions.Load;
var
  s: string;
begin
  if Open then
  begin
    try
      FileNamePattern := ReadString('FileNameTemplate', FileNamePattern);
      StartingNumberAsString := ReadString('StartingNumber', StartingNumberAsString);
      AllowRenameFileUnderCursor := ReadBoolean('AllowRenameFileUnderCursor', AllowRenameFileUnderCursor);
      SavePatternSettings := ReadBoolean('SavePatternSettings', SavePatternSettings);
      SavedPatterns.Clear;
      if ReadStringEx('SavedPatterns', s) then
        SavedPatterns.AsString := s;
      ClearChanged;
    finally
      Close;
    end;
  end;
end;

procedure TOptions.Save(Force: boolean);
begin
  if not (Force or Changed) then
    Exit;
  if Open then
  begin
    try
      WriteString('FileNameTemplate', FileNamePattern);
      WriteString('StartingNumber', StartingNumberAsString);
      WriteBoolean('AllowRenameFileUnderCursor', AllowRenameFileUnderCursor);
      WriteBoolean('SavePatternSettings', SavePatternSettings);
      WriteString('SavedPatterns', SavedPatterns.AsString);
      ClearChanged;
    finally
      Close;
    end;
  end;
end;

procedure TOptions.SetAllowRenameFileUnderCursor(const Value: boolean);
begin
  SetConfigOption(fAllowRenameFileUnderCursor, Value);
end;

procedure TOptions.SetFileNamePattern(const Value: string);
begin
  SetConfigOption(fFileNamePattern, Value);
end;

procedure TOptions.SetSavePatternSettings(const Value: boolean);
begin
  SetConfigOption(fSavePatternSettings, Value);
end;

procedure TOptions.SetStartingNumberAsString(const Value: string);
begin
  SetConfigOption(fStartingNumberAsString, Value);
end;

{ TOptions.TSavedPatternList }

function TOptions.TSavedPatternList.Add(const FileNamePattern, StartingNumberAsString: string): TSavedPatternInfo;
begin
  if not Find(FileNamePattern, Result) then
  begin
    Result := TSavedPatternInfo.Create(Self, FileNamePattern);
    try
      List.Add(Result.FileNamePattern, Result);
      SetChanged;
    except
      FreeAndNil(Result);
      Raise;
    end;
  end;
  Result.StartingNumberAsString := StartingNumberAsString;
end;

procedure TOptions.TSavedPatternList.Assign(Source: TSavedPatternList);
var
  Enum: TPair<string, TSavedPatternInfo>;
  Item: TSavedPatternInfo;
begin
  Clear;
  for Enum in Source.List do
  begin
    Item := TSavedPatternInfo.Create(Self, Enum.Value.FileNamePattern);
    try
      Item.StartingNumberAsString := Enum.Value.StartingNumberAsString;
    except
      FreeAndNil(Item);
      Continue;
    end;
    List.Add(Item.FileNamePattern, Item);
    SetChanged;
  end;
end;

procedure TOptions.TSavedPatternList.Clear;
begin
  if List.Count > 0 then
    SetChanged;
  List.Clear;
end;

constructor TOptions.TSavedPatternList.Create(Options: TOptions);
begin
  inherited Create;
  fOptions := Options;
  fList := TInternalList.Create([doOwnsValues]);
end;

procedure TOptions.TSavedPatternList.Delete(const FileNamePattern: string);
var
  Info: TSavedPatternInfo;
begin
  if Find(FileNamePattern, Info) then
  begin
    List.Remove(FileNamePattern);
    SetChanged;
  end;
end;

destructor TOptions.TSavedPatternList.Destroy;
begin
  FreeAndNil(fList);
  inherited;
end;

function TOptions.TSavedPatternList.Find(const FileNamePattern: string; out Info: TSavedPatternInfo): boolean;
begin
  Result := List.TryGetValue(FileNamePattern, Info);
end;

function TOptions.TSavedPatternList.GetAsString: string;
var
  Enum: TPair<string, TSavedPatternInfo>;
begin
  Result := '';
  for Enum in List do
  begin
    Result := Result + Enum.Value.AsString + #10;
  end;
end;

procedure TOptions.TSavedPatternList.SetAsString(const Value: string);
var
  Input, ItemStr: string;
  Item: TSavedPatternInfo;
begin
  Input := Value;
  while ExtractFirstLine(Input, ItemStr) do
  begin
    Item := TSavedPatternInfo.Create(Self, '');
    try
      Item.AsString := ItemStr;
      List.Add(Item.FileNamePattern, Item);
    except
      // Ignore invalid elements
    end;
  end;
end;

procedure TOptions.TSavedPatternList.SetChanged;
begin
  if Assigned(Options) then
    Options.SetChanged;
end;

{ TOptions.TSavedPatternInfo }

constructor TOptions.TSavedPatternInfo.Create(List: TSavedPatternList; const FileNamePattern: string);
begin
  inherited Create;
  fList := List;
  fFileNamePattern := FileNamePattern;
  fStartingNumberAsString := '0';
  SetChanged;
end;

function TOptions.TSavedPatternInfo.GetAsString: string;
begin
  Result := FileNamePattern + #9 + StartingNumberAsString;
end;

function TOptions.TSavedPatternInfo.GetStartingNumber: integer;
begin
  Result := StrToInt(StartingNumberAsString);
end;

function TOptions.TSavedPatternInfo.GetStartingNumberLength: integer;
begin
  Result := Length(StartingNumberAsString);
end;

procedure TOptions.TSavedPatternInfo.SetChanged;
begin
  if Assigned(List) then
    List.SetChanged;
end;

procedure TOptions.TSavedPatternInfo.SetAsString(const Value: string);
var
  Input, FileNamePattern: string;
begin
  Input := Value;
  if not ExtractFirstToken(Input, #9, FileNamePattern) then
    raise Exception.Create('Invalid argument for TOptions.TSavedPatternInfo.SetAsString');
  if Self.fFileNamePattern <> FileNamePattern then
  begin
    Self.fFileNamePattern := FileNamePattern;
    SetChanged;
  end;
  Self.StartingNumberAsString := Input;
end;

procedure TOptions.TSavedPatternInfo.SetStartingNumberAsString(const Value: string);
begin
  if Value <> fStartingNumberAsString then
  begin
    fStartingNumberAsString := Value;
    SetChanged;
  end;
end;

end.
