"""Widget for setting a dimension value (e.g. width or height)."""

import gi
from gi.repository import GLib
gi.require_version('Gtk', '3.0')
from gi.repository import GObject
from gi.repository import Gtk

from . import placeholders_combo_box as placeholders_combo_box_


__all__ = [
  'DimensionBox',
]


class DimensionBox(Gtk.Box):

  __gsignals__ = {'value-changed': (GObject.SignalFlags.RUN_FIRST, None, ())}

  _POPOVER_PERCENT_OBJECT_BORDER_WIDTH = 6

  def __init__(
        self,
        default_pixel_value,
        default_percent_value,
        default_percent_property,
        default_other_value,
        min_value,
        max_value,
        units,
        default_unit_str,
        pixel_unit_str,
        percent_unit_str,
        custom_percent_unit_str,
        percent_placeholders,
        percent_placeholder_default_name,
        percent_property_names,
        percent_property_labels,
        percent_placeholder_attribute_map,
        widget_spacing=5,
  ):
    super().__init__()

    self._default_pixel_value = default_pixel_value
    self._default_percent_value = default_percent_value
    self._default_percent_property = default_percent_property
    self._default_other_value = default_other_value
    self._min_value = min_value
    self._max_value = max_value
    self._units = units
    self._default_unit_str = default_unit_str
    self._pixel_unit_str = pixel_unit_str
    self._percent_unit_str = percent_unit_str
    self._custom_percent_unit_str = custom_percent_unit_str
    self._percent_placeholders = percent_placeholders
    self._percent_placeholder_default_name = percent_placeholder_default_name
    self._percent_property_names = percent_property_names
    self._percent_property_labels = percent_property_labels
    self._percent_placeholder_attribute_map = percent_placeholder_attribute_map
    self._widget_spacing = widget_spacing

    self._current_pixel_value = self._default_pixel_value
    self._current_percent_value = self._default_percent_value
    self._current_percent_property = self._default_percent_property
    self._current_other_value = self._default_other_value

    self._previous_other_unit_str = None

    self._percent_placeholders_to_groups = {}
    for key_tuple in percent_placeholder_attribute_map:
      for key in key_tuple:
        self._percent_placeholders_to_groups[key] = key_tuple

    self._init_gui()

  def get_value(self):
    value = {
      'pixel_value': self._current_pixel_value,
      'percent_value': self._current_percent_value,
      'other_value': self._current_other_value,
      'unit': self._get_unit_str(),
    }

    percent_object = self._percent_object_combo_box.get_value()
    if percent_object is not None:
      value['percent_object'] = percent_object

    if self._current_percent_property is not None:
      value['percent_property'] = self._current_percent_property

    return value

  def set_value(self, data):
    if data.get('unit') is not None:
      with GObject.signal_handler_block(
            self._unit_combo_box, self._on_unit_combo_box_changed_handler_id):
        self._set_unit_str(data['unit'])

    if 'pixel_value' in data:
      self._current_pixel_value = data['pixel_value']

    if 'percent_value' in data:
      self._current_percent_value = data['percent_value']

    if 'other_value' in data:
      self._current_other_value = data['other_value']

    self._set_spin_button_value(recalculate_other_value=False)

    self._previous_other_unit_str = self._get_unit_str()

    if data.get('percent_object') is not None:
      with GObject.signal_handler_block(
            self._percent_object_combo_box, self._on_percent_object_combo_box_changed_handler_id):
        self._percent_object_combo_box.set_value(data['percent_object'])

    if data.get('percent_property') is not None:
      self._current_percent_property = data['percent_property']

    self._show_hide_percent_property_combo_boxes()
    self._set_percent_property_values()

    self._update_tooltip()

  def get_digits(self):
    return self._spin_button.get_digits()

  def set_digits(self, digits):
    self._spin_button.set_digits(digits)

  def _init_gui(self):
    self.set_orientation(Gtk.Orientation.HORIZONTAL)
    self.set_spacing(self._widget_spacing)

    self._spin_button = Gtk.SpinButton(
      adjustment=Gtk.Adjustment(
        value=self._default_pixel_value,
        lower=self._min_value if self._min_value is not None else -GLib.MAXDOUBLE,
        upper=self._max_value if self._max_value is not None else GLib.MAXDOUBLE,
        step_increment=1,
        page_increment=10,
      ),
      digits=2,
      numeric=True,
    )

    self._unit_store, self._unit_combo_box = self._create_unit_combo_box()

    self._percent_object_combo_box = placeholders_combo_box_.PlaceholdersComboBox(
      placeholders=self._percent_placeholders,
      default_placeholder_name=self._percent_placeholder_default_name,
    )
    self._percent_object_combo_box.show_all()

    self._percent_object_label = Gtk.Label(
      label=_('Object and property:'),
      xalign=0.0,
    )
    self._percent_object_label.show_all()

    self._create_percent_property_combo_boxes()

    self._percent_object_box = Gtk.Box(
      orientation=Gtk.Orientation.VERTICAL,
      spacing=self._widget_spacing,
      border_width=self._POPOVER_PERCENT_OBJECT_BORDER_WIDTH,
    )
    self._percent_object_box.pack_start(self._percent_object_label, False, False, 0)
    self._percent_object_box.pack_start(self._percent_object_combo_box, False, False, 0)
    for combo_box in self._combo_boxes_per_percent_placeholder_group.values():
      self._percent_object_box.pack_start(combo_box, False, False, 0)
    self._percent_object_box.show_all()

    self._button_edit_unit = Gtk.Button(
      image=Gtk.Image.new_from_icon_name('document-edit', Gtk.IconSize.BUTTON),
      tooltip_text=_('Edit unit'),
    )
    self._button_edit_unit.set_no_show_all(True)

    self._popover_percent_object = Gtk.Popover()
    self._popover_percent_object.add(self._percent_object_box)
    self._popover_percent_object.set_constrain_to(Gtk.PopoverConstraint.NONE)
    self._popover_percent_object.set_relative_to(self._button_edit_unit)

    if len(self._unit_store) > 0:
      self._set_unit_str(self._default_unit_str)

    self._on_spin_button_changed_handler_id = self._spin_button.connect(
      'value-changed', self._on_spin_button_changed)
    self._on_unit_combo_box_changed_handler_id = self._unit_combo_box.connect(
      'changed', self._on_unit_combo_box_changed)
    self._on_percent_object_combo_box_changed_handler_id = self._percent_object_combo_box.connect(
      'changed', self._on_percent_object_combo_box_changed)

    self._property_combo_box_changed_handler_ids = {}
    for key, combo_box in self._combo_boxes_per_percent_placeholder_group.items():
      self._property_combo_box_changed_handler_ids[key] = combo_box.connect(
        'changed', self._on_percent_property_combo_box_changed)

    self._button_edit_unit.connect('clicked', self._on_button_edit_unit_clicked)

    self._show_and_set_percent_property_based_on_percent_object()
    self._show_hide_button_edit_unit()

    self.pack_start(self._spin_button, False, False, 0)
    self.pack_start(self._unit_combo_box, False, False, 0)
    self.pack_start(self._button_edit_unit, False, False, 0)

  def _create_unit_combo_box(self):
    self._unit_store = Gtk.ListStore(GObject.TYPE_STRING)

    for unit_abbreviation in self._units:
      self._unit_store.append((unit_abbreviation,))

    self._unit_combo_box = Gtk.ComboBox(
      model=self._unit_store,
      active=0,
      id_column=0,
    )

    renderer_text = Gtk.CellRendererText()
    self._unit_combo_box.pack_start(renderer_text, True)
    self._unit_combo_box.add_attribute(renderer_text, 'text', 0)
    self._unit_combo_box.show_all()
    self._unit_combo_box.set_no_show_all(True)

    return self._unit_store, self._unit_combo_box

  def _create_percent_property_combo_boxes(self):
    self._models_per_percent_placeholder_group = {}
    self._combo_boxes_per_percent_placeholder_group = {}

    for percent_placeholder_group, properties in self._percent_placeholder_attribute_map.items():
      model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_STRING)

      for name, label in zip(self._percent_property_names, self._percent_property_labels):
        if name in properties:
          model.append((name, label if label is not None else ''))

      combo_box = Gtk.ComboBox(
        model=model,
        active=0,
        id_column=0,
      )

      renderer_text = Gtk.CellRendererText()
      combo_box.pack_start(renderer_text, True)
      combo_box.add_attribute(renderer_text, 'text', 1)
      combo_box.show_all()
      combo_box.set_no_show_all(True)

      self._models_per_percent_placeholder_group[percent_placeholder_group] = model
      self._combo_boxes_per_percent_placeholder_group[percent_placeholder_group] = combo_box

  def _on_spin_button_changed(self, _spin_button):
    active_unit_str = self._get_unit_str()
    value = self._spin_button.get_value()

    if active_unit_str in [self._percent_unit_str, self._custom_percent_unit_str]:
      self._current_percent_value = value
    elif active_unit_str == self._pixel_unit_str:
      self._current_pixel_value = value
    else:
      self._current_other_value = value

    self.emit('value-changed')

  def _on_unit_combo_box_changed(self, _combo_box):
    self._show_hide_button_edit_unit()

    self._set_spin_button_value()

    self._previous_other_unit_str = self._get_unit_str()

    self._update_tooltip()

    self.emit('value-changed')

  def _on_percent_object_combo_box_changed(self, _combo_box):
    self._show_and_set_percent_property_based_on_percent_object()

    self._update_tooltip()

    self.emit('value-changed')

  def _on_percent_property_combo_box_changed(self, _combo_box):
    percent_object = self._percent_object_combo_box.get_value()

    if percent_object is None:
      return

    if isinstance(percent_object, dict):
      placeholder_group = self._percent_placeholders_to_groups[percent_object['name']]
    else:
      placeholder_group = self._percent_placeholders_to_groups[percent_object]

    self._current_percent_property[placeholder_group] = (
      self._combo_boxes_per_percent_placeholder_group[placeholder_group].get_active_id())

    self._update_tooltip()

    self.emit('value-changed')

  def _on_button_edit_unit_clicked(self, _button):
    self._popover_percent_object.popup()

  def _get_unit_str(self):
    return self._unit_combo_box.get_active_id()

  def _set_unit_str(self, unit_str):
    self._unit_combo_box.set_active_id(unit_str)

  def _update_tooltip(self):
    if self._get_unit_str() == self._custom_percent_unit_str:
      percent_object_name = self._percent_object_combo_box.get_text()

      percent_property_str = ''

      for combo_box in self._combo_boxes_per_percent_placeholder_group.values():
        if combo_box.get_visible():
          active_row_index = combo_box.get_active()
          if active_row_index != -1:
            percent_property_str = combo_box.get_model()[active_row_index][1]

          break

      if percent_property_str:
        percent_property_str = f', {percent_property_str}'

      tooltip_text = _('percent from: {}{}').format(percent_object_name, percent_property_str)
    else:
      tooltip_text = self._units[self._get_unit_str()].get_name()

    self._unit_combo_box.set_tooltip_text(tooltip_text)

  def _show_hide_button_edit_unit(self):
    if (self._get_unit_str() == self._custom_percent_unit_str
        and len(self._percent_placeholders) > 0):
      self._button_edit_unit.show()
    else:
      self._button_edit_unit.hide()

  def _set_spin_button_value(self, recalculate_other_value=True):
    with GObject.signal_handler_block(self._spin_button, self._on_spin_button_changed_handler_id):
      active_unit_str = self._get_unit_str()

      if active_unit_str in [self._percent_unit_str, self._custom_percent_unit_str]:
        self._spin_button.set_value(self._current_percent_value)
      elif active_unit_str == self._pixel_unit_str:
        self._spin_button.set_value(self._current_pixel_value)
      else:
        if self._previous_other_unit_str is not None and recalculate_other_value:
          inch_value = self._other_value_to_inch(
            self._current_other_value, self._previous_other_unit_str)
          self._current_other_value = self._inch_to_other_value(inch_value, active_unit_str)

          self._spin_button.set_value(self._current_other_value)
        else:
          self._spin_button.set_value(self._current_other_value)

  def _show_and_set_percent_property_based_on_percent_object(self):
    placeholder_group = self._show_hide_percent_property_combo_boxes()

    if placeholder_group is None or self._current_percent_property is None:
      return

    with GObject.signal_handler_block(
          self._combo_boxes_per_percent_placeholder_group[placeholder_group],
          self._property_combo_box_changed_handler_ids[placeholder_group]):
      self._combo_boxes_per_percent_placeholder_group[placeholder_group].set_active_id(
        self._current_percent_property[placeholder_group])

  def _show_hide_percent_property_combo_boxes(self):
    percent_object = self._percent_object_combo_box.get_value()

    if percent_object is not None:
      if isinstance(percent_object, dict):
        percent_placeholder_group = self._percent_placeholders_to_groups[percent_object['name']]
      else:
        percent_placeholder_group = self._percent_placeholders_to_groups[percent_object]

      for key in self._percent_placeholder_attribute_map:
        if key != percent_placeholder_group:
          self._combo_boxes_per_percent_placeholder_group[key].hide()

      self._combo_boxes_per_percent_placeholder_group[percent_placeholder_group].show()

      return percent_placeholder_group
    else:
      for key in self._percent_placeholder_attribute_map:
        self._combo_boxes_per_percent_placeholder_group[key].hide()

      return None

  def _set_percent_property_values(self):
    if self._current_percent_property is None:
      return

    for percent_placeholder_group in self._percent_placeholder_attribute_map:
      with GObject.signal_handler_block(
            self._combo_boxes_per_percent_placeholder_group[percent_placeholder_group],
            self._property_combo_box_changed_handler_ids[percent_placeholder_group]):
        self._combo_boxes_per_percent_placeholder_group[percent_placeholder_group].set_active_id(
          self._current_percent_property[percent_placeholder_group])

  def _other_value_to_inch(self, other_value, unit_str):
    factor = self._units[unit_str].get_factor()

    if factor != 0.0:
      return other_value / factor
    else:
      return other_value

  def _inch_to_other_value(self, inch_value, unit_str):
    return inch_value * self._units[unit_str].get_factor()


GObject.type_register(DimensionBox)
