unit uDirSync;
{$INCLUDE 'DirSync.inc'}

interface

uses
  Windows, SysUtils, Classes,
  {$IFDEF FAR3} Plugin3, {$ELSE} PluginW, {$ENDIF} FarKeysW, FarColor,
  uSynchronizeDirectories, uLog, uFAR;

procedure SetStartupInfoW(const Info: TPluginStartupInfo); stdcall;
procedure ExitFARW( {$IFDEF FAR3} const Info: TExitInfo {$ENDIF} ); stdcall;
{$IFDEF FAR3}
procedure GetGlobalInfoW(var Info: TGlobalInfo); stdcall;
function OpenW(var Info: TOpenInfo): THandle; stdcall;
{$ELSE}
function OpenPluginW(OpenFrom: integer; Item: integer): THandle; stdcall;
{$ENDIF}
procedure GetPluginInfoW(var Info: TPluginInfo); stdcall;
function ProcessEditorEventW( {$IFDEF FAR3} const Info: TProcessEditorEventInfo {$ELSE} Event: integer; Param: Pointer {$ENDIF} ): TIntPtr; stdcall;
function ProcessEditorInputW( {$IFDEF FAR3} const Info: TProcessEditorInputInfo {$ELSE} const Info: TInputRecord {$ENDIF} ): TIntPtr; stdcall;
function ConfigureW( {$IFDEF FAR3} var Info: TConfigureInfo {$ELSE} ItemNumber: integer {$ENDIF} ): TIntPtr; stdcall;

function SyncTasks: TSynchronizeTaskList;

implementation

uses
  uSystem, uDirSyncConsts, uMessages, uDiscoverDifferences,
  uOptions, uConfigDialog, uEditorTasks;

const
  PLUGIN_TITLE = 'DirSync';
  PLUGIN_DESCRIPTION = 'Synchronize Directory';
  PLUGIN_AUTHOR = 'Pepak, http://www.pepak.net';

var
  InternalSyncTasks: TSynchronizeTaskList = nil;

function SyncTasks: TSynchronizeTaskList;
begin
  if InternalSyncTasks = nil then
    InternalSyncTasks := TSynchronizeTaskList.Create(True);
  Result := InternalSyncTasks;
end;

function FindEditor(EditorID: integer; out SyncTask: TSynchronizeTask; out SyncTaskIndex: integer): boolean;
var
  Info: TEditorInfo;
  FileName: string;
begin
  Result := False;
  if EditorID = -1 then
    if TFarEditor.GetInfo(EditorID, Info) then
      EditorID := Info.EditorID;
  if SyncTasks.FindByEditorID(Info.EditorID, SyncTask, SyncTaskIndex) then
    Result := True
  else
    if TFarEditor.GetFileName(EditorID, FileName) then
      if SyncTasks.FindByFileName(FileName, SyncTask, SyncTaskIndex) then
        Result := True;
end;

procedure SetStartupInfoW(const Info: TPluginStartupInfo);
begin
  FarApi := Info;
  FSF := Info.FSF^;
  FarApi.FSF := @FSF;
end;

procedure ExitFARW( {$IFDEF FAR3} const Info: TExitInfo {$ENDIF} );
begin
  FreeAndNil(InternalSyncTasks);
end;

{$IFDEF FAR3}
procedure GetGlobalInfoW(var Info: TGlobalInfo);
var
  FN: string;
  VerHigh, VerLow, VerBuild: LongWord;
begin
  FillChar(Info, Sizeof(Info), 0);
  Info.StructSize := Sizeof(Info);
  Info.MinFarVersion := MakeFarVersion( {$IFDEF FAR3} 3, 0, 0, 0, 0 {$ELSE} 2, 0, 1807 {$ENDIF} );
  FN := GetModuleName(HInstance);
  if GetFileVersion(FN, VerHigh, VerLow, VerBuild) then
    Info.Version := MakeFarVersion( {$IFDEF FAR3} VerHigh, VerLow, 0, VerBuild, 0 {$ELSE} VerHigh, VerLow, VerBuild {$ENDIF} )
  else
    Info.Version := MakeFarVersion( {$IFDEF FAR3} 0, 0, 0, 0, 0 {$ELSE} 0, 0 ,0 {$ENDIF} );
  Info.Guid := PLUGIN_GUID;
  Info.Title := PLUGIN_TITLE;
  Info.Description := PLUGIN_DESCRIPTION;
  Info.Author := PLUGIN_AUTHOR;
end;
{$ENDIF}

var
  PluginInfoInitialized: boolean = False;
  PanelMenuStrings:  array[0..0] of PFarChar;
  PanelMenuGUIDs:    array[0..0] of TGUID;
  ConfigMenuStrings: array[0..0] of PFarChar;
  ConfigMenuGUIDs:   array[0..0] of TGUID;

procedure GetPluginInfoW(var Info: TPluginInfo);
begin
  // Initialize messages and GUIDs
  if not PluginInfoInitialized then begin
    PanelMenuStrings[0] := GetMsg(MTitle);
    PanelMenuGUIDs[0] := MENU_PANEL_GUID;
    ConfigMenuStrings[0] := GetMsg(MTitle);
    ConfigMenuGUIDs[0] := MENU_CONFIG_GUID;
    PluginInfoInitialized := True;
  end;
  FillChar(Info, Sizeof(Info), 0);
  Info.StructSize := Sizeof(Info);
  Info.Flags := PF_DIALOG or PF_EDITOR;
  {$IFDEF FAR3}
  Info.PluginMenu.Count := Length(PanelMenuStrings);
  Info.PluginMenu.Strings := @PanelMenuStrings[0];
  Info.PluginMenu.Guids := @PanelMenuGUIDs[0];
  Info.PluginConfig.Count := Length(ConfigMenuStrings);
  Info.PluginConfig.Strings := @ConfigMenuStrings[0];
  Info.PluginConfig.Guids := @ConfigMenuGUIDs[0];
  {$ELSE}
  Info.PluginMenuStringsNumber := Length(PanelMenuStrings);
  Info.PluginMenuStrings := @PanelMenuStrings[0];
  Info.PluginConfigStringsNumber := Length(ConfigMenuStrings);
  Info.PluginConfigStrings := @ConfigMenuStrings[0];
  {$ENDIF}
end;

{$IFDEF FAR3}
function OpenW(var Info: TOpenInfo): THandle;
{$ELSE}
function OpenPluginW(OpenFrom: integer; Item: integer): THandle;
{$ENDIF}
var
  Diff: TDiscoverDifferences;
  Edit: TEditorTasks;
begin
  if {$IFDEF FAR3} Info.OpenFrom = OPEN_EDITOR {$ELSE} OpenFrom = OPEN_EDITOR {$ENDIF} then begin
    Edit := TEditorTasks.Create;
    try
      Edit.EditorID := -1; // active editor
      Result := Edit.Execute;
    finally
      FreeAndNil(Edit);
    end;
  end
  else begin
    Diff := TDiscoverDifferences.Create;
    try
      Result := Diff.Execute;
    finally
      FreeAndNil(Diff);
    end;
  end;
end;

function ProcessEditorEventW( {$IFDEF FAR3} const Info: TProcessEditorEventInfo {$ELSE} Event: integer; Param: Pointer {$ENDIF} ): TIntPtr;
var
  SyncFile, NewSyncFile: TSynchronizeTask;
  SyncFileIndex: integer;
  NewDiffFileName: string;
  KeepNewDiffFile: boolean;
  ReturnToEditor: boolean;
begin
  Result := 0;
  {$IFDEF FAR3}
  if Info.StructSize >= Sizeof(Info) then
  {$ENDIF}
    if {$IFDEF FAR3} Info.Event {$ELSE} Event {$ENDIF} = EE_CLOSE then begin
      if FindEditor( {$IFDEF FAR3} Info.EditorID {$ELSE} -1 {$ENDIF} , SyncFile, SyncFileIndex ) then
        try
          SyncFile.Execute(ReturnToEditor);
          if ReturnToEditor then begin
            {$MESSAGE HINT 'It would be best if I could abort the EE_CLOSE. But apparently that is not possible (2014-04-11).'}
            NewDiffFileName := TFarUtils.TempFileName;
            KeepNewDiffFile := False;
            try
              if CopyFile(PChar(SyncFile.FileName), PChar(NewDiffFileName), False) then begin
                NewSyncFile := SyncTasks.Add(0, NewDiffFileName, SyncFile.LeftDirectory, SyncFile.RightDirectory);
                if NewSyncFile <> nil then
                  if NewSyncFile.ShowEditor then
                    KeepNewDiffFile := True;
              end;
            finally
              if not KeepNewDiffFile then
                DeleteFile(NewDiffFileName);
            end;
          end;
        finally
          if SyncFileIndex >= 0 then
            SyncTasks.Delete(SyncFileIndex);
        end;
    end;
end;

function ProcessEditorInputW( {$IFDEF FAR3} const Info: TProcessEditorInputInfo {$ELSE} const Info: TInputRecord {$ENDIF} ): TIntPtr;
{$IFDEF EDITOR_SHORTCUTS_ACTIVE}

  function RunEditorTask(const EditorTask: TEditorTasks.TEditorTask): TIntPtr;
    var
      Tasks: TEditorTasks;
    begin
      //Log('Running editor task %d', [Integer(EditorTask)]);
      Tasks := TEditorTasks.Create;
      try
        Tasks.EditorID := -1;
        if Tasks.ExecuteTask(EditorTask) then begin
          Result := 1;
          TFarEditor.Refresh(-1);
          //Log('Running editor task %d -> success', [Integer(EditorTask)]);
        end
        else begin
          Result := 0;
          //Log('Running editor task %d -> failure', [Integer(EditorTask)]);
        end;
      finally
        FreeAndNil(Tasks);
      end;
    end;

var
  ShiftFlags: DWORD;
  P: PInputRecord;
{$ENDIF}
begin
  Result := 0;
  {$IFDEF EDITOR_SHORTCUTS_ACTIVE}
  {$IFDEF FAR3}
  P := @Info.Rec;
  {$ELSE}
  P := @Info;
  {$ENDIF}
  {$IFDEF FAR3}
  if Info.StructSize >= Sizeof(Info) then
  {$ENDIF}
    if P^.EventType = KEY_EVENT then
      if P^.Event.KeyEvent.bKeyDown then begin
        ShiftFlags := P^.Event.KeyEvent.dwControlKeyState and (LEFT_ALT_PRESSED or LEFT_CTRL_PRESSED or RIGHT_ALT_PRESSED or RIGHT_CTRL_PRESSED or SHIFT_PRESSED);
        //Log('Editor input. Char=%d, VirtualKeyCode=%d, VirtualScanCode=%d, ShiftFlags = %x', [Ord(P^.Event.KeyEvent.UnicodeChar), P^.Event.KeyEvent.wVirtualKeyCode, P^.Event.KeyEvent.wVirtualScanCode, ShiftFlags]);
        if (ShiftFlags = LEFT_ALT_PRESSED) or (ShiftFlags = RIGHT_ALT_PRESSED) then begin
          if P^.Event.KeyEvent.wVirtualKeyCode = VK_F1 then
            Result := RunEditorTask(etCopyToLeft)
          else if P^.Event.KeyEvent.wVirtualKeyCode = VK_F2 then
            Result := RunEditorTask(etCopyToRight)
          else if P^.Event.KeyEvent.wVirtualKeyCode = VK_F3 then
            Result := RunEditorTask(etCompare);
        end
        else if (ShiftFlags = LEFT_CTRL_PRESSED) or (ShiftFlags = RIGHT_CTRL_PRESSED) then begin
          if P^.Event.KeyEvent.wVirtualKeyCode = VK_F1 then
            Result := RunEditorTask(etEditLeft)
          else if P^.Event.KeyEvent.wVirtualKeyCode = VK_F2 then
            Result := RunEditorTask(etEditRight)
          else if P^.Event.KeyEvent.wVirtualKeyCode = VK_F3 then
            Result := RunEditorTask(etFileInfo)
        end;
      end;
  {$ENDIF}
end;

function ConfigureW( {$IFDEF FAR3} var Info: TConfigureInfo {$ELSE} ItemNumber: integer {$ENDIF} ): TIntPtr;
var
  Dlg: TConfigDialog;
begin
  Result := 0;
  {$IFDEF FAR3}
  if Info.StructSize >= Sizeof(Info) then begin
  {$ENDIF}
    //if Info.GUID^ = MENU_CONFIG_GUID then begin
      Dlg := TConfigDialog.Create;
      try
        Dlg.Options := TDirSyncOptions.Create;
        try
          Dlg.Options.Load;
          if Dlg.Execute then
            Result := 1;
        finally
          Dlg.Options.Free;
          Dlg.Options := nil;
        end;
      finally
        FreeAndNil(Dlg);
      end;
    //end;
  {$IFDEF FAR3}
  end;
  {$ENDIF}
end;


exports
  SetStartupInfoW,
  ExitFARW,
  {$IFDEF FAR3}
  GetGlobalInfoW,
  OpenW,
  {$ELSE}
  OpenPluginW,
  {$ENDIF}
  GetPluginInfoW,
  ProcessEditorEventW,
  ProcessEditorInputW,
  ConfigureW;

initialization
  {$IFDEF FAR3}
  uFar.PluginID := PLUGIN_GUID;
  {$ENDIF}
  InternalSyncTasks := nil;

finalization
  FreeAndNil(InternalSyncTasks);

end.
