# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program 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 2
#  of the License, or (at your option) any later version.
#
#  This program 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 this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

import bpy

from mathutils import Vector
from idprop.types import IDPropertyArray, IDPropertyGroup

ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector)

# Maximum length of an array property for which a multi-line
# edit field will be displayed in the Custom Properties panel.
MAX_DISPLAY_ROWS = 4


def rna_idprop_ui_get(item, create=True):
    try:
        return item['_RNA_UI']
    except:
        if create:
            item['_RNA_UI'] = {}
            return item['_RNA_UI']
        else:
            return None


def rna_idprop_ui_del(item):
    try:
        del item['_RNA_UI']
    except KeyError:
        pass


def rna_idprop_quote_path(prop):
    return "[\"%s\"]" % bpy.utils.escape_identifier(prop)


def rna_idprop_ui_prop_update(item, prop):
    prop_path = rna_idprop_quote_path(prop)
    prop_rna = item.path_resolve(prop_path, False)
    if isinstance(prop_rna, bpy.types.bpy_prop):
        prop_rna.update()


def rna_idprop_ui_prop_get(item, prop, create=True):

    rna_ui = rna_idprop_ui_get(item, create)

    if rna_ui is None:
        return None

    try:
        return rna_ui[prop]
    except:
        rna_ui[prop] = {}
        return rna_ui[prop]


def rna_idprop_ui_prop_clear(item, prop, remove=True):
    rna_ui = rna_idprop_ui_get(item, False)

    if rna_ui is None:
        return

    try:
        del rna_ui[prop]
    except KeyError:
        pass
    if remove and len(item.keys()) == 1:
        rna_idprop_ui_del(item)


def rna_idprop_context_value(context, context_member, property_type):
    space = context.space_data

    if space is None or isinstance(space, bpy.types.SpaceProperties):
        pin_id = space.pin_id
    else:
        pin_id = None

    if pin_id and isinstance(pin_id, property_type):
        rna_item = pin_id
        context_member = "space_data.pin_id"
    else:
        rna_item = eval("context." + context_member)

    return rna_item, context_member


def rna_idprop_has_properties(rna_item):
    keys = rna_item.keys()
    nbr_props = len(keys)
    return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)


def rna_idprop_value_to_python(value):
    if isinstance(value, IDPropertyArray):
        return value.to_list()
    elif isinstance(value, IDPropertyGroup):
        return value.to_dict()
    else:
        return value


def rna_idprop_value_item_type(value):
    is_array = isinstance(value, ARRAY_TYPES) and len(value) > 0
    item_value = value[0] if is_array else value
    return type(item_value), is_array


def rna_idprop_ui_prop_default_set(item, prop, value):
    defvalue = None
    try:
        prop_type, is_array = rna_idprop_value_item_type(item[prop])

        if prop_type in {int, float, str}:
            if is_array and isinstance(value, ARRAY_TYPES):
                value = [prop_type(item) for item in value]
                if any(value):
                    defvalue = value
            else:
                defvalue = prop_type(value)
    except KeyError:
        pass
    except ValueError:
        pass

    if defvalue:
        rna_ui = rna_idprop_ui_prop_get(item, prop, True)
        rna_ui["default"] = defvalue
    else:
        rna_ui = rna_idprop_ui_prop_get(item, prop)
        if rna_ui:
            rna_ui.pop("default", None)

    return defvalue


def rna_idprop_ui_create(
        item, prop, *, default,
        min=0.0, max=1.0,
        soft_min=None, soft_max=None,
        description=None,
        overridable=False,
        subtype=None,
):
    """Create and initialize a custom property with limits, defaults and other settings."""

    proptype, is_array = rna_idprop_value_item_type(default)

    # Sanitize limits
    if proptype is bool:
        min = soft_min = False
        max = soft_max = True

    if soft_min is None:
        soft_min = min
    if soft_max is None:
        soft_max = max

    # Assign the value
    item[prop] = default

    rna_idprop_ui_prop_update(item, prop)

    # Clear the UI settings
    rna_ui_group = rna_idprop_ui_get(item, True)
    rna_ui_group[prop] = {}
    rna_ui = rna_ui_group[prop]

    # Assign limits and default
    if proptype in {int, float, bool}:
        # The type must be exactly the same
        rna_ui["min"] = proptype(min)
        rna_ui["soft_min"] = proptype(soft_min)
        rna_ui["max"] = proptype(max)
        rna_ui["soft_max"] = proptype(soft_max)

        if default and (not is_array or any(default)):
            rna_ui["default"] = default

        if is_array and subtype and subtype != 'NONE':
            rna_ui["subtype"] = subtype

    # Assign other settings
    if description is not None:
        rna_ui["description"] = description

    prop_path = rna_idprop_quote_path(prop)

    item.property_overridable_library_set(prop_path, overridable)

    return rna_ui


def draw(layout, context, context_member, property_type, use_edit=True):

    def assign_props(prop, val, key):
        prop.data_path = context_member
        prop.property = key

        try:
            prop.value = str(val)
        except:
            pass

    rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)

    # poll should really get this...
    if not rna_item:
        return

    from bpy.utils import escape_identifier

    if rna_item.id_data.library is not None:
        use_edit = False
    is_lib_override = rna_item.id_data.override_library and rna_item.id_data.override_library.reference

    assert(isinstance(rna_item, property_type))

    items = rna_item.items()
    items.sort()

    # TODO: Allow/support adding new custom props to overrides.
    if use_edit and not is_lib_override:
        row = layout.row()
        props = row.operator("wm.properties_add", text="Add")
        props.data_path = context_member
        del row

    show_developer_ui = context.preferences.view.show_developer_ui
    rna_properties = {prop.identifier for prop in rna_item.bl_rna.properties if prop.is_runtime} if items else None

    layout.use_property_split = True
    layout.use_property_decorate = False  # No animation.

    flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)

    for key, val in items:

        if key == '_RNA_UI':
            continue

        is_rna = (key in rna_properties)

        # only show API defined for developers
        if is_rna and not show_developer_ui:
            continue

        to_dict = getattr(val, "to_dict", None)
        to_list = getattr(val, "to_list", None)

        # val_orig = val  # UNUSED
        if to_dict:
            val = to_dict()
            val_draw = str(val)
        elif to_list:
            val = to_list()
            val_draw = str(val)
        else:
            val_draw = val

        row = layout.row(align=True)
        box = row.box()

        if use_edit:
            split = box.split(factor=0.75)
            row = split.row()
        else:
            split = box.split(factor=1.00)
            row = split.row()

        row.alignment = 'RIGHT'

        row.label(text=key, translate=False)

        # explicit exception for arrays.
        show_array_ui = to_list and not is_rna and 0 < len(val) <= MAX_DISPLAY_ROWS

        if show_array_ui and isinstance(val[0], (int, float)):
            row.prop(rna_item, '["%s"]' % escape_identifier(key), text="")
        elif to_dict or to_list:
            row.label(text=val_draw, translate=False)
        else:
            if is_rna:
                row.prop(rna_item, key, text="")
            else:
                row.prop(rna_item, '["%s"]' % escape_identifier(key), text="")

        if use_edit:
            row = split.row(align=True)
            # Do not allow editing of overridden properties (we cannot use a poll function of the operators here
            # since they's have no access to the specific property...).
            row.enabled = not(is_lib_override and key in rna_item.id_data.override_library.reference)
            if is_rna:
                row.label(text="API Defined")
            elif is_lib_override:
                row.label(text="Library Override")
            else:
                props = row.operator("wm.properties_edit", text="Edit")
                assign_props(props, val_draw, key)
                props = row.operator("wm.properties_remove", text="", icon='REMOVE')
                assign_props(props, val_draw, key)

    del flow


class PropertyPanel:
    """
    The subclass should have its own poll function
    and the variable '_context_path' MUST be set.
    """
    bl_label = "Custom Properties"
    bl_options = {'DEFAULT_CLOSED'}
    bl_order = 1000  # Order panel after all others

    @classmethod
    def poll(cls, context):
        rna_item, _context_member = rna_idprop_context_value(context, cls._context_path, cls._property_type)
        return bool(rna_item)

    """
    def draw_header(self, context):
        rna_item, context_member = rna_idprop_context_value(context, self._context_path, self._property_type)
        tot = len(rna_item.keys())
        if tot:
            self.layout().label(text="%d:" % tot)
    """

    def draw(self, context):
        draw(self.layout, context, self._context_path, self._property_type)
