
{$ifdef fpc}{$mode objfpc}{$h+}{$endif}
{$apptype console}

uses Variants, SysUtils;

type
  TTest = class(TCustomVariantType)
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source: TVarData; const Indirect: Boolean); override;
    procedure DispInvoke(Dest: PVarData; var Source: TVarData; CallDesc: PCallDesc; Params: Pointer); override;
  end;

procedure TTest.Clear(var V: TVarData);
begin
end;

procedure TTest.Copy(var Dest: TVarData; const Source: TVarData; const Indirect: Boolean);
begin
end;

procedure TTest.DispInvoke(Dest: PVarData; var Source: TVarData; CallDesc: PCallDesc; Params: Pointer);
var
  tmp: Word;
begin
  if (CallDesc^.ArgCount =2) and Assigned(Dest) then
  begin
    //writeln(HexStr(PPointer(Params)^), ' ', HexStr(PPointer(Params)[1]));
    WordRec(tmp).Lo := CallDesc^.ArgTypes[0];
    WordRec(tmp).Hi := CallDesc^.ArgTypes[1];
    // !! FPC passes args right-to-left, Delphi does same left-to-right
    // Moreover, IDispatch needs args right-to-left, and Variant Dispatch needs left-to-right. Nice, huh?
    {$ifdef fpc}
    tmp := Swap(tmp);
    {$endif}
    Variant(Dest^) := tmp;
  end;  
end;

type
  TTestClass=class
    u8: byte;
    u16: word;
    u32: longword;
{$ifdef fpc}
    u64: qword;
{$endif}
    s8: shortint;
    s16: smallint;
    s32: longint;
    s64: int64;

    cy: currency;

    b: boolean;
    bb: bytebool;
    wb: wordbool;
    lb: longbool;

    sgl: single;
    dbl: double;
    ext: extended;
    dt: TDateTime;

    fsstr: shortstring;
    fastr: ansistring;
    fwstr: widestring;
{$ifdef fpc}
    fustr: unicodestring;
{$endif}

    fvar: Variant;
    fintf: IInterface;
    fdisp: IDispatch;

    property u8prop: Byte read u8;
    property u16prop: Word read u16;
    property u32prop: LongWord read u32;
{$ifdef fpc}
    property u64prop: QWord read u64;
{$endif}
    property s8prop: ShortInt read s8;
    property s16prop: SmallInt read s16;
    property s32prop: LongInt read s32;
    property s64prop: Int64 read s64;

    property cyprop: currency read cy;
    property bprop: boolean read b;
    property bbprop: bytebool read bb;
    property wbprop: wordbool read wb;
    property lbprop: longbool read lb;

    property sglprop: single read sgl;
    property dblprop: double read dbl;
    property extprop: extended read ext;
    property dtprop: TDateTime read dt;

    property varprop: Variant read fvar;
    property intfprop: IInterface read fintf;
    property dispprop: IDispatch read fdisp;

    property sstr: shortstring read fsstr;
    property astr: ansistring read fastr;
    property wstr: widestring read fwstr;
{$ifdef fpc}
    property ustr: unicodestring read fustr;
{$endif}
  end;

var
  cv: TCustomVariantType;
  code: Integer;
  cl: TTestClass;
  v: Variant;

// using negative values of Expected to check that arg is passed by-value only
procedure test(const id: string; const act: Variant; expected: Integer);
var
  tmp: word;
  absexp: Integer;
begin
  tmp := act;
  absexp := abs(expected);
  write(id, WordRec(tmp).Lo,', ', WordRec(tmp).Hi);
  if (expected >= 0) and (WordRec(tmp).Lo <> (expected or $80)) then
  begin
    write(' BYREF failed');
    Code := Code or 1;
  end;  
  if WordRec(tmp).Hi <> absexp then
  begin
    write(' BYVAL failed');
    Code := Code or 2;
  end;
  writeln;
end;

begin
  Code := 0;
  cv := TTest.Create;
  cl := TTestClass.Create;
  TVarData(v).vType := cv.VarType;

  test('u8:    ', v.foo(cl.u8, cl.u8prop), varbyte);
  
  test('u16:    ', v.foo(cl.u16, cl.u16prop), varword);       // (Uncertain) D7: treated as Integer
  test('u32:    ', v.foo(cl.u32, cl.u32prop), varlongword);   // (Uncertain) D7: treated as Integer ByRef
  test('s8:     ', v.foo(cl.s8, cl.s8prop), varshortint);     // (Uncertain) D7: treated as Integer

  test('s16:    ', v.foo(cl.s16, cl.s16prop), varsmallint);
  test('s32:    ', v.foo(cl.s32, cl.s32prop), varinteger);
  test('s64:    ', v.foo(cl.s64, cl.s64prop), varint64);
{$ifdef fpc}
  test('u64:    ', v.foo(cl.u64, cl.u64prop), varword64);
{$endif}
  
  test('wordbool:', v.foo(cl.wb, cl.wbprop), varBoolean);
  test('curncy:  ', v.foo(cl.cy, cl.cyprop), varCurrency);
  
  test('single:  ', v.foo(cl.sgl, cl.sglprop), varSingle);
  test('double:  ', v.foo(cl.dbl, cl.dblprop), varDouble);
  test('extended:', v.foo(cl.ext, cl.extprop), -varDouble);  // not a COM type, passed by value
  
  test('date:    ', v.foo(cl.dt, cl.dtprop), varDate);

  test('ansistr: ', v.foo(cl.fastr, cl.astr), varStrArg);
  test('widestr: ', v.foo(cl.fwstr, cl.wstr), varOleStr);
{$ifdef fpc}
  test('unistr:  ', v.foo(cl.fustr, cl.ustr), varUStrArg);
{$endif}
  test('variant: ', v.foo(cl.fvar, cl.varprop), varVariant);
  
  test('IUnknown:', v.foo(cl.fintf, cl.intfprop), varUnknown);
  test('IDispatch:', v.foo(cl.fdisp, cl.dispprop), varDispatch);
  
  // not an COM type, passed by value; Delphi uses varStrArg
  test('shortstr:', v.foo(cl.fsstr, cl.sstr), -varOleStr);
  // not an COM type, passed by value
  test('longbool:', v.foo(cl.lb, cl.lbprop), -varBoolean);

  // typecasted ordinals (only one arg is actually used)
  test('u8+cast: ', v.foo(byte(55), byte(55)), -varByte);
  test('u16+cast:', v.foo(word(55), word(55)), -varWord);
  test('u32+cast:', v.foo(longword(55), longword(55)), -varLongWord);
{$ifdef fpc}
  test('u64+cast:', v.foo(qword(55), qword(55)), -varQWord);
{$endif}
  test('s8+cast:', v.foo(shortint(55), shortint(55)), -varShortInt);
  test('s16+cast:', v.foo(smallint(55), smallint(55)), -varSmallInt);
  test('s32+cast:', v.foo(longint(55), longint(55)), -varInteger);
  test('s64+cast:', v.foo(int64(55), int64(55)), -varInt64);

  cl.Free;
  if Code <> 0 then
    writeln('Errors: ', Code);
  Halt(Code);

end.