library libmml;

{$mode objfpc}{$H+}

uses
  cmem,Classes,interfaces,graphics,client,sysutils,MufasaTypes,dtmutil, dtm;

//{$R *.res}

Const
  RESULT_OK = 0;
  RESULT_FALSE = 1;
  RESULT_ERROR = -1;

  MOUSE_UP = 0;
  MOUSE_DOWN = 1;

{
    Global variables.
    To actually read the last_error, make sure you copy it to a safe place
    directly after the function call.
}
var
  last_error: String;
  debug: boolean;

 { This must be called on Library load }

function init: integer;  cdecl;
begin
  last_error := '';
  debug := true;
  result := RESULT_OK;
end;

procedure set_last_error(s: string);
begin
  last_error := s;
  if debug then
      writeln('ERROR: ' + s);
end;

{ Validate the TClient. If it is NULL, set last error and return false }
function validate_client(C: TClient): boolean; inline;
begin
  result := Assigned(C);
  if not result then
  begin
    last_error := 'PClient is NULL';
    if debug then
      writeln(last_error);
  end;
end;

{
  Create a TClient.
  You can use multiple, but you'll have to manage them yourself.
}
function create_client: PtrUInt; cdecl;
var
  C: TClient;
begin
  try
    C := TClient.Create('');
    Result := PtrUInt(C);
  except on e : Exception do
  begin
    // FIXME UINT negative
    result := PtrUInt(RESULT_ERROR);
    set_last_error(e.message);
  end;
  end;
  writeln(format('C: %d, IOManager: %d', [PtrUInt(C), PtrUInt(C.IOManager)]));
end;

{ Destroy a TClient }
function destroy_client(C: TClient): integer; cdecl;
begin
  try
    C.Free;
  except on e : Exception do
  begin
    result := RESULT_ERROR;
    set_last_error(e.message);
  end;
  end;
end;

{ Set (verbose) debug on/off }
procedure set_debug(v: Boolean); cdecl;
begin
  debug := v;
end;

{ Get debug }
function get_debug: boolean; cdecl;
begin
  exit(debug);
end;

{
  VERY IMPORTANT: If you use get_last_error, you must immediately store the
  resulting string somewhere else. As soon as you do other calls, the last error
  may be reset or assigned a different memory position, making your old
  pointer invalid.
}
function get_last_error: pchar; cdecl;
begin
  exit(@last_error[1]);
end;

{ Turn an array into a pointer. The pointer memory is not managed by FPC, so we can pass
  it along happily. It'll have to be freed by the external control though }
function array_to_ptr(ptr: Pointer; size: PtrUInt; objsize: PtrUInt): Pointer; cdecl;
begin
  result := GetMem(objsize * size);
  Move(ptr^, result^, objsize * size);
end;

{ Free memory previously allocated by libMML }
function free_ptr(ptr: pointer): boolean; cdecl;
begin
  result := Assigned(ptr);
  if not result then
  begin
    set_last_error('TClient is NULL');
    if debug then
      writeln(last_error);
  end else
    FreeMem(ptr);
end;

{ Allocate memory with libMML }
function alloc_mem(size, objsize: PtrUInt): Pointer; cdecl;
begin
  result := GetMem(size * objsize);
end;

{ Reallocate memory with libMML }
function realloc_mem(ptr: Pointer; size, objsize: PtrUInt): Pointer; cdecl;
begin
  result := ReAllocMem(ptr, size*objsize);
end;

{ Mouse }

{ Returns mouse position of client C to point t }
function get_mouse_pos(C: TClient; var t: tpoint): integer; cdecl;

begin
  if not validate_client(C) then
    exit(RESULT_ERROR);

  try
    C.IOManager.GetMousePos(t.x,t.y);
    result := RESULT_OK;
  except on e : Exception do
    begin
      result := RESULT_ERROR;
      set_last_error(e.Message);
    end;
  end;
end;

{ Set mouse position of client C to point t }
function set_mouse_pos(C: TClient; var t: tpoint): integer; cdecl;
begin
  try
    C.IOManager.MoveMouse(t.x,t.y);
    result := RESULT_OK;
  except on e : Exception do
    begin
        result := RESULT_ERROR;
        set_last_error(e.Message);
    end;
  end;
end;


{ Helper function }
function ConvIntClickType(Int : Integer) : TClickType; inline;
begin
  case int of
    0 : result := mouse_Left;
    1 : result := mouse_Right;
    2 : result := mouse_Middle;
  end;
end;

{ Return the state of a mouse button given client C }
function get_mouse_button_state(C: TClient; But: Integer): Integer;  cdecl;
begin
  try
    if C.IOManager.IsMouseButtonDown(ConvIntClickType(But)) then
      result := MOUSE_DOWN
    else
      result := MOUSE_UP;
  except on e : Exception do
    begin
      result := RESULT_ERROR;
      set_last_error(e.Message);
    end;
  end;
end;

{ Set the state of a mouse button given client C }
function set_mouse_button_state(C: TClient; But, State, X, Y: Integer): Integer;  cdecl;
begin
  try
    if State = MOUSE_UP then
    begin
      C.IOManager.ReleaseMouse(X, Y, ConvIntClickType(But));
      result := RESULT_OK;
    end else if state = MOUSE_DOWN then
    begin
      C.IOManager.HoldMouse(X, Y, ConvIntClickType(But));
      result := RESULT_OK;
    end;
  except on e : Exception do
    begin
      result := RESULT_ERROR;
      set_last_error(e.Message);
    end;
  end;
end;


{ Colour }

function get_color(C: TClient; x, y: Integer;
                   out color: Integer): Integer; cdecl;
begin
  try
    color := C.IOManager.GetColor(x, y);
    if color > -1 then
        result := RESULT_OK
    else
        result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

{ Find color on client C in area (x1,y1,x2,y2) and return coordinate (if any) in x, y }
function find_color(C: TClient; var x, y: Integer;
                    color, x1, y1, x2, y2: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColor(x, y, color, x1, y1, x2, y2) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.Message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_color_tolerance(C: TClient; var x, y: Integer; color: Integer;
                              tol, x1, y1, x2, y2: Integer): Integer;  cdecl;

begin
  try
    if C.MFinder.FindColorTolerance(x, y, color, x1, y1, x2, y2, tol) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.Message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_color_tolerance_optimised(C: TClient; var x, y: Integer;
                                        var len: Integer; col: Integer;
                                        x1, y1, x2, y2: Integer;
                                        tol: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColorToleranceOptimised(x, y, col, x1, y1, x2, y2,
                                            tol) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_colors(C: TClient; var ptr: PPoint; var len: Integer;
                     color, x1, y1, x2, y2: Integer): Integer;  cdecl;
var
  TPA: TPointArray;
begin
  setlength(TPA, 0);
  try
    C.MFinder.FindColors(TPA, color, x1, y1, x2, y2);
  except on e : Exception do
    begin
      set_last_error(e.Message);
      result := RESULT_ERROR;
    end;
  end;

  len := Length(TPA);
  if len > 0 then
    result := RESULT_OK
  else
  begin
    setlength(tpa, 0);
    exit(RESULT_FALSE);
  end;

  ptr := array_to_ptr(Pointer(@TPA[0]), len, sizeof(TPoint));
  setlength(tpa, 0);
end;

function find_colors_tolerance(C: TClient; var ptr: PPoint; var len: Integer;
               color, tol, x1, y1, x2, y2: Integer): Integer;  cdecl;
var
  TPA: TPointArray;
begin
  try
    C.MFinder.FindColorsTolerance(TPA, color, x1, y1, x2, y2, tol);
  except on e : Exception do
    begin
      set_last_error(e.Message);
      result := RESULT_ERROR;
    end;
  end;

  len := Length(TPA);
  if len > 0 then
    result := RESULT_OK
  else
  begin
    setlength(tpa, 0);
    exit(RESULT_FALSE);
  end;

  ptr := array_to_ptr(Pointer(@TPA[0]), len, sizeof(TPoint));
  setlength(TPA, 0);
end;

function find_colors_tolerance_optimised(C: TClient; var ptr: PPoint;
                                         var len: Integer; col: Integer;
                                         x1, y1, x2, y2: Integer;
                                         tol: Integer): Integer; cdecl;
var
  TPA: TPointArray;
begin
  try
      C.MFinder.FindColorsToleranceOptimised(TPA, col, x1, y1, x2, y2, tol);
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;

  len := Length(TPA);
  if len > 0 then
    result := RESULT_OK
  else
  begin
    setlength(tpa, 0);
    exit(RESULT_FALSE);
  end;

  ptr := array_to_ptr(Pointer(@TPA[0]), len, sizeof(TPoint));
  setlength(TPA, 0);
end;

function similar_colors(C: TClient; col1, col2, tol: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.SimilarColors(col1, col2, tol) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_FALSE;
    end;
  end;
end;

function count_color(C: TClient; out count: Integer;
                     Color, xs, ys, xe, ye: Integer): Integer; cdecl;
begin
  try
    count := C.MFinder.CountColor(Color, xs, ys, xe, ye);
    if count > 0 then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function count_color_tolerance(C: TClient; out count: Integer; col: Integer;
                               xs, ys, xe, ye, tol: Integer): Integer; cdecl;
begin
  try
    count := C.MFinder.CountColorTolerance(col, xs, ys, xe, ye, tol);
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;

  if count > 0 then
    result := RESULT_OK
  else
    result := RESULT_FALSE;
end;

function find_color_spiral(C: TClient; var x, y: Integer;
                           col, xs, ys, xe, ye: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColorSpiral(x, y, col, xs, ys, xe, ye) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_color_spiral_tolerance(C: TClient; var x, y: Integer;
                                     col, xs, ys, xe, ye: Integer;
                                     tol: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColorSpiralTolerance(x, y, col, xs, ys, xe, ye,
      tol) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_colored_area(C: TClient; var x, y: Integer;
                           col, xs, ys, xe, ye, minA: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColoredArea(x, y, col, xs, ys, xe, ye, minA) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function find_colored_area_tolerance(C: TClient; var x, y: Integer;
                                     col, xs, ys, xe, ye, minA: Integer;
                                     tol: Integer): Integer; cdecl;
begin
  try
    if C.MFinder.FindColoredAreaTolerance(x, y, col,
                                          xs, ys, xe, ye, minA, tol) then
      result := RESULT_OK
    else
      result := RESULT_FALSE;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function set_tolerance_speed(C: TClient; nCTS: Integer): Integer; cdecl;
begin
  try
    C.MFinder.SetToleranceSpeed(nCTS);
    result := RESULT_OK;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function get_tolerance_speed(C: TClient; out cts: Integer): Integer; cdecl;
begin
  try
    cts := C.MFinder.GetToleranceSpeed;
    result := RESULT_OK;
  except on e: Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end
  end;
end;

function set_tolerance_speed_2_modifiers(C: TClient;
                                         nHue, nSat: Extended): Integer; cdecl;
begin
  try
    C.MFinder.SetToleranceSpeed2Modifiers(nHue, nSat);
    result := RESULT_OK;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

function get_tolerance_speed_2_modifiers(C: TClient; out hueMod: Extended;
                                         out satMod: Extended): Integer; cdecl;
var
  h, s: Extended;
begin
  try
    C.MFinder.GetToleranceSpeed2Modifiers(h, s);
    hueMod := h;
    satMod := s;
    result := RESULT_OK;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_ERROR;
    end;
  end;
end;

{ DTM }

{ FIXME: DTM has not been tested yet! }

{ Create a MDTM}
function create_dtm(PointLen: integer; Points: PMDTMPoint; DTM: TMDTM): integer;
    cdecl;
var
  i: integer;
begin
  DTM := TMDTM.Create;
  for i := 0 to PointLen - 1 do
    DTM.AddPoint(Points[i]);

  if DTM.Valid then
    exit(RESULT_OK);

  DTM.Free;
  set_last_error('Invalid DTM');
  result := RESULT_ERROR;
end;

{ Delete a MDTM. Don't delete it if it is managed! use remove_dtm instead }
function delete_dtm(C: TClient; DTM: TMDTM): integer; cdecl;
begin
  if not assigned(DTM) then
  begin
    set_last_error('DTM is NULL');
    exit(RESULT_ERROR);
  end;

  DTM.Free;

  result := RESULT_OK;
end;

{ Add a previously created DTM to the DTM Manager }
function add_dtm(C: TClient; DTM: TMDTM; var index: integer): integer; cdecl;
begin
  if not assigned(DTM) then
  begin
    set_last_error('DTM is NULL');
    exit(RESULT_ERROR);
  end;

  try
    index := C.MDTMs.AddDTM(DTM);
    exit(RESULT_OK);
  except on e : Exception do
    result := RESULT_ERROR;
  end;
end;

{ Remove a previously added DTM from the DTM manager. This also frees the DTM }
function remove_dtm(C: TClient; DTMi: integer): integer; cdecl;
begin
  C.MDTMs.FreeDTM(DTMi);
end;

{ Find a DTM given DTM index i, client C in area x1,y1,x2,y2. Return coord at x, y. }
function find_dtm(C: TClient; DTMi: integer; var x, y: integer; x1, y1, x2,
    y2: integer): integer; cdecl;
var
  res: boolean;
begin
  try
    res := C.MFinder.FindDTM(C.MDTMs.DTM[DTMi], x, y, x1, y1, x2, y2);
  except on e : Exception do
    begin;
      result := RESULT_ERROR;
      set_last_error(e.Message);
    end;
  end;

  if res then
    result := RESULT_OK
  else
    result := RESULT_FALSE;
end;

{ Find a DTM given DTM index i, client C in area x1,y1,x2,y2. Return coord at x, y. }
function find_dtms(C: TClient; DTMi: integer; ptr: PPoint; x1, y1, x2, 
    y2: integer): integer; cdecl;
var
  res: boolean;
  len: integer;
  TPA: TPointArray;
begin
  try
    res := C.MFinder.FindDTMs(C.MDTMs.DTM[DTMi], TPA, x1, y1, x2, y2);
  except on e : Exception do
  begin;
    result := RESULT_ERROR;
    set_last_error(e.Message);
  end;
  end;

  len := Length(TPA);
  if len > 0 then
    result := RESULT_OK
  else
  begin
    setlength(tpa, 0);
    exit(RESULT_FALSE);
  end;

  ptr := array_to_ptr(Pointer(@TPA[0]), len, sizeof(TPoint));
  setlength(TPA, 0);
end;

function set_array_target(C: TClient; Arr: PRGB32; Size: TPoint): integer; 
    cdecl;
begin
  if not assigned(Arr) then
  begin
    set_last_error('Arr is not assigned');
    exit(RESULT_FALSE);
  end;

  try
    C.IOManager.SetTarget(Arr, Size);
    result := RESULT_OK;
  except on e : Exception do
    begin
      set_last_error(e.message);
      result := RESULT_FALSE;
    end;
  end;
end;

exports

  init,
  create_client,
  destroy_client,
  get_last_error,
  get_debug,
  set_debug,
  alloc_mem,
  realloc_mem,
  free_ptr,

  get_mouse_pos, set_mouse_pos,
  get_mouse_button_state, set_mouse_button_state,

  get_color,

  find_color, find_color_tolerance,

  find_colors, find_colors_tolerance,

  create_dtm, delete_dtm, add_dtm, remove_dtm,

  find_dtm,

  set_array_target;


begin
end.
