from __future__ import annotations

import ast

import gi
gi.require_version('Babl', '0.1')
from gi.repository import Babl
gi.require_version('Gegl', '0.4')
from gi.repository import Gegl
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
from gi.repository import GLib
from gi.repository import GObject

from src import utils

from .. import meta as meta_
from . import _base


_SETTING_GUI_TYPES = meta_.SETTING_GUI_TYPES

__all__ = [
  'ColorSetting',
]


class ColorSetting(_base.Setting):
  """Class for settings holding `Gegl.Color` instances or a list of values
  representing `Gegl.Color`.

  If you need to instantiate a `ColorSetting` before registering a plug-in
  procedure, pass a list of values. Otherwise, you may experience crashes due to
  a missing call to `Gegl.init()`, or warnings about already registered GEGL
  operations when calling `Gegl.init()` prematurely.

  Allowed GIMP PDB types:
  * `Gegl.Color`

  Default value: `[0.0, 0.0, 0.0, 1.0]` (black color).

  Message IDs for invalid values:
  * ``'invalid_value'``:
    The color assigned is not a `Gegl.Color` instance or a list/tuple.
  """

  _ALLOWED_PDB_TYPES = [Gegl.Color]

  _REGISTRABLE_TYPE_NAME = 'color'

  _ALLOWED_GUI_TYPES = [_SETTING_GUI_TYPES.color_button]

  # Return the list in a function to ensure returning a copy.
  _DEFAULT_DEFAULT_VALUE = lambda self: [0.0, 0.0, 0.0, 1.0]

  def __init__(
        self,
        name: str,
        has_alpha: bool = True,
        **kwargs,
  ):
    self._has_alpha = has_alpha

    super().__init__(name, **kwargs)

  @classmethod
  def get_value_as_color(cls, value):
    """Returns the specified value converted to a `Gegl.Color` instance."""
    if isinstance(value, (list, tuple)):
      color = Gegl.Color()

      if len(value) >= 4:
        color.set_rgba(*value[:4])

      return color
    else:
      return value

  @property
  def has_alpha(self) -> bool:
    """Returns ``True`` if this color setting supports the alpha channel."""
    return self._has_alpha

  @property
  def value_for_pdb(self):
    """Setting value converted to a `Gegl.Color` instance."""
    return self.get_value_as_color(self._value)

  def _raw_to_value(self, raw_value):
    if isinstance(raw_value, list) and len(raw_value) == 1:
      if not (len(raw_value[0]) == 1 and len(raw_value[0][0]) == 2):
        return raw_value

      color_entry_name, color_entry_data = raw_value[0][0][0], raw_value[0][0][1]

      if color_entry_name != 'color':
        return raw_value

      babl_format = Babl.format(color_entry_data[0])

      try:
        color_data_length = int(color_entry_data[1])
      except Exception:
        return raw_value

      color_data = ast.literal_eval(f'b"{color_entry_data[2]}"')

      if color_data_length != Babl.format_get_bytes_per_pixel(babl_format):
        return raw_value

      if len(color_entry_data) < 5 or color_entry_data[3] == 0:
        color = Gegl.Color()
        color.set_bytes(babl_format, GLib.Bytes(color_data))

        rgba = color.get_rgba()

        return [rgba.red, rgba.green, rgba.blue, rgba.alpha]
      else:
        profile = Gimp.ColorProfile.new_from_icc_profile(
          ast.literal_eval(f'b"{color_entry_data[4]}"'))
        space = profile.get_space(Gimp.ColorRenderingIntent.RELATIVE_COLORIMETRIC)
        babl_format_with_space = Babl.format_with_space(
          Babl.format_get_encoding(babl_format), space)

        color = Gegl.Color()
        color.set_bytes(babl_format_with_space, GLib.Bytes(color_data))

        rgba = color.get_rgba()

        return [rgba.red, rgba.green, rgba.blue, rgba.alpha]
    else:
      return raw_value

  def _value_to_raw(self, value):
    if isinstance(value, Gegl.Color):
      color = value.get_rgba()
      return [color.red, color.green, color.blue, color.alpha]
    else:
      return value

  def _value_to_string(self, value):
    if not isinstance(value, Gegl.Color):
      color = None

      if isinstance(value, (list, tuple)):
        color = Gegl.Color()

        if len(value) >= 4:
          color.set_rgba(*value[:4])
    else:
      color = value

    if color is None:
      color = Gegl.Color()

    babl_format = color.get_format()
    babl_format_encoding = Babl.format_get_encoding(babl_format)
    color_bytes = color.get_bytes(babl_format).get_data()
    color_bytes_escaped = utils.bytes_to_octal_escaped_string(color_bytes)

    return f'(color "{babl_format_encoding}" {len(color_bytes)} "{color_bytes_escaped}" 0)'

  def _validate(self, color):
    if not isinstance(color, (Gegl.Color, list, tuple)):
      return 'invalid color', 'invalid_value'

  def _get_pdb_param(self):
    return [
      self._REGISTRABLE_TYPE_NAME,
      self._pdb_name,
      self._display_name,
      self._pdb_description,
      self._has_alpha,
      self._default_value,
      GObject.ParamFlags.READWRITE,
    ]
