#!/usr/bin/env python

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
# 
#   http:#www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

from xml.dom.minidom import parse, parseString, Node
from cStringIO       import StringIO
from stat            import *
from errno           import *
import os
import os.path
import filecmp

#=====================================================================================
#
#=====================================================================================
class Template:
  def __init__ (self, filename, handler):
    self.filename = filename
    self.handler  = handler

  def expandLine (self, line, stream, object):
    cursor = 0
    while 1:
      sub = line.find ("/*MGEN:", cursor)
      if sub == -1:
        stream.write (line[cursor:len (line)])
        return

      subend = line.find("*/", sub)
      stream.write (line[cursor:sub])
      cursor = subend + 2

      tag    = line[sub:subend]
      dotPos = tag.find (".")
      if dotPos == -1:
        raise ValueError ("Invalid tag: %s" % tag)
      tagObject = tag[7:dotPos]
      tagName   = tag[dotPos + 1:len (tag)]

      self.handler (object, stream, tagObject, tagName)

  def expand (self, object):
    fd     = open (self.filename)
    stream = StringIO ()
    
    for line in fd:
      self.expandLine (line, stream, object)
    fd.close ()

    return stream

#=====================================================================================
#
#=====================================================================================
class Generator:
  def createPath (self, path):
    exists = True
    try:
      mode = os.stat (path)[ST_MODE]
    except OSError, (err,text):
      if err == ENOENT:
        exists = False
      else:
        raise
    if exists and not S_ISDIR (mode):
      raise ValueError ("path is not directory: %s" % path)
    if not exists:
      pair = os.path.split (path)
      self.createPath (pair[0])
      os.mkdir (path)

  def normalize (self, path):
    newpath = os.path.normcase (os.path.normpath (path))
    self.createPath (newpath)
    return newpath + "/"
  
  def __init__ (self, destDir, templateDir):
    self.dest      = self.normalize (destDir)
    self.input     = self.normalize (templateDir)
    self.filelists = {}
    self.filelists["h"]   = []
    self.filelists["cpp"] = []
    self.filelists["mk"]  = []
    self.templateFiles    = []

  def genDisclaimer (self, stream):
    stream.write ("// This source file was created by a code generator.\n")
    stream.write ("// Please do not edit.")

  def fileExt (self, path):
    dot = path.rfind (".")
    if dot == -1:
      return ""
    return path[dot + 1:]

  def writeIfChanged (self, stream, target, force=False):
    ext = self.fileExt (target)
    self.filelists[ext].append (target)
    tempFile = self.dest + "gen.tmp"
    fd = open (tempFile, "w")
    fd.write  (stream.getvalue ())
    fd.close  ()

    try:
      if not force and filecmp.cmp (target, tempFile):
        os.remove (tempFile)
        return
    except:
      pass

    try:
      os.remove (target)
    except:
      pass

    os.rename (tempFile, target)
    print "Generated:", target    

  def targetClassFile (self, _class, templateFile):
    dot = templateFile.find(".")
    if dot == -1:
      raise ValueError ("Invalid template file name %s" % templateFile)
    extension = templateFile[dot:len (templateFile)]
    path = self.dest + _class.getName ().capitalize () + extension
    return path

  def targetMethodFile (self, method, templateFile):
    """ Return the file name for a method file """
    dot = templateFile.rfind(".")
    if dot == -1:
      raise ValueError ("Invalid template file name %s" % templateFile)
    extension = templateFile[dot:]
    path = self.dest + "Args" + method.getFullName () + extension
    return path

  def substHandler (self, object, stream, tagObject, tag):
    if tagObject == "Root":
      obj = "self"
    else:
      obj = "object"  # MUST be the same as the 2nd formal parameter

    call = obj + ".gen" + tag + "(stream)"
    eval (call)

  def makeClassFiles (self, templateFile, schema):
    """ Generate an expanded template per schema class """
    classes  = schema.getClasses ()
    template = Template (self.input + templateFile, self.substHandler)
    self.templateFiles.append (templateFile)
    for _class in classes:
      target = self.targetClassFile (_class, templateFile)
      stream = template.expand (_class)
      self.writeIfChanged (stream, target)

  def makeMethodFiles (self, templateFile, schema):
    """ Generate an expanded template per method-with-arguments """
    classes  = schema.getClasses ()
    template = Template (self.input + templateFile, self.substHandler)
    self.templateFiles.append (templateFile)
    for _class in classes:
      methods = _class.getMethods ()
      for method in methods:
        if method.getArgCount () > 0:
          target = self.targetMethodFile (method, templateFile)
          stream = template.expand (method)
          self.writeIfChanged (stream, target)

  def makeMakeFile (self, target):
    stream = StringIO ()
    stream.write ("# Generated makefile fragment.\n\n")
    stream.write ("mgen_generator=$(mgen_dir)/main.py \\\n")
    stream.write ("    $(mgen_dir)/generate.py \\\n")
    stream.write ("    $(mgen_dir)/schema.py \\\n")
    stream.write ("    $(top_srcdir)/../specs/management-types.xml \\\n")
    stream.write ("    $(top_srcdir)/../specs/management-schema.xml \\\n    ")
    first = 1
    for template in self.templateFiles:
      if first == 1:
        first = 0
      else:
        stream.write (" \\\n    ")
      stream.write ("$(mgen_dir)/templates/" + template)
    
    stream.write ("\n\nmgen_broker_cpp=")
    first = 1
    for file in self.filelists["cpp"]:
      if first == 1:
        first = 0
      else:
        stream.write (" \\\n    ")
      stream.write (file.replace ("../src", "."))
    stream.write ("\n\n")

    stream.write ("# Header file install rules.\n")
    stream.write ("qpid_managementdir = $(includedir)/qpid/management\n")
    stream.write ("dist_qpid_management_HEADERS = ")
    first = 1
    for file in self.filelists["h"]:
      if first == 1:
        first = 0
      else:
        stream.write (" \\\n    ")
      stream.write (file.replace ("../src", "."))
    stream.write ("\n\n")

    stream.write ("if GENERATE\n")
    stream.write ("$(srcdir)/managementgen.mk: $(mgen_generator)\n")
    stream.write ("\t$(mgen_cmd)\n")
    stream.write ("\n$(mgen_generator):\n")
    stream.write ("endif\n")

    self.writeIfChanged (stream, target, force=True)
