/* ios.pk - IO spaces related utilities.  */

/* Copyright (C) 2021, 2022, 2023, 2024 Jose E. Marchesi.  */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* This pickle contains utilities related to poke IO spaces.  */

var ios_dumpval_styles
= ["dump-val1", "dump-val2", "dump-val3", "dump-val4"];

type IOS_Dumpval_Info =
  struct
  {
    string name;
    int<32> index; /* 0 means non-mapped.  */
    int<32> style;
    offset<uint<64>,b> offset;
    offset<uint<64>,b> size;
  };

/* The following variables get updated every time ios_dump_bytes gets
   invoked with a VAL:

   ios_dumpval holds the values of the elements contained in VAL
   (struct fields, array elements).

   ios_dumpval_info holds a descriptor for each element contained in
   VAL, used by ios_dump_bytes to colorize the byte and ascii ranges.

   ios_dumpval_last contains VAL.  */

var ios_dumpval = any[]();
var ios_dumpval_info = IOS_Dumpval_Info[]();
var ios_dumpval_last = any[1](0);

/* Print a byte dump of an area in a given IO space.

   IOS is the IO space from which dump bytes.

   FROM is the offset from which start dumping bytes.  This location
   is truncated to bytes.

   SIZE is an offset specifying the amount of bytes to dump.  The
   offset is rounded up to the next byte.

   VAL may be either a signed 32-bit integer or a mapped value.  If a
   mapped value, the byte dump corresponds to the contents of the
   value and field/element information is also printed with color
   codes.  If VAL is a positive integer, then it is used to index the
   ios_dumpval array to get the mapped value whose bytes are printed.
   If VAL is a negative integer, then it is ignored.  Defaults to -1.

   GROUP_BY determines how the bytes are grouped together in the
   output.  Defaults to two bytes.

   CLUSTER_BY specifies to display additioanl space after the
   specified number of groups have been displayed.  Defaults to 8.

   RULER is a boolean determining whether to include a ruler line in
   the output.  Defaults to 0.

   ASCII is a boolean determining whether to include an ASCII dump.
   Defaults to 0.

   UNKNOWN_BYTE is the string to use to denote bytes that can't be
   read from the IO space.  Defaults to "??".  Note this string is
   expected to be of length two characters.

   NONPRINTABLE_CHAR is the character code to use to denote
   non-printable characters.  Defaults to '.'.

   MAX_LINE_WIDTH is the maximum number of columns to use in the
   terminal.  Additional lines are used whenever necessary.  If this
   argument has a negative value then no limit applies.  Defaults to
   -1.

   This function may raise E_io, E_perm, and E_eof on several error
   conditions.  */

fun ios_dump_bytes = (int<32> ios,
                      offset<uint<64>,b> from,
                      offset<uint<64>,b> size,
                      any val = -1,
                      offset<uint<64>,b> group_by = 2#B,
                      int<32> cluster_by = 8,
                      int<32> ruler_p = 0,
                      int<32> ascii_p = 0,
                      string unknown_byte = "??",
                      uint<8> nonprintable_char = '.',
                      int<32> max_line_width = -1) void:
{
  var print_val_p = 0;

  fun get_val_style = (offset<uint<64>,b> offset) string:
  {
    for (var idx = 0; idx < ios_dumpval'length; ++idx)
      {
        var info = ios_dumpval_info[idx];
        if (offset >= info.offset && offset < info.offset + info.size)
          return ios_dumpval_styles[info.style % ios_dumpval_styles'length];
      }
    return "";
  }

  fun print_ruler = (offset<uint<64>,b> offset) void:
  {
    var o = 0#B;

    fun itoa = (uint<8> x) uint<8>:
    {
      if (x >= 0 && x <= 9) return x + '0';
      if (x >= 10 && x <= 15) return x + 'A' - 10;
    }

   if (offset > 0xffff_ffff#B)
     printf "%<dump-ruler:FEDCBA9876543210 %>";
   else
     printf "%<dump-ruler:76543210 %>";

    for (s in ["00", "11", "22", "33", "44", "55", "66",
	       "77", "88", "99", "aa", "bb", "cc", "dd",
	       "ee", "ff"])
      {
	if (o % group_by == 0#B)
	  printf "%<dump-ruler: %>";
	printf "%<dump-ruler:%s%>", s;
        o++;
	if (o < 16#B && (o % (cluster_by * group_by)) == 0#B)
	   printf (" ");
      }
    if (ascii_p)
      {
	var x = 0 as uint<8>;
	var s = "";
	while (x < 16)
	  {
	    s = s + itoa(x) as string;
            x++;
	    if (x < 16 && (x % ((cluster_by * group_by)/1#B)) == 0)
	      s = s + " ";
	  }
	printf "%<dump-ruler:  %s%>", s;
      }
    print "\n";
  }

  fun print_ascii = (offset<uint<64>,b> offset,
                     offset<uint<64>,b> top,
                     offset<uint<64>,b> step,
                     offset<uint<64>,b> group_by,
                     int<32> cluster_by) int<32>:
  {
    var len = 0;
    var style = "";
    var unknown_byte_style = "";
    print("  ");
    len += 2;
    var o = 0#B;
    while (o < step && offset + o < top)
      {
        if (print_val_p)
          {
            style = get_val_style (offset + o);
            unknown_byte_style = style;
          }

        if (!print_val_p && style == "")
          {
            style = "dump-ascii";
            unknown_byte_style = "dump-unknown";
          }

        try
        {
          var v = uint<8> @ ios : (offset + o);

          if (style != "")
            term_begin_class (style);
          if (v < ' ' || v > '~')
            printf "%c", nonprintable_char;
          else
            printf "%c", v;
          if (style != "")
            term_end_class (style);
          len += 1;
        }
        catch if E_io
        {
          if (style != "")
            term_begin_class (style);
          printf "%c", nonprintable_char;
          if (style != "")
            term_end_class (style);
          len += 1;
        }
        o++;
	if (o < 16#B && (o % (cluster_by * group_by)) == 0#B)
          {
	    printf (" ");
            len += 1;
          }
      }

    return len;
  }

  fun print_values = (offset<uint<64>,b> offset,
                      offset<uint<64>,b> step,
                      offset<uint<64>,b> top,
                      int<32> col) void:
  {
    var o = 0#b;
    var c = col;
    while (o < step && offset + o < top)
      {
        for (var idx = 0; idx < ios_dumpval'length; ++idx)
          {
            var info = ios_dumpval_info[idx];
            if (info.offset == offset + o)
              {
                var val = ios_dumpval[idx];
                var style = ios_dumpval_styles[info.style % ios_dumpval_styles'length];

                if (max_line_width >= 0
                    && c + info.name'length + 4 > max_line_width)
                  {
                    print "\n" + (" " * col);
                    c = col;
                  }

                print " ";
                if (info.index >= 0)
                  {
                    hserver_print_hl ('c',
                                      format ("%i32d", idx),
                                      format ("dump :val %i32d", idx),
                                      lambda void:
                                        {
                                          ios_dump_bytes :ios ios
                                                         :from from
                                                         :size size
                                                         :val val
                                                         :group_by group_by
                                                         :cluster_by cluster_by
                                                         :ruler_p ruler_p
                                                         :ascii_p ascii_p
                                                         :unknown_byte unknown_byte
                                                         :nonprintable_char nonprintable_char
                                                         :max_line_width max_line_width;
                                        }
                                     );
                    print ("=");
                    c += 4;
                  }
                term_begin_class (style);
                print (info.name);
                c += info.name'length;
                term_end_class (style);
                break;
              }
          }
        o += 4#b;
      }
  }

  fun print_data = (offset<uint<64>,b> offset,
                    offset<uint<64>,b> top,
                    offset<uint<64>,b> step,
                    offset<uint<64>,b> group_by,
                    int<32> cluster_by) void:
  {
    var col = 0;
    for (; offset < top; offset += step)
      {
        if (offset > 0xffff_ffff#B)
          {
            printf ("%<dump-address:%u64x:%>", offset / #B);
            col += 16 + 1;
          }
        else
          {
            printf ("%<dump-address:%u32x:%>", offset / #B);
            col += 8 + 1;
          }

	var o = 0#B;
	try
	{
	  while (o < step && offset + o < top)
	    {
              try
              {
                var b = uint<8> @ ios : (offset + o);
                if (o % group_by == 0#B)
                  {
                    print " ";
                    col += 1;
                  }

                if (print_val_p)
                  {
                    /* Go down nibble level.  */
                    var style = get_val_style (offset + o);

                    if (style != "")
                      term_begin_class (style);
                    printf ("%u4x", (b .>> 4) & 0xf);
                    if (style != "")
                      term_end_class (style);

                    style = get_val_style (offset + o + 4#b);

                    if (style != "")
                      term_begin_class (style);
                    printf ("%u4x", b & 0xf);
                    if (style != "")
                      term_end_class (style);
                  }
                else
                  printf ("%u8x", b);

                col += 2;
              }
              catch if E_io
              {
                if (o % group_by == 0#B)
                  {
                    print " ";
                    col += 1;
                  }
                printf ("%<dump-unknown:%s%>", unknown_byte);
                col += 2;
              }

              o++;
	      if (o < 16#B && (o % (cluster_by * group_by)) == 0#B)
                {
		  printf (" ");
                  col += 1;
                }
	    }
	}
	catch if E_eof {}
	if (ascii_p)
	  {
            for (var t = o; t < step; ++t)
              {
                if (t % group_by == 0#B)
                  {
                    print " ";
                    col += 1;
                  }
                print ("  ");
                col += 2;
              }
	    col += print_ascii (offset, top, step, group_by, cluster_by);
            if (print_val_p)
              {
                for (var t = o; t < step; ++t)
                  {
                    print " ";
                    col += 1;
                  }
              }
	  }
        if (print_val_p)
          {
            if (!ascii_p)
              {
                for (var t = o; t < step; ++t)
                  {
		    if (o % group_by == 0#B)
                      {
		        print " ";
                        col += 1;
                      }
                    print ("  ");
                    col += 2;
                  }
	      }
            print_values (offset, step, top, col);
          }
	print "\n";
        col = 0;
      }
  }

  /* First of all, we require a readable IO space.  */
  if (! (ioflags (ios) & IOS_F_READ))
    raise E_perm;

  /* If `val' is specified, it can be:
     negative int<32> -> for "no value".
     positive int<32> -> for a mapped value stored in ios_dumpval.
     other   -> mapped value from which to derive `from'
                and `size'.
   */
  if (val isa int<32>)
    {
      if (val as int<32> > 0 && val as int<32> > ios_dumpval'length)
        raise Exception { code = EC_inval,
                          name = "invalid argument",
                          msg = "invalid VAL index" };

      if (val as int<32> >= 0)
        {
          val = ios_dumpval[val as int<32>];

          if (!val'mapped)
            raise Exception { code = EC_inval,
                              name = "invalid argument",
                              msg = "indexed VAL is not mapped" };
        }
    }

  if (val'mapped)
    {
      ios_dumpval_last[0] = val;
      from = val'offset - (val'offset % 16#B);
      size = val'size + (val'offset % 16#B);
      ios = val'ios;
      print_val_p = 1;

      /* Reset ios_dumpval and ios_dumpval_info.  */
      ios_dumpval = any[]();
      ios_dumpval_info = IOS_Dumpval_Info[]();

      /* Populate the array with the fields of VAL.  */
      for (var idx = 0UL, style = 0; idx < val'length; ++idx)
        {
          /* If the field is optional, check it is actually present.  If it
             is not, do not add it to the array.  */
          if (val'elem (idx) ?! E_inval)
            continue;

          /* Add the element and its info to the arrays.  */
          var elem = val'elem (idx);
          apush (ios_dumpval, val'elem (idx));
          apush (ios_dumpval_info,
                 IOS_Dumpval_Info {
                   name = val'ename (idx) == "" ? "<anon>" : val'ename (idx),
                   index = elem'mapped ? idx as int<32> : -1,
                   offset = val'eoffset (idx),
                   size = val'esize (idx),
                   style = style++ });
        }
    }

  /* The `dump' command is byte-oriented.
     The base offset is truncated to bytes.
     The offset is rounded up to the next byte.  */
  var offset = from as offset<uint<64>,B>;
  var top = from + size + (size % 1#B);

  if (ruler_p)
    print_ruler (offset);

  try print_data :offset offset :top top :step 16#B
                 :group_by group_by :cluster_by cluster_by;
  catch if E_eof { print "\n"; }
}

/* Copy a range of bytes between IO spaces.

   FROM_IOS is the origin IO space, i.e. the IO space from which bytes
   are to be copied.

   TO_IOS is the destination IO space, i.e. the IO space to which
   bytes are to be copied.

   FROM is a byte offset in FROM_IOS with the beginning of the range
   to copy.

   TO is a byte offset in TO_IOS where the bytes will be copied.

   SIZE is a byte offset specifying the amount of data to be copied.  */

fun ios_copy_bytes = (int<32> from_ios, int<32> to_ios,
                      offset<uint<64>,B> from,
                      offset<uint<64>,B> to,
                      offset<uint<64>,B> size) void:
{
 if (size == 0#B
     || (to == from && to_ios == from_ios))
   return;

 /* Determine the best step size to operate with.  */
 var step = 1#B;

 if (size % 8#B == 0#B)
   step = 8#B;
 else if (size % 4#B == 0#B)
   step = 4#B;
 else if (size % 2#B == 0#B)
   step = 2#B;

 /* Change the endianness to big endian, so we get the right ordering
    of the bytes when operating on steps > 1#B.  */
 var endian = get_endian;
 set_endian (ENDIAN_BIG);

 /* Copy the stuff.  */
 for (var end = from + size;
      from < end;
      from += step, to += step)
   {
     if (step == 8#B)
       uint<64> @ to_ios : to = uint<64> @ from_ios : from;
     else if (step == 4#B)
       uint<32> @ to_ios : to = uint<32> @ from_ios : from;
     else if (step == 2#B)
       uint<16> @ to_ios : to = uint<16> @ from_ios : from;
     else
       uint<8> @ to_ios : to = uint<8> @ from_ios : from;
   }

 /* Cleanup.  */
 set_endian (endian);
}

/* Save a range of bytes from an IO space to a file.

   IOS is the IO space from which save bytes.

   FILE is the name of the file where to save the bytes.

   TO and SIZE are byte offsets in the IO space determining the range
   of bytes to save.

   By default the output file is truncated if it already exists.  If
   APPEND_P is true, then the data is instead appended at the end of
   the file.  */

fun ios_save_bytes = (int<32> ios, string file,
                      offset<uint<64>,B> from,
                      offset<uint<64>,B> size,
                      int<32> append_p) void:
{
  if (size == 0#B)
    return;

 /* Determine the proper mode for the output IOS and open it.  */
 var flags = IOS_F_WRITE;

 if (append_p)
   flags = flags | IOS_F_READ;
 else
   flags = flags | IOS_F_CREATE;

 var file_ios = open (file, flags);

 /* Determine the output offset.  */
 var output_offset = 0#B;
 if (append_p)
   output_offset = iosize (file_ios);

  ios_copy_bytes :from_ios ios :to_ios file_ios :from from
                 :to output_offset :size size;

  /* Cleanup.  */
  close (file_ios);
}
