/// remote access to a mORMot server using SmartMobileStudio
{{#uri}}
// - retrieved from {{protocol}}://{{host}}/{{uri}}
// at {{time}} using "{{templateName}}" template
{{/uri}}
{{^uri}}
// - generated at {{time}}
{{/uri}}
unit {{fileName}};

{
  WARNING:
    This unit has been generated by a mORMot {{mORMotVersion}} server.
    Any manual modification of this file may be lost after regeneration.

  Synopse mORMot framework. Copyright (C) {{year}} Arnaud Bouchez
    Synopse Informatique - http://synopse.info

  This unit is released under a MPL/GPL/LGPL tri-license,
  and therefore may be freely included in any application.

  This unit would work on Smart Mobile Studio 2.1.1 and later.
}

interface

uses
  SmartCL.System,
  System.Types,
  SynCrossPlatformSpecific,
  SynCrossPlatformREST;

{{! recursive partials used to write records type definition}}
{{<writerec}}record
{{#fields}}
{{nestedIdentation}}    {{propName}}: {{#typePascal}}{{typePascal}};{{/typePascal}}{{#nestedRecord}}{{>writerec}}{{nestedIdentation}}    end;{{/nestedRecord}}{{#nestedSimpleArray}}array of {{typePascal}};{{/nestedSimpleArray}}{{#nestedRecordArray}}array of {{>writerec}}{{nestedIdentation}}    end;{{/nestedRecordArray}}
{{/fields}}{{/writerec}}
{{#withEnumerates}}
type // define some enumeration types, used below
{{#enumerates}}
  {{name}} = ({{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}});
{{/enumerates}}

{{/withEnumerates}}
{{#withSets}}
type // define some set types, used below
{{#sets}}
  {{name}} = set of({{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}});
{{/sets}}

{{/withSets}}
{{#withRecords}}
type // define some record types, used as properties below
{{#records}}
  {{name}} = {{>writerec}}  end;

{{/records}}
{{/withRecords}}
{{#withArrays}}
type // define some dynamic array types, used as properties below
{{#arrays}}
  {{name}} = array of {{typeSource}};
{{/arrays}}

{{/withArrays}}
type{{<methodAsynch}}{{methodName}}({{#args}}{{#dirInput}}{{argName}}: {{typePascal}}; {{/dirInput}}{{/args}}
      onSuccess: procedure({{#args}}{{#dirOutput}}{{argName}}: {{typePascal}}{{commaOutResult}}{{/dirOutput}}{{/args}}); onError: TSQLRestEvent);{{/methodAsynch}}{{<methodSynch}}_{{methodName}}({{#args}}{{^dirResult}}{{dirNoOut}} {{argName}}: {{typePascal}}{{commaArg}}{{/dirResult}}{{/args}}){{#args}}{{#dirResult}}: {{typePascal}}{{/dirResult}}{{/args}};{{/methodSynch}}
{{#orm}}
{{^isInMormotPas}}
  /// map "{{tableName}}" table
  {{className}} = class(TSQLRecord)
  protected
    {{#fields}}
    f{{name}}: {{typePascal}}; 
    {{/fields}}
    // those overridden methods will emulate the needed RTTI
    class function ComputeRTTI: TRTTIPropInfos; override;
    procedure SetProperty(FieldIndex: integer; const Value: variant); override;
    function GetProperty(FieldIndex: integer): variant; override;
  public
{{#fields}}
{{#isSQLRecord}}    // defined as {{name}}: {{typeDelphi}} on the server
{{/isSQLRecord}}
    property {{name}}: {{typePascal}} read f{{name}} write f{{name}};
{{/fields}}
  end;
  
{{/isInMormotPas}}
{{/orm}}
{{#soa.services}}
  /// service accessible {{#uri}}via {{protocol}}://{{host}}/{{root}}/{{uri}}{{/uri}}
  // - this service will run in sic{{instanceCreationName}} mode
  // - synchronous and asynchronous methods are available, depending on use case
  // - synchronous _*() methods will block the browser execution, so won't be
  // appropriate for long process - on error, they may raise EServiceException
  {{#isClientDriven}}
  // - you should call explicitly Free to release the server instance
  {{/isClientDriven}}
  TService{{interfaceURI}} = class(TServiceClientAbstract{{#isClientDriven}}ClientDriven{{/isClientDriven}})
  public
    /// will initialize an access to the remote service
    constructor Create(aClient: TSQLRestClientURI); override;
{{#methods}}

    procedure {{>methodAsynch}}
    {{verb}} {{>methodSynch}}
{{/methods}}
  end;

{{/soa.services}}

const
  /// the server port{{#uri}}, corresponding to {{protocol}}://{{host}}{{/uri}}
  SERVER_PORT = {{port}};
  /// the server model root name{{#uri}}, corresponding to {{protocol}}://{{host}}/{{root}}{{/uri}}
  SERVER_ROOT = '{{root}}';


/// return the database Model corresponding to this server
function GetModel(aRoot: string=SERVER_ROOT): TSQLModel;

/// create a TSQLRestClientHTTP instance and connect to the server
// - it will use by default port {{port}} over root '{{root}}'{{#host}}, corresponding
// to {{protocol}}://{{host}}/{{root}}{{/host}}
{{#authClass}}
// - secure connection will be established via {{.}}
// with the supplied credentials
{{/authClass}}
// - request will be asynchronous, and trigger onSuccess or onError event
procedure GetClient(const aServerAddress{{#authClass}}, aUserName,aPassword{{/authClass}}: string;
  onSuccess, onError: TSQLRestEvent;
  aServerPort: integer=SERVER_PORT; aServerRoot: string=SERVER_ROOT;
  aHttps: boolean={{#https}}true{{/https}}{{^https}}false{{/https}});

{{#withHelpers}}
// publish some low-level helpers for variant conversion
// - used internally: you should not need those functions in your end-user code
{{#enumerates}}
function Variant2{{name}}(const _variant: variant): {{name}};
function {{name}}ToText(const value: {{name}}): string;
{{/enumerates}}
{{#records}}
function Variant2{{name}}(const Value: variant): {{name}};
function {{name}}2Variant(const Value: {{name}}): variant;
{{/records}}
{{#arrays}}
function Variant2{{name}}(const _variant: variant): {{name}};
function {{name}}2Variant(const _array: {{name}}): variant;
{{/arrays}}

{{/withHelpers}}

implementation
{{<setrec}}{{#fields}}
{{#isSimple}}  result.{{fullPropName}} := Value.{{fullPropName}};
{{/isSimple}}{{#nestedRecord}}{{>setrec}}{{/nestedRecord}}{{#fromVariant}}  result.{{fullPropName}} := {{fromVariant}}(Value.{{fullPropName}});
{{/fromVariant}}{{#nestedSimpleArray}}  if VariantType(Value.{{fullPropName}})=jvArray then
    for var i := 0 to integer(Value.{{fullPropName}}.length)-1 do
      result.{{fullPropName}}.Add({{typePascal}}(Value.{{fullPropName}}[i]));
{{/nestedSimpleArray}}{{#nestedRecordArray}}  if VariantType(Value.{{fullPropName}})=jvArray then begin
    var tmp: {{name}};
    tmp.{{propName}}.SetLength(1);
    for var n := 0 to integer(Value.{{fullPropName}}.length)-1 do begin
      var source := Value.{{fullPropName}}[n];
      var dest := tmp.{{propName}}[0];
{{#fields}}
      dest.{{propName}} := {{#fromVariant}}{{fromVariant}}({{/fromVariant}}source.{{propName}}{{#fromVariant}}){{/fromVariant}};
{{/fields}}
      result.{{fullPropName}}.Add(dest);
    end;
  end;
{{/nestedRecordArray}}{{/fields}}{{/setrec}}
{{<getrec}}{{#fields}}
{{#isSimple}}  result.{{fullPropName}} := Value.{{fullPropName}};
{{/isSimple}}{{#nestedRecord}}  result.{{fullPropName}} := new JObject;
{{>getrec}}{{/nestedRecord}}{{#toVariant}}  result.{{fullPropName}} := {{toVariant}}(Value.{{fullPropName}});
{{/toVariant}}{{#nestedSimpleArray}}  result.{{fullPropName}} := variant(Value.{{fullPropName}});
{{/nestedSimpleArray}}{{#nestedRecordArray}}  result.{{fullPropName}} := TVariant.CreateArray;
  for var source in Value.{{fullPropName}} do begin
    var dest: variant := new JObject;
{{#fields}}
    dest.{{propName}} := {{#toVariant}}{{toVariant}}({{/toVariant}}source.{{propName}}{{#toVariant}}){{/toVariant}};
{{/fields}}
    result.{{fullPropName}}.push(dest);
  end;
{{/nestedRecordArray}}{{/fields}}{{/getrec}}
{{#withEnumerates}}
{ Some helpers for enumerates types }

{$HINTS OFF} // for begin asm return ... end; end below

// those functions will use the existing generated string array constant
// defined by the SMS compiler for each enumeration

{{#enumerates}}
function Variant2{{name}}(const _variant: variant): {{name}};
begin
  asm return @VariantToEnum(@_variant,@{{name}}); end;
end;

function {{name}}ToText(const value: {{name}}): string;
begin
  asm return @{{name}}[@value]; end;
end;

{{/enumerates}}
{$HINTS ON}

{{/withEnumerates}}
{{#withRecords}}
{ Some helpers for record types:
  due to potential obfuscation of generated JavaScript, we can't assume
  that the JSON used for transmission would match record fields naming }
{{#records}}

function Variant2{{name}}(const Value: variant): {{name}};
begin
{{>setrec}}
end;

function {{name}}2Variant(const Value: {{name}}): variant;
begin
  result := new JObject;
{{>getrec}}
end;
{{/records}}

{{/withRecords}}
{{#withArrays}}

{ Some helpers for dynamic array types }

{{#arrays}}
function Variant2{{name}}(const _variant: variant): {{name}};
var tmp: {{typeSource}};
begin
  if VariantType(_variant)=jvArray then
    for var i := 0 to integer(_variant.Length)-1 do begin
      tmp := {{#fromVariant}}{{fromVariant}}{{/fromVariant}}(_variant[i]);
      result.Add(tmp);
    end;
end;

function {{name}}2Variant(const _array: {{name}}): variant;
var i: integer;
begin
  result := TVariant.CreateArray;
  for i := 0 to high(_array) do
    result.push({{#toVariant}}{{toVariant}}{{/toVariant}}(_array[i]));
end;

{{/arrays}}
{{/withArrays}}
{{#orm}}

{{^isInMormotPas}}

{ {{className}} }

class function {{className}}.ComputeRTTI: TRTTIPropInfos;
begin
  result := TRTTIPropInfos.Create(
    [{{#fields}}'{{name}}'{{comma}}{{/fields}}],
    [{{#fields}}{{typekindname}}{{comma}}{{/fields}}]);
end;

procedure {{className}}.SetProperty(FieldIndex: integer; const Value: variant);
begin
  case FieldIndex of
  0: fID := Value;
  {{#fields}}
  {{index}}: f{{name}} := {{#fromVariant}}{{fromVariant}}({{/fromVariant}}Value{{#fromVariant}}){{/fromVariant}};
  {{/fields}}
  end;
end;

function {{className}}.GetProperty(FieldIndex: integer): variant;
begin
  case FieldIndex of
  0: result := fID;
  {{#fields}}
  {{index}}: result := {{#toVariant}}{{toVariant}}({{/toVariant}}f{{name}}{{#toVariant}}){{/toVariant}};
  {{/fields}}
  end;
end;

{{/isInMormotPas}}
{{/orm}}

function GetModel(aRoot: string): TSQLModel;
begin
  result := TSQLModel.Create([{{#orm}}{{className}}{{comma}}{{/orm}}],aRoot);
end;

procedure GetClient(const aServerAddress{{#authClass}}, aUserName,aPassword{{/authClass}}: string;
  onSuccess, onError: TSQLRestEvent; aServerPort: integer; aServerRoot: string;
  aHttps: boolean);
begin
  var client := TSQLRestClientHTTP.Create(aServerAddress,aServerPort,
    GetModel(aServerRoot),true,aHttps); // aOwnModel=true
  client.Connect(
  lambda
    try
      if client.ServerTimeStamp=0 then begin
        if Assigned(onError) then
          onError(client);
        exit;
      end;
{{#authClass}}
      if not client.SetUser({{.}},aUserName,aPassword) then begin
        if Assigned(onError) then
          onError(client);
        exit;
      end;
{{/authClass}}
      if Assigned(onSuccess) then
        onSuccess(client);
    except
      if Assigned(onError) then
        onError(client);
    end;
  end,
  onError);
end;

{{#soa.services}}

{ TService{{interfaceURI}} }

constructor TService{{interfaceURI}}.Create(aClient: TSQLRestClientURI);
begin
  fServiceName := '{{interfaceURI}}';
  fServiceURI := '{{uri}}';
  fInstanceImplementation := sic{{instanceCreationName}};
  fContractExpected := '{{contractExpected}}';
  inherited Create(aClient);
end;


{{#methods}}
procedure TService{{interfaceURI}}.{{>methodAsynch}}
begin 
  fClient.CallRemoteServiceAsynch(self,'{{methodName}}',{{ArgsOutputCount}},
    [{{#args}}{{#dirInput}}{{#toVariant}}{{toVariant}}({{argName}}){{/toVariant}}{{^toVariant}}{{argName}}{{/toVariant}}{{commaInSingle}}{{/dirInput}}{{/args}}],
    lambda (res: array of Variant)
      onSuccess({{#args}}{{#dirOutput}}{{#fromVariant}}{{fromVariant}}({{/fromVariant}}res[{{indexOutResult}}{{#fromVariant}}){{/fromVariant}}{{#commaOutResult}},{{/commaOutResult}}{{/dirOutput}}{{/args}});
    end, onError{{#resultIsServiceCustomAnswer}}, true{{/resultIsServiceCustomAnswer}}); 
end;

{{verb}} TService{{interfaceURI}}.{{>methodSynch}}
begin
  var res := fClient.CallRemoteServiceSynch(self,'{{methodName}}',{{ArgsOutputCount}},
    [{{#args}}{{#dirInput}}{{#toVariant}}{{toVariant}}({{argName}}){{/toVariant}}{{^toVariant}}{{argName}}{{/toVariant}}{{commaInSingle}}{{/dirInput}}{{/args}}]{{#resultIsServiceCustomAnswer}},true{{/resultIsServiceCustomAnswer}});
{{#args}}{{#dirOutput}}  {{argName}} := {{#fromVariant}}{{fromVariant}}({{/fromVariant}}res[{{indexOutResult}}{{#fromVariant}}){{/fromVariant}};
{{/dirOutput}}{{/args}}end;


{{/methods}}
{{/soa.services}}

end.