(********************************************************************)
(*                                                                  *)
(*  bytedata.s7i  Convert byte data to and from strings             *)
(*  Copyright (C) 2009, 2013 - 2015, 2019 - 2023  Thomas Mertes     *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "file.s7i";
include "bigint.s7i";
include "bstring.s7i";


(**
 *  Enumeration of signedness values.
 *  Signedness defines if negative values can be represented.
 *  Defines: UNSIGNED and SIGNED.
 *  Signed values use the twos-complement representation.
 *)
const type: signedness is new enum
    UNSIGNED, SIGNED
  end enum;


(**
 *  Enumeration of endianness values.
 *  Endianness defines how the bytes of a data are ordered within memory.
 *  Defines: LE (little-endian) and BE (big-endian).
 *  A little-endian representation starts with the least significant byte.
 *  A big-endian representation starts with the most significant byte.
 *)
const type: endianness is new enum
    LE, BE
  end enum;


(**
 *  Get a null terminated string from ''stri'' starting from ''startPos''.
 *  @param stri string of bytes from which the result is obtained.
 *  @param startPos Start position of the null terminated string.
 *  @return the null terminated string without the null ('\0;') character.
 *)
const func string: fromAsciiz (in string: stri, in integer: startPos) is func
  result
    var string: resultStri is "";
  local
    var integer: nullPos is 0;
  begin
    nullPos := pos(stri, '\0;', startPos);
    if nullPos = 0 then
      resultStri := stri[startPos ..];
    else
      resultStri := stri[startPos .. pred(nullPos)];
    end if;
  end func;


(**
 *  Read a null terminated string from ''stri'' starting from ''currPos''.
 *  CurrPos is advanced after the null ('\0;') character. When there is
 *  no null character the string is assumed to extend to the end of ''stri''.
 *  In this case ''currPos'' is advanced beyond the length of ''stri''.
 *  @param stri string of bytes from which the result is obtained.
 *  @param currPos Start position of the null terminated string.
 *                 The function advances ''currPos'' to refer to the position
 *                 behind the terminating null ('\0;') character.
 *  @return the null terminated string without the null ('\0;') character.
 *)
const func string: getAsciiz (in string: stri, inout integer: currPos) is func
  result
    var string: resultStri is "";
  local
    var integer: nullPos is 0;
  begin
    nullPos := pos(stri, '\0;', currPos);
    if nullPos = 0 then
      resultStri := stri[currPos ..];
      currPos := succ(length(stri));
    else
      resultStri := stri[currPos .. pred(nullPos)];
      currPos := succ(nullPos);
    end if;
  end func;


(**
 *  Read a null terminated string from ''inFile''.
 *  The file position is advanced after the null ('\0;') character.
 *  @param inFile File from which the result is obtained.
 *  @return the null terminated string without the null ('\0;') character.
 *)
const func string: getAsciiz (inout file: inFile) is func
  result
    var string: resultStri is "";
  local
    var char: ch is ' ';
  begin
    ch := getc(inFile);
    while ch <> '\0;' do
      resultStri &:= ch;
      ch := getc(inFile);
    end while;
  end func;


(**
 *  Read a string from ''inFile'' until the ''terminator'' character is found.
 *  If a ''terminator'' is found the string before the ''terminator'' is
 *  returned and the ''terminator'' character is assigned to inFile.bufferChar.
 *  The file position is advanced after the ''terminator'' character.
 *  If no ''terminator'' is found the rest of the string is returned and
 *  EOF is assigned to the inFile.bufferChar.
 *  @return the string read without the ''terminator'' or the rest of the
 *          file if no ''terminator'' is found.
 *)
const func string: getStriUpToChar (inout file: inFile, in char: terminator) is func
  result
    var string: resultStri is "";
  local
    var char: ch is ' ';
  begin
    ch := getc(inFile);
    while ch <> terminator and ch <> EOF do
      resultStri &:= ch;
      ch := getc(inFile);
    end while;
    inFile.bufferChar := ch;
  end func;


(**
 *  Convert a [[string]] of bytes to its hexadecimal representation.
 *  Each byte is represented by two hexadecimal digits.
 *   hex("!;Mn")  returns "213b4d6e"
 *  @return the hexadecimal representation of the given [[string]].
 *  @exception RANGE_ERROR If characters beyond '\255;' are present.
 *)
const func string: hex (in string: stri) is func
  result
    var string: hex is "";
  local
    var char: ch is ' ';
  begin
    for ch range stri do
      if ch > '\255;' then
        raise RANGE_ERROR;
      end if;
      hex &:= ord(ch) radix 16 lpad0 2;
    end for;
  end func;


(**
 *  Convert a [[string]] with hexadecimal digits to a string of bytes.
 *  Each byte in the result is represented by two hexadecimal digits in ''hex''.
 *   hexToBytes("6d")        returns "m"
 *   hexToBytes("213b4d6e")  returns "!;Mn"
 *  @return the byte [[string]] that corresponds to the given
 *          hexadecimal string ''hex''.
 *  @exception RANGE_ERROR If the length of ''hex'' is odd or
 *             if ''hex'' contains other characters than hexadecimal digits.
 *)
const func string: hex2Bytes (in string: hex) is func
  result
    var string: stri is "";
  local
    var integer: index is 0;
  begin
    if odd(length(hex)) then
      raise RANGE_ERROR;
    else
      for index range 1 to length(hex) step 2 do
        stri &:= char(integer(hex[index fixLen 2], 16));
      end for;
    end if;
  end func;


const func string: bytes (in integer: number, UNSIGNED, BE) is action "INT_BYTES_BE_UNSIGNED";

const func string: bytes (in integer: number, UNSIGNED, LE) is action "INT_BYTES_LE_UNSIGNED";

const func string: bytes (in integer: number, SIGNED, BE) is action "INT_BYTES_BE_SIGNED";

const func string: bytes (in integer: number, SIGNED, LE) is action "INT_BYTES_LE_SIGNED";


(**
 *  Convert an [[integer]] into a [[string]] of bytes.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460, SIGNED, BE)  returns "TEST"
 *   bytes(1497451343, SIGNED, LE)  returns "OKAY"
 *  @param number Integer number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a [[string]] with the shortest binary representation of ''number''.
 *  @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in integer: number,
    in signedness: signed, in endianness: endian) is DYNAMIC;


const func string: bytes (in integer: number, UNSIGNED, BE, in integer: length) is action "INT_N_BYTES_BE_UNSIGNED";

const func string: bytes (in integer: number, UNSIGNED, LE, in integer: length) is action "INT_N_BYTES_LE_UNSIGNED";

const func string: bytes (in integer: number, SIGNED, BE, in integer: length) is action "INT_N_BYTES_BE_SIGNED";

const func string: bytes (in integer: number, SIGNED, LE, in integer: length) is action "INT_N_BYTES_LE_SIGNED";


(**
 *  Convert an [[integer]] into a [[string]] of bytes with the given ''length''.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460, SIGNED, BE, 5)  returns "\0;TEST"
 *   bytes(1413829460, SIGNED, BE, 4)  returns "TEST"
 *   bytes(1413829460, SIGNED, BE, 3)  raises RANGE_ERROR
 *   bytes(1497451343, SIGNED, LE, 5)  returns "OKAY\0;"
 *   bytes(1497451343, SIGNED, LE, 4)  returns "OKAY"
 *   bytes(1497451343, SIGNED, LE, 3)  raises RANGE_ERROR
 *   bytes(-1246382667, SIGNED, BE, 6)   returns "ÿÿµµµµ"
 *   bytes(-1246382667, SIGNED, LE, 6)   returns "µµµµÿÿ"
 *   bytes(-1246382667, SIGNED, LE, 4)   returns "µµµµ"
 *   bytes(-1246382667, SIGNED, LE, 3)   raises RANGE_ERROR
 *   bytes(-123456789, UNSIGNED, BE, 4)  raises RANGE_ERROR
 *   bytes(-123456789, UNSIGNED, LE, 4)  raises RANGE_ERROR
 *   bytes(3048584629, UNSIGNED, BE, 4)  returns "µµµµ"
 *   bytes(3048584629, UNSIGNED, BE, 5)  returns "\0;µµµµ"
 *   bytes(3048584629, UNSIGNED, LE, 6)  returns "µµµµ\0;\0;"
 *  @param number Integer number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @param length Determines the length of the result string.
 *  @return a [[string]] of ''length'' bytes with the binary representation of ''number''.
 *  @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED, or
 *                         if ''length'' is negative or zero, or
 *                         if the result would not fit in ''length'' bytes.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in integer: number,
    in signedness: signed, in endianness: endian, in integer: length) is DYNAMIC;


const func string: bytes (in bigInteger: number, UNSIGNED, BE) is
  return str(bStriBe(number, FALSE));

const func string: bytes (in bigInteger: number, UNSIGNED, LE) is
  return str(bStriLe(number, FALSE));

const func string: bytes (in bigInteger: number, SIGNED, BE) is
  return str(bStriBe(number, TRUE));

const func string: bytes (in bigInteger: number, SIGNED, LE) is
  return str(bStriLe(number, TRUE));


(**
 *  Convert a [[bigint|bigInteger]] into a [[string]] of bytes.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460_, SIGNED, BE)                        returns "TEST"
 *   bytes(1497451343_, SIGNED, LE)                        returns "OKAY"
 *   bytes(8316866959935304777_, UNSIGNED, LE)             returns "It works"
 *   bytes(54818063270363344731475178867_, UNSIGNED, BE)   returns "± plus-minus"
 *   bytes(54818063270363344731475178867_, SIGNED, BE)     returns "\0;± plus-minus"
 *   bytes(-24410099243900992862068771469_, SIGNED, BE)    returns "± plus-minus"
 *   bytes(-24410099243900992862068771469_, UNSIGNED, LE)  raises RANGE_ERROR
 *  @param number BigInteger number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a [[string]] with the shortest binary representation of ''number''.
 *  @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in bigInteger: number,
    in signedness: signed, in endianness: endian) is DYNAMIC;


const func string: bytes (in bigInteger: number, UNSIGNED, BE, in integer: length) is func
  result
    var string: stri is "";
  begin
    stri := bytes(number, UNSIGNED, BE);
    if length(stri) < length then
      stri := "\0;" mult (length - length(stri)) & stri;
    elsif length(stri) <> length then
      raise RANGE_ERROR;
    end if;
  end func;


const func string: bytes (in bigInteger: number, UNSIGNED, LE, in integer: length) is func
  result
    var string: stri is "";
  begin
    stri := bytes(number, UNSIGNED, LE);
    if length(stri) < length then
      stri &:= "\0;" mult (length - length(stri));
    elsif length(stri) <> length then
      raise RANGE_ERROR;
    end if;
  end func;


const func string: bytes (in bigInteger: number, SIGNED, BE, in integer: length) is func
  result
    var string: stri is "";
  begin
    stri := bytes(number, SIGNED, BE);
    if length(stri) < length then
      if number >= 0_ then
        stri := "\0;" mult (length - length(stri)) & stri;
      else
        stri := "\255;" mult (length - length(stri)) & stri;
      end if;
    elsif length(stri) <> length then
      raise RANGE_ERROR;
    end if;
  end func;


const func string: bytes (in bigInteger: number, SIGNED, LE, in integer: length) is func
  result
    var string: stri is "";
  begin
    stri := bytes(number, SIGNED, LE);
    if length(stri) < length then
      if number >= 0_ then
        stri &:= "\0;" mult (length - length(stri));
      else
        stri &:= "\255;" mult (length - length(stri));
      end if;
    elsif length(stri) <> length then
      raise RANGE_ERROR;
    end if;
  end func;


(**
 *  Convert a [[bigint|bigInteger]] to a [[string]] of bytes with a ''length''.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460_, SIGNED, BE, 5)    returns "\0;TEST"
 *   bytes(1413829460_, SIGNED, BE, 4)    returns "TEST"
 *   bytes(1413829460_, SIGNED, BE, 3)    raises RANGE_ERROR
 *   bytes(-1246382667_, SIGNED, BE, 6)   returns "ÿÿµµµµ"
 *   bytes(-1246382667_, SIGNED, LE, 6)   returns "µµµµÿÿ"
 *   bytes(-1246382667_, SIGNED, LE, 4)   returns "µµµµ"
 *   bytes(-1246382667_, SIGNED, LE, 3)   raises RANGE_ERROR
 *   bytes(3048584629_, UNSIGNED, BE, 4)  returns "µµµµ"
 *   bytes(3048584629_, UNSIGNED, LE, 6)  returns "µµµµ\0;\0;"
 *  @param number BigInteger number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @param length Length of the result string.
 *  @return a [[string]] of bytes with ''length'' representing ''number''.
 *  @exception RANGE_ERROR If the result does not fit into ''length''.
 *  @exception RANGE_ERROR If ''number'' is negative and ''signed'' is UNSIGNED.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in bigInteger: number,
    in signedness: signed, in endianness: endian, in integer: length) is DYNAMIC;


const func integer: bytes2Int (in string: byteStri, UNSIGNED, BE) is action "INT_BYTES_BE_2_UINT";

const func integer: bytes2Int (in string: byteStri, UNSIGNED, LE) is action "INT_BYTES_LE_2_UINT";

const func integer: bytes2Int (in string: byteStri, SIGNED, BE) is action "INT_BYTES_BE_2_INT";

const func integer: bytes2Int (in string: byteStri, SIGNED, LE) is action "INT_BYTES_LE_2_INT";


(**
 *  Convert a [[string]] of bytes to an [[integer]].
 *   bytes2Int("I\150;\2;\210;", UNSIGNED, BE)  returns  1234567890
 *   bytes2Int("\210;\2;\150;I", UNSIGNED, LE)  returns  1234567890
 *   bytes2Int(":\222;h\177;", UNSIGNED, BE)    returns   987654321
 *  @param byteStri String of bytes to be converted. The bytes are
 *         interpreted as binary representation with a base of 256.
 *  @param signed Determines the [[#signedness|signedness]] of ''byteStri''.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         ''bstri'' is interpreted as signed value in the twos-complement
 *         representation. In this case the result is negative if
 *         the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of ''byteStri''.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return an integer created from ''byteStri''.
 *  @exception RANGE_ERROR If ''byteStri'' is empty or
 *             if characters beyond '\255;' are present or
 *             if the result value cannot be represented with an integer.
 *)
const func integer: bytes2Int (in string: byteStri,
    in signedness: signed, in endianness: endian) is DYNAMIC;


const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, BE) is
  return bStriBe2BigInt(bstring(byteStri), FALSE);

const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, LE) is
  return bStriLe2BigInt(bstring(byteStri), FALSE);

const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, BE) is
  return bStriBe2BigInt(bstring(byteStri), TRUE);

const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, LE) is
  return bStriLe2BigInt(bstring(byteStri), TRUE);


(**
 *  Convert a [[string]] of bytes to a [[bigint|bigInteger]].
 *   bytes2BigInt("I\150;\2;\210;", SIGNED, BE)        returns  1234567890_
 *   bytes2BigInt("\210;\2;\150;I", UNSIGNED, LE)      returns  1234567890_
 *   bytes2BigInt(":\222;h\177;", SIGNED, BE)          returns   987654321_
 *   bytes2BigInt("\139;\208;\3;\152;", UNSIGNED, BE)  returns  2345665432_
 *   bytes2BigInt("\139;\208;\3;\152;", SIGNED, BE)    returns -1949301864_
 *   bytes2BigInt("\152;\3;\208;\139;", UNSIGNED, LE)  returns  2345665432_
 *  @param byteStri String of bytes to be converted. The bytes are
 *         interpreted as binary representation with a base of 256.
 *  @param signed Determines the [[#signedness|signedness]] of ''byteStri''.
 *         Possible values are UNSIGNED and SIGNED. If ''signed'' is SIGNED
 *         ''bstri'' is interpreted as signed value in the twos-complement
 *         representation. In this case the result is negative if
 *         the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianness|endianness]] of ''byteStri''.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a bigInteger created from ''byteStri''.
 *  @exception RANGE_ERROR If ''byteStri'' is empty or
 *             if characters beyond '\255;' are present.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func bigInteger: bytes2BigInt (in string: byteStri,
    in signedness: signed, in endianness: endian) is DYNAMIC;


(**
 *  Read two bytes from a file and return their little-endian value.
 *)
const func integer: getUInt16Le (inout file: inFile) is
  return bytes2Int(gets(inFile, 2), UNSIGNED, LE);


(**
 *  Read four bytes from a file and return their little-endian value.
 *)
const func integer: getUInt32Le (inout file: inFile) is
  return bytes2Int(gets(inFile, 4), UNSIGNED, LE);


(**
 *  Read two bytes from a file and return their big-endian value.
 *)
const func integer: getUInt16Be (inout file: inFile) is
  return bytes2Int(gets(inFile, 2), UNSIGNED, BE);


(**
 *  Read four bytes from a file and return their big-endian value.
 *)
const func integer: getUInt32Be (inout file: inFile) is
  return bytes2Int(gets(inFile, 4), UNSIGNED, BE);