# ##### 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 #####

import logging
import os
from typing import Any

import bpy
from bpy.props import BoolProperty, FloatVectorProperty, IntProperty, StringProperty

from . import colors, global_vars, paths, search, ui_bgl, utils


draw_time = 0
eval_time = 0

bk_logger = logging.getLogger(__name__)

verification_icons = {
    "ready": "vs_ready.png",
    "deleted": "vs_deleted.png",
    "uploaded": "vs_uploaded.png",
    "uploading": "vs_uploading.png",
    "on_hold": "vs_on_hold.png",
    "validated": None,
    "rejected": "vs_rejected.png",
}


def get_approximate_text_width(st):
    size = 10
    for s in st:
        if s in "i|":
            size += 2
        elif s in " ":
            size += 4
        elif s in "sfrt":
            size += 5
        elif s in "ceghkou":
            size += 6
        elif s in "PadnBCST3E":
            size += 7
        elif s in "GMODVXYZ":
            size += 8
        elif s in "w":
            size += 9
        elif s in "m":
            size += 10
        else:
            size += 7
    return size  # Convert to picas


def draw_text_block(
    x=0, y=0, width=40, font_size=10, line_height=15, text="", color=colors.TEXT
):
    lines = text.split("\n")
    nlines = []
    for l in lines:
        nlines.extend(
            search.split_subs(
                l,
            )
        )

    column_lines = 0
    for l in nlines:
        ytext = y - column_lines * line_height
        column_lines += 1
        ui_bgl.draw_text(l, x, ytext, font_size, color)


def get_large_thumbnail_image(asset_data):
    """Get thumbnail image from asset data"""
    ui_props = bpy.context.window_manager.blenderkitUI
    iname = utils.previmg_name(ui_props.active_index, fullsize=True)
    directory = paths.get_temp_dir(f"{ui_props.asset_type.lower()}_search")
    tpath = os.path.join(directory, asset_data["thumbnail"])
    # if asset_data['assetType'] == 'hdr':
    #     tpath = os.path.join(directory, asset_data['thumbnail'])
    image_ready = global_vars.DATA["images available"].get(tpath)
    if image_ready is False or not asset_data["thumbnail"]:
        tpath = paths.get_addon_thumbnail_path("thumbnail_not_available.jpg")
    if image_ready is None:
        tpath = paths.get_addon_thumbnail_path("thumbnail_notready.jpg")

    img = utils.get_hidden_image(tpath, iname, colorspace="")
    return img


def get_full_photo_thumbnail(asset_data):
    """Get full photo thumbnail from asset data. This is different from the large thumbnail
    as the photo_thumbnails are not available on the asset data root, but inside the files[].
    We need to get the data from files[] where assetType=='photo_thumbnail'."""
    # Find the photo thumbnail file
    photo_file = None
    for file in asset_data.get("files", []):
        if file.get("fileType") == "photo_thumbnail":
            photo_file = file
            break

    if photo_file is None:
        bk_logger.warning("No photo thumbnail file found in asset data")
        return None

    photo_url = photo_file.get("thumbnailMiddleUrl")
    if photo_url is None:
        bk_logger.warning("No thumbnail URL found in photo file")
        return None

    # Get the directory and construct the path
    ui_props = bpy.context.window_manager.blenderkitUI
    directory = paths.get_temp_dir(f"{ui_props.asset_type.lower()}_search")
    photo_name = os.path.basename(photo_url)
    tpath = os.path.join(directory, photo_name)

    # Load the image into Blender
    if os.path.exists(tpath):
        img = utils.get_hidden_image(tpath, photo_name, colorspace="")
        return img

    bk_logger.info(f"Photo thumbnail file not found at path: {tpath}")
    return None


def is_rating_possible() -> tuple[bool, bool, Any, Any]:
    # TODO remove this, but first check and reuse the code for new rating system...
    ao = bpy.context.active_object
    ui = bpy.context.window_manager.blenderkitUI  # type: ignore[attr-defined]
    preferences = bpy.context.preferences.addons[__package__].preferences  # type: ignore
    # first test if user is logged in.
    if preferences.api_key == "":  # type: ignore
        return False, False, None, None
    if global_vars.RATINGS is not None and ui.down_up == "SEARCH":
        if bpy.context.mode in ("SCULPT", "PAINT_TEXTURE"):
            b = utils.get_active_brush()
            ad = b.get("asset_data")
            if ad is not None:
                rated = bpy.context.scene["assets rated"].get(ad["assetBaseId"])  # type: ignore
                return True, rated, b, ad
        if ao is not None:
            ad = None
            # crawl parents to reach active asset. there could have been parenting so we need to find the first onw
            ao_check = ao
            while ad is None or (ad is None and ao_check.parent is not None):
                s = bpy.context.scene
                ad = ao_check.get("asset_data")  # type: ignore[attr-defined]
                if ad is not None and ad.get("assetBaseId") is not None:
                    s["assets rated"] = s.get("assets rated", {})  # type: ignore
                    rated = s["assets rated"].get(ad["assetBaseId"])  # type: ignore
                    # originally hidden for already rated assets
                    return True, rated, ao_check, ad
                elif ao_check.parent is not None:
                    ao_check = ao_check.parent
                else:
                    break
            # check also materials
            m = ao.active_material
            if m is not None:
                ad = m.get("asset_data")  # type: ignore

                if ad is not None and ad.get("assetBaseId"):
                    rated = bpy.context.scene["assets rated"].get(ad["assetBaseId"])  # type: ignore
                    return True, rated, m, ad

        # if t>2 and t<2.5:
        #     ui_props.rating_on = False

    return False, False, None, None


def mouse_in_region(r, mx, my):
    if 0 < my < r.height and 0 < mx < r.width:
        return True
    else:
        return False


class ParticlesDropDialog(bpy.types.Operator):
    """Tooltip"""

    bl_idname = "object.blenderkit_particles_drop"
    bl_label = "BlenderKit particle plants object drop"
    bl_options = {"REGISTER", "INTERNAL"}

    asset_search_index: IntProperty(  # type: ignore[valid-type]
        name="Asset index",
        description="Index of the asset in asset bar",
        default=0,
    )

    model_location: FloatVectorProperty(name="Location", default=(0, 0, 0))  # type: ignore[valid-type]

    model_rotation: FloatVectorProperty(  # type: ignore[valid-type]
        name="Rotation", default=(0, 0, 0), subtype="QUATERNION"
    )

    target_object: StringProperty(  # type: ignore[valid-type]
        name="Target object",
        description="The object to which the particles will get applied",
        default="",
        options={"SKIP_SAVE"},
    )

    @classmethod
    def poll(cls, context):
        return True

    def draw(self, context):
        layout = self.layout
        message = (
            "This asset is a particle setup. BlenderKit can apply particles to the active/drag-drop object."
            "The number of particles is caluclated automatically, but if there are too many particles,"
            " BlenderKit can do the following steps to make sure Blender continues to run:\n"
            "\n1.Switch to bounding box view of the particles."
            "\n2.Turn down number of particles that are shown in the view."
            "\n3.Hide the particle system completely from the 3D view."
            "as a result of this, it's possible you'll see the particle setup only in render view or "
            "rendered images. You should still be careful and test particle systems on smaller objects first."
        )
        utils.label_multiline(layout, text=message, width=600)
        row = layout.row()
        op = row.operator("scene.blenderkit_download", text="Append as plane")
        op.tooltip = "Append particles as stored in the asset file.\n You can link the particles to your target object manually"
        op.asset_index = self.asset_search_index
        op.model_location = self.model_location
        op.model_rotation = self.model_rotation
        op.target_object = ""
        op.replace = False
        op.replace_resolution = False

        op = row.operator("scene.blenderkit_download", text="Append on target")
        op.tooltip = (
            "Append and adjust particles counts automatically to the target object."
        )
        op.asset_index = self.asset_search_index
        op.model_location = self.model_location
        op.model_rotation = self.model_rotation
        op.target_object = self.target_object
        op.replace = False
        op.replace_resolution = False

    def execute(self, context):
        wm = context.window_manager
        return wm.invoke_popup(self, width=600)


# class MaterialDropDialog(bpy.types.Operator):
#     """Tooltip"""
#     bl_idname = "object.blenderkit_material_drop"
#     bl_label = "BlenderKit material drop on linked objects"
#     bl_options = {'REGISTER', 'INTERNAL'}
#
#     asset_search_index: IntProperty(name="Asset index",
#                                     description="Index of the asset in asset bar",
#                                     default=0,
#                                     )
#
#     model_location: FloatVectorProperty(name="Location",
#                                         default=(0, 0, 0))
#
#     model_rotation: FloatVectorProperty(name="Rotation",
#                                         default=(0, 0, 0),
#                                         subtype='QUATERNION')
#
#     target_object: StringProperty(
#         name="Target object",
#         description="The object to which the particles will get applied",
#         default="", options={'SKIP_SAVE'})
#
#     target_material_slot: IntProperty(name="Target material slot",
#                                     description="Index of the material on the object to be changed",
#                                     default=0,
#                                     )
#
#     @classmethod
#     def poll(cls, context):
#         return True
#
#     def draw(self, context):
#         layout = self.layout
#         message = "This asset is linked to the scene from an external file and cannot have material appended." \
#                   " Do you want to bring it into Blender Scene?"
#         utils.label_multiline(layout, text=message, width=400)
#
#     def execute(self, context):
#         for c in bpy.data.collections:
#             for o in c.objects:
#                 if o.name != self.target_object:
#                     continue;
#                 for empty in bpy.context.visible_objects:
#                     if not(empty.instance_type == 'COLLECTION' and empty.instance_collection == c):
#                         continue;
#                     utils.activate(empty)
#                     break;
#         bpy.ops.object.blenderkit_bring_to_scene()
#         bpy.ops.scene.blenderkit_download(True,
#                                           # asset_type=ui_props.asset_type,
#                                           asset_index=self.asset_search_index,
#                                           model_location=self.model_rotation,
#                                           model_rotation=self.model_rotation,
#                                           target_object=self.target_object,
#                                           material_target_slot = self.target_slot)
#         return {'FINISHED'}
#
#     def invoke(self, context, event):
#         wm = context.window_manager
#         return wm.invoke_props_dialog(self, width=400)


class TransferBlenderkitData(bpy.types.Operator):
    """Regenerate cobweb"""

    bl_idname = "object.blenderkit_data_trasnfer"
    bl_label = "Transfer BlenderKit data"
    bl_description = "Transfer blenderKit metadata from one object to another when fixing uploads with wrong parenting"
    bl_options = {"REGISTER", "UNDO"}

    def execute(self, context):
        source_ob = bpy.context.active_object
        for target_ob in bpy.context.selected_objects:
            if target_ob != source_ob:
                # target_ob.property_unset('blenderkit')
                for k in source_ob.blenderkit.keys():
                    if k in ("name",):
                        continue
                    target_ob.blenderkit[k] = source_ob.blenderkit[k]
        # source_ob.property_unset('blenderkit')
        return {"FINISHED"}


class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""

    bl_idname = "wm.modal_timer_operator"
    bl_label = "Modal Timer Operator"

    _timer = None

    def modal(self, context, event):
        if event.type in {"RIGHTMOUSE", "ESC"}:
            self.cancel(context)
            return {"CANCELLED"}

        if event.type == "TIMER":
            # change theme color, silly!
            color = context.preferences.themes[0].view_3d.space.gradients.high_gradient
            color.s = 1.0
            color.h += 0.01

        return {"PASS_THROUGH"}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, window=context.window)
        wm.modal_handler_add(self)
        return {"RUNNING_MODAL"}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


class AssetBarModalStarter(bpy.types.Operator):
    """Needed for starting asset bar with correct context"""

    bl_idname = "view3d.run_assetbar_start_modal"
    bl_label = "BlenderKit assetbar modal starter"
    bl_description = "Assetbar modal starter"
    bl_options = {"INTERNAL"}

    keep_running: BoolProperty(  # type: ignore[valid-type]
        name="Keep Running", description="", default=True, options={"SKIP_SAVE"}
    )
    do_search: BoolProperty(  # type: ignore[valid-type]
        name="Run Search", description="", default=False, options={"SKIP_SAVE"}
    )
    _timer = None

    def modal(self, context, event):
        if event.type == "TIMER":
            # change theme color, silly!
            if bpy.app.version < (4, 0, 0):
                C_dict = bpy.context.copy()  # let's try to get the right context
                bpy.ops.view3d.blenderkit_asset_bar_widget(
                    C_dict,
                    "INVOKE_REGION_WIN",
                    keep_running=self.keep_running,
                    do_search=self.do_search,
                )
            else:
                bpy.ops.view3d.blenderkit_asset_bar_widget(
                    "INVOKE_REGION_WIN",
                    keep_running=self.keep_running,
                    do_search=self.do_search,
                )
            self.cancel(context)
            return {"FINISHED"}

        return {"PASS_THROUGH"}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.02, window=context.window)
        wm.modal_handler_add(self)
        return {"RUNNING_MODAL"}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)


class RunAssetBarWithContext(bpy.types.Operator):
    """This operator can run from a timer and assign a context to modal starter"""

    bl_idname = "view3d.run_assetbar_fix_context"
    bl_label = "BlenderKit assetbar with fixed context"
    bl_description = "Run assetbar with fixed context"
    bl_options = {"INTERNAL"}

    keep_running: BoolProperty(  # type: ignore[valid-type]
        name="Keep Running", description="", default=True, options={"SKIP_SAVE"}
    )
    do_search: BoolProperty(  # type: ignore[valid-type]
        name="Run Search", description="", default=False, options={"SKIP_SAVE"}
    )

    # def modal(self, context, event):
    #     return {'RUNNING_MODAL'}

    def execute(self, context):
        # possibly only since blender 3.0?
        # if check_context(context):
        #     bpy.ops.view3d.blenderkit_asset_bar_widget('INVOKE_REGION_WIN', keep_running=self.keep_running,
        #                                            do_search=self.do_search)

        C_dict = utils.get_fake_context()

        if bpy.app.version < (4, 0, 0):
            if C_dict.get("window"):  # no 3d view, no asset bar.
                bpy.ops.view3d.run_assetbar_start_modal(
                    C_dict, keep_running=self.keep_running, do_search=self.do_search
                )
        else:
            with context.temp_override(**C_dict):
                bpy.ops.view3d.run_assetbar_start_modal(
                    keep_running=self.keep_running, do_search=self.do_search
                )

        return {"FINISHED"}


classes = (
    AssetBarModalStarter,
    RunAssetBarWithContext,
    TransferBlenderkitData,
    ParticlesDropDialog,
)

# store keymap items here to access after registration
addon_keymapitems = []


# @persistent
def pre_load(context):
    ui_props = bpy.context.window_manager.blenderkitUI
    ui_props.assetbar_on = False
    ui_props.turn_off = True
    # TODO: is this needed?
    # preferences = bpy.context.preferences.addons[__package__].preferences
    # preferences.login_attempt = False


def register_ui():
    for c in classes:
        bpy.utils.register_class(c)

    wm = bpy.context.window_manager

    # spaces solved by registering shortcut to Window. Couldn't register object mode before somehow.
    if not wm.keyconfigs.addon:
        return
    km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type="EMPTY")
    # asset bar shortcut
    kmi = km.keymap_items.new(
        "view3d.run_assetbar_fix_context",
        "SEMI_COLON",
        "PRESS",
        ctrl=False,
        shift=False,
    )
    kmi.properties.keep_running = False
    kmi.properties.do_search = False
    addon_keymapitems.append(kmi)
    # fast rating shortcut
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps["Window"]
    kmi = km.keymap_items.new(
        "wm.blenderkit_menu_rating_upload", "R", "PRESS", ctrl=False, shift=False
    )
    addon_keymapitems.append(kmi)
    # kmi = km.keymap_items.new(upload.FastMetadata.bl_idname, 'F', 'PRESS', ctrl=True, shift=False)
    # addon_keymapitems.append(kmi)


def unregister_ui():
    pre_load(bpy.context)

    for c in classes:
        bpy.utils.unregister_class(c)

    wm = bpy.context.window_manager
    if not wm.keyconfigs.addon:
        return

    km = wm.keyconfigs.addon.keymaps.get("Window")
    if km:
        for kmi in addon_keymapitems:
            try:
                km.keymap_items.remove(kmi)
            except:
                pass
    del addon_keymapitems[:]
