
program Substitute;

{$IFDEF MSWINDOWS}
{$APPTYPE CONSOLE}
{$ENDIF}

{$IFDEF UNIX}
{$DEFINE USECTHREADS}
{$ENDIF}

uses
{$IFDEF UNIX}
  CThreads,
  CWString,
{$ENDIF}
  Classes,
  SysUtils,
  IniFiles,
  Connect,
  ChessGame,
  Detect,
  Utils;

{$I version}

var
{$IFDEF DEBUG}
  LLogFileName: string;
{$ENDIF}
  LChessGame: TChessGame;
  LCS: TRTLCriticalSection;

{$IFDEF DEBUG}
procedure LogLn(const ALine: string; const AForceRewrite: boolean = FALSE);
var
  LFile: text;
begin
  Assign(LFile, LLogFileName);
  if FileExists(LLogFileName) and not AForceRewrite then
    Append(LFile)
  else
    Rewrite(LFile);
  WriteLn(LFile, FormatDateTime('hh:nn:ss:zzz', Time) + ' ' + ALine);
  Close(LFile);
end;
{$ENDIF}

type
  TListener = class(TThread)
  private
    FMessage: TStringList;
    FWaitAfterRead: cardinal;
    procedure SendMessage;
  protected
    procedure Execute; override;
  public
    constructor Create(const ACreateSuspended: boolean; const AWaitAfterRead: cardinal);
    destructor Destroy; override;
  end;

constructor TListener.Create(const ACreateSuspended: boolean; const AWaitAfterRead: cardinal);
begin
  inherited Create(ACreateSuspended);
  FreeOnTerminate := FALSE;
  FMessage := TStringList.Create;
  FWaitAfterRead := AWaitAfterRead;
end;

destructor TListener.Destroy;
begin
  FMessage.Free;
  inherited Destroy;
end;

procedure TListener.Execute;
begin
  while not Terminated do
  begin
    FMessage.Text := ReadProcessOutput;
    if FMessage.Count > 0 then
      SendMessage;
    Sleep(FWaitAfterRead);
  end;
end;

procedure TListener.SendMessage;
var
  LMove, LText: string;
  i: integer;
begin
(*
{$IFDEF DEBUG}
  for i := 0 to Pred(FMessage.Count) do
    LogLn('<< ' + FMessage[i]);
{$ENDIF}
  LText := Trim(FMessage.Text);
  LMove := DetectEngineMove(LText);
  if Length(LMove) > 0 then
  begin
{$IFDEF DEBUG}
    LogLn('** Engine move detected: ' + LMove);
{$ENDIF}
    if LChessGame.IsPromotion(LMove) then
    begin
{$IFDEF DEBUG}
      LogLn('** Promotion move');
{$ENDIF}
      if Length(LMove) = 4 then
      begin
{$IFDEF DEBUG}
        LogLn('** Missing suffix');
{$ENDIF}
        LText := Concat(LText, 'q');
      end;
    end;
    
    EnterCriticalSection(LCS);
    try
      LChessGame.DoMove(LMove);
    finally
      LeaveCriticalSection(LCS);
    end;
  end;
  
  WriteLn(LText);
  Flush(output);
*)

  for i := 0 to Pred(FMessage.Count) do
  begin
{$IFDEF DEBUG}
    LogLn('<< ' + FMessage[i]);
{$ENDIF}
    LText := FMessage[i];
    LMove := DetectEngineMove(LText);
    if Length(LMove) > 0 then
    begin
{$IFDEF DEBUG}
      LogLn('** Engine move detected: ' + LMove);
{$ENDIF}
      if LChessGame.IsPromotion(LMove) then
      begin
{$IFDEF DEBUG}
        LogLn('** Promotion move');
{$ENDIF}
        if Length(LMove) = 4 then
        begin
{$IFDEF DEBUG}
          LogLn('** Missing suffix');
{$ENDIF}
          LText := Concat(LText, 'q');
        end;
      end;
      
      EnterCriticalSection(LCS);
      try
        LChessGame.DoMove(LMove);
      finally
        LeaveCriticalSection(LCS);
      end;
    end;
    
    WriteLn(LText);
    Flush(output);
  end;
end;

const
  (* Default settings *)
  CProcessName    = 'umax';
  CRestAfterRead  = 20;
  (* INI file sections and keys *)
  CSectionProcess = 'process';
  CSectionTime    = 'time';
  CKeyProcessName = 'name';
  CKeyRest        = 'rest';
  (* Start position *)
  CStartPosition  = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';
  
var
  LIniFileName,
  LProcessName: string;
  LListener: TThread;
  LInput: string;
  LWaitAfterRead: cardinal;
  LMove: string;
  
begin
  LChessGame := TChessGame.Create(CStartPosition);
  
  InitCriticalSection(LCS);
  
{$IFDEF DEBUG}
  LLogFileName := LogFileName(ParamStr(0));
  LogLn('** ' + CAppInfo, TRUE);
{$ENDIF}
  
  LIniFileName := ChangeFileExt(ParamStr(0), '.ini');
  (* Try to read executable name from INI file. *)
  with TIniFile.Create(LIniFileName) do
  try
    LProcessName := ReadString(CSectionProcess, CKeyProcessName, CProcessName);
    LWaitAfterRead := ReadInteger(CSectionTime, CKeyRest, CRestAfterRead);
  finally
    Free;
  end;
  
  (* Try to read executable name from command line. *)
  if ParamCount = 1 then
    LProcessName := ParamStr(1);
  
  if not FileExists(LProcessName) then
  begin
    WriteLn(StdErr, 'Cannot find ' + LProcessName);
    Exit;
  end;
  
  (* Create INI file if it doesn't exist. *)
  if not FileExists(LIniFileName) then
    with TIniFile.Create(LIniFileName) do
    try
      WriteString(CSectionProcess, CKeyProcessName, CProcessName);
      WriteInteger(CSectionTime, CKeyRest, CRestAfterRead);
      UpdateFile;
    finally
      Free;
    end;
{$IFDEF DEBUG}
  LogLn('** Executable: ' + LProcessName);
{$ENDIF}
  if CreateConnectedProcess(LProcessName) then
  begin
    LListener := TListener.Create(TRUE, LWaitAfterRead);
    LListener.Priority := tpNormal;
    LListener.Start;
    
    while ProcessRunning and not Eof do
    begin
      ReadLn(LInput);
{$IFDEF DEBUG}
      LogLn('>> ' + LInput);
{$ENDIF}
      if LInput = 'new' then
      begin
{$IFDEF DEBUG}
        LogLn('** New game');
{$ENDIF}
        LChessGame.Free;
        LChessGame := TChessGame.Create(CStartPosition);
      end;
      LMove := DetectGuiMove(LInput);
      if Length(LMove) > 0 then
      begin
{$IFDEF DEBUG}
        LogLn('** GUI move detected: ' + LMove);
{$ENDIF}
        EnterCriticalSection(LCS);
        try
          LChessGame.DoMove(LMove);
        finally
          LeaveCriticalSection(LCS);
        end;
      end;
      WriteProcessInput(LInput);
    end;
    LListener.Terminate;
    LListener.WaitFor;
    LListener.Free;
    FreeConnectedProcess;
  end else
    WriteLn(ErrOutput, 'Cannot create process');
  
  LChessGame.Free;
  DoneCriticalSection(LCS);
end.
