#
#  Copyright (C) 2016 STFC Rutherford Appleton Laboratory, UK.
#
#  Author: David Waterman
#  Acknowledgements: based on ideas and code by Nat Echols and Martin Noble.
#

from lxml import etree
import re

def debug_console():
  '''Start python console at the current code point.'''
  import sys

  # use exception trick to pick up the current frame
  try:
    raise None
  except:
    frame = sys.exc_info()[2].tb_frame.f_back

  # evaluate commands in current namespace
  namespace = frame.f_globals.copy()
  namespace.update(frame.f_locals)

  try:
    # Start IPython console if IPython is available.
    from IPython import embed
    embed(user_ns=namespace)
  except ImportError:
    # Otherwise use basic python console
    import code
    code.interact(banner='='*80, local=namespace)


class Phil2Etree(object):
  """
  Convert a phil_scope to an etree using container and content elements, as
  used by ccp4i2. Retain the hierarchical scope structure using containers.
  Attributes of phil scopes and definitions will be mapped to qualifiers as
  follows:

    short_caption --> guiLabel
    help          --> toolTip
    expert_level  --> guiDefinition / expertLevel
    style         --> guiDefinition / style
    caption       --> guiDefinition / caption
    multiple      --> guiDefinition / multiple

  At present, these attributes of phil scopes and definitions are not preserved:

    optional
    call
    sequential_format
    disable_add
    disable_delete
  """

  # define mappings for .type to <className>
  phil_type_as_class_name = {"str" : "CString",
                             "int" : "CInt",
                             "bool" : "CBoolean",
                             "ternary" : "CString",
                             "float" : "CFloat",
                             "choice" : "CString",
                             "path" : "CDataFile"}

  def __init__(self, phil_scope):
    self.phil_scope = phil_scope

  def __call__(self, root_id):

    phil_params = etree.Element('container', id=root_id)
    self.convertScope(self.phil_scope, phil_params)
    return phil_params

  @staticmethod
  def sanitize_text(s):
    return s.replace('<', '&lt;').replace('>', '&gt;')

  def parse_choice_options(self, phil_def):
    s = ",".join([re.sub("\*", "", word.value) for word in phil_def.words])
    return self.sanitize_text(s)

  def parse_captions(self, phil_def):
    s = ",".join([re.sub("_"," ",item) for item in phil_def.caption.split()])
    return self.sanitize_text(s)

  def attributesToQualifiers(self, qualifiers, phil_obj, force_label=True):
    # guiLabel
    if phil_obj.short_caption is not None:
      gui_label = etree.SubElement(qualifiers, "guiLabel")
      gui_label.text = self.sanitize_text(phil_obj.short_caption)
    elif force_label:
      gui_label = etree.SubElement(qualifiers, "guiLabel")
      gui_label.text = self.sanitize_text(phil_obj.name)
    # toolTip
    if phil_obj.help is not None:
      toolTip = etree.SubElement(qualifiers, "toolTip")
      toolTip.text = self.sanitize_text(phil_obj.help)
    # guiDefinition
    guiDefinition = etree.SubElement(qualifiers, "guiDefinition")
    if phil_obj.expert_level is not None:
      expert_level = etree.SubElement(guiDefinition, "expertLevel")
      expert_level.text = str(phil_obj.expert_level)
    if phil_obj.style is not None:
      style = etree.SubElement(guiDefinition, "style")
      style.text = self.sanitize_text(str(phil_obj.style))
    if phil_obj.caption is not None:
      caption = etree.SubElement(guiDefinition, "caption")
      caption.text = self.sanitize_text(str(phil_obj.caption))
    if phil_obj.multiple is not None:
      multiple = etree.SubElement(guiDefinition, "multiple")
      multiple.text = self.sanitize_text(str(phil_obj.multiple))
    return

  def definitionToElement(self, keyword, phil_params):
    value = keyword.extract()
    elem = etree.SubElement(phil_params, "content")
    elem.set("id", keyword.full_path().replace('.','__'))
    elem_class = etree.SubElement(elem, "className")

    # Map phil type to class and qualifiers
    phil_type = keyword.type.phil_type
    if phil_type == "bool" and str(value) not in ["True", "False"]:
      phil_type = "ternary"
    elem_class.text = self.phil_type_as_class_name.get(phil_type, "CString")
    qualifiers = etree.SubElement(elem, "qualifiers")
    self.attributesToQualifiers(qualifiers, keyword)

    # Set defaults for strings and bools
    if (phil_type in ["bool", "str"]) and (value is not None):
      default = etree.SubElement(qualifiers, "default")
      default.text = self.sanitize_text(str(value))

    # Set default for ternary logic, treated like a choice
    elif (phil_type == "ternary"):
      enum = etree.SubElement(qualifiers, "enumerators")
      enum.text = "True,False," + self.sanitize_text(str(value))
      default = etree.SubElement(qualifiers, "default")
      default.text = self.sanitize_text(str(value))
      only = etree.SubElement(qualifiers, "onlyEnumerators")
      only.text = "True"

    # Set attributes for choices
    elif (phil_type == "choice"):
      if (keyword.type.multi):
        # enumerators do not map to PHIL's multi choice well. In that case
        # just use a string.
        default = etree.SubElement(qualifiers, "default")
        default.text = keyword.as_str().split('=')[1].strip()
      else:
        enum = etree.SubElement(qualifiers, "enumerators")
        enum.text = self.parse_choice_options(keyword)
        default = etree.SubElement(qualifiers, "default")
        if isinstance(value, list):
          value = " ".join(["*" + self.sanitize_text(str(v)) for v in value])
        default.text = self.sanitize_text(str(value))
        only = etree.SubElement(qualifiers, "onlyEnumerators")
        only.text = "True"

    # Set defaults and limits for numerics
    elif (phil_type in ["int", "float"]) :
      default = etree.SubElement(qualifiers, "default")
      default.text = self.sanitize_text(str(value))
      if (keyword.type.value_min is not None) :
        min = etree.SubElement(qualifiers, "min")
        min.text = str(keyword.type.value_min)
      if (keyword.type.value_max is not None) :
        max = etree.SubElement(qualifiers, "max")
        max.text = str(keyword.type.value_max)
    return elem

  def convertScope(self, scope, container):
    for obj in scope.objects:
      if obj.is_definition:
        keywordElement = self.definitionToElement(obj, container)
        container.append(keywordElement)
      elif obj.is_scope:
        print 'Object is scope', obj.full_path()
        sub_container = etree.Element('container', id=obj.full_path().replace('.','__'))
        qualifiers = etree.SubElement(sub_container, "qualifiers")
        self.attributesToQualifiers(qualifiers, obj, force_label=False)
        container.append(sub_container)
        self.convertScope(obj, sub_container)
    return
