#!/usr/bin/python2.5
#
# Copyright 2009 Emilie Gillet.
#
# Author: Emilie Gillet (emilie.o.gillet@gmail.com)
#
# 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/>.
#
# -----------------------------------------------------------------------------
#
# Generates .cc and .h files for string, lookup tables, etc.

"""Compiles python string tables/arrays into .cc and .h files."""

import os
import string
import sys


class ResourceEntry(object):
  
  def __init__(self, index, key, value, dupe_of, table):
    self._index = index
    self._key = key
    self._value = value
    self._dupe_of = self._key if dupe_of is None else dupe_of
    self._table = table

  @property
  def variable_name(self):
    return '%s_%s' % (self._table.prefix.lower(), self._dupe_of)

  @property
  def declaration(self):
    c_type = self._table.c_type
    name = self.variable_name
    return 'const %(c_type)s %(name)s[] PROGMEM' % locals()
    
  def Declare(self, f):
    if self._dupe_of == self._key:
      # Dupes are not declared.
      f.write('extern %s;\n' % self.declaration)

  def DeclareAlias(self, f):
    prefix = self._table.prefix
    key = self._key.upper()
    index = self._index
    if self._table.python_type == str:
      comment = '  // %s' % self._value
      size = None
    else:
      comment = ''
      size = len(self._value)
    f.write('#define %(prefix)s_%(key)s %(index)d%(comment)s\n' % locals())
    if not size is None:
      f.write('#define %(prefix)s_%(key)s_SIZE %(size)d\n' % locals())
  
  def Compile(self, f):
    # Do not create declaration for dupes.
    if self._dupe_of != self._key:
      return
    
    declaration = self.declaration
    if self._table.python_type == str:
      value = self._value
      f.write('static %(declaration)s = "%(value)s";\n' % locals())
    else:
      f.write('%(declaration)s = {\n' % locals())
      n_elements = len(self._value)
      for i in xrange(0, n_elements, 8):
        f.write('  ');
        f.write(', '.join(
            '%6d' % self._value[j] for j in xrange(i, min(n_elements, i + 8))))
        f.write(',\n');
      f.write('};\n')
    

class ResourceTable(object):
  
  def __init__(self, resource_tuple):
    self.name = resource_tuple[1]
    self.prefix = resource_tuple[2]
    self.c_type = resource_tuple[3]
    self.python_type = resource_tuple[4]
    self.ram_based_table = resource_tuple[5]
    self.entries = []
    self._ComputeIdentifierRewriteTable()
    keys = set()
    values = {}
    for index, entry in enumerate(resource_tuple[0]):
      if self.python_type == str:
        # There is no name/value for string entries
        key, value = entry, entry.strip()
      else:
        key, value = entry

      # Add a prefix to avoid key duplicates.
      key = self._MakeIdentifier(key)
      while key in keys:
        key = '_%s' % key
      keys.add(key)
      hashable_value = tuple(value)
      self.entries.append(ResourceEntry(index, key, value,
          values.get(hashable_value, None), self))
      if not hashable_value in values:
        values[hashable_value] = key
  
  def _ComputeIdentifierRewriteTable(self):
    in_chr = ''.join(map(chr, range(256)))
    out_chr = [ord('_')] * 256
    # Tolerated characters.
    for i in string.uppercase + string.lowercase + string.digits:
      out_chr[ord(i)] = ord(i.lower())

    # Rewritten characters.
    in_rewritten = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09~*+=><^"|'
    out_rewritten = '0123456789TPSeglxpv'
    for rewrite in zip(in_rewritten, out_rewritten):
      out_chr[ord(rewrite[0])] = ord(rewrite[1])

    table = string.maketrans(in_chr, ''.join(map(chr, out_chr)))
    bad_chars = '\t\n\r-:()[]"\',;'
    self._MakeIdentifier = lambda s:s.translate(table, bad_chars)
  
  def DeclareEntries(self, f):
    if self.python_type != str:
      for entry in self.entries:
        entry.Declare(f)

  def DeclareAliases(self, f):
    for entry in self.entries:
      entry.DeclareAlias(f)
  
  def Compile(self, f):
    # Write a declaration for each entry.
    for entry in self.entries:
      entry.Compile(f)
    
    # Write the resource pointer table.
    modifier = 'PROGMEM ' if not self.ram_based_table else ''
    c_type = self.c_type
    name = self.name
    f.write(
        '\n\n%(modifier)sconst %(c_type)s* const %(name)s_table[] = {\n' % locals())
    for entry in self.entries:
      f.write('  %s,\n' % entry.variable_name)
    f.write('};\n\n')


class ResourceLibrary(object):
  
  def __init__(self, root):
    self._tables = []
    self._root = root
    # Create resource table objects for all resources.
    for resource_tuple in root.resources:
      # Split a multiline string into a list of strings
      if resource_tuple[-2] == str:
        resource_tuple = list(resource_tuple)
        resource_tuple[0] = [x for x in resource_tuple[0].split('\n') if x]
        resource_tuple = tuple(resource_tuple)
      self._tables.append(ResourceTable(resource_tuple))

  @property
  def max_num_entries(self):
    max_num_entries = 0
    for table in self._tables:
      max_num_entries = max(max_num_entries, len(table.entries))
    return max_num_entries

  def _OpenNamespace(self, f):
    if self._root.namespace:
      f.write('\nnamespace %s {\n\n' % self._root.namespace)

  def _CloseNamespace(self, f):
    if self._root.namespace:
      f.write('\n}  // namespace %s\n' % self._root.namespace)

  def _DeclareTables(self, f):
    for table in self._tables:
      f.write('extern const %s* const %s_table[];\n\n' % (table.c_type, table.name)) 

  def _DeclareEntries(self, f):
    for table in self._tables:
      table.DeclareEntries(f)

  def _DeclareAliases(self, f):
    for table in self._tables:
      table.DeclareAliases(f)
  
  def _CompileTables(self, f):
    for table in self._tables:
      table.Compile(f)
  
  def GenerateHeader(self):
    root = self._root
    f = file(os.path.join(root.target, 'resources.h'), 'wb')
    # Write header and header guard
    header_guard = root.target.replace(os.path.sep, '_').upper()
    header_guard = '%s_RESOURCES_H_' % header_guard
    f.write(root.header + '\n\n')
    f.write('#ifndef %s\n' % header_guard)
    f.write('#define %s\n\n' % header_guard)
    f.write(root.includes + '\n\n')
    if root.create_specialized_manager:
      f.write('#include "avrlib/resources_manager.h"\n')
    self._OpenNamespace(f)
    f.write('typedef %s ResourceId;\n\n' % \
        root.types[self.max_num_entries > 255])
    self._DeclareTables(f)
    self._DeclareEntries(f)
    self._DeclareAliases(f)
    if root.create_specialized_manager:
      f.write('typedef avrlib::ResourcesManager<\n')
      f.write('    ResourceId,\n')
      f.write('    avrlib::ResourcesTables<\n')
      f.write('        %s_table,\n' % root.resources[0][1])
      f.write('        %s_table> > ResourcesManager; \n' % root.resources[1][1])
    self._CloseNamespace(f)
    f.write('\n#endif  // %s\n' % (header_guard))
    f.close()
    
  def GenerateCc(self):
    root = self._root
    file_name = os.path.join(self._root.target, 'resources.cc')
    f = file(file_name, 'wb')
    f.write(self._root.header + '\n\n')
    f.write('#include "%s"\n' % file_name.replace('.cc', '.h'))
    self._OpenNamespace(f)
    self._CompileTables(f)
    self._CloseNamespace(f)
    f.close()


def Compile(path):
  # A hacky way of loading the py file passed as an argument as a module +
  # a descent along the module path.
  base_name = os.path.splitext(path)[0]
  sys.path += [os.path.abspath('.')]
  resource_module = __import__(base_name.replace('/', '.'))
  for part in base_name.split('/')[1:]:
    resource_module = getattr(resource_module, part)

  library = ResourceLibrary(resource_module)
  library.GenerateHeader()
  library.GenerateCc()


def main(argv):
  for i in xrange(1, len(argv)):
    Compile(argv[i])


if __name__ == '__main__':
  main(sys.argv)
