# Copyright (c) 2018-2023  University of Houston
# Copyright (c) 2024  Kent State Univerity CAE-Networking Lab

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import ipywidgets as widgets # pylint: disable=F0401

class Layouts(object):
  TINYBUTTON = widgets.Layout(width="25px", padding="0px")
  ROWNUMCOL = widgets.Layout(width="50px")
  AUTOWIDTH = widgets.Layout(width="auto")
  DDWIDTH = widgets.Layout(width="150px")

class TableOptions(object):
  def __init__ (self, **kwargs):
    self.title = None
    self.allow_add = True
    self.add_on_right = False
    self.allow_reorder = True
    self.allow_delete = True
    self.row_numbers = True
    self.footer_button_label = "Submit"
    self.footer_button_handler = None
    self.num_rows_default = 1
    self.num_rows_min = 1
    self.num_rows_max = 100

    self.__dict__.update(kwargs)


class ColumnDesc(object):
  def __init__ (self, title, layout):
    self.title = title
    self.layout = layout
    self.collayout = widgets.Layout() 

  def buildWidget (self, rownum = None):
    raise NotImplementedError()

  def buildTitleWidget (self):
    return widgets.HTML("<b>%s</b>" % (self.title), layout = Layouts.AUTOWIDTH)


class HeaderColumn(ColumnDesc):
  def __init__ (self, title, layout = Layouts.AUTOWIDTH):
    super().__init__(title, layout)

  def buildTitleWidget (self):
    return widgets.HTML("<i>%s</i>" % (self.title), layout = self.layout)

  def buildWidget (self, _):
    return widgets.Label("")


class TextInputColumn(ColumnDesc):
  def __init__ (self, title, layout = Layouts.AUTOWIDTH, default = None):
    super(TextInputColumn, self).__init__(title, layout)
    self.default = default

  def buildWidget (self, _):
    return widgets.Text(value = self.default, layout = self.layout)


class IntInputColumn(ColumnDesc):
  def __init__ (self, title, layout = Layouts.AUTOWIDTH, min = None, max = None, default = None):
    super(IntInputColumn, self).__init__(title, layout)
    self.min = min
    self.max = max
    self.default = default

  def buildWidget (self, _):
    if self.min or self.max:
      return widgets.BoundedIntText(value = self.default, min = self.min, max = self.max, layout = self.layout)
    return widgets.IntText(value = self.default, layout = self.layout)


class DropdownColumn(ColumnDesc):
  ALL = 1
  MULTIPLE_ONLY = 2

  def __init__ (self, title, layout = Layouts.AUTOWIDTH, values = None, allow_none = 1):
    super(DropdownColumn, self).__init__(title, layout)
    if allow_none == DropdownColumn.MULTIPLE_ONLY and values and len(values) == 1:
      self.values = []
    else:
      if isinstance(values[0], tuple):
        self.values = [("  -", None)]
      else:
        self.values = ["  -"]
    if values:
      self.values.extend(values)

  def buildWidget (self, _):
    return widgets.Dropdown(options = self.values, layout = self.layout)


class FixedTextColumn(ColumnDesc):
  def __init__ (self, title, values, layout = Layouts.AUTOWIDTH):
    super(FixedTextColumn, self).__init__(title, layout)
    self._values = values

  def buildWidget (self, rownum):
    return widgets.Label(value = self._values[rownum], layout = self.layout)


class InputTable(object):
  def __init__ (self, cols, data, table_opts):
    self._topts = table_opts
    if not self._topts:
      self._topts = TableOptions()

    self._corder = [x.title for x in cols]
    self._cdescs = {x.title : x for x in cols}
    self._cols = {x.title : widgets.VBox([], layout = x.collayout) for x in cols}

    self._col_rnums = widgets.VBox([], layout=widgets.Layout(align_content="center"))
    self._col_controls = widgets.VBox([])

    self._hbox = None

    self.header = {}
    self.footer = {}
    self.rows = []

    self._build()

  def getAnswerData (self):
    vals = [row.getAnswerData() for row in self.rows]
    return vals

  def setRowMetadata (self):
    for idx,row in enumerate(self.rows):
      row.rownum = idx

  def _build (self):
    self.header["_rnum"] = widgets.HTML("&nbsp;", layout=Layouts.AUTOWIDTH)
    for colname in self._corder:
      self.header[colname] = self._cdescs[colname].buildTitleWidget()
    self.header["_controls"] = widgets.HTML("&nbsp;", layout=Layouts.AUTOWIDTH)
    self.footer["_controls"] = widgets.VBox([])
    self.footer["_rnum"] = widgets.Label()

    if self._topts.allow_add:
      self.addEmptyRow()
      addbutton = widgets.Button(icon="plus-circle", layout=Layouts.ROWNUMCOL, tooltip = "Add Row")
      addbutton.on_click(self.buttonAddRow)
      if self._topts.add_on_right:
        self.footer["_controls"] = addbutton
      else:
        self.footer["_rnum"] = addbutton

    for colname in self._corder:
      self.footer[colname] = widgets.VBox([])

  def buttonAddRow (self, _):
    self.addEmptyRow()
    self.display()

  def buttonDeleteRow (self, button):
    if len(self.rows) <= 1:
      return
    del self.rows[button._row]

    self.setRowMetadata()
    self.display()

  def buttonMoveRowDown (self, button):
    if len(self.rows) <= button._row+1:
      return
    oldrow = self.rows[button._row+1]
    self.rows[button._row+1] = self.rows[button._row]
    self.rows[button._row] = oldrow

    self.setRowMetadata()
    self.display()

  def buttonMoveRowUp (self, button):
    if button._row == 0:
      return
    oldrow = self.rows[button._row-1]
    self.rows[button._row-1] = self.rows[button._row]
    self.rows[button._row] = oldrow

    self.setRowMetadata()
    self.display()

  def addEmptyRow (self):
    self.rows.append(InputRow(self, len(self.rows)))

  def addRows (self, count):
    for x in range(count):
      self.rows.append(InputRow(self, len(self.rows)))

  def composeColumns (self):
    rnums = []
    rnums.append(self.header["_rnum"])
    for row in self.rows:
      rnums.append(row._wrownum)
    rnums.append(self.footer["_rnum"])
    self._col_rnums.children = rnums

    for colname in self._corder:
      wl = []
      wl.append(self.header[colname])
      for row in self.rows:
        wl.append(row._widgets[colname])
      wl.append(self.footer[colname])
      self._cols[colname].children = wl

    ctrls = []
    ctrls.append(self.header["_controls"])
    for row in self.rows:
      ctrls.append(row._wcontrols)
    ctrls.append(self.footer["_controls"])
    self._col_controls.children = ctrls

  def display (self):
    self.composeColumns()

    if not self._hbox:
      self._col_rnums.layout.display = 'flex'
      self._col_rnums.layout.flex_flow = 'column'
      self._col_rnums.layout.align_items = 'center'

      wcols = [self._col_rnums]
      for colname in self._corder:
        wcols.append(self._cols[colname])
      wcols.append(self._col_controls)
      self._hbox = widgets.HBox(wcols)

    return self._hbox


class InputRow(object):
  def __init__ (self, table, rownum):
    self.table = table
    self._rownum = rownum
    if self.table._topts.row_numbers:
      self._wrownum = widgets.Label(str(rownum+1), layout = Layouts.AUTOWIDTH)
    else:
      self._wrownum = widgets.Label("", layout = Layouts.AUTOWIDTH)

    self._checkstatus = widgets.HTML(value="&nbsp;")

    self._widgets = {}
    self._wcontrols = widgets.HBox([])
    self._buildEmpty()

  @property
  def checkstatus (self):
    return None

  @checkstatus.setter
  def checkstatus (self, val):
    if val is None:
      self._checkstatus.value = "&nbsp;"
    elif val is True:
      self._checkstatus.value = "&#9989;"
    elif val is False:
      self._checkstatus.value = "&#10060;"

  def getAnswerData (self):
    data = {}
    for k,v in self._widgets.items():
      data[k] = v.value
    return data

  def _buildEmpty (self):
    btn_children = []
    if self.table._topts.allow_delete:
      rowdelbutton = widgets.Button(icon="minus-circle", layout=Layouts.TINYBUTTON, tooltip = "Delete Row")
      rowdelbutton._row = self.rownum
      rowdelbutton.on_click(self.table.buttonDeleteRow)
      btn_children.append(rowdelbutton)

    if self.table._topts.allow_reorder:
      rowupbutton = widgets.Button(icon="caret-up", layout=Layouts.TINYBUTTON, tooltip = "Move Row Up")
      rowupbutton._row = self.rownum
      rowupbutton.on_click(self.table.buttonMoveRowUp)
      rowdownbutton = widgets.Button(icon="caret-down", layout=Layouts.TINYBUTTON, tooltip = "Move Row Down")
      rowdownbutton._row = self.rownum
      rowdownbutton.on_click(self.table.buttonMoveRowDown)
      btn_children.extend([rowupbutton, rowdownbutton])

    btn_children.append(self._checkstatus)
    self._wcontrols.children = btn_children

    for name,desc in self.table._cdescs.items():
      self._widgets[name] = desc.buildWidget(self._rownum)

  @property
  def rownum (self):
    return self._rownum

  @rownum.setter
  def rownum (self, val):
    self._rownum = val
    for button in self._wcontrols.children:
      button._row = val
    if self.table._topts.row_numbers:
      self._wrownum.value = str(val+1)
