unit SyNodePluginIntf;

interface

{$I Synopse.inc} // define HASINLINE CPU32 CPU64 OWNNORMTOUPPER
{$I SyNode.inc}   //define WITHASSERT

uses
  SynCommons,
  SpiderMonkey;

type
  {$ifdef USERECORDWITHMETHODS}TSMPluginRec = record
    {$else}TSMPluginRec = object{$endif}
    cx: PJSContext;
    Exp: PJSRootedObject;
    Req: PJSRootedObject;
    Module: PJSRootedObject;
    filename: PWideChar;
    dirname: PWideChar;
    constructor Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
    function require(aFileName: string): PJSObject;
  end;

  TCustomSMPlugin = class
  protected
    fCx: PJSContext; 
    procedure UnInit; virtual;
    procedure Init(const rec: TSMPluginRec); virtual;
  public
    constructor Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
    destructor Destroy; override;
  end;

  TThreadRec = record
    threadID: TThreadID;
    plugin: TCustomSMPlugin;
  end;
  TCustomSMPluginType = class of TCustomSMPlugin;

type
  TNsmFunction = function(cx: PJSContext; argc: uintN; vals: PjsvalVector; thisObj, calleeObj: PJSObject): jsval; cdecl;

const
  ptVoid = 0;
  ptInt = 1;
  ptStr = 2;
  ptObj = 3;
  ptBuffer = 100;
  ptAny = 500;

type
  PNSMCallInfo = ^TNSMCallInfo;
  TNSMCallInfo = record
    func: TNsmFunction;
    argc: Integer;
    argt: PInt64Array;
  end;

  TNSMCallInfoArray = array of TNSMCallInfo;

  /// Helper to call a native function depending on JS function arguments.
  // As a side effect will verify JS function arguments types. Can be used e.g. as:
  // ! const
  // !   overload1Args: array [0..0] of uintN = ( ptInt );
  // !   overload2Args: array [0..1] of uintN = ( ptInt, SyNodePluginIntf.ptStr );
  // !   overloads: array [0..1] of TNSMCallInfo = (
  // !     (func: @SLReadLn_impl; argc: Length(overload1Args); argt: @overload1Args),
  // !     (func: @SLReadLn_impl; argc: Length(overload2Args); argt: @overload2Args));
  // ! begin
  // !   Result := nsmCallFunc(cx, argc, vp, @overloads, Length(overloads));
  // ! end;
  function nsmCallFunc(cx: PJSContext; argc: uintN;  var vp: JSArgRec; const overloads: TNSMCallInfoArray; overloadsCount: Integer = 1; isConstructor: Boolean = false): Boolean; cdecl;

const
  StaticROAttrs = JSPROP_ENUMERATE or JSPROP_READONLY or JSPROP_PERMANENT;

implementation

uses
  SysUtils;

function isParamCorrect(paramType: uintN; val: jsval): Boolean;
begin
  case paramType of
    ptVoid: result := val.isVoid;
    ptInt: result := val.isInteger;
    ptStr: result := val.isString;
    ptObj: result := val.isObject;
    ptBuffer: result := val.isObject and (val.asObject.IsArrayBufferObject or val.asObject.IsArrayBufferViewObject);
    ptAny: result := True;
    else result := false;
  end;
end;

function nsmCallFunc(cx: PJSContext; argc: uintN; var vp: JSArgRec; const overloads: TNSMCallInfoArray; overloadsCount: Integer = 1; isConstructor: Boolean = false): Boolean; cdecl;
var
  thisObj, calleeObj: PJSObject;
  vals: PjsvalVector;
  i,j: Integer;
  overloadCase: PNSMCallInfo;
  IsCalled, IsCorrect: Boolean;
begin
  Result := False;
  try
    if isConstructor xor vp.IsConstructing then
      raise ESMException.Create('JS_IS_CONSTRUCTING');

    thisObj := vp.thisObject[cx];
    calleeObj := vp.calleObject;
    vals := vp.argv;
    IsCalled := false;

    for i := 0 to overloadsCount - 1 do begin
      overloadCase := @overloads[i];
      if (overloadCase <> nil) then begin
        if argc = overloadCase.argc then begin
          IsCorrect := true;
          for j := 0 to overloadCase.argc - 1 do begin
            IsCorrect := isParamCorrect(overloadCase.argt[j], vals[j]);
            if not IsCorrect then Break;
          end;
          if IsCorrect then begin
            vp.rval := overloadCase.func(cx, argc, vals, thisObj, calleeObj);
            IsCalled := True;
            Break;
          end;
        end;
      end;
    end;

    if not IsCalled then
      raise ESMException.CreateUTF8('There is no overloaded function "%" with such a list of arguments', [calleeObj.GetFunctionId().ToSynUnicode(cx)]);

    Result := True;
  except
    on E: Exception do
    begin
      JSError(cx, E);
    end;
  end;
end;

{ TCustomSMPlugin }


destructor TCustomSMPlugin.Destroy;
begin
  fCx.BeginRequest;
  try
    UnInit;
  finally
    fCx.EndRequest;
  end;
  inherited;
end;

procedure TCustomSMPlugin.Init(const rec: TSMPluginRec);
begin
end;

procedure TCustomSMPlugin.UnInit;
begin
end;

constructor TCustomSMPlugin.Create(aCx: PJSContext; aExports_, aRequire,
  aModule: PJSRootedObject; _filename, _dirname: PWideChar);
var
  rec: TSMPluginRec;
begin
  fCx := aCx;
  rec.Create(aCx, aExports_, aRequire, aModule, _filename, _dirname);
  fCx.BeginRequest;
  try
    Init(rec);
  finally
    fCx.EndRequest;
  end;

end;

constructor TSMPluginRec.Create(aCx: PJSContext; aExports_: PJSRootedObject; aRequire: PJSRootedObject; aModule: PJSRootedObject; _filename: PWideChar; _dirname: PWideChar);
begin
  cx := aCx;
  Exp := aExports_;
  Req := aRequire;
  Module := aModule;
  filename := _filename;
  dirname := _dirname;
end;

function TSMPluginRec.require(aFileName: string): PJSObject;
var
  arg, rval: jsval;
begin
  arg := cx.NewJSString(aFileName).ToJSVal;
  if not Module.ptr.CallFunction(cx, req.ptr, 1, @arg, rval) then
     raise ESMException.Create('Error require '+aFileName);
  Result := rval.asObject;
end;

end.
