# This file is part of the nesC compiler.
#    Copyright (C) 2002 Intel Corporation
# 
# The attached "nesC" software is provided to you under the terms and
# conditions of the GNU General Public License Version 2 as published by the
# Free Software Foundation.
# 
# nesC 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 nesC; see the file COPYING.  If not, write to
# the Free Software Foundation, 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

true;

sub gen() {
    my ($classname, @spec) = @_;

    require migdecode;
    &decode(@spec);

    &usage("no classname name specified") if !defined($csharp_classname);

    $csharp_extends = "tinyos.message.Message" if !defined($csharp_extends);
    # See if name has a namespace specifier
    if ($csharp_classname =~ /(.*)\.([^.]*)$/) {
	$namespace = $1;
	$csharp_classname = $2;
    }

    print "/**\n";
    print " * This class is automatically generated by mig. DO NOT EDIT THIS FILE.\n";
    print " * This class implements a C# interface to the '$csharp_classname'\n";
    print " * message type.\n";
    print " */\n\n";

    print "using System;\n";
    print "namespace $namespace {\n\n" if $namespace;

    print "public class $csharp_classname : $csharp_extends {\n\n";

    print "    /** The default size of this message type in bytes. */\n";
    print "    public const int DEFAULT_MESSAGE_SIZE = $size;\n\n";

    print "    /** The Active Message type associated with this message. */\n";
    print "    public const int AM_TYPE = $amtype;\n\n";

    print "    /** Create a new $csharp_classname of size $size. */\n";
    print "    public $csharp_classname() : base(DEFAULT_MESSAGE_SIZE) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /** Create a new $csharp_classname of the given data_length. */\n";
    print "    public $csharp_classname(int data_length) : base(data_length) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname with the given data_length\n";
    print "     * and base offset.\n";
    print "     */\n";
    print "    public $csharp_classname(int data_length, int base_offset) : base(data_length, base_offset) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname using the given byte array\n";
    print "     * as backing store.\n";
    print "     */\n";
    print "    public $csharp_classname(byte[] data) : base(data) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname using the given byte array\n";
    print "     * as backing store, with the given base offset.\n";
    print "     */\n";
    print "    public $csharp_classname(byte[] data, int base_offset) : base(data, base_offset) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname using the given byte array\n";
    print "     * as backing store, with the given base offset and data length.\n";
    print "     */\n";
    print "    public $csharp_classname(byte[] data, int base_offset, int data_length) : base(data, base_offset, data_length) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname embedded in the given message\n";
    print "     * at the given base offset.\n";
    print "     */\n";
    print "    public $csharp_classname(tinyos.message.Message msg, int base_offset) : base(msg, base_offset, DEFAULT_MESSAGE_SIZE) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "     * Create a new $csharp_classname embedded in the given message\n";
    print "     * at the given base offset and length.\n";
    print "     */\n";
    print "    public $csharp_classname(tinyos.message.Message msg, int base_offset, int data_length) : base(msg, base_offset, data_length) {\n";
    print "        amTypeSet(AM_TYPE);\n";
    print "    }\n\n";

    print "    /**\n";
    print "    /* Return a string representation of this message. Includes the\n";
    print "     * message type name and the non-indexed field values.\n";
    print "     */\n";
    print "    public string toString() {\n";
    print "      string s = \"Message <$csharp_classname> \\n\";\n";
    for (@fields) {
	($field, $type, $bitlength, $offset, $amax, $abitsize, $aoffset) = @{$_};
        print "      try {\n";
	$csfield = $field;
	$csfield =~ s/\./_/g;
	if (!@$amax) {
	  ($csharptype, $csharp_access, $arrayspec) = &csharpbasetype($type, $bitlength, 0);
	  if ($csharptype eq "float") {
	    print "        s += string.Format(\"  [$field={0}]\\n\", get_$csfield());\n";
	  } else {
	    print "        s += string.Format(\"  [$field={0:x}]\\n\", get_$csfield());\n";
	  }
	} elsif (@$amax == 1 && $$amax[0] != 0) {
	  ($cstype, $csharp_access, $arrayspec) = &csharpbasetype($type, $bitlength, @$amax);
          print "        s += \"  [$field=\";\n";
          print "        for (int i = 0; i < $$amax[0]; i++) {\n";
	  print "          s += string.Format(\"{0} \", get_$csfield());\n";
#	  if ($cstype eq "float") {
#	    print "          s += string.Format(\"{0} \", get_$csfield());\n";
#	  } else {
#	    if ($bitlength > 32) {
#	      print "          s += string.Format(\"{0:x} \", get_$csfield() & 0xffffffffffffffff);\n";
#	    }
#	    elsif (bitlength > 16) {
#	      print "          s += string.Format(\"{0:x} \", get_$csfield() & 0xffffffff);\n";
#	    }
#	    elsif (bitlength > 8) {
#	      print "          s += string.Format(\"{0:x} \", get_$csfield() & 0xffff);\n";
#	    }
#	    else {
#	      print "          s += string.Format(\"{0:x} \", get_$csfield() & 0xff);\n";
#	    }
#	  }
          print "        }\n";
          print "        s += \"]\\n\";\n";
	}
        print "      } catch (IndexOutOfRangeException aioobe) { /* Skip field */ }\n";
    }
    print "      return s;\n";
    print "    }\n\n";

    print "    // Message-type-specific access methods appear below.\n\n";
    for (@fields) {
	($field, $type, $bitlength, $offset, $amax, $abitsize, $aoffset) = @{$_};

        # Determine if array
	if (@$amax) {
	  $isarray = 1;
	  $arraydims = @$amax;
	  $arraysize_bits = $$amax[0] * $$abitsize[0];
	  $index = 0;
	  @args = map { $index++; "int index$index" } @{$amax};
	  $argspec = join(", ", @args);
	  $index = 0;
	  @passargs = map { $index++; "index$index" } @{$amax};
	  $passargs = join(", ", @passargs);
	} else {
	  $isarray = 0;
	  $arraydims = 0;
	  $arraysize_bits = 0;
	  $argspec = "";
	  $passargs = "";
	}

        # Determine if signed
	if ($basetype eq "U" || $basetype eq "BU") {
	  $signed = 0; $signedstr = ", unsigned";
	} elsif ($basetype eq "I" || $basetype eq "BI") {
	  $signed = 1; $signedstr = ", signed";
	} elsif ($basetype eq "F" || $basetype eq "D" || $basetype eq "LD") {
	  $signed = 1; $signstr = "";
	}

        # Get field type and accessor
	$csfield = $field;
	$csfield =~ s/\./_/g;
	($cstype, $csharp_access, $arrayspec) = &csharpbasetype($type, $bitlength, $arraydims);

	print "    /////////////////////////////////////////////////////////\n";
	print "    // Accessor methods for field: $field\n";
	if ($isarray) {
	  print "    //   Field type: $cstype$arrayspec$signedstr\n";
	  print "    //   Offset (bits): $offset\n";
	  print "    //   Size of each element (bits): $bitlength\n";
	} else {
	  print "    //   Field type: $cstype$signedstr\n";
	  print "    //   Offset (bits): $offset\n";
	  print "    //   Size (bits): $bitlength\n";
	}
	print "    /////////////////////////////////////////////////////////\n\n";
        ### isSigned
	if ($signed) { $strue = "true"; } else { $strue = "false"; }
	print "    /**\n";
	print "     * Return whether the field '$field' is signed ($strue).\n";
	print "     */\n";
	print "    public static bool isSigned_$csfield() {\n";
	print "        return $strue;\n";
	print "    }\n\n";

        ### isArray
	if ($isarray) { $atrue = "true"; } else { $atrue = "false"; }
	print "    /**\n";
	print "     * Return whether the field '$field' is an array ($atrue).\n";
	print "     */\n";
	print "    public static bool isArray_$csfield() {\n";
	print "        return $atrue;\n";
	print "    }\n\n";

        ### Offset
	print "    /**\n";
	print "     * Return the offset (in bytes) of the field '$field'\n";
	if ((int($offset) % 8) != 0) {
	  print "     * WARNING: This field is not byte-aligned (bit offset $offset).\n";
	}
	print "     */\n";

	print "    public static int offset_$csfield($argspec) {\n";
	if ($isarray) {
	    printoffset($offset, $amax, $abitsize, $aoffset, 0);
	} else {
	    print "        return ($offset / 8);\n";
	}
	print "    }\n\n";

	print "    /**\n";
	print "     * Return the offset (in bits) of the field '$field'\n";
	print "     */\n";
	print "    public static int offsetBits_$csfield($argspec) {\n";
	if ($isarray) {
	  printoffset($offset, $amax, $abitsize, $aoffset, 1);
	} else {
	  print "        return $offset;\n";
	}
	print "    }\n\n";


	if (!$isarray) {
	  ### For non-array fields

          ### Get
  	  print "    /**\n";
  	  print "     * Return the value (as a $cstype) of the field '$field'\n";
  	  print "     */\n";
  	  print "    public $cstype get_$csfield() {\n";
  	  print "        return ($cstype)get$csharp_access(offsetBits_$csfield(), $bitlength);\n";
  	  print "    }\n\n";

          ### Set
  	  print "    /**\n";
  	  print "     * Set the value of the field '$field'\n";
  	  print "     */\n";
  	  print "    public void set_$csfield($cstype value) {\n";
  	  print "        set$csharp_access(offsetBits_$csfield(), $bitlength, value);\n";
  	  print "    }\n\n";

          ### Size
  	  print "    /**\n";
	  print "     * Return the size, in bytes, of the field '$field'\n";
	  if ((int($bitlength) % 8) != 0) {
	    print "     * WARNING: This field is not an even-sized number of bytes ($bitlength bits).\n";
	  } 
	  print "     */\n";
	  print "    public static int size_$csfield() {\n";
	  if ((int($bitlength) % 8) != 0) {
  	    print "        return ($bitlength / 8) + 1;\n";
	  } else {
  	    print "        return ($bitlength / 8);\n";
          }
	  print "    }\n\n";

  	  print "    /**\n";
	  print "     * Return the size, in bits, of the field '$field'\n";
	  print "     */\n";
	  print "    public static int sizeBits_$csfield() {\n";
	  print "        return $bitlength;\n";
	  print "    }\n\n";

	} else {
          ### For array fields

          ### Get
  	  print "    /**\n";
  	  print "     * Return the entire array '$field' as a $cstype$arrayspec\n";
  	  print "     */\n";
  	  print "    public $cstype$arrayspec get_$csfield() {\n";
	  &printarrayget($csfield, $cstype, $arrayspec, $bitlength, $amax, $abitsize);
  	  print "    }\n\n";

          ### Set
  	  print "    /**\n";
  	  print "     * Set the contents of the array '$field' from the given $cstype$arrayspec\n";
  	  print "     */\n";
  	  print "    public void set_$csfield($cstype$arrayspec value) {\n";
	  &printarrayset($csfield, $cstype, $arrayspec, $bitlength, $amax, $abitsize);
  	  print "    }\n\n";

          ### GetElement
  	  print "    /**\n";
  	  print "     * Return an element (as a $cstype) of the array '$field'\n";
  	  print "     */\n";
  	  print "    public $cstype getElement_$csfield($argspec) {\n";
  	  print "        return ($cstype)get$csharp_access(offsetBits_$csfield($passargs), $bitlength);\n";
  	  print "    }\n\n";

          ### SetElement
  	  print "    /**\n";
  	  print "     * Set an element of the array '$field'\n";
  	  print "     */\n";
  	  print "    public void setElement_$csfield($argspec, $cstype value) {\n";
  	  print "        set$csharp_access(offsetBits_$csfield($passargs), $bitlength, value);\n";
  	  print "    }\n\n";

	  if ($arraysize_bits != 0) {
            ### Total size (when array size is known)
  	    print "    /**\n";
	    print "     * Return the total size, in bytes, of the array '$field'\n";
	    if ((int($arraysize_bits) % 8) != 0) {
  	      print "     * WARNING: This array is not an even-sized number of bytes ($arraysize_bits bits).\n";
	    } 
	    print "     */\n";
	    print "    public static int totalSize_$csfield() {\n";
	    print "        return ($arraysize_bits / 8);\n";
	    print "    }\n\n";

  	    print "    /**\n";
	    print "     * Return the total size, in bits, of the array '$field'\n";
	    print "     */\n";
	    print "    public static int totalSizeBits_$csfield() {\n";
	    print "        return $arraysize_bits;\n";
	    print "    }\n\n";
	  }

          ### Element size
	  print "    /**\n";
	  print "     * Return the size, in bytes, of each element of the array '$field'\n";
	  if ((int($bitlength) % 8) != 0) {
	    print "     * WARNING: This field is not an even-sized number of bytes ($bitlength bits).\n";
	  }
	  print "     */\n";
	  print "    public static int elementSize_$csfield() {\n";
	  print "        return ($bitlength / 8);\n";
	  print "    }\n\n";

	  print "    /**\n";
	  print "     * Return the size, in bits, of each element of the array '$field'\n";
	  print "     */\n";
	  print "    public static int elementSizeBits_$csfield() {\n";
	  print "        return $bitlength;\n";
	  print "    }\n\n";

          ### Number of dimensions
	  print "    /**\n";
	  print "     * Return the number of dimensions in the array '$field'\n";
	  print "     */\n";
	  print "    public static int numDimensions_$csfield() {\n";
	  print "        return $arraydims;\n";
	  print "    }\n\n";

          ### Number of elements
	  if ($arraydims == 1 && $$amax[0] != 0) {
            # For 1D arrays where the size of the array is known
  	    print "    /**\n";
	    print "     * Return the number of elements in the array '$field'\n";
	    print "     */\n";
	    print "    public static int numElements_$csfield() {\n";
	    print "        return $$amax[0];\n";
	    print "    }\n\n";
	  } 
	  print "    /**\n";
      	  print "     * Return the number of elements in the array '$field'\n";
	  print "     * for the given dimension.\n";
	  print "     */\n";
	  print "    public static int numElements_$csfield(int dimension) {\n";
	  print "      int[] array_dims = { ";
	  foreach $e (@$amax) { print "$e, "; }
	  print " };\n";
	  print "        if (dimension < 0 || dimension >= $arraydims) throw new IndexOutOfRangeException();\n";
	  print "        if (array_dims[dimension] == 0) throw new ArgumentException(\"Array dimension \"+dimension+\" has unknown size\");\n";
	  print "        return array_dims[dimension];\n";
	  print "    }\n\n";

          ### String conversions (for 1D arrays of 8-bit values)
	  if ($arraydims == 1 && $bitlength == 8) {
	      print "    /**\n";
	      print "     * Fill in the array '$field' with a String\n";
	      print "     */\n";
	      print "    public void setString_$csfield(string s) { \n";
	      if ($amax[0] != 0) {
                print "         int len = Math.Min(s.Length, $$amax[0]-1);\n";
	      } else {
                print "         int len = s.Length;\n";
	      }
	      print "         int i;\n";
	      print "         for (i = 0; i < len; i++) {\n";
	      print "             setElement_$csfield(i, ($cstype)s[i]);\n";
              print "         }\n";
	      print "         setElement_$csfield(i, ($cstype)0); //null terminate\n";
	      print "    }\n\n";

  	      print "    /**\n";
 	      print "     * Read the array '$field' as a String\n";
	      print "     */\n";
	      print "    public string getString_$csfield() { \n";
	      if ($$amax[0] == 0) {
                print "         char[] carr = new char[tinyos.message.Message.MAX_CONVERTED_STRING_LENGTH];\n";
	      } else {
                print "         char[] carr = new char[Math.Min(tinyos.message.Message.MAX_CONVERTED_STRING_LENGTH,$$amax[0])];\n";
              }
	      print "         int i;\n";
	      print "         for (i = 0; i < carr.Length; i++) {\n";
	      print "             if ((char)getElement_$csfield(i) == (char)0) break;\n";
	      print "             carr[i] = (char)getElement_$csfield(i);\n";
              print "         }\n";
              print "         return new string(carr,0,i);\n";
	      print "    }\n\n";
	  }
	}
    }

    print "}\n";
    print "} // End of namespace $namespace\n" if $namespace;
}

sub csharpbasetype()
{
    my ($basetype, $bitlength, $arraydims) = @_;
    my $cstype, $acc;

    # Pick the C# type whose range is closest to the corresponding C type
    if ($basetype eq "U" || $basetype eq "BU") {
      $acc = "UIntElement" if $basetype eq "U";
      $acc = "UIntBEElement" if $basetype eq "BU";
      if ($bitlength <= 8) { $cstype = "byte"; }
      elsif ($bitlength <= 16) { $cstype = "ushort"; }
      elsif ($bitlength <= 32) { $cstype = "uint"; }
      else { $cstype = "ulong"; }
    }
    elsif ($basetype eq "I" || $basetype eq "BI") {
      $acc = "SIntElement" if $basetype eq "I";
      $acc = "SIntBEElement" if $basetype eq "BI";
      if ($bitlength <= 8) { $cstype = "byte"; }
      elsif ($bitlength <= 16) { $cstype = "short"; }
      elsif ($bitlength <= 32) { $cstype = "int"; }
      else { $cstype = "long"; }
    }
    elsif ($basetype eq "F" || $basetype eq "D" || $basetype eq "LD") {
      $acc = "FloatElement";
      $cstype = "float";
    }

    if ($arraydims > 0) {
      # For array types
      $arrayspec = "";
      for ($i = 0; $i < $arraydims; $i++) {
	$arrayspec = "[]" . $arrayspec;
      }
    } 

    return ($cstype, $acc, $arrayspec);
}

sub printoffset()
{
    my ($offset, $max, $bitsize, $aoffset, $inbits) = @_;

    print "        int offset = $offset;\n";
    for ($i = 1; $i <= @$max; $i++) {
	# check index bounds. 0-sized arrays don't get an upper-bound check
	# (they represent variable size arrays. Normally they should only
	# occur as the first-dimension of the last element of the structure)
	if ($$max[$i - 1] != 0) {
	    print "        if (index$i < 0 || index$i >= $$max[$i - 1]) throw new IndexOutOfRangeException();\n";
	}
	else {
	    print "        if (index$i < 0) throw new IndexOutOfRangeException();\n";
	}
	print "        offset += $$aoffset[$i - 1] + index$i * $$bitsize[$i - 1];\n";
    }
    if ($inbits) {
      print "        return offset;\n";
    } else {
      print "        return (offset / 8);\n";
    }
}

sub printarrayget() {
  my ($csfield, $cstype, $arrayspec, $bitlength, $amax, $abitsize) = @_;

  # Check whether array has known size
  for ($i = 0; $i < @$amax; $i++) {
    if ($$amax[$i] == 0) {
      print "        throw new ArgumentException(\"Cannot get field as array - unknown size\");\n";
      return;
    }
  }

  print "        $cstype$arrayspec tmp = new $cstype";
  for ($i = 0; $i < @$amax; $i++) {
    print "[$$amax[$i]]";
  }
  print ";\n";

  $indent = " ";
  for ($i = 0; $i < @$amax; $i++) {
    print "      $indent for (int index$i = 0; index$i < numElements_$csfield($i); index$i++) {\n";
    $indent = $indent . "  ";
  }
  $indent = $indent . "  ";
  print "      $indent tmp";
  for ($i = 0; $i < @$amax; $i++) {
    print "[index$i]";
  }
  print " = getElement_$csfield(";
  for ($i = 0; $i < @$amax; $i++) {
    print "index$i";
    if ($i != @$amax-1) { print ","; }
  }
  print ");\n";

  $indent = substr($indent, 0, length($indent)-2);
  for ($i = 0; $i < @$amax; $i++) {
    $indent = substr($indent, 0, length($indent)-2);
    print "      $indent }\n";
  }
  print "        return tmp;\n";
}

sub printarrayset() {
  my ($csfield, $cstype, $arrayspec, $bitlength, $amax, $abitsize) = @_;

  $indent = " ";
  $val = "";
  for ($i = 0; $i < @$amax; $i++) {
    print "      $indent for (int index$i = 0; index$i < value$val.Length; index$i++) {\n";
    $val = $val . "[index$i]";
    $indent = $indent . "  ";
  }
  $indent = $indent . "  ";
  print "      $indent setElement_$csfield(";
  for ($i = 0; $i < @$amax; $i++) {
    print "index$i";
    if ($i != @$amax-1) { print ","; }
  }
  print ", value";
  for ($i = 0; $i < @$amax; $i++) {
    print "[index$i]";
  }
  print ");\n";

  $indent = substr($indent, 0, length($indent)-2);
  for ($i = 0; $i < @$amax; $i++) {
    $indent = substr($indent, 0, length($indent)-2);
    print "      $indent }\n";
  }
}




