"""Built-in "Insert Background" and "Insert Foreground" actions."""

import os

import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp

from src import exceptions
from src import invoker as invoker_
from src import placeholders as placeholders_
from src.procedure_groups import *

from src import utils_pdb
from src.pypdb import pdb


__all__ = [
  'InsertBackgroundFromFileAction',
  'InsertForegroundFromFileAction',
  'InsertBackgroundFromColorTagsAction',
  'InsertForegroundFromColorTagsAction',
  'merge_background',
  'merge_foreground',
  'on_after_add_insert_background_foreground_for_layers',
]


class InsertLayerFromFileAction(invoker_.CallableCommand):

  # noinspection PyAttributeOutsideInit
  def _initialize(self, image_batcher, image_file, insert_mode):
    self._image_copies = []

    image_batcher.invoker.add(_delete_images_on_cleanup, ['cleanup_contents'], [self._image_copies])

    if (image_file is not None
        and image_file.get_path() is not None
        and os.path.exists(image_file.get_path())):
      self._image_to_insert = pdb.gimp_file_load(
        run_mode=Gimp.RunMode.NONINTERACTIVE,
        file=image_file)

      self._image_copies.append(self._image_to_insert)
    else:
      self._image_to_insert = None

  def _process(self, image_batcher, image_file, insert_mode):
    if self._image_to_insert is None:
      raise exceptions.SkipCommand(_('Image file not specified.'))

    image = image_batcher.current_image
    current_parent = image_batcher.current_layer.get_parent()

    position = image.get_item_position(image_batcher.current_layer)
    if insert_mode == 'after':
      position += 1

    _insert_layers(image, self._image_to_insert.get_layers(), current_parent, position)


class InsertBackgroundFromFileAction(InsertLayerFromFileAction):

  def _initialize(self, image_batcher, image_file, *_args, **_kwargs):
    return super()._initialize(image_batcher, image_file, 'after')

  def _process(self, image_batcher, image_file, *_args, **_kwargs):
    return super()._process(image_batcher, image_file, 'after')


class InsertForegroundFromFileAction(InsertLayerFromFileAction):

  def _initialize(self, image_batcher, image_file, *_args, **_kwargs):
    return super()._initialize(image_batcher, image_file, 'before')

  def _process(self, image_batcher, image_file, *_args, **_kwargs):
    return super()._process(image_batcher, image_file, 'before')


def _delete_images_on_cleanup(_batcher, images):
  for image in images:
    utils_pdb.try_delete_image(image)

  images.clear()


class InsertTaggedLayersAction(invoker_.CallableCommand):

  # noinspection PyAttributeOutsideInit
  def _initialize(self, layer_batcher, tag, tagged_items_for_preview, insert_mode):
    if layer_batcher.is_preview:
      tagged_items = tagged_items_for_preview
    else:
      tagged_items = layer_batcher.item_tree.iter(with_folders=False, filtered=False)

    self._processed_tagged_items = [
      item for item in tagged_items
      if tag != Gimp.ColorTag.NONE and item.raw.is_valid() and item.raw.get_color_tag() == tag]

  def _process(self, layer_batcher, tag, tagged_items_for_preview, insert_mode):
    if not self._processed_tagged_items:
      return

    image = layer_batcher.current_image
    current_parent = layer_batcher.current_layer.get_parent()

    position = image.get_item_position(layer_batcher.current_layer)
    if insert_mode == 'after':
      position += 1

    _insert_layers(
       image, [item.raw for item in self._processed_tagged_items], current_parent, position)


class InsertBackgroundFromColorTagsAction(InsertTaggedLayersAction):

  def _initialize(self, layer_batcher, color_tag, tagged_items, *_args, **_kwargs):
    return super()._initialize(layer_batcher, color_tag, tagged_items, 'after')

  def _process(self, layer_batcher, color_tag, tagged_items, *_args, **_kwargs):
    return super()._process(layer_batcher, color_tag, tagged_items, 'after')


class InsertForegroundFromColorTagsAction(InsertTaggedLayersAction):

  def _initialize(self, layer_batcher, color_tag, tagged_items, *_args, **_kwargs):
    return super()._initialize(layer_batcher, color_tag, tagged_items, 'before')

  def _process(self, layer_batcher, color_tag, tagged_items, *_args, **_kwargs):
    return super()._process(layer_batcher, color_tag, tagged_items, 'before')


def _insert_layers(image, layers, parent, position):
  first_tagged_layer_position = position
  
  for i, layer in enumerate(layers):
    layer_copy = utils_pdb.copy_and_paste_layer(
      layer, image, parent, first_tagged_layer_position + i, True, True, True)
    layer_copy.set_visible(True)

  if parent is None:
    children = image.get_layers()
  else:
    children = parent.get_children()

  merged_tagged_layer = None

  if len(layers) == 1:
    merged_tagged_layer = children[first_tagged_layer_position]
  else:
    second_to_last_tagged_layer_position = first_tagged_layer_position + len(layers) - 2
    # It should not matter which items we obtain the color tag from as all
    # items have the same color tag.
    merged_color_tag = children[second_to_last_tagged_layer_position].get_color_tag()

    for i in range(second_to_last_tagged_layer_position, first_tagged_layer_position - 1, -1):
      merged_tagged_layer = image.merge_down(children[i], Gimp.MergeType.EXPAND_AS_NECESSARY)

    # The merged-down layer does not possess the attributes of the original
    # layers, including the color tag, so we set it explicitly. This ensures
    # that tagged group layers are merged properly in "Merge back-/foreground"
    # actions.
    merged_tagged_layer.set_color_tag(merged_color_tag)

  return merged_tagged_layer


def merge_background(batcher, merge_type=Gimp.MergeType.EXPAND_AS_NECESSARY, *_args, **_kwargs):
  _merge_layer(batcher, merge_type, _get_background_layer, 'current_layer')


def merge_foreground(batcher, merge_type=Gimp.MergeType.EXPAND_AS_NECESSARY, *_args, **_kwargs):
  _merge_layer(batcher, merge_type, _get_foreground_layer, 'inserted_layer')


def _get_background_layer(batcher):
  return placeholders_.get_background_layer(None, batcher)


def _get_foreground_layer(batcher):
  return placeholders_.get_foreground_layer(None, batcher)


def _merge_layer(batcher, merge_type, get_inserted_layer_func, layer_to_merge_str):
  inserted_layer = get_inserted_layer_func(batcher)
  
  if inserted_layer is not None:
    name = batcher.current_layer.get_name()
    visible = batcher.current_layer.get_visible()
    orig_color_tag = batcher.current_layer.get_color_tag()
    
    if layer_to_merge_str == 'current_layer':
      layer_to_merge_down = batcher.current_layer
    elif layer_to_merge_str == 'inserted_layer':
      layer_to_merge_down = inserted_layer
    else:
      raise ValueError('invalid value for "layer_to_merge_str"')
    
    batcher.current_layer.set_visible(True)
    
    merged_layer = batcher.current_image.merge_down(layer_to_merge_down, merge_type)

    # Avoid errors if merge failed for some reason.
    if merged_layer is not None:
      merged_layer.set_name(name)

      batcher.current_layer = merged_layer

      batcher.current_layer.set_visible(visible)
      batcher.current_layer.set_color_tag(orig_color_tag)


def on_after_add_insert_background_foreground_for_layers(
      _actions,
      action,
      _orig_action_dict,
      tagged_items_setting,
):
  if action['orig_name'].value in [
       'insert_background_for_layers', 'insert_foreground_for_layers']:
    action['arguments/tagged_items'].gui.set_visible(False)
    _sync_tagged_items_with_action(tagged_items_setting, action)


def _sync_tagged_items_with_action(tagged_items_setting, action):

  def _on_tagged_items_changed(tagged_items_setting_, action_):
    action_['arguments/tagged_items'].set_value(tagged_items_setting_.value)

  _on_tagged_items_changed(tagged_items_setting, action)

  tagged_items_setting.connect_event('value-changed', _on_tagged_items_changed, action)


INSERT_BACKGROUND_FOR_IMAGES_DICT = {
  'name': 'insert_background_for_images',
  'function': InsertBackgroundFromFileAction,
  'display_name': _('Insert Background'),
  'menu_path': _('Layers and Composition'),
  'description': _(
    'Inserts the specified image behind the current layer.'
    '\n\nYou can apply subsequent actions on the background using'
    ' "{}" instead of "{}" (if available).'
  ).format(_('Layer Below (Background)'), _('Current Layer')),
  'display_options_on_create': True,
  'additional_tags': [CONVERT_GROUP, EDIT_AND_SAVE_IMAGES_GROUP, EXPORT_IMAGES_GROUP],
  'arguments': [
    {
      'type': 'file',
      'name': 'image_file',
      'default_value': None,
      'action': Gimp.FileChooserAction.OPEN,
      'display_name': _('Image'),
      'none_ok': True,
    },
    {
      'type': 'string',
      'name': 'merge_action_name',
      'default_value': '',
      'gui_type': None,
    },
  ],
}

INSERT_BACKGROUND_FOR_LAYERS_DICT = {
  'name': 'insert_background_for_layers',
  'function': InsertBackgroundFromColorTagsAction,
  'display_name': _('Insert Background'),
  'menu_path': _('Layers and Composition'),
  'description': _(
    'Inserts layers having the specified color tag behind the current layer.'
    '\n\nYou can apply subsequent actions on the background using'
    ' "{}" instead of "{}" (if available).'
  ).format(_('Layer Below (Background)'), _('Current Layer')),
  'display_options_on_create': True,
  'additional_tags': [EDIT_LAYERS_GROUP, EXPORT_LAYERS_GROUP],
  'arguments': [
    {
      'type': 'enum',
      'name': 'color_tag',
      'enum_type': Gimp.ColorTag,
      'excluded_values': [Gimp.ColorTag.NONE],
      'display_name': _('Color tag'),
      'default_value': Gimp.ColorTag.BLUE,
    },
    {
      'type': 'tagged_items',
      'name': 'tagged_items',
      'default_value': [],
      'gui_type': None,
      'tags': ['ignore_reset'],
    },
    {
      'type': 'string',
      'name': 'merge_action_name',
      'default_value': '',
      'gui_type': None,
    },
    {
      'type': 'string',
      'name': 'condition_name',
      'default_value': '',
      'gui_type': None,
    },
  ],
}

INSERT_FOREGROUND_FOR_IMAGES_DICT = {
  'name': 'insert_foreground_for_images',
  'function': InsertForegroundFromFileAction,
  'display_name': _('Insert Foreground'),
  'menu_path': _('Layers and Composition'),
  'description': _(
    'Inserts the specified image in front of the current layer.'
    '\n\nYou can apply subsequent actions on the foreground using'
    ' "{}" instead of "{}" (if available).'
  ).format(_('Layer Above (Foreground)'), _('Current Layer')),
  'display_options_on_create': True,
  'additional_tags': [CONVERT_GROUP, EDIT_AND_SAVE_IMAGES_GROUP, EXPORT_IMAGES_GROUP],
  'arguments': [
    {
      'type': 'file',
      'name': 'image_file',
      'default_value': None,
      'action': Gimp.FileChooserAction.OPEN,
      'display_name': _('Image'),
      'none_ok': True,
    },
    {
      'type': 'string',
      'name': 'merge_action_name',
      'default_value': '',
      'gui_type': None,
    },
  ],
}

INSERT_FOREGROUND_FOR_LAYERS_DICT = {
  'name': 'insert_foreground_for_layers',
  'function': InsertForegroundFromColorTagsAction,
  'display_name': _('Insert Foreground'),
  'menu_path': _('Layers and Composition'),
  'description': _(
    'Inserts layers having the specified color tag in front of the current layer.'
    '\n\nYou can apply subsequent actions on the foreground using'
    ' "{}" instead of "{}" (if available).'
  ).format(_('Layer Above (Foreground)'), _('Current Layer')),
  'display_options_on_create': True,
  'additional_tags': [EDIT_LAYERS_GROUP, EXPORT_LAYERS_GROUP],
  'arguments': [
    {
      'type': 'enum',
      'name': 'color_tag',
      'enum_type': Gimp.ColorTag,
      'excluded_values': [Gimp.ColorTag.NONE],
      'display_name': _('Color tag'),
      'default_value': Gimp.ColorTag.GREEN,
    },
    {
      'type': 'tagged_items',
      'name': 'tagged_items',
      'default_value': [],
      'gui_type': None,
      'tags': ['ignore_reset'],
    },
    {
      'type': 'string',
      'name': 'merge_action_name',
      'default_value': '',
      'gui_type': None,
    },
    {
      'type': 'string',
      'name': 'condition_name',
      'default_value': '',
      'gui_type': None,
    },
  ],
}

MERGE_BACKGROUND_DICT = {
  'name': 'merge_background',
  'function': merge_background,
  'display_name': _('Merge Background'),
  'menu_path': _('Layers and Composition'),
  # This action is added/removed automatically alongside `insert_background_for_*`.
  'additional_tags': [],
  'arguments': [
    {
      'type': 'enum',
      'name': 'merge_type',
      'enum_type': Gimp.MergeType,
      'default_value': Gimp.MergeType.EXPAND_AS_NECESSARY,
      'excluded_values': [Gimp.MergeType.FLATTEN_IMAGE],
      'display_name': _('Merge type'),
    },
    {
      'type': 'bool',
      'name': 'last_enabled_value',
      'default_value': True,
      'gui_type': None,
    },
  ],
}

MERGE_FOREGROUND_DICT = {
  'name': 'merge_foreground',
  'function': merge_foreground,
  'display_name': _('Merge Foreground'),
  'menu_path': _('Layers and Composition'),
  # This action is added/removed automatically alongside `insert_foreground_for_*`.
  'additional_tags': [],
  'arguments': [
    {
      'type': 'enum',
      'name': 'merge_type',
      'enum_type': Gimp.MergeType,
      'default_value': Gimp.MergeType.EXPAND_AS_NECESSARY,
      'excluded_values': [Gimp.MergeType.FLATTEN_IMAGE],
      'display_name': _('Merge type'),
    },
    {
      'type': 'bool',
      'name': 'last_enabled_value',
      'default_value': True,
      'gui_type': None,
    },
  ],
}
