#    This file is part of Diamond.
#
#    Diamond 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.
#
#    Diamond 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 Diamond.  If not, see <http://www.gnu.org/licenses/>.

from gi.repository import GObject as gobject
from gi.repository import Gtk as gtk
from gi.repository import Gdk as gdk
from gi.repository import Pango as pango
from gi.repository import GtkSource as gtksource

from . import dialogs
from . import datatype
from . import mixedtree
from . import plist

class DataWidget(gtk.VBox):

  __gsignals__ = { "on-store"  : (gobject.SignalFlags.RUN_LAST, gobject.TYPE_NONE, ())}
  fontsize = 12

  def __init__(self):
    gtk.VBox.__init__(self)
    
    frame = self.frame = gtk.Frame()
    
    label = gtk.Label()
    label.set_markup("<b>Data</b>")
    
    frame.set_label_widget(label)
    frame.set_shadow_type(gtk.ShadowType.NONE)

    self.pack_start(frame, True, True, 0)
    self.buttons = None
    return

  def set_buttons(self, buttons):
    self.buttons = buttons
    buttons.connect("revert", self.revert)
    buttons.connect("store", self.store)
    buttons.show_all()

  def update(self, node):

    self.node = node

    if not self.is_node_editable():
      self.set_data_fixed()
    elif node.is_tensor(self.geometry_dim_tree):
      self.set_data_tensor()
    elif isinstance(node.datatype, tuple):
      self.set_data_combo()
    else:
      self.set_data_entry()

    return
 
  def revert(self, button = None):
    """
    "Revert Data" button click signal handler. Reverts data in the data frame.
    """

    self.update(self.node)

    return 
  
  def store(self, button = None):
    """
    "Store Data" button click signal handler. Stores data from the data frame
    in the treestore.
    """

    if not self.is_node_editable():
      return True
    elif self.node.is_tensor(self.geometry_dim_tree):
      return self.data_tensor_store()
    elif isinstance(self.node.datatype, tuple):
      return self.data_combo_store()
    else:
      return self.data_entry_store()

# This would be nice, look at it later
#    if self.scherror.errlist_is_open():
#      if self.scherror.errlist_type == 0:
#         self.scherror.on_validate_schematron()
#      else:
#         self.scherror.on_validate()

  def is_node_editable(self):
    return (self.node is not None
       and self.node.active
       and self.node.datatype is not None
       and self.node.datatype != "fixed"
       and (not self.node.is_tensor(self.geometry_dim_tree) or self.geometry_dim_tree.data is not None))
       # not A or B == A implies B
       # A B T
       # 0 0 1
       # 0 1 1
       # 1 0 0
       # 1 1 1

  def add_scrolled_window(self):
    scrolledWindow = gtk.ScrolledWindow()
    scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC, gtk.PolicyType.AUTOMATIC)
    self.frame.add(scrolledWindow)
    scrolledWindow.show()
    return scrolledWindow

  def add_text_view(self):
    scrolledWindow = self.add_scrolled_window()
    self.set_child_packing(self.frame, True, True, 0, gtk.PackType.START)
    
    buf = gtksource.Buffer()
    lang_manager = gtksource.LanguageManager()
    buf.set_highlight_matching_brackets(True)
    if self.node is not None and self.node.is_code():
      codelanguage = self.node.get_code_language();
      if codelanguage in lang_manager.get_language_ids():
        language = lang_manager.get_language(codelanguage)
      else:
        language = lang_manager.get_language("python")
      buf.set_language(language)
      buf.set_highlight_syntax(True)
    textview = gtksource.View.new_with_buffer(buf)
    textview.set_auto_indent(True)
    textview.set_insert_spaces_instead_of_tabs(True)
    textview.set_tab_width(2)
    if self.node is not None and self.node.is_code():
      textview.set_show_line_numbers(True)
      font_desc = pango.FontDescription("monospace")
      if font_desc:
        textview.modify_font(font_desc)

    textview.set_pixels_above_lines(2)
    textview.set_pixels_below_lines(2)
    textview.set_wrap_mode(gtk.WrapMode.WORD)
    textview.connect("focus-in-event", self.entry_focus_in)

    scrolledWindow.add(textview)
    textview.show()
    return textview

  def set_data_empty(self):
    """
    Empty the data frame.
    """

    if self.frame.get_child() is not None:
      if isinstance(self.data, gtk.TextView) or isinstance(self.data, gtksource.View):
        self.data.handler_block_by_func(self.entry_focus_in)
      elif isinstance(self.data, gtk.ComboBox):
        self.data.handler_block_by_func(self.combo_focus_child)

      self.frame.remove(self.frame.get_child())

    self.interacted = False

    return

  def set_data_fixed(self):
    """
    Create a non-editable text view to show help or fixed data.
    """
    
    self.set_data_empty()
   
    self.data = self.add_text_view()
    self.set_fontsize()

    self.data.get_buffer().create_tag("tag")
    text_tag = self.data.get_buffer().get_tag_table().lookup("tag")

    self.data.set_cursor_visible(False)
    self.data.set_editable(False)
    self.buttons.hide()
    text_tag.set_property("foreground", "grey")

    if self.node is None:
      self.data.get_buffer().set_text("")
    elif not self.node.active:
      self.data.get_buffer().set_text("Inactive node")
    elif self.node.datatype is None:
      self.data.get_buffer().set_text("No data")
    elif self.node.is_tensor(self.geometry_dim_tree):
      self.data.get_buffer().set_text("Dimension not set")
    else: # self.node.datatype == "fixed":
      self.data.get_buffer().set_text(self.node.data)
 
    buffer_bounds = self.data.get_buffer().get_bounds()
    self.data.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])

    return

  def set_data_entry(self):
    """
    Create a text view for data entry in the data frame.
    """

    self.set_data_empty()

    self.data = self.add_text_view()
    self.set_fontsize()

    self.data.get_buffer().create_tag("tag")
    text_tag = self.data.get_buffer().get_tag_table().lookup("tag")
 
    self.data.set_cursor_visible(True)
    self.data.set_editable(True)
    self.buttons.show()

    if self.node.data is None:
      self.data.get_buffer().set_text(datatype.print_type(self.node.datatype))
      text_tag.set_property("foreground", "blue")
    else:
      self.data.get_buffer().set_text(self.node.data)

    buffer_bounds = self.data.get_buffer().get_bounds()
    self.data.get_buffer().apply_tag(text_tag, buffer_bounds[0], buffer_bounds[1])

    return

  def set_data_tensor(self):
    """
    Create a table container packed with appropriate widgets for tensor data entry
    in the node data frame.
    """

    self.set_data_empty()

    scrolledWindow = self.add_scrolled_window()

    dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
    self.data = gtk.Table(dim1, dim2)
    scrolledWindow.add_with_viewport(self.data)
    scrolledWindow.get_child().set_property("shadow-type", gtk.ShadowType.NONE)

    self.set_child_packing(self.frame, True, True, 0, gtk.PackType.START)

    self.show_all()
    self.buttons.show()

    is_symmetric = self.node.is_symmetric_tensor(self.geometry_dim_tree)
    for i in range(dim1):
      for j in range(dim2):
        iindex = dim1 - i - 1
        jindex = dim2 - j - 1

        entry = gtk.Entry()
        self.data.attach(entry, jindex, jindex + 1, iindex, iindex + 1)

        if not is_symmetric or i >= j:
          entry.show()
          entry.connect("focus-in-event", self.tensor_element_focus_in, jindex, iindex)

          if self.node.data is None:
            entry.set_text(datatype.print_type(self.node.datatype.datatype))
            entry.modify_text(gtk.StateFlags.NORMAL, gdk.color_parse("blue"))
          else:
            entry.set_text(self.node.data.split(" ")[jindex + iindex * dim2])

    self.interacted = [False for i in range(dim1 * dim2)]
    self.set_fontsize()

    return

  def set_data_combo(self):
    """
    Create a combo box for node data selection in the node data frame. Add an
    entry if required.
    """

    self.set_data_empty()

    if isinstance(self.node.datatype[0], tuple):
      self.data = gtk.combo_box_entry_new_text()
    else:
      self.data = gtk.ComboBoxText.new()

    self.frame.add(self.data)
    self.data.show()

    self.data.connect("set-focus-child", self.combo_focus_child)

    self.set_child_packing(self.frame, False, False, 0, gtk.PackType.START)

    if isinstance(self.node.datatype[0], tuple):
      self.buttons.show()
    else:
      self.buttons.hide()

    if self.node.data is None:
      if isinstance(self.node.datatype[0], tuple):
        self.data.child.set_text("Select " + datatype.print_type(self.node.datatype[1]) + "...")
      else:
        self.data.append_text("Select...")
        self.data.set_active(0)
      self.data.get_child().modify_text(gtk.StateFlags.NORMAL, gdk.color_parse("blue"))
      self.data.get_child().modify_text(gtk.StateFlags.PRELIGHT, gdk.color_parse("blue"))

    if isinstance(self.node.datatype[0], tuple):
      options = self.node.datatype[0]
    else:
      options = self.node.datatype

    for (i, opt) in enumerate(options):
      self.data.append_text(opt)
      if self.node.data == opt:
        self.data.set_active(i)

    if (isinstance(self.node.datatype[0], tuple)
       and self.node.data is not None
       and self.node.data not in self.node.datatype[0]):
      self.data.child.set_text(self.node.data)

    self.data.connect("changed", self.combo_changed)

    self.set_fontsize()

    return

  def data_entry_store(self):
    """
    Attempt to store data read from a textview packed in the data frame.
    """

    new_data = self.data.get_buffer().get_text(*self.data.get_buffer().get_bounds(), include_hidden_chars=True)

    if new_data == "":
      return True

    if self.node.data is None and not self.interacted:
      return True
    else:
      value_check = self.node.validity_check(self.node.datatype, new_data)
      if value_check is None:
        dialogs.error(None, "Invalid value entered")
        return False
      elif value_check != self.node.data:
        self.node.set_data(value_check)
        if (isinstance(self.node, mixedtree.MixedTree)
           and "shape" in list(self.node.child.attrs.keys())
           and self.node.child.attrs["shape"][0] is int
           and isinstance(self.node.datatype, plist.List)
           and self.node.datatype.cardinality == "+"):
          self.node.child.set_attr("shape", str(len(value_check.split(" "))))

        self.emit("on-store")
        self.interacted = False

    return True

  def data_tensor_store(self):
    """
    Attempt to store data read from tensor data entry widgets packed in the 
    data frame.
    """

    dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
    is_symmetric = self.node.is_symmetric_tensor(self.geometry_dim_tree)

    if True not in self.interacted:
      return True

    entry_values = []
    for i in range(dim1):
      for j in range(dim2):
        if is_symmetric and i > j:
          entry_values.append(self.data.get_children()[i + j * dim1].get_text())
        else:
          entry_values.append(self.data.get_children()[j + i * dim2].get_text())

    changed = False
    for i in range(dim1):
      for j in range(dim2):
        if (self.interacted[j + i * dim2]
           and entry_values[j + i * dim2] != ""
           and (self.node.data is None
                or self.node.data.split(" ")[j + i * dim2] != entry_values[j + i * dim2])):
          changed = True
          break

    if not changed:
      return True
    elif (self.node.data is None and False in self.interacted) or "" in entry_values:
      dialogs.error(None, "Invalid value entered")
      return False

    new_data = ""
    for i in range(dim1):
      for j in range(dim2):
        new_data += " " + entry_values[j + i * dim2]

    value_check = self.node.validity_check(self.node.datatype, new_data)
    if value_check is None:
      return False
    elif not value_check == self.node.data:
      self.node.set_data(value_check)

      dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
      if int(self.node.child.get_attr("rank")) == 1:
        self.node.child.set_attr("shape", str(dim1))
      else:
        self.node.child.set_attr("shape", str(dim1) + " " + str(dim2))

      self.emit("on-store")
      self.interacted = [False for i in range(dim1 * dim2)]

    return True

  def data_combo_store(self):
    """
    Attempt to store data read from a combo box entry packed in the node data.
    """

    if not isinstance(self.node.datatype[0], tuple):
      return True

    new_data = self.data.get_text()

    if self.node.data is None and not self.interacted:
      return True
    elif not new_data in self.node.datatype[0]:
      new_data = self.node.validity_check(self.node.datatype[1], new_data)
      if new_data is None:
        return False

    if not new_data == self.node.data:
      self.node.set_data(new_data)
      self.emit("on-store")
      self.interacted = False

    return True
  
  def entry_focus_in(self, widget, event):
    """
    Called when a text view data entry widget gains focus. Used to delete the
    printable_type placeholder.
    """

    if (self.node is not None
       and self.node.datatype is not None
       and not self.node.is_tensor(self.geometry_dim_tree)
       and self.node.data is None
       and not self.interacted):
      self.data.get_buffer().set_text("")

    self.interacted = True

    return

  def tensor_element_focus_in(self, widget, event, row, col):
    """
    Called when a tensor data entry widget gains focus. Used to delete the
    printable_type placeholder.
    """

    dim1, dim2 = self.node.tensor_shape(self.geometry_dim_tree)
    if not self.interacted[row + col * dim2]:
      self.interacted[row + col * dim2] = True
      if self.node.is_symmetric_tensor(self.geometry_dim_tree):
        self.interacted[col + row * dim1] = True
      if self.node.data is None:
        widget.set_text("")
        widget.modify_text(gtk.StateFlags.NORMAL, gdk.color_parse("black"))

    return

  def combo_focus_child(self, container, widget):
    """
    Called when a data selection widget gains focus. Used to delete the select
    placeholder.
    """
    if not self.interacted:
      self.interacted = True
      if self.node.data is None:
        self.data.handler_block_by_func(self.combo_changed)
        if isinstance(self.node.datatype[0], tuple):
          self.data.child.set_text("")
        else:
          self.data.remove(0)

        self.data.get_child().modify_text(gtk.StateFlags.NORMAL, gdk.color_parse("black"))
        self.data.get_child().modify_text(gtk.StateFlags.PRELIGHT, gdk.color_parse("black"))
        self.data.handler_unblock_by_func(self.combo_changed)

    return

  def combo_changed(self, combo_box):
    """
    Called when a data combo box element is selected. Updates data in the
    treestore.
    """

    if not isinstance(self.node.datatype[0], tuple):
      text = self.data.get_active_text()
      if text is None:
        return

      self.node.set_data(text)
      self.emit("on-store")      
      self.interacted = False
    return

  def increase_font(self):
    self.fontsize = self.fontsize + 2
    self.set_fontsize()

  def decrease_font(self):
    if self.fontsize > 0:
      self.fontsize = self.fontsize - 2
      self.set_fontsize()

  def set_fontsize(self):
    if self.frame.get_child() is not None:
      if isinstance(self.data, gtk.TextView) or isinstance(self.data, gtksource.View):
        self.data.modify_font(pango.FontDescription(str(self.fontsize)))
        self.data.show()
      elif isinstance(self.data, gtk.ComboBox):
        self.data.get_child().modify_font(pango.FontDescription(str(self.fontsize)))
      elif isinstance(self.data, gtk.Table):
        dim1, dim2 = self.data.get_size()
        for i in range(dim1):
          for j in range(dim2):
            self.data.get_children()[i + j * dim1].modify_font(pango.FontDescription(str(self.fontsize)))

gobject.type_register(DataWidget)
