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

# Modified by Philip Levis <pal@cs.stanford.edu> to include support
#   for network types. [1/4/2006]
# Written by Geoffrey Mainland <mainland@eecs.harvard.edu>
#

true;

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

    require migdecode;
    &decode(@spec);

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

    $python_extends = "tinyos.message.Message.Message" if !defined($python_extends);
    if ($python_extends =~ /(.*)\.([^.]*)$/) {
	$package = $1;
    }

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

    if (defined($package)) {
	print "import $package\n\n";
    }

    print "# The default size of this message type in bytes.\n";
    print "DEFAULT_MESSAGE_SIZE = $size\n\n";

    print "# The Active Message type associated with this message.\n";
    print "AM_TYPE = $amtype\n\n";

    print "class $python_classname($python_extends):\n";

    print "    # Create a new $python_classname of size $size.\n";
    print "    def __init__(self, data=\"\", addr=None, gid=None, base_offset=0, data_length=$size):\n";
    print "        $python_extends.__init__(self, data, addr, gid, base_offset, data_length)\n";
    print "        self.amTypeSet(AM_TYPE)\n";
    print "    \n";

    print "    # Get AM_TYPE\n";
#    print "    \@classmethod\n";
    print "    def get_amType(cls):\n";
    print "        return AM_TYPE\n";
    print "    \n";
    print "    get_amType = classmethod(get_amType)\n";
    print "    \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 "    def __str__(self):\n";
    print "        s = \"Message <$python_classname> \\n\"\n";
    for (@fields) {
	($field, $type, $bitlength, $offset, $amax, $abitsize, $aoffset) = @{$_};
        print "        try:\n";
	$pythonfield = $field;
	$pythonfield =~ s/\./_/g;
	if (!@$amax) {
	  ($pythontype, $python_access, $arrayspec) = &pythonbasetype($type, $bitlength, 0);
	  if ($pythontype eq "float") {
	    print "            s += \"  [$field=%f]\\n\" % (self.get_$pythonfield())\n";
	  } else {
	    print "            s += \"  [$field=0x%x]\\n\" % (self.get_$pythonfield())\n";
	  }
	} elsif (@$amax == 1 && $$amax[0] != 0) {
	  ($pythontype, $python_access, $arrayspec) = &pythonbasetype($type, $bitlength, @$amax);
          print "            s += \"  [$field=\";\n";
          print "            for i in range(0, $$amax[0]):\n";
	  if ($pythontype eq "float") {
	    print "                s += \"%f \" % (self.getElement_$pythonfield(i))\n";
	  } else {
	    if ($bitlength > 32) {
	      print "                s += \"0x%x \" % (self.getElement_$pythonfield(i))\n";
	    }
	    elsif ($bitlength > 16) {
	      print "                s += \"0x%x \" % (self.getElement_$pythonfield(i) & 0xffffffff)\n";
	    }
	    elsif ($bitlength > 8) {
	      print "                s += \"0x%x \" % (self.getElement_$pythonfield(i) & 0xffff)\n";
	    }
	    else {
	      print "                s += \"0x%x \" % (self.getElement_$pythonfield(i) & 0xff)\n";
	    }
	  }
          print "            s += \"]\\n\";\n"
	} else {
	    print "            pass\n";
	}
        print "        except:\n";
        print "            pass\n";
    }
    print "        return s\n";

    print "\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++; "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") {
	  $signed = 0; $signedstr = ", unsigned";
	} elsif ($basetype eq "I") {
	  $signed = 1; $signedstr = ", signed";
	} elsif ($basetype eq "F" || $basetype eq "D" || $basetype eq "LD") {
	  $signed = 1; $signstr = "";
	}

        # Get field type and accessor
	$pythonfield = $field;
	$pythonfield =~ s/\./_/g;
	($pythontype, $python_access, $arrayspec, $big_endian) = &pythonbasetype($type, $bitlength, $arraydims);

	print "    #\n";
	print "    # Accessor methods for field: $field\n";
	if ($isarray) {
	  print "    #   Field type: $pythontype$arrayspec$signedstr\n";
	  print "    #   Offset (bits): $offset\n";
	  print "    #   Size of each element (bits): $bitlength\n";
	} else {
	  print "    #   Field type: $pythontype$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 "    def isSigned_$pythonfield(self):\n";
	print "        return $strue\n";
	print "    \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 "    def isArray_$pythonfield(self):\n";
	print "        return $atrue\n";
	print "    \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";

	if ($isarray) {
	    print "    def offset_$pythonfield(self, $argspec):\n";
	    printoffset($offset, $amax, $abitsize, $aoffset, 0);
	} else {
	    print "    def offset_$pythonfield(self):\n";
	    print "        return ($offset / 8)\n";
	}
	print "    \n";

	print "    #\n";
	print "    # Return the offset (in bits) of the field '$field'\n";
	print "    #\n";
	if ($isarray) {
	  print "    def offsetBits_$pythonfield(self, $argspec):\n";
	  printoffset($offset, $amax, $abitsize, $aoffset, 1);
	} else {
	  print "    def offsetBits_$pythonfield(self):\n";
	  print "        return $offset\n";
	}
	print "    \n";


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

          ### Get
  	  print "    #\n";
  	  print "    # Return the value (as a $pythontype) of the field '$field'\n";
  	  print "    #\n";
  	  print "    def get_$pythonfield(self):\n";
  	  print "        return self.get$python_access(self.offsetBits_$pythonfield(), $bitlength, $big_endian)\n";
  	  print "    \n";

          ### Set
  	  print "    #\n";
  	  print "    # Set the value of the field '$field'\n";
  	  print "    #\n";
  	  print "    def set_$pythonfield(self, value):\n";
  	  print "        self.set$python_access(self.offsetBits_$pythonfield(), $bitlength, value, $big_endian)\n";
  	  print "    \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 "    def size_$pythonfield(self):\n";
	  if ((int($bitlength) % 8) != 0) {
  	    print "        return ($bitlength / 8) + 1\n";
	  } else {
  	    print "        return ($bitlength / 8)\n";
          }
	  print "    \n";

  	  print "    #\n";
	  print "    # Return the size, in bits, of the field '$field'\n";
	  print "    #\n";
	  print "    def sizeBits_$pythonfield(self):\n";
	  print "        return $bitlength\n";
	  print "    \n";

	} else {
          ### For array fields

          ### Get
  	  print "    #\n";
  	  print "    # Return the entire array '$field' as a $pythontype$arrayspec\n";
  	  print "    #\n";
  	  print "    def get_$pythonfield(self):\n";
	  &printarrayget($pythonfield, $pythontype, $arrayspec, $bitlength, $amax, $abitsize);
  	  print "    \n";

          ### Set
  	  print "    #\n";
  	  print "    # Set the contents of the array '$field' from the given $pythontype$arrayspec\n";
  	  print "    #\n";
  	  print "    def set_$pythonfield(self, value):\n";
	  &printarrayset($pythonfield, $pythontype, $arrayspec, $bitlength, $amax, $abitsize);

          ### GetElement
  	  print "    #\n";
  	  print "    # Return an element (as a $pythontype) of the array '$field'\n";
  	  print "    #\n";
  	  print "    def getElement_$pythonfield(self, $argspec):\n";
  	  print "        return self.get$python_access(self.offsetBits_$pythonfield($passargs), $bitlength, $big_endian)\n";
  	  print "    \n";

          ### SetElement
  	  print "    #\n";
  	  print "    # Set an element of the array '$field'\n";
  	  print "    #\n";
  	  print "    def setElement_$pythonfield(self, $argspec, value):\n";
  	  print "        self.set$python_access(self.offsetBits_$pythonfield($passargs), $bitlength, value, $big_endian)\n";
  	  print "    \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 "    def totalSize_$pythonfield(self):\n";
	    print "        return ($arraysize_bits / 8)\n";
	    print "    \n";

  	    print "    #\n";
	    print "    # Return the total size, in bits, of the array '$field'\n";
	    print "    #\n";
	    print "    def totalSizeBits_$pythonfield(self):\n";
	    print "        return $arraysize_bits\n";
	    print "    \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 "    def elementSize_$pythonfield(self):\n";
	  print "        return ($bitlength / 8)\n";
	  print "    \n";

	  print "    #\n";
	  print "    # Return the size, in bits, of each element of the array '$field'\n";
	  print "    #\n";
	  print "    def elementSizeBits_$pythonfield(self):\n";
	  print "        return $bitlength\n";
	  print "    \n";

          ### Number of dimensions
	  print "    #\n";
	  print "    # Return the number of dimensions in the array '$field'\n";
	  print "    #\n";
	  print "    def numDimensions_$pythonfield(self):\n";
	  print "        return $arraydims\n";
	  print "    \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 "    def numElements_$pythonfield():\n";
	    print "        return $$amax[0]\n";
	    print "    \n";
	  } 
	  print "    #\n";
      	  print "    # Return the number of elements in the array '$field'\n";
	  print "    # for the given dimension.\n";
	  print "    #\n";
	  print "    def numElements_$pythonfield(self, dimension):\n";
	  print "        array_dims = [ ";
	  foreach $e (@$amax) { print "$e, "; }
	  print " ]\n";
	  print "        if dimension < 0 or dimension >= $arraydims:\n";
	  print "            raise IndexException\n";
	  print "        if array_dims[dimension] == 0:\n";
	  print "            raise IndexError\n";
	  print "        return array_dims[dimension]\n";
	  print "    \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 "    def setString_$pythonfield(self, s):\n";
	      if ($amax[0] != 0) {
                print "         l = min(len(s), $$amax[0]-1)\n";
	      } else {
                print "         l = len(s)\n";
	      }
	      print "         for i in range(0, l):\n";
	      print "             self.setElement_$pythonfield(i, ord(s[i]));\n";
	      print "         self.setElement_$pythonfield(l, 0) #null terminate\n";
	      print "    \n";

  	      print "    #\n";
 	      print "    # Read the array '$field' as a String\n";
	      print "    #\n";
	      print "    def getString_$pythonfield(self):\n";
	      print "        carr = \"\";\n";
	      print "        for i in range(0, 4000):\n";
	      print "            if self.getElement_$pythonfield(i) == chr(0):\n";
	      print "                break\n";
	      print "            carr += self.getElement_$pythonfield(i)\n";
              print "        return carr\n";
	      print "    \n";
	  }
	}
    }
}

sub pythonbasetype()
{
    my ($basetype, $bitlength, $arraydims) = @_;
    my $jtype, $acc;

    # Pick the python type whose range is closest to the corresponding C type
    $big_endian = 0;
    if (substr($basetype,0,1) eq "B") {
      $big_endian = 1;
      $basetype = substr($basetype, 1, length($basetype));
    }
    
    if ($basetype eq "U") {
      $acc = "UIntElement";
      if ($bitlength < 8) { $jtype = "byte"; }
      elsif ($bitlength < 16) { $jtype = "short"; }
      elsif ($bitlength < 32) { $jtype = "int"; }
      else { $jtype = "long"; }
    }
    elsif ($basetype eq "I") {
      $acc = "SIntElement";
      if ($bitlength <= 8) { $jtype = "byte"; }
      elsif ($bitlength <= 16) { $jtype = "short"; }
      elsif ($bitlength <= 32) { $jtype = "int"; }
      else { $jtype = "long"; }
    }
    elsif ($basetype eq "F" || $basetype eq "D" || $basetype eq "LD") {
      $acc = "FloatElement";
      $jtype = "float";
    }

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

    return ($jtype, $acc, $arrayspec, $big_endian);
}

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

    print "        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 or index$i >= $$max[$i - 1]:\n";
	    print "            raise IndexError\n";
	}
	else {
	    print "        if index$i < 0:\n";
	    print "            raise IndexError\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 ($pythonfield, $pythontype, $arrayspec, $bitlength, $amax, $abitsize) = @_;

  # Check whether array has known size
  for ($i = 0; $i < @$amax; $i++) {
    if ($$amax[$i] == 0) {
      print "        raise IndexError\n";
      return;
    }
  }

  print "        tmp = ";
  $temp = "None";
  for ($i = 0; $i < @$amax; $i++) {
    $temp = "[$temp]*$$amax[$i]";
  }
  print "$temp\n";

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

  print "        return tmp\n";
}

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

  $indent = " ";
  $val = "";
  for ($i = 0; $i < @$amax; $i++) {
    print "      $indent for index$i in range(0, len(value)):\n";
    $val = $val . "[index$i]";
    $indent = $indent . "    ";
  }
  $indent = $indent . "";
  print "      $indent self.setElement_$pythonfield(";
  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\n";
}
