/// `buffer` module support bindings for SyNode
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SyNodeBinding_buffer;

interface
{$I Synopse.inc}
{$I SyNode.inc}
{$UNDEF HASINLINE}
type
  TEncoding = (ASCII, UTF8, BASE64, UCS2, BINARY, HEX, BUFFER);
  TEndianness = (kLittleEndian, kBigEndian);
// BINARY is a deprecated alias of LATIN1.
const  LATIN1 = BINARY;
implementation
uses
  SysUtils,
{$IFDEF MSWINDOWS}
  Windows,
{$ENDIF}
  SynCommons,
  SyNode,
  SpiderMonkey;

//const
//  BUFFER_PROTO_SLOT = JSCLASS_GLOBAL_SLOT_COUNT + 1;


const
  _Endianness: record
  case boolean of
    true: (u8: array[0..1] of UInt8);
    false: (u16: uint16);
  end = (u8: (1, 0));

var
  Endianness: TEndianness;

const kMaxLength = (1 shl 28) - 16;

const OUT_OF_RANGE = 'out of range index';
      NOT_A_HEX = 'Not a hex';

procedure getBufDataAndLength(cx: PJSContext; bufObj: PJSRootedObject; out bufData: pointer; out  bufLen: size_t);{$ifdef HASINLINE}inline;{$endif}
const MUST_BE_A_BUFFER = 'argument should be a Buffer';
begin
  if not bufObj.ptr.IsTypedArrayObject then
    raise ESMTypeException.Create(MUST_BE_A_BUFFER);
  bufData := Pointer(bufObj.ptr.GetUint8ArrayData);{ + bufObj.ptr.GetTypedArrayByteOffset}
  bufLen := bufObj.ptr.GetTypedArrayByteLength;
end;

function ParseEncoding(const encoding: RawUTF8; default_encoding: TEncoding): TEncoding; {$ifdef HASINLINE}inline;{$endif}
begin
  Result := default_encoding;
  case Length(encoding) of
    3: if IdemPChar(Pointer(encoding), 'HEX') then
         Result := HEX;
    4: if IdemPChar(Pointer(encoding), 'UTF8') then
         Result := UTF8
       else if IdemPChar(Pointer(encoding), 'UCS2') then
         Result := UCS2;
    5: if IdemPChar(Pointer(encoding), 'UTF-8') then
         Result := UTF8
       else if IdemPChar(Pointer(encoding), 'UCS-2') then
         Result := UCS2
       else if IdemPChar(Pointer(encoding), 'ASCII') then
         Result := ASCII;
    6: if IdemPChar(Pointer(encoding), 'BASE64') then
         Result := BASE64
       else if IdemPChar(Pointer(encoding), 'LATIN1') then
         Result := LATIN1
       else if IdemPChar(Pointer(encoding), 'BINARY') then
         Result := LATIN1 // BINARY is a deprecated alias of LATIN1.
       else if IdemPChar(Pointer(encoding), 'BUFFER') then
         Result := BUFFER;
    7: if IdemPChar(Pointer(encoding), 'UTF16LE') then
         Result := UCS2;
    8: if IdemPChar(Pointer(encoding), 'UTF16-LE') then
         Result := UCS2;
  end;
end;

function StringSlice(cx: PJSContext; argc: uintN; var vp: jsargRec; encoding: TEncoding): Boolean; {$ifdef HASINLINE}inline;{$endif}
var
  bufData: pointer;
  length: size_t;
  tmp_str: RawByteString;
  p: PAnsiChar;
  in_argv: PjsvalVector;
  start: size_t;
  end_: size_t;
  bufLen: size_t;
  this: PJSRootedObject;
const
  TWO_ARGRUMENTS_EXPECTED = '2 arguments expected';
begin
  try
    Result := True;
    this := cx.NewRootedObject(vp.thisObject[cx]);
    try
      getBufDataAndLength(cx, this, bufData, bufLen);
    finally
      cx.FreeRootedObject(this);
    end;
    if bufLen = 0 then begin
{$IFDEF SM52}
      vp.rval := cx.EmptyString.ToJSVal;
{$ELSE}
      vp.rval := cx.rt.EmptyString.ToJSVal;
{$ENDIF}
      Exit;
    end;

    if argc < 2 then
      raise ESMException.Create(TWO_ARGRUMENTS_EXPECTED);

    in_argv := vp.argv;
    if in_argv[0].isNumber then
      start := in_argv[0].asInt64
    else
      start := 0;

    bufData := PAnsiChar(bufData) + start; //Pointer(UIntPtr(bufData) + start);

    if in_argv[1].isNumber then
      end_ := in_argv[1].asInt64
    else
      end_ := bufLen;
    if end_ < start then
      end_ := start;
    if end_ > bufLen then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    length := end_ - start;
    case encoding of
    // todo: check ascii(and $7F)
      ASCII: begin
        SetString(tmp_str, PAnsiChar(bufData), length);
        p := pointer(tmp_str);
        while length >= 4 do begin
          PCardinal(p)^ := PCardinal(p)^ and $7F7F7F7F;
          Inc(p,4);
          dec(length, 4);
        end;
        if length >= 2 then begin
          PWord(p)^ := PWord(p)^ and $7F7F;
          Inc(p,2);
          dec(length, 2);
        end;
        if length >= 1 then begin
          PByte(p)^ := PByte(p)^ and $7F;
          Inc(p,1);
        end;

        vp.rval := JS_NewStringCopyN(cx, Pointer(tmp_str), System.length(tmp_str)).ToJSVal;
      end;

      LATIN1: vp.rval := JS_NewStringCopyN(cx, bufData, length).ToJSVal;
      UTF8: vp.rval := cx.NewJSString(bufData, length, CP_UTF8).ToJSVal;
      BASE64: vp.rval := cx.NewJSString(BinToBase64(bufData, length)).ToJSVal;
      //todo: optimize
      HEX: vp.rval := cx.NewJSString(LowerCase(BinToHex(bufData, length))).ToJSVal;
      UCS2: vp.rval := cx.NewJSString(bufData, length div 2).ToJSVal;
    end;
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function cpSlice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  bufData: pointer;
  length: size_t;
  in_argv: PjsvalVector;
  start: size_t;
  end_: size_t;
  bufLen: size_t;
  this: PJSRootedObject;
  destCP: integer;
const
  USAGE = 'cpSlice(start, length, cp: integer)';
begin
  try
    Result := True;
    this := cx.NewRootedObject(vp.thisObject[cx]);
    try
      getBufDataAndLength(cx, this, bufData, bufLen);
    finally
      cx.FreeRootedObject(this);
    end;
    if bufLen = 0 then begin
{$IFDEF SM52}
      vp.rval := cx.EmptyString.ToJSVal;
{$ELSE}
      vp.rval := cx.rt.EmptyString.ToJSVal;
{$ENDIF}
      Exit;
    end;

    if argc < 3 then
      raise ESMException.Create(USAGE);

    in_argv := vp.argv;
    if in_argv[0].isNumber then
      start := in_argv[0].asInt64
    else
      start := 0;

    bufData := PAnsiChar(bufData) + start;

    if in_argv[1].isNumber then
      end_ := in_argv[1].asInt64
    else
      end_ := bufLen;

    if end_ < start then
      end_ := start;
    if end_ > bufLen then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    length := end_ - start;

    if in_argv[2].isNumber then
      destCP := in_argv[2].asInteger
    else
      destCP := CP_UTF8;
    vp.rval := cx.NewJSString(bufData, length, destCP).ToJSVal;
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function asciiSlice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, ASCII);
end;

function latin1Slice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, LATIN1);
end;

function utf8Slice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, UTF8);
end;

function base64Slice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, BASE64);
end;

function hexSlice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, HEX);
end;

function ucs2Slice(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringSlice(cx, argc, vp, UCS2);
end;

function isFirstByteInChar(value: byte): boolean; {$ifdef HASINLINE}inline;{$endif}
//0xxxxxxx or 11xxxxxx
begin
  result := (value < 128) or (value >= 192);
end;

/// This array is a lookup table that translates AnsiChar characters drawn from the "Base64 Alphabet" (as specified
// in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
// alphabet but fall within the bounds of the array are translated to -1.
//
// Whitespaces are translated to -2.
//
// Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both
// URL_SAFE and STANDARD base64.
//
// Thanks to "commons" project in ws.apache.org for this code.
// http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
const
  DECODE_TABLE: array[AnsiChar] of shortint = (
//   0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, // 00-0f
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
    -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - /
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
    -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, // 40-4f A-O
    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 70-7a p-z
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
);

/// Conversion from URL_SAFE and STANDARD base64 encoded text into binary data with features:
// - padding is optional
// - whitespaces (' ', \r, \n) are skipped to be compartible with
//   transfer encoding for MIME (RFC 2045);
//   for example SOAP/E-Mail use \r\n to split long Base64 string into parts
// - will decode up to maxBinLength into bin buffer
// - can optinaly accept a 2 bytes representation of str (0xC1 0x0 0xC2 0x0 ...) as
//   returned by SpiderMonkey engine GetTwoByteStringCharsAndLength function.
//   In this case strLen is duplet count
// Return length of resulting bin or 0 in case of invalid Base64 input
//
// To estimate a length of result buffer function should be called as
//   estimatedLength := anyBase64ToBin(str, strLen, nil, strLen);
function anyBase64ToBin(str: PAnsiChar; strLen: PtrInt;
  bin: PAnsiChar; maxBinLength: PtrInt;
  strIsTwoBytes: boolean = false): PtrInt;
var
  maxS, maxB, bPos, sPos: PtrInt;
  v: uint32;
  c: AnsiChar;
  mul: byte;

  function failTolerantDecode: boolean;
  var lo, hi: smallint;
  begin
    repeat
      c := str[sPos]; lo := DECODE_TABLE[c];
      inc(sPos, 1 shl mul);
      if (lo >= 0) then break; // Legal character
      if ((c = '=') or (sPos >= strLen)) then
        exit(false); // Stop decoding
    until false;
    //exp
    if (sPos >= strLen) then exit(false);
    if (bPos >= maxBinLength) then exit(false);
    hi := lo;

    // first byte
    repeat
      c := str[sPos]; lo := DECODE_TABLE[c];
      inc(sPos, 1 shl mul);
      if (lo >= 0) then break; // Legal character
      if ((c = '=') or (sPos >= strLen)) then
        exit(false); // Stop decoding
    until false;
    if bin <> nil then
      bin[bPos] := AnsiChar( ((hi and $3F) shl 2) or ((lo and $30) shr 4) );
    inc(bPos);
    if (sPos >= strLen) then exit(false);
    if (bPos >= maxBinLength) then exit(false);
    hi := lo;

    // second byte
    repeat
      c := str[sPos]; lo := DECODE_TABLE[c];
      inc(sPos, 1 shl mul);
      if (lo >= 0) then break; // Legal character
      if ((c = '=') or (sPos >= strLen)) then
        exit(false); // Stop decoding
    until false;
    if bin <> nil then
      bin[bPos] := AnsiChar( ((hi and $0F) shl 4) or ((lo and $3C) shr 2) );
    inc(bPos);
    if (sPos >= strLen) then exit(false);
    if (bPos >= maxBinLength) then exit(false);
    hi := lo;

    // third byte
    repeat
      c := str[sPos]; lo := DECODE_TABLE[c];
      inc(sPos, 1 shl mul);
      if (lo >= 0) then break; // Legal character
      if ((c = '=') or (sPos >= strLen)) then
        exit(false); // Stop decoding
    until false;
    if bin <> nil then
      bin[bPos] := AnsiChar( ((hi and $03) shl 6) or ((lo and $3F) shr 0) );
    inc(bPos);
    if (sPos >= strLen) then exit(false);
    if (bPos >= maxBinLength) then exit(false);
    Result := true;
  end;
begin
  Result := 0;
  if strIsTwoBytes then begin
    mul := 1; strLen := strLen shl mul;
  end else
    mul := 0;
  maxB := (maxBinLength div 3) * 3;
  maxS := (strLen div (4 shl mul)) * (4 shl mul);
  bPos := 0;
  sPos := 0;
  while (bPos < maxB) and (sPos < maxS) do begin
    v := DECODE_TABLE[str[sPos + (0 shl mul)]] shl 24 or
         DECODE_TABLE[str[sPos + (1 shl mul)]] shl 16 or
         DECODE_TABLE[str[sPos + (2 shl mul)]] shl 8 or
         DECODE_TABLE[str[sPos + (3 shl mul)]];
    // If MSB is set, input contains whitespace or is not valid base64
    if v and $80808080 > 0 then begin
      if not failTolerantDecode() then
        exit(bPos);
      maxB := bPos + (strLen shr mul - bPos) div 4 * 4; // Align maxB again
    end else begin
      if bin <> nil then begin
        bin[bPos + 0] := AnsiChar( ((v shr 22) and $FC) or ((v shr 20) and $03) );
        bin[bPos + 1] := AnsiChar( ((v shr 12) and $F0) or ((v shr 10) and $0F) );
        bin[bPos + 2] := AnsiChar( ((v shr  2) and $C0) or ((v shr  0) and $3F) );
      end;
      inc(bPos, 3);
      inc(sPos, 4 shl mul);
    end;
  end;
  if (sPos < strLen) and (bPos < maxBinLength) then
    failTolerantDecode();
  Result := bPos;
end;

function StringBytesWrite(bufData: Pointer; max_length: size_t; str: Pointer; strLen: size_t; isLatin1: Boolean; encoding:TEncoding): size_t; {$ifdef HASINLINE}inline;{$endif}
var
  strLenInBytes: size_t;
  i: PtrInt;
  ch: PAnsiChar;
begin
  if isLatin1 then
    strLenInBytes := strLen
  else
    strLenInBytes := strLen * 2;

  Result := strLenInBytes;
  if (encoding = HEX) then Result := Result shr 1;
  if isLatin1  and (encoding = UCS2) then Result := Result shl 1;
  if not isLatin1 and (encoding in [ASCII, LATIN1]) then Result := Result shr 1;
  if Result > max_length then
    Result := max_length;

  case encoding of
    ASCII, LATIN1: begin
      if isLatin1 then begin
        MoveFast(str^, bufData^, Result)
      end else begin
        for i := 0 to Result - 1 do
          Puint8Vector(bufData)[i] := Puint8Vector(str)[i*2];
      end;
    end;
    UTF8: begin
      if isLatin1 then begin
        if Result < strLen then
        while (Result > 0) and not isFirstByteInChar(pByte(PtrUInt(str)+Result)^) do
          Dec(Result);
        MoveFast(str^, bufData^, Result);
      end else begin
        raise ESMException.Create('Critical Error');
      end;
    end;
    UCS2: begin
      if isLatin1 then begin
        // todo: optimize
//        SetLength(UnicodeBuf, Result shr 1);
//        WinAnsiConvert.AnsiBufferToUnicode(Pointer(UnicodeBuf), str, Result shr 1);
//        MoveFast(Pointer(UnicodeBuf)^, bufData^, Result);
        Latin1AnsiConvert.AnsiBufferToUnicode(bufData, str, Result shr 1, true);
      end else begin
        Result := Result and (not 1);
        MoveFast(str^, bufData^, Result);
      end;
    end;
    BASE64: begin
      i := anyBase64ToBin(str, strLen, bufData, max_length, not isLatin1);
      if i = -1 then
        Result := 0 // error in base64
      else if i > max_length then
        Result := max_length
      else
        Result := i;
    end;
    HEX: begin
      if isLatin1 then begin
        ch := PAnsiChar(str);
        for i := 0 to Result - 1 do begin
          if (ConvertHexToBin[Ord(ch^)] > 15) or
             (ConvertHexToBin[Ord((ch+1)^)] > 15)
          then begin
            Result := i;
            Break;
          end;
          inc(ch, 2);
        end;
        HexToBin(str, bufData, Result);
      end else begin
        raise ESMTypeException.Create(NOT_A_HEX);
      end;
    end;
  end;
end;

function StringWrite(cx: PJSContext; argc: uintN; var vp: jsargRec; encoding: TEncoding): Boolean; {$ifdef HASINLINE}inline;{$endif}
var
  bufData: pointer;
  str: Pointer;
  strUtf8: RawUTF8;
  strLen: size_t;
  bytesWrite: size_t;
  isLatin1: Boolean;

  in_argv: PjsvalVector;
  _str: PJSString;
  offset: size_t;
  max_length: size_t;
  bufLen: size_t;
  this: PJSRootedObject;

const
  THREE_ARGRUMENTS_EXPECTED = '3 arguments expected';
  FIRST_ARGRUMENT_MUST_BE_A_STRING = 'Argument must be a string';
  OFFSET_IS_OUT_OF_BOUNDS = 'Offset is out of bounds';
begin
  try
    Result := True;

    if argc <3 then
      raise ESMException.Create(THREE_ARGRUMENTS_EXPECTED);
    in_argv := vp.argv;
    if not in_argv[0].isString then
      raise ESMException.Create(FIRST_ARGRUMENT_MUST_BE_A_STRING);

    _str :=  in_argv[0].asJSString;
    if encoding = UTF8 then begin
      strUtf8 := _str.ToUTF8(cx);
      str := Pointer(strUtf8);
      strLen := Length(strUtf8);
      isLatin1 := True;
      encoding := UTF8;
    end else begin
      isLatin1 := _str.HasLatin1Chars;
      if isLatin1 then
        str := _str.GetLatin1StringCharsAndLength(cx, strLen)
      else begin
        str := _str.GetTwoByteStringCharsAndLength(cx, strLen);
      end;
    end;

    if (encoding = HEX) and (strLen and 1 = 1) then
      raise ESMTypeException.Create(NOT_A_HEX);

    this := cx.NewRootedObject(vp.thisObject[cx]);
    try
      getBufDataAndLength(cx, this,bufData, bufLen);
    finally
      cx.FreeRootedObject(this);
    end;

    if in_argv[1].isNumber then
      offset := in_argv[1].asInt64
    else
      offset := 0;

    bufData := Pointer(UIntPtr(bufData) + offset);

    if offset > bufLen then
      raise ESMRangeException.Create(OFFSET_IS_OUT_OF_BOUNDS);

    if in_argv[2].isNumber then
      max_length := in_argv[2].asInt64
    else
      max_length := bufLen - offset;

    if max_length > bufLen - offset then
      max_length := bufLen - offset;

    if max_length = 0 then begin
      vp.rval := SimpleVariantToJSval(cx, 0);
      exit;
    end;

    bytesWrite := StringBytesWrite(bufData, max_length, str, strLen, isLatin1, encoding);

    vp.rval := SimpleVariantToJSval(cx, bytesWrite);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function asciiWrite(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, ASCII);
end;

function Latin1Write(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, LATIN1);
end;

function utf8Write(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, UTF8);
end;

function ucs2Write(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, UCS2);
end;

function base64Write(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, BASE64);
end;

function hexWrite(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := StringWrite(cx, argc, vp, HEX);
end;

// bytesCopied = buffer.copy(target[, targetStart][, sourceStart][, sourceEnd]);
function Copy(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  this, target: PJSRootedObject;
  sourceData, targetData: Pointer;
  val_i64: Int64;
  sourceLength, targetLength: size_t;
  targetStart, sourceStart, sourceEnd: size_t;
  to_copy: UInt32;
  in_argv: PjsvalVector;
const
  sInvalidCall = 'usage: copy(target: Buffer, [targetStart = 0], [sourceStart = 0], [sourceEnd = this.length]);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 1) or (not in_argv[0].isObject) then
      raise ESMException.Create(sInvalidCall);

    this := cx.NewRootedObject(vp.thisObject[cx]);
    try
      getBufDataAndLength(cx, this, sourceData, sourceLength);
    finally
      cx.FreeRootedObject(this);
    end;

    target := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, target, targetData, targetLength);
    finally
      cx.FreeRootedObject(target);
    end;

    if (argc > 1) and (in_argv[1].isNumber) then begin
      val_i64 := in_argv[1].asInt64;
      if (val_i64 < $FFFFFFFF) and (val_i64 >= 0) then
        targetStart := val_i64
      else
        raise ESMRangeException.Create(OUT_OF_RANGE);
    end else
      targetStart := 0;

    if (argc > 2) and (in_argv[2].isNumber) then begin
      val_i64 := in_argv[2].asInt64;
      if (val_i64 < $FFFFFFFF) and (val_i64 >= 0) then
        sourceStart := val_i64
      else
        raise ESMRangeException.Create(OUT_OF_RANGE);
    end else
      sourceStart := 0;

    if (argc > 3) and (in_argv[3].isNumber) then begin
      val_i64 := in_argv[3].asInt64;
      if (val_i64 < $FFFFFFFF) and (val_i64 >= 0) then
        sourceEnd := val_i64
      else
        raise ESMRangeException.Create(OUT_OF_RANGE);
    end else
      sourceEnd := sourceLength;

    if (targetStart >= targetLength) or (sourceStart >= sourceEnd) then begin
      vp.rval := SimpleVariantToJSval(cx, 0);
      exit;
    end;

    if (sourceStart > sourceLength) then
      raise ESMRangeException.Create(OUT_OF_RANGE);

    if (sourceEnd - sourceStart > targetLength - targetStart) then
      sourceEnd := sourceStart + targetLength - targetStart;

    if sourceEnd - sourceStart > targetLength - targetStart then
      to_copy := targetLength - targetStart
    else
      to_copy := sourceEnd - sourceStart;

    if to_copy > targetLength - sourceStart then
      to_copy := targetLength - sourceStart;

    MoveFast(Pointer(UIntPtr(sourceData) + sourceStart)^, Pointer(UIntPtr(targetData) + targetStart)^, to_copy);

    vp.rval := SimpleVariantToJSval(cx, to_copy);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function setupBufferJS(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;
  proto: PJSRootedObject;
const
  sInvalidCall = 'usage: setupBufferJS(proto: Object);';
  props = JSPROP_ENUMERATE or JSPROP_ENUMERATE or JSPROP_PERMANENT;
  // From GlobalObject.h getOrCreateTypedArrayPrototype
  TypedArraySlotIndex = JSCLASS_GLOBAL_APPLICATION_SLOTS + Ord(JSProto_LIMIT) + Ord(JSProto_TypedArray);
begin
  try
    Result := True;

    in_argv := vp.argv;
    if (argc < 1) or not (in_argv[0].isObject) then
      raise ESMException.Create(sInvalidCall);

    proto := cx.NewRootedObject(in_argv[0].asObject);

    try
      //set_buffer_prototype_object
      vp.thisObject[cx].ReservedSlot[0] := proto.ptr.ToJSValue;
      proto.ptr.DefineFunction(cx, 'asciiSlice', asciiSlice, 2, props);
      proto.ptr.DefineFunction(cx, 'base64Slice', base64Slice, 2, props);
      proto.ptr.DefineFunction(cx, 'latin1Slice', latin1Slice, 2, props);
      proto.ptr.DefineFunction(cx, 'hexSlice', hexSlice, 2, props);
      proto.ptr.DefineFunction(cx, 'ucs2Slice', ucs2Slice, 2, props);
      proto.ptr.DefineFunction(cx, 'utf8Slice', utf8Slice, 2, props);
      proto.ptr.DefineFunction(cx, 'cpSlice', cpSlice, 2, props);

      proto.ptr.DefineFunction(cx, 'asciiWrite', asciiWrite, 3, props);
      proto.ptr.DefineFunction(cx, 'base64Write', base64Write, 3, props);
      proto.ptr.DefineFunction(cx, 'latin1Write', asciiWrite, 3, props); // the same as asciiWrite
      proto.ptr.DefineFunction(cx, 'hexWrite', hexWrite, 3, props);
      proto.ptr.DefineFunction(cx, 'ucs2Write', ucs2Write, 3, props);
      proto.ptr.DefineFunction(cx, 'utf8Write', utf8Write, 3, props);

      proto.ptr.DefineFunction(cx, 'copy', Copy, 0, props);
    finally
      cx.FreeRootedObject(proto);
    end;

  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function createFromString(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;
  encoding: TEncoding;
  tmp: RawByteString;

  _str: PJSString;
  isLatin1: Boolean;
  str: Pointer;
  strLen: size_t;
  strUtf8: RawUTF8;

  buf: PJSRootedObject;
  bufData: Pointer;
  size: PtrInt;
  actual: size_t;
  proto: PJSRootedObject;
  val: jsval;
const
  sInvalidCall = 'usage: createFromString(string, encoding: String);';
  UNKNOWN_ENCODING = 'unknown encoding';
begin
  try
    Result := True;

    in_argv := vp.argv;
    if (argc < 2) or (not in_argv[0].isString) or (not in_argv[1].isString) then
      raise ESMException.Create(sInvalidCall);
    encoding := ParseEncoding(in_argv[1].asJSString.ToUTF8(cx), UTF8);
    _str := in_argv[0].asJSString;

    if encoding = UTF8 then begin
      strUtf8 := _str.ToUTF8(cx);
      str := Pointer(strUtf8);
      strLen := Length(strUtf8);
      isLatin1 := True;
      encoding := UTF8;
    end else begin
      isLatin1 := _str.HasLatin1Chars;
      if isLatin1 then
        str := _str.GetLatin1StringCharsAndLength(cx, strLen)
      else begin
        str := _str.GetTwoByteStringCharsAndLength(cx, strLen);
      end;
    end;

    case encoding of
      ASCII, LATIN1: size := strLen;
      BUFFER, UTF8: size := Length(_str.ToUTF8(cx));//todo fix it
      UCS2: size := strLen shl 1;
      BASE64: begin
        size := (strLen * 3) shr 2 + 2; // estimate
        setLength(tmp, size);
        size := anyBase64ToBin(str, strLen, pointer(tmp), size, not isLatin1);
        if size < 0 then size := 0;
        proto := cx.NewRootedObject(vp.thisObject[cx].ReservedSlot[0].asObject.Ctor[cx]);
        try
          val.asInteger := size;
          buf := cx.NewRootedObject(cx.New(proto.ptr, 1, @val));
          try
            bufData := buf.ptr.GetUint8ArrayData;
            vp.rval := buf.ptr.ToJSValue;
          finally
            cx.FreeRootedObject(buf);
          end;
          MoveFast(pointer(tmp)^, bufData^, size);
        finally
          cx.FreeRootedObject(proto);
        end;
        exit;
      end;
      HEX: size := strLen shr 1;
      else
        raise ESMException.Create(UNKNOWN_ENCODING);
    end;

    proto := cx.NewRootedObject(vp.thisObject[cx].ReservedSlot[0].asObject.Ctor[cx]);
    try
      val.asInteger := size;
      buf := cx.NewRootedObject(cx.New(proto.ptr, 1, @val));
      try
        bufData := buf.ptr.GetUint8ArrayData;
        vp.rval := buf.ptr.ToJSValue;
      finally
        cx.FreeRootedObject(buf);
      end;

      actual := StringBytesWrite(bufData, size, str, strLen, isLatin1, encoding);
      if actual = 0 then begin
        val.asInteger := 0;
        buf := cx.NewRootedObject(cx.New(proto.ptr, 1, @val));
        try
          vp.rval := buf.ptr.ToJSValue;
        finally
          cx.FreeRootedObject(buf);
        end;
      end
    finally
      cx.FreeRootedObject(proto);
    end;
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function byteLengthUtf8(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;
const
  sInvalidCall = 'usage: byteLengthUtf8(string: String);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 1) or (not in_argv[0].isString) then
      raise ESMException.Create(sInvalidCall);
    vp.rval := SimpleVariantToJSval(cx, Length(in_argv[0].asJSString.ToUTF8(cx)));
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function memcmp(const X, Y; Size: size_t): Integer;
begin
  result := StrLIComp(PAnsiChar(@x), PAnsiChar(@y) ,size);
end;
//asm
//  mov esi,X
//  mov edi,Y
//  mov ecx,Size
//  mov dl,cl
//  and dl,3
//  shr ecx,2
//  xor eax,eax
//  rep cmpsd
//  jb @@less
//  ja @@great
//  mov cl,dl
//  rep cmpsb
//  jz @@end
//  ja @@great
// @@less:
//  dec eax
//  jmp @@end
// @@great:
//  inc eax
// @@end:
//end;

function compare(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;
  bufa, bufb: PJSRootedObject;
  bufa_data, bufb_data: Pointer;
  bufa_length, bufb_length: size_t;
  cmp_length: size_t;
  res: Integer;
const
  sInvalidCall = 'usage: compare(buf1, buf: Buffer);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 2) or not (in_argv[0].isObject) or not (in_argv[1].isObject) then
      raise ESMException.Create(sInvalidCall);

    bufa := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, bufa, bufa_data, bufa_length);
    finally
      cx.FreeRootedObject(bufa);
    end;

    bufb := cx.NewRootedObject(in_argv[1].asObject);
    try
      getBufDataAndLength(cx, bufb, bufb_data, bufb_length);
    finally
      cx.FreeRootedObject(bufb);
    end;

    if bufa_length > bufb_length then
      cmp_length := bufb_length
    else
      cmp_length := bufa_length;

    if cmp_length > 0 then
      res := memcmp(bufa_data^, bufb_data^, cmp_length)
    else
      res := 0;

    if res = 0 then begin
      if bufa_length > bufb_length then
        res := 1
      else if bufb_length > bufa_length then
        res := -1;
    end else begin
      if res > 0 then
        res := 1
      else
        res := -1;
    end;

    vp.rval := SimpleVariantToJSval(cx, res);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function compareOffset(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;

  target, source: PJSRootedObject;
  targetData, sourceData: Pointer;
  target_start, source_start, source_end, target_end, source_length, target_length: size_t;
  to_cmp: size_t;
  res: Integer;
const
  sInvalidCall = 'usage: compareOffset(source, target: Buffer; start, sourceStart[, targetEnd= target.length][, sourceEnd=source.length]: Number);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 4) or not (in_argv[0].isObject) or not (in_argv[1].isObject)
      or not(in_argv[2].isNumber) or not (in_argv[3].isNumber)
    then
      raise ESMException.Create(sInvalidCall);

    source := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, source, sourceData, source_length);
    finally
      cx.FreeRootedObject(source);
    end;

    target := cx.NewRootedObject(in_argv[1].asObject);
    try
      getBufDataAndLength(cx, target, targetData, target_length);
    finally
      cx.FreeRootedObject(target);
    end;

    target_start := in_argv[2].asInt64;
    source_start := in_argv[3].asInt64;
    if (argc > 4) and in_argv[4].isNumber then
      target_end := in_argv[4].asInt64
    else
      target_end := source_length;
    if (argc > 5) and in_argv[4].isNumber then
      source_end := in_argv[5].asInt64
    else
      source_end := target_length;

    if source_start > source_length then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    if target_start > target_length then
      raise ESMRangeException.Create(OUT_OF_RANGE);

    if source_start > source_end then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    if target_start > target_end then
      raise ESMRangeException.Create(OUT_OF_RANGE);

    if source_end - source_start > target_end - target_start then
      to_cmp := target_end - target_start
    else
      to_cmp := source_end - source_start;

    if to_cmp > source_length - source_start then
      to_cmp := source_length - source_start;

    if to_cmp > 0 then
      res := memcmp(Pointer(UIntPtr(sourceData) + source_start)^, Pointer(UIntPtr(targetData) + target_start)^, to_cmp)
    else
      res := 0;

    if res = 0 then begin
      if source_end - source_start > target_end - target_start then
        res := 1
      else if target_end - target_start > source_end - source_start then
        res := -1;
    end else begin
      if res > 0 then
        res := 1
      else
        res := -1;
    end;

    vp.rval := SimpleVariantToJSval(cx, res);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function fill(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;

  target: PJSRootedObject;
  targetData: Pointer;
  targetLength: size_t;
  _start, _end, fill_length: size_t;
  str: PJSString;
  str_length: size_t;
  str_lengthInBytes: size_t;
  str_data: Pointer;
  isLatin1: Boolean;
  encoding: TEncoding;
  strUtf8: RawUTF8;

  valObj: PJSRootedObject;
  valData: Pointer;
  valLength: size_t;
  Need_start_fill: boolean;

  valByte: Byte;

  in_there: size_t;
  ptr: Pointer;
const
  sInvalidCall = 'usage: fill(target: Buffer; val: *; start, end: Number[; encoding: String = "UTF8"]);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 4) or (not in_argv[0].isObject) or (not in_argv[2].isNumber) or (not in_argv[3].isNumber) then
      raise ESMException.Create(sInvalidCall);

    vp.rval := JSVAL_VOID;
    target := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, target, targetData, targetLength);
    finally
      cx.FreeRootedObject(target);
    end;

    _start := in_argv[2].asInt64;
    _end := in_argv[3].asInt64;
    fill_length := _end - _start;

    if fill_length + _start > targetLength then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    Need_start_fill := False;
    // First check if Buffer has been passed.
    if in_argv[1].isObject and not in_argv[1].isNull then begin
      valObj := cx.NewRootedObject(in_argv[1].asObject);
      try
        if valObj.ptr.IsTypedArrayObject then begin
          getBufDataAndLength(cx, valObj, valData, valLength);
          str_length := valLength;
          if str_length > fill_length then
            str_lengthInBytes := fill_length
          else
            str_lengthInBytes := str_length;
          MoveFast(valData^, Pointer(UIntPtr(targetData) + _start)^, str_lengthInBytes);
          Need_start_fill := true;
        end;
      finally
        cx.FreeRootedObject(valObj);
      end;
    end;
    if not Need_start_fill then begin
      // Then coerce everything that's not a string.
      if not in_argv[1].isString then begin
        valByte := in_argv[1].asInt64 and 255;
        FillChar(Pointer(UIntPtr(targetData) + _start)^, fill_length, valByte);
        Exit;
      end;

      if (argc > 4) and (in_argv[4].isString) then
        encoding := ParseEncoding(in_argv[4].asJSString.ToUTF8(cx), UTF8)
      else
        encoding := UTF8;

      str :=  in_argv[1].asJSString;
      if encoding = UTF8 then begin
        strUtf8 := str.ToUTF8(cx);
        str_data := Pointer(strUtf8);
        str_length := Length(strUtf8);
        isLatin1 := True;
        encoding := UTF8;
      end else begin
        isLatin1 := str.HasLatin1Chars;
        if isLatin1 then begin
          str_data := str.GetLatin1StringCharsAndLength(cx, str_length);
        end else begin
          str_data := str.GetTwoByteStringCharsAndLength(cx, str_length);
        end;
      end;
      if encoding = UCS2 then
        str_lengthInBytes := str_length * 2
      else
        str_lengthInBytes := str_length;

      if str_lengthInBytes > fill_length then
        str_lengthInBytes := fill_length;

      case encoding of
        UTF8: begin
          if isLatin1 then
            MoveFast(str_data^, Pointer(UIntPtr(targetData) + _start)^, str_lengthInBytes)
//          else
//            RawUnicodeToUtf8(Pointer(UIntPtr(targetData) + _start), str_length, str_data, str_length,[ccfNoTrailingZero]);
        end;
        UCS2: begin
          if isLatin1 then begin
            Latin1AnsiConvert.AnsiBufferToUnicode(Pointer(UIntPtr(targetData) + _start), str_data, str_lengthInBytes shr 1);
          end else
            MoveFast(str_data^, Pointer(UIntPtr(targetData) + _start)^, str_lengthInBytes);
        end;
        else begin
          str_lengthInBytes := StringBytesWrite(Pointer(UIntPtr(targetData) + _start), fill_length, str_data, str_length, isLatin1, encoding);
          if str_lengthInBytes = 0 then
            Exit;
        end;
      end;
    end;

    if str_lengthInBytes >= fill_length then
      exit;

    in_there := str_lengthInBytes;
    ptr := Pointer(UIntPtr(targetData) + _start + str_lengthInBytes);
    while (in_there < fill_length - in_there) do begin
      MoveFast(Pointer(UIntPtr(targetData) + _start)^, ptr^, in_there);
      ptr := Pointer(UIntPtr(ptr) + in_there);
      in_there := in_there shl 1;
    end;

    if in_there < fill_length then
      MoveFast(Pointer(UIntPtr(targetData) + _start)^, ptr^, fill_length - in_there);

  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

// Computes the offset for starting an indexOf or lastIndexOf search.
// Returns either a valid offset in [0...<length - 1>], ie inside the Buffer,
// or -1 to signal that there is no possible match.
function IndexOfOffset(length: size_t; offset_i64: Int64; is_forward: Boolean): Int64; {$ifdef HASINLINE}inline;{$endif}
var
  length_i64: Int64;
begin
  length_i64 := length;
  if length_i64 = 0 then
    // Empty buffer, no match.
    Result := -1
  else if offset_i64 < 0 then begin
    if offset_i64 + length_i64 >= 0 then
      // Negative offsets count backwards from the end of the buffer.
      Result := length_i64 + offset_i64
    else if is_forward then
      // indexOf from before the start of the buffer: search the whole buffer.
      Result := 0
    else
      // lastIndexOf from before the start of the buffer: no match.
      Result := -1
  end else begin
    if offset_i64 < length_i64 then
      // Valid positive offset.
      Result := offset_i64
    else if is_forward then
      // indexOf from past the end of the buffer: no match.
      Result := -1
    else
      // lastIndexOf from past the end of the buffer: search the whole buffer.
      Result := length_i64 - 1;
  end;
end;

function SearchString(haystack: Pointer; haystack_length: size_t; needle: Pointer; needle_length: size_t; offset: size_t; is_forward: Boolean; isTwoByte: Boolean): size_t;  {$ifdef HASINLINE}inline;{$endif}
var
  pStart, pEnd: Pointer;
  delta: integer;
begin;
// todo: optimize it
  if is_forward then begin
      pStart := @Puint8Vector(haystack)[offset];
      pEnd := @Puint8Vector(haystack)[haystack_length - needle_length];
  end else begin
  // todo check is offset for start or for end in this case
      if haystack_length - needle_length < offset then
        offset := haystack_length - needle_length;
      pStart := @Puint8Vector(haystack)[offset];
      pEnd := haystack;
  end;

  if isTwoByte then
    delta := 2
  else
    delta := 1;

  Result := haystack_length;

  while
    (is_forward and (PtrUInt(pStart) <= PtrUInt(pEnd)))
    or
    (not is_forward and (PtrUInt(pEnd) <= PtrUInt(pStart)))
  do begin
    if CompareMem(pStart, needle, needle_length) then begin
      Result := PtrUInt(pStart) - PtrUInt(haystack);
      Break;
    end;
    if is_forward then
      pStart := Pointer(PtrUInt(pStart) + delta)
    else
      pStart := Pointer(PtrUInt(pStart) - delta)
  end;

end;

function indexOfBuffer(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;

  sourceObj: PJSRootedObject;
  sourceData: Pointer;
  sourceLength: size_t;

  bufObj: PJSRootedObject;
  bufData: Pointer;
  bufLength: size_t;

  offset_i64: Int64;
  opt_offset: Int64;
  offset: size_t;
  encoding: TEncoding;
  is_forward: Boolean;

  haystack: Pointer;
  haystack_length: size_t;
  needle: Pointer;
  needle_length: size_t;

  res: size_t;
const
  sInvalidCall = 'usage: indexOfBuffer(source, buf: Buffer; offset: Number; encoding: String; is_forward: boolean);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 5) or (not in_argv[0].isObject) or (not in_argv[1].isObject)
      or (not in_argv[2].isNumber) or (not in_argv[4].isBoolean)
    then
      raise ESMException.Create(sInvalidCall);

    vp.rval := SimpleVariantToJSval(cx, -1);

    sourceObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, sourceObj, sourceData, sourceLength);
    finally
      cx.FreeRootedObject(sourceObj);
    end;

    bufObj := cx.NewRootedObject(in_argv[1].asObject);
    try
      getBufDataAndLength(cx, bufObj, bufData, bufLength);
    finally
      cx.FreeRootedObject(bufObj);
    end;


    if in_argv[3].isString then
      encoding := ParseEncoding(in_argv[3].asJSString.ToUTF8(cx), UTF8)
    else
      encoding := UTF8;

    is_forward := in_argv[4].asBoolean;

    haystack := sourceData;
    haystack_length := sourceLength;
    needle := bufData;
    needle_length := bufLength;

    if (needle_length = 0) or (haystack_length = 0) then
      Exit;

    offset_i64 := in_argv[2].asInt64;

    opt_offset := IndexOfOffset(haystack_length, offset_i64, is_forward);
    if opt_offset <= -1 then
      Exit;
    offset := opt_offset;
    if offset >=  haystack_length then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    if (is_forward and (needle_length + offset > haystack_length)) or (needle_length > haystack_length) then
      Exit;

    if encoding = UCS2 then begin
      if (haystack_length < 2) or (needle_length < 2) then
        Exit;
        res := SearchString(haystack, haystack_length, needle, needle_length, offset, is_forward, true);
    end else
      res := SearchString(haystack, haystack_length, needle, needle_length, offset, is_forward, false);
    if res = haystack_length then
      Exit;
    vp.rval := SimpleVariantToJSval(cx, res);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function indexOfNumber(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;

  sourceObj: PJSRootedObject;
  sourceData: Pointer;
  sourceLength: size_t;

  needle: UInt32;

  offset_i64: Int64;
  opt_offset: Int64;
  offset: size_t;
  is_forward: Boolean;

  res: size_t;

const
  sInvalidCall = 'usage: indexOfNumber(source: Buffer; val, offset: Number; is_forward: boolean);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 3) or (not in_argv[0].isObject) or (not in_argv[1].isNumber) or (not in_argv[2].isNumber) or (not in_argv[3].isBoolean) then
      raise ESMException.Create(sInvalidCall);

    vp.rval := SimpleVariantToJSval(cx, -1);
    sourceObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, sourceObj, sourceData, sourceLength);
    finally
      cx.FreeRootedObject(sourceObj);
    end;

    needle := in_argv[1].asInt64;
    offset_i64 := in_argv[2].asInt64;
    is_forward := in_argv[3].asBoolean;

    opt_offset := IndexOfOffset(sourceLength, offset_i64, is_forward);

    if opt_offset <= -1 then
      Exit;
    offset := opt_offset;

    if offset >=  sourceLength then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    //todo: optimize
    res := SearchString(sourceData, sourceLength, @needle, 1, offset, is_forward, False);
    if res = sourceLength then
      Exit;
    vp.rval := SimpleVariantToJSval(cx, res);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function indexOfString(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
var
  in_argv: PjsvalVector;

  sourceObj: PJSRootedObject;
  sourceData: Pointer;
  sourceLength: size_t;

//  bufObj: PJSRootedObject;
//  bufData: Pointer;
//  bufLength: size_t;

  offset_i64: Int64;
  opt_offset: Int64;
  offset: size_t;
  encoding: TEncoding;
  is_forward: Boolean;

  haystack: Pointer;
  haystack_length: size_t;
  needle: PJSString;
  needle_length: size_t;
  needleData: Pointer;
  isLatin1: Boolean;
  needleUtf8: RawUTF8;
  needleSynUnicode: SynUnicode;

  res: size_t;

const
  sInvalidCall = 'usage: indexOfString(source: Buffer; val: String; offset: Number; encoding: String; is_forward: boolean);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 5) or (not in_argv[0].isObject) or (not in_argv[1].isString) or (not in_argv[2].isNumber)
        or (not in_argv[4].isBoolean) then
      raise ESMException.Create(sInvalidCall);

    vp.rval := SimpleVariantToJSval(cx, -1);
    if in_argv[3].isString then
      encoding := ParseEncoding(in_argv[3].asJSString.ToUTF8(cx), UTF8)
    else
      encoding := UTF8;

    sourceObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, sourceObj, sourceData, sourceLength);
    finally
      cx.FreeRootedObject(sourceObj);
    end;

    needle := in_argv[1].asJSString;
    offset_i64 := in_argv[2].asInt64;
    is_forward := in_argv[4].asBoolean;

    haystack := sourceData;
    // Round down to the nearest multiple of 2 in case of UCS2.
    if encoding = UCS2 then
      haystack_length := sourceLength and (not 1)
    else
      haystack_length := sourceLength;

    isLatin1 := needle.HasLatin1Chars;

    if isLatin1 then
      needleData := needle.GetLatin1StringCharsAndLength(cx, needle_length)
    else begin
      needleData := needle.GetTwoByteStringCharsAndLength(cx, needle_length);
    end;
    if encoding = UCS2 then
      needle_length := needle_length shl 1;

    if (needle_length = 0) or (haystack_length = 0) then
      Exit;

    opt_offset := IndexOfOffset(haystack_length, offset_i64, is_forward);
    if opt_offset <= -1 then
      Exit;
    offset := opt_offset;
    if offset >=  haystack_length then
      raise ESMRangeException.Create(OUT_OF_RANGE);
    if (is_forward and (needle_length + offset > haystack_length)) or (needle_length > haystack_length) then
      Exit;

    res := haystack_length;

    if encoding = UCS2 then begin
      if (needleData = nil) or (haystack_length < 2) or (needle_length < 2) then
        Exit;
      if Endianness = kBigEndian then begin
        raise ESMException.Create('Not implemented yet'); //todo
      end else begin
        if isLatin1 then
          needleSynUnicode := needle.ToSynUnicode(cx)
        else
          SetString(needleSynUnicode, PWideChar(needleData), needle_length);
        res := SearchString(haystack, haystack_length, Pointer(needleSynUnicode), Length(needleSynUnicode), offset, is_forward, true);
      end;
    end else if encoding = UTF8 then begin
    //todo: optimize
      needleUtf8 := needle.ToUTF8(cx);
      if needleUtf8 = '' then
        Exit;
      res := SearchString(haystack, haystack_length, Pointer(needleUtf8), Length(needleUtf8), offset, is_forward, false);
    end else if encoding = LATIN1 then begin
      res := SearchString(haystack, haystack_length, needleData, needle_length, offset, is_forward, false);
    end;

    if res = haystack_length then
      Exit;

    vp.rval := SimpleVariantToJSval(cx, res);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

procedure Swizzle(start: Pointer; len: size_t);  {$ifdef HASINLINE}inline;{$endif}
var
  _start: PAnsiChar absolute start;
  _end: PAnsiChar;
  tmp: AnsiChar;
begin
  _end := _start + (len - 1);
  while (_start < _end) do begin
    tmp := _start^;
    _start^ := _end^;
    _end^ := tmp;
    inc(_start);
    Dec(_end);
  end;
end;

const
  sFloatType: array[boolean] of RawUTF8 = ('Float', 'Double');
  sFloatEndian: array[boolean] of RawUTF8 = ('LE', 'BE');
  cFloatSize: array[boolean] of size_t = (4, 8);

function ReadFloatGeneric(cx: PJSContext; argc: uintN; var vp: jsargRec; endian: TEndianness; isDouble: Boolean): Boolean;  {$ifdef HASINLINE}inline;{$endif}
var
  in_argv: PjsvalVector;

  bufObj: PJSRootedObject;
  bufData: Pointer;
  bufLength: size_t;

  offset: size_t;

  d: Double;
  s: Single absolute d;
const
  sInvalidCall = 'usage: read%%(buf: Buffer, offset: Number);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 2) or (not in_argv[0].isObject)  or (not in_argv[1].isNumber) then
      raise ESMException.CreateUTF8(sInvalidCall, [sFloatType[isDouble], sFloatEndian[endian = kLittleEndian]]);

    bufObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, bufObj, bufData, bufLength);
    finally
      cx.FreeRootedObject(bufObj);
    end;

    offset := in_argv[1].asInt64;
    if offset + cFloatSize[isDouble] > bufLength then
      raise ESMRangeException.Create(OUT_OF_RANGE);

    MoveFast(Pointer(PtrUInt(bufData) + offset)^, d, cFloatSize[isDouble]);

    if endian <> Endianness then
      Swizzle(@d, cFloatSize[isDouble]);

    if isDouble then
      vp.rval := SimpleVariantToJSval(cx, d)
    else
      vp.rval := SimpleVariantToJSval(cx, s);
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function readDoubleBE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := ReadFloatGeneric(cx, argc, vp, kBigEndian, true);
end;

function readDoubleLE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := ReadFloatGeneric(cx, argc, vp, kLittleEndian, true);
end;

function readFloatBE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := ReadFloatGeneric(cx, argc, vp, kBigEndian, False);
end;

function readFloatLE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := ReadFloatGeneric(cx, argc, vp, kLittleEndian, False);
end;

function WriteFloatGeneric(cx: PJSContext; argc: uintN; var vp: jsargRec; endian: TEndianness; isDouble: Boolean): Boolean;  {$ifdef HASINLINE}inline;{$endif}
var
  in_argv: PjsvalVector;

  bufObj: PJSRootedObject;
  bufData: Pointer;
  bufLength: size_t;

  offset: size_t;
  should_assert: Boolean;
  memcpy_num: size_t;

  d: Double;
  s: Single absolute d;
const
  sInvalidCall = 'usage: Write%%(buf: Buffer; val: Number; offset: Number; [shouldAssert: boolean = false]);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 3) or (not in_argv[0].isObject)  or (not in_argv[1].isNumber) or (not in_argv[2].isNumber) then
      raise ESMException.CreateUTF8(sInvalidCall, [sFloatType[isDouble], sFloatEndian[endian = kLittleEndian]]);

    should_assert := argc < 4;
    bufObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, bufObj, bufData, bufLength);
    finally
      cx.FreeRootedObject(bufObj);
    end;

    if isDouble then
      d := in_argv[1].asDouble
    else
      s := in_argv[1].asDouble;

    offset := in_argv[2].asInt64;

    if should_assert and ((offset + cFloatSize[isDouble] > bufLength) or (offset + cFloatSize[isDouble] < cFloatSize[isDouble]))then
      raise ESMRangeException.Create(OUT_OF_RANGE);

    if (offset + cFloatSize[isDouble] > bufLength) then
      memcpy_num := bufLength - offset
    else
      memcpy_num := cFloatSize[isDouble];

    if endian <> Endianness then
      Swizzle(@d, cFloatSize[isDouble]);

    MoveFast(d, Pointer(PtrUInt(bufData) + offset)^, memcpy_num);

    vp.rval := JSVAL_VOID;
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function writeDoubleBE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := WriteFloatGeneric(cx, argc, vp, kBigEndian, True);
end;

function writeDoubleLE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := WriteFloatGeneric(cx, argc, vp, kLittleEndian, True);
end;

function writeFloatBE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := WriteFloatGeneric(cx, argc, vp, kBigEndian, False);
end;

function writeFloatLE(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := WriteFloatGeneric(cx, argc, vp, kLittleEndian, False);
end;

function swap(cx: PJSContext; argc: uintN; var vp: jsargRec; bytesCount: byte): Boolean;  {$ifdef HASINLINE}inline;{$endif}
var
  in_argv: PjsvalVector;

  bufObj: PJSRootedObject;
  bufData: Pointer;
  bufLength: size_t;

  i: size_t;
const
  sInvalidCall = 'usage: swap%(buf: Buffer);';
begin
  try
    Result := True;
    in_argv := vp.argv;
    if (argc < 1) or (not in_argv[0].isObject)then
      raise ESMException.CreateUTF8(sInvalidCall, [1 shl bytesCount]);

    bufObj := cx.NewRootedObject(in_argv[0].asObject);
    try
      getBufDataAndLength(cx, bufObj, bufData, bufLength);
    finally
      cx.FreeRootedObject(bufObj);
    end;

    if bufLength mod bytesCount <> 0 then
      raise ESMRangeException.Create(OUT_OF_RANGE);

     i := 0;
     while (i < bufLength) do begin
       Swizzle(bufData, bytesCount);
       bufData := Pointer(PtrUInt(bufData) + bytesCount);
       Inc(i, bytesCount);
     end;

    vp.rval := in_argv[0];
  except
    on E: Exception do
    begin
      Result := False;
      vp.rval := JSVAL_VOID;
      JSError(cx, E);
    end;
  end;
end;

function swap16(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := swap(cx, argc, vp, 2);
end;

function swap32(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := swap(cx, argc, vp, 4);
end;

function swap64(cx: PJSContext; argc: uintN; var vp: jsargRec): Boolean; cdecl;
begin
  Result := swap(cx, argc, vp, 8);
end;

const
  BufferBinding_class: JSClass = (name: 'BufferBinding';
    flags:
      (1 shl JSCLASS_RESERVED_SLOTS_SHIFT);
    );

function SyNodeBindingProc_buffer(const aEngine: TSMEngine; const bindingNamespaceName: SynUnicode): jsval;
var
  obj: PJSRootedObject;
  cx: PJSContext;
const
  props = JSPROP_ENUMERATE or JSPROP_ENUMERATE or JSPROP_PERMANENT;
begin
  cx := aEngine.cx;
  obj := cx.NewRootedObject(cx.NewObject(@BufferBinding_class));

  obj.ptr.DefineFunction(cx, 'setupBufferJS', setupBufferJS, 0, props);
  obj.ptr.DefineFunction(cx, 'createFromString', createFromString, 0, props);

  obj.ptr.DefineFunction(cx, 'byteLengthUtf8', byteLengthUtf8, 0, props);
  obj.ptr.DefineFunction(cx, 'compare', compare, 0, props);
  obj.ptr.DefineFunction(cx, 'compareOffset', compareOffset, 0, props);
  obj.ptr.DefineFunction(cx, 'fill', fill, 0, props);
  obj.ptr.DefineFunction(cx, 'indexOfBuffer', indexOfBuffer, 0, props);
  obj.ptr.DefineFunction(cx, 'indexOfNumber', indexOfNumber, 0, props);
  obj.ptr.DefineFunction(cx, 'indexOfString', indexOfString, 0, props);

  obj.ptr.DefineFunction(cx, 'readDoubleBE', readDoubleBE, 0, props);
  obj.ptr.DefineFunction(cx, 'readDoubleLE', readDoubleLE, 0, props);
  obj.ptr.DefineFunction(cx, 'readFloatBE', readFloatBE, 0, props);
  obj.ptr.DefineFunction(cx, 'readFloatLE', readFloatLE, 0, props);

  obj.ptr.DefineFunction(cx, 'writeDoubleBE', writeDoubleBE, 0, props);
  obj.ptr.DefineFunction(cx, 'writeDoubleLE', writeDoubleLE, 0, props);
  obj.ptr.DefineFunction(cx, 'writeFloatBE', writeFloatBE, 0, props);
  obj.ptr.DefineFunction(cx, 'writeFloatLE', writeFloatLE, 0, props);

  obj.ptr.DefineFunction(cx, 'swap16', swap16, 0, props);
  obj.ptr.DefineFunction(cx, 'swap32', swap32, 0, props);
  obj.ptr.DefineFunction(cx, 'swap64', swap64, 0, props);

  obj.ptr.DefineProperty(cx, 'kMaxLength', SimpleVariantToJSval(cx, kMaxLength), props);
  obj.ptr.DefineProperty(cx, 'kStringMaxLength', SimpleVariantToJSval(cx, kMaxLength), props);

  Result := obj.ptr.ToJSValue;

  cx.FreeRootedObject(obj);
end;

initialization
  if _Endianness.u16 = 1 then
    Endianness := kLittleEndian
  else
    Endianness := kBigEndian;

  TSMEngineManager.RegisterBinding('buffer', SyNodeBindingProc_buffer);

end.
