unit uEditorTasks;
{$INCLUDE 'DirSync3.inc'}

interface

uses
  Windows, SysUtils, Classes, Generics.Collections, Generics.Defaults,
  {$IFDEF FAR3} Plugin3, {$ELSE} PluginW, {$ENDIF} FarKeysW, FarColor,
  uSystem, uFiles, uFAR, uLog,
  uDirSyncConsts, uDirSync, uSynchronizeDirectories, uMessages,
  uDifferences, uSynchronizeUI;

type
  TEditorTasks = class
  private
    fEditorInfo: TEditorInfo;
    fSyncTask: TSynchronizeTask;
  protected
    function GetEditorID: integer;
    procedure SetEditorID(const Value: integer);
    function GetFileInfo(out Info: TDifferenceItem): boolean;
    property SyncTask: TSynchronizeTask read fSyncTask;
  public
    type
      TEditorTask = (
        etNone, etCopyToLeft, etCopyToRight, etCompare,
        etEditLeft, etEditRight, etFileInfo,
        etFindNextToLeft, etFindNextToRight
      );
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    function Execute: integer;
    function ExecuteTask(ATask: TEditorTask): boolean;
    function AskUserWhichTask: TEditorTask;
    function CompareFiles: boolean;
    function FileInfo: boolean;
    function EditFile(IsLeftDirectory: boolean): boolean;
    function ChangeCopyDirection(CopyToLeft: boolean): boolean;
    function FindNextDifference(Differences: TDifferenceTypes): boolean;
    property EditorID: integer read GetEditorID write SetEditorID;
  end;

implementation

{ TEditorHelper }

function TEditorTasks.AskUserWhichTask: TEditorTask;

  procedure SetMenuItem(var MenuItem: TFarMenuItem; TextID: TMessages {$IFDEF FAR3_UP} ; Flags: TMenuItemFlags {$ENDIF} );
    begin
      FillChar(MenuItem, Sizeof(MenuItem), 0);
      MenuItem.TextPtr := GetMsg(TextID);
      {$IFDEF FAR3_UP}
      MenuItem.Flags := Flags;
      {$ELSE}
      MenuItem.Selected := 0;
      MenuItem.Checked := 0;
      MenuItem.Separator := 0;
      {$ENDIF}
    end;

var
  MenuItems: packed array[0..7] of TFarMenuItem;
begin
  SetMenuItem(MenuItems[0], MOverwriteLeft {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[1], MOverwriteRight {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[2], MCompare {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[3], MEditLeft {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[4], MEditRight {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[5], MShowFileInfo {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[6], MFindNextToLeft {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  SetMenuItem(MenuItems[7], MFindNextToRight {$IFDEF FAR3_UP} , MIF_NONE {$ENDIF} );
  case FarApi.Menu( {$IFDEF FAR3_UP} PLUGIN_GUID, MENU_EDITOR_GUID {$ELSE} FarApi.ModuleNumber {$ENDIF} , -1, -1, 0, FMENU_WRAPMODE, GetMsg(MTitle), nil, nil, nil, nil, @MenuItems[0], Length(MenuItems)) of
    0:   Result := etCopyToLeft;
    1:   Result := etCopyToRight;
    2:   Result := etCompare;
    3:   Result := etEditLeft;
    4:   Result := etEditRight;
    5:   Result := etFileInfo;
    6:   Result := etFindNextToLeft;
    7:   Result := etFindNextToRight;
    else Result := etNone;
  end;
end;

function TEditorTasks.ChangeCopyDirection(CopyToLeft: boolean): boolean;
var
  DestDifferenceType: TDifferenceType;
  SrcDifferenceTypes: set of TDifferenceType;
  CurrentLine, SelStart, SelLength, OriginalLine, OriginalTopLine: TIntPtr;
  DiffInfo: TDifferenceItem;
  Line: string;
  IsBlock: boolean;
begin
  //Log('ChangeCopyDirection(%d)', [Integer(CopyToLeft)]);
  if CopyToLeft then begin
    DestDifferenceType := dtOlderLeft;
    SrcDifferenceTypes := [dtOlderRight, dtDifferent];
  end
  else begin
    DestDifferenceType := dtOlderRight;
    SrcDifferenceTypes := [dtOlderLeft, dtDifferent];
  end;
  IsBlock := fEditorInfo.BlockType <> BTYPE_NONE;
  //Log('- IsBlock = %d', [Integer(IsBlock)]);
  OriginalTopLine := fEditorInfo.TopScreenLine;
  OriginalLine := fEditorInfo.CurLine;
  if IsBlock then
    CurrentLine := fEditorInfo.BlockStartLine
  else
    CurrentLine := OriginalLine;
  DiffInfo := TDifferenceItem.Create;
  try
    repeat
      //Log('- Loop started');
      //Log('  - CurrentLine = %d', [CurrentLine]);
      if IsBlock then
        if CurrentLine >= fEditorInfo.TotalLines then
          Break
        else begin
          TFarEditor.SetCursorPosition(EditorID, CurrentLine);
          Inc(CurrentLine);
        end;
      if not TFarEditor.GetString(EditorID, -1, Line, SelStart, SelLength) then
        Break;
      //Log('  - Got string: "%s"', [Line]);
      //Log('  - SelStart = %d, SelLength = %d', [SelStart, SelLength]);
      if IsBlock then
        if SelLength < 0 then
          Break
        else if SelStart > 1 then
          Continue
        else
          Line := Copy(Line, SelStart, SelLength);
      //Log('  - Selected string: "%s"', [Line]);
      if DiffInfo.SetAsString(Line) then
        if DiffInfo.DifferenceType in SrcDifferenceTypes then begin
          DiffInfo.DifferenceType := DestDifferenceType;
          DiffInfo.UsedMatch := umUnknown;
          TFarEditor.SetString(EditorID, -1, DiffInfo.AsString);
        end;
      //Log('  - Loop ended');
    until not IsBlock;
  finally
    FreeAndNil(DiffInfo);
  end;
  if IsBlock then
    TFarEditor.SetCursorPosition(EditorID, OriginalLine, -1, -1, OriginalTopLine, -1, -1);
  //Log('Done');
  Result := True;
end;

procedure TEditorTasks.Clear;
begin
  FillChar(fEditorInfo, Sizeof(fEditorInfo), 0);
  fSyncTask := nil;
end;

function TEditorTasks.CompareFiles: boolean;
var
  Info: TDifferenceItem;
begin
  Result := False;
  if SyncTask <> nil then
    if GetFileInfo(Info) then begin
      SyncTask.VisualCompare(SyncTask.LeftDirectory + Info.FileName, SyncTask.RightDirectory + Info.FileName);
      Result := True;
    end;
end;

constructor TEditorTasks.Create;
begin
  inherited Create;
  Clear;
end;

destructor TEditorTasks.Destroy;
begin
  inherited;
end;

function TEditorTasks.EditFile(IsLeftDirectory: boolean): boolean;
var
  Info: TDifferenceItem;
  FN: string;
begin
  Result := False;
  if SyncTask <> nil then
    if GetFileInfo(Info) then begin
      if IsLeftDirectory then
        FN := SyncTask.LeftDirectory + Info.FileName
      else
        FN := SyncTask.RightDirectory + Info.FileName;
    SyncTask.Edit(FN);
    Result := True;
    end;
end;

function TEditorTasks.Execute: integer;
begin
  Result := {$IFDEF FAR3_UP} 0 {$ELSE} Integer(INVALID_HANDLE_VALUE) {$ENDIF} ;
  if SyncTask <> nil then
    ExecuteTask(AskUserWhichTask);
end;

function TEditorTasks.ExecuteTask(ATask: TEditorTask): boolean;
begin
  Result := False;
  if SyncTask <> nil then
    case ATask of
      etNone:            Result := False;
      etCopyToLeft:      Result := ChangeCopyDirection(True);
      etCopyToRight:     Result := ChangeCopyDirection(False);
      etCompare:         Result := CompareFiles;
      etEditLeft:        Result := EditFile(True);
      etEditRight:       Result := EditFile(False);
      etFileInfo:        Result := FileInfo;
      etFindNextToLeft:  Result := FindNextDifference([dtMissingLeft, dtOlderLeft, dtDifferent]);
      etFindNextToRight: Result := FindNextDifference([dtMissingRight, dtOlderRight, dtDifferent]);
    end;
end;

function TEditorTasks.FileInfo: boolean;

  function GetColumns(const Col1, Col2, Col3: string): string;
    begin
      Result := Format('%-20.20s %20.20s %20.20s', [Col1, Col2, Col3]);
    end;

  function GetFileSizeStr(const FileName: string): string;
    var
      Size: int64;
    begin
      Size := GetFileSize(FileName);
      if Size >= 1000000000000 then
        Result := FormatCurr('###,###,##0" MiB"', Size div $100000)
      else if Size >= 0 then
        Result := FormatCurr('###,###,###,##0', Size)
      else
        Result := GetMsgPas(MUnknown);
    end;

  function GetFileDateStr(Index: integer; const FileName: string): string;
    var
      Dates: array[0..2] of TDateTime;
    begin
      if GetFileDateTime(FileName, Dates[0], Dates[1], Dates[2]) then
        Result := FormatDateTime('dd.mm.yyyy hh:nn:ss', Dates[Index])
      else
        Result := GetMsgPas(MUnknown);
    end;

var
  Info: TDifferenceItem;
  LeftFN, RightFN: string;
  Msg: array[0..10] of string;
begin
  Result := False;
  if SyncTask <> nil then
    if GetFileInfo(Info) then begin
      LeftFN := SyncTask.LeftDirectory + Info.FileName;
      RightFN := SyncTask.RightDirectory + Info.FileName;
      Msg[ 0] := GetMsgPas(MTitle);
      Msg[ 1] := GetMsgPas(MShowingFileInfo);
      Msg[ 2] := Info.FileName;
      Msg[ 3] := '';
      Msg[ 4] := GetColumns('',                               GetMsgPas(MLeftFile),      GetMsgPas(MRightFile));
      Msg[ 5] := GetColumns(GetMsgPas(MFileSize),             GetFileSizeStr(LeftFN),    GetFileSizeStr(RightFN));
      Msg[ 6] := GetColumns(GetMsgPas(MFileCreationTime),     GetFileDateStr(0, LeftFN), GetFileDateStr(0, RightFN));
      Msg[ 7] := GetColumns(GetMsgPas(MFileModificationTime), GetFileDateStr(1, LeftFN), GetFileDateStr(1, RightFN));
      Msg[ 8] := GetColumns(GetMsgPas(MFileAccessTime),       GetFileDateStr(2, LeftFN), GetFileDateStr(2, RightFN));
      Msg[ 9] := '';
      Msg[10] := GetMsgPas(MOk);
      TFarUtils.ShowMessage(Msg, 0, 1);
      Result := True;
    end;
end;

function TEditorTasks.FindNextDifference(Differences: TDifferenceTypes): boolean;
var
  DiffInfo: TDifferenceItem;
  LineNr: integer;
  Line: string;
begin
  Result := False;
  DiffInfo := TDifferenceItem.Create;
  try
    for LineNr := Succ(fEditorInfo.CurLine) to Pred(fEditorInfo.TotalLines) do begin
      if TFarEditor.GetString(EditorID, LineNr, Line) then begin
        if DiffInfo.SetAsString(Line) then begin
          if DiffInfo.DifferenceType in Differences then begin
            TFarEditor.SetCursorPosition(EditorID, LineNr);
            Result := True;
            Break;
          end;
        end;
      end;
    end;
  finally
    FreeAndNil(DiffInfo);
  end;
end;

function TEditorTasks.GetEditorID: integer;
begin
  if {$IFDEF FAR3_UP} (fEditorInfo.StructSize = 0) or {$ENDIF} (SyncTask = nil) then
    Result := -1
  else
    Result := fEditorInfo.EditorID;
end;

function TEditorTasks.GetFileInfo(out Info: TDifferenceItem): boolean;
var
  s: string;
begin
  Result := False;
  if SyncTask <> nil then
    if TFarEditor.GetString(EditorID, fEditorInfo.CurLine, s) then begin
      Info := TDifferenceItem.Create;
      if Info.SetAsString(s) then
        Result := True
      else begin
        FreeAndNil(Info);
        Result := False;
      end;
    end;
end;

procedure TEditorTasks.SetEditorID(const Value: integer);
var
  Editor: TFarEditorManaged;
begin
  if not TFarEditor.GetInfo(Value, fEditorInfo) then
    Clear
  else if not TFarEditorManaged.FindByEditorID(fEditorInfo.EditorID, Editor) then
    Clear
  else if not (Editor is TSynchronizeTask) then
    Clear
  else
    fSyncTask := TSynchronizeTask(Editor);
end;

end.
