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

/* Copyright (C) 2021, 2022, 2023 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.  */

/* 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.

   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 '.'.

   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,
                      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 = '.') void:
{
  fun print_ruler = (offset<uint<64>,b> offset) void:
  {
    var o = 0#B;

    fun itoa = (uint<8> x) char:
    {
      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) void:
  {
    print("  ");
    var o = 0#B;
    while (o < step && offset + o < top)
      {
        try
        {
          var v = byte @ ios : (offset + o);

          if (v < ' ' || v > '~')
            printf "%<dump-ascii:%c%>", nonprintable_char;
          else
            printf "%<dump-ascii:%c%>", v;
        }
        catch if E_io
        {
            printf "%<dump-unknown:%c%>", nonprintable_char;
        }
        o++;
	if (o < 16#B && (o % (cluster_by * group_by)) == 0#B)
	   printf (" ");
      }
  }

  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:
  {
    for (; offset < top; offset += step)
      {
        if (offset > 0xffff_ffff#B)
          printf ("%<dump-address:%u64x:%>", offset / #B);
        else
          printf ("%<dump-address:%u32x:%>", offset / #B);

	var o = 0#B;
	try
	{
	  while (o < step && offset + o < top)
	    {
              try
              {
                var b = byte @ ios : (offset + o);
                if (o % group_by == 0#B)
                     print " ";
                printf ("%u8x", b);
             }
              catch if E_io
              {
                if (o % group_by == 0#B)
                     print " ";
                printf ("%<dump-unknown:%s%>", unknown_byte);
              }

              o++;
	      if (o < 16#B && (o % (cluster_by * group_by)) == 0#B)
		printf (" ");
	    }
	}
	catch if E_eof {}
	if (ascii_p)
	  {
	    while (o < step)
	      {
		if (o % group_by == 0#B)
		  print " ";
		print ("  ");
                o++;
	      }
	    print_ascii (offset, top, step, group_by, cluster_by);
	  }
	print "\n";
      }
  }

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

  /* 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
       byte @ to_ios : to = byte @ 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);
}
