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


if "bpy" in locals():
    import importlib

    paths = importlib.reload(paths)
    ratings = importlib.reload(ratings)
    utils = importlib.reload(utils)
    search = importlib.reload(search)
    upload = importlib.reload(upload)
    ui_bgl = importlib.reload(ui_bgl)
    download = importlib.reload(download)
    bg_blender = importlib.reload(bg_blender)
    colors = importlib.reload(colors)
    tasks_queue = importlib.reload(tasks_queue)
else:
    from blenderkit import paths, ratings, utils, search, upload, ui_bgl, download, bg_blender, colors, tasks_queue

import bpy

import math, random

from bpy.props import (
    BoolProperty,
    StringProperty
)

from bpy_extras import view3d_utils
import mathutils
from mathutils import Vector
import time
import os

handler_2d = None
handler_3d = None
active_area = None
active_area = None
active_window = None
active_region = None

reports = []

mappingdict = {
    'MODEL': 'model',
    'SCENE': 'scene',
    'MATERIAL': 'material',
    'TEXTURE': 'texture',
    'BRUSH': 'brush'
}

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'

}


# class UI_region():
#      def _init__(self, parent = None, x = 10,y = 10 , width = 10, height = 10, img = None, col = None):

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 add_report(text='', timeout=5, color=colors.GREEN):
    global reports
    # check for same reports and just make them longer by the timeout.
    for old_report in reports:
        if old_report.text == text:
            old_report.timeout = old_report.age + timeout
            return
    report = Report(text=text, timeout=timeout, color=color)
    reports.append(report)


class Report():
    def __init__(self, text='', timeout=5, color=(.5, 1, .5, 1)):
        self.text = text
        self.timeout = timeout
        self.start_time = time.time()
        self.color = color
        self.draw_color = color
        self.age = 0

    def fade(self):
        fade_time = 1
        self.age = time.time() - self.start_time
        if self.age + fade_time > self.timeout:
            alpha_multiplier = (self.timeout - self.age) / fade_time
            self.draw_color = (self.color[0], self.color[1], self.color[2], self.color[3] * alpha_multiplier)
            if self.age > self.timeout:
                global reports
                try:
                    reports.remove(self)
                except Exception as e:
                    pass;

    def draw(self, x, y):
        if bpy.context.area == active_area:
            ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)


def get_asset_under_mouse(mousex, mousey):
    s = bpy.context.scene
    ui_props = bpy.context.scene.blenderkitUI
    r = bpy.context.region

    search_results = s.get('search results')
    if search_results is not None:

        h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
        for b in range(0, h_draw):
            w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)
            for a in range(0, w_draw):
                x = ui_props.bar_x + a * (ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset
                y = ui_props.bar_y - ui_props.margin - (ui_props.thumb_size + ui_props.margin) * (b + 1)
                w = ui_props.thumb_size
                h = ui_props.thumb_size

                if x < mousex < x + w and y < mousey < y + h:
                    return a + ui_props.wcount * b + ui_props.scrolloffset

                #   return search_results[a]

    return -3


def draw_bbox(location, rotation, bbox_min, bbox_max, progress=None, color=(0, 1, 0, 1)):
    ui_props = bpy.context.scene.blenderkitUI

    rotation = mathutils.Euler(rotation)

    smin = Vector(bbox_min)
    smax = Vector(bbox_max)
    v0 = Vector(smin)
    v1 = Vector((smax.x, smin.y, smin.z))
    v2 = Vector((smax.x, smax.y, smin.z))
    v3 = Vector((smin.x, smax.y, smin.z))
    v4 = Vector((smin.x, smin.y, smax.z))
    v5 = Vector((smax.x, smin.y, smax.z))
    v6 = Vector((smax.x, smax.y, smax.z))
    v7 = Vector((smin.x, smax.y, smax.z))

    arrowx = smin.x + (smax.x - smin.x) / 2
    arrowy = smin.y - (smax.x - smin.x) / 2
    v8 = Vector((arrowx, arrowy, smin.z))

    vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8]
    for v in vertices:
        v.rotate(rotation)
        v += Vector(location)

    lines = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5],
             [2, 6], [3, 7], [0, 8], [1, 8]]
    ui_bgl.draw_lines(vertices, lines, color)
    if progress != None:
        color = (color[0], color[1], color[2], .2)
        progress = progress * .01
        vz0 = (v4 - v0) * progress + v0
        vz1 = (v5 - v1) * progress + v1
        vz2 = (v6 - v2) * progress + v2
        vz3 = (v7 - v3) * progress + v3
        rects = (
            (v0, v1, vz1, vz0),
            (v1, v2, vz2, vz1),
            (v2, v3, vz3, vz2),
            (v3, v0, vz0, vz3))
        for r in rects:
            ui_bgl.draw_rect_3d(r, color)


def get_rating_scalevalues(asset_type):
    xs = []
    if asset_type == 'model':
        scalevalues = (0.5, 1, 2, 5, 10, 25, 50, 100, 250)
        for v in scalevalues:
            a = math.log2(v)
            x = (a + 1) * (1. / 9.)
            xs.append(x)
    else:
        scalevalues = (0.2, 1, 2, 3, 4, 5)
        for v in scalevalues:
            a = v
            x = v / 5.
            xs.append(x)
    return scalevalues, xs


def draw_ratings_bgl():
    # return;
    ui = bpy.context.scene.blenderkitUI

    rating_possible, rated, asset, asset_data = is_rating_possible()

    if rating_possible:  # (not rated or ui_props.rating_menu_on):
        bkit_ratings = asset.bkit_ratings
        bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
        textcol = (1, 1, 1, 1)

        r = bpy.context.region
        font_size = int(ui.rating_ui_scale * 20)

        if ui.rating_button_on:
            img = utils.get_thumbnail('star_white.png')

            ui_bgl.draw_image(ui.rating_x,
                              ui.rating_y - ui.rating_button_width,
                              ui.rating_button_width,
                              ui.rating_button_width,
                              img, 1)

            # if ui_props.asset_type != 'BRUSH':
            #     thumbnail_image = props.thumbnail
            # else:
            #     b = utils.get_active_brush()
            #     thumbnail_image = b.icon_filepath

            directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
            tpath = os.path.join(directory, asset_data['thumbnail_small'])

            img = utils.get_hidden_image(tpath, 'rating_preview')
            ui_bgl.draw_image(ui.rating_x + ui.rating_button_width,
                              ui.rating_y - ui.rating_button_width,
                              ui.rating_button_width,
                              ui.rating_button_width,
                              img, 1)
            # ui_bgl.draw_text( 'rate asset %s' % asset_data['name'],r.width - rating_button_width + margin, margin, font_size)
            return

        ui_bgl.draw_rect(ui.rating_x,
                         ui.rating_y - ui.rating_ui_height - 2 * ui.margin - font_size,
                         ui.rating_ui_width + ui.margin,
                         ui.rating_ui_height + 2 * ui.margin + font_size,
                         bgcol)
        if asset_data['assetType'] == 'model':
            ui_img_name = 'rating_ui.png'
        else:
            ui_img_name = 'rating_ui_empty.png'
            text = 'Try to estimate how many hours it would take for a professional artist to create this asset:'
            tx = ui.rating_x + ui.workhours_bar_x
            # draw_text_block(x=tx, y=ui.rating_y, width=80, font_size=20, line_height=15, text=text, color=colors.TEXT)

        img = utils.get_thumbnail(ui_img_name)
        ui_bgl.draw_image(ui.rating_x,
                          ui.rating_y - ui.rating_ui_height - 2 * ui.margin,
                          ui.rating_ui_width,
                          ui.rating_ui_height,
                          img, 1)
        img = utils.get_thumbnail('star_white.png')

        quality = bkit_ratings.rating_quality
        work_hours = bkit_ratings.rating_work_hours

        for a in range(0, quality):
            ui_bgl.draw_image(ui.rating_x + ui.quality_stars_x + a * ui.star_size,
                              ui.rating_y - ui.rating_ui_height + ui.quality_stars_y,
                              ui.star_size,
                              ui.star_size,
                              img, 1)

        img = utils.get_thumbnail('bar_slider.png')
        # for a in range(0,11):
        if work_hours > 0.2:
            if asset_data['assetType'] == 'model':
                complexity = math.log2(work_hours) + 2  # real complexity
                complexity = (1. / 9.) * (complexity - 1) * ui.workhours_bar_x_max
            else:
                complexity = work_hours / 5 * ui.workhours_bar_x_max
            ui_bgl.draw_image(
                ui.rating_x + ui.workhours_bar_x + int(
                    complexity),
                ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y,
                ui.workhours_bar_slider_size,
                ui.workhours_bar_slider_size, img, 1)
            ui_bgl.draw_text(
                str(round(work_hours, 1)),
                ui.rating_x + ui.workhours_bar_x - 50,
                ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y + 10, font_size)
        # (0.5,1,2,4,8,16,32,64,128,256)
        # ratings have to be different for models and brushes+materials.

        scalevalues, xs = get_rating_scalevalues(asset_data['assetType'])
        for v, x in zip(scalevalues, xs):
            ui_bgl.draw_rect(ui.rating_x + ui.workhours_bar_x + int(
                x * ui.workhours_bar_x_max) - 1 + ui.workhours_bar_slider_size / 2,
                             ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y,
                             2,
                             5,
                             textcol)
            ui_bgl.draw_text(str(v),
                             ui.rating_x + ui.workhours_bar_x + int(
                                 x * ui.workhours_bar_x_max),
                             ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y - 30,
                             font_size)
        if work_hours > 0.2 and quality > 0.2:
            text = 'Thanks for rating asset %s' % asset_data['name']
        else:
            text = 'Rate asset %s.' % asset_data['name']
        ui_bgl.draw_text(text,
                         ui.rating_x,
                         ui.rating_y - ui.margin - font_size,
                         font_size)


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 draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
    region = bpy.context.region
    scale = bpy.context.preferences.view.ui_scale
    t = time.time()

    ttipmargin = 5
    textmargin = 10

    font_height = int(12 * scale)
    line_height = int(15 * scale)
    nameline_height = int(23 * scale)

    lines = text.split('\n')
    alines = author.split('\n')
    ncolumns = 2
    # nlines = math.ceil((len(lines) - 1) / ncolumns)
    nlines = max(len(lines) - 1, len(alines))  # math.ceil((len(lines) - 1) / ncolumns)

    texth = line_height * nlines + nameline_height

    if not img or max(img.size[0], img.size[1]) == 0:
        return;
    isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
    isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))

    estimated_height = 2 * ttipmargin + textmargin + isizey

    if estimated_height > y:
        scaledown = y / (estimated_height)
        scale *= scaledown
        # we need to scale these down to have correct size if the tooltip wouldn't fit.
        font_height = int(12 * scale)
        line_height = int(15 * scale)
        nameline_height = int(23 * scale)

        lines = text.split('\n')

        texth = line_height * nlines + nameline_height
        isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
        isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))

    name_height = int(18 * scale)

    x += 2 * ttipmargin
    y -= 2 * ttipmargin

    width = isizex + 2 * ttipmargin

    properties_width = 0
    for r in bpy.context.area.regions:
        if r.type == 'UI':
            properties_width = r.width

    x = min(x + width, region.width - properties_width) - width

    bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
    bgcol1 = (bgcol[0], bgcol[1], bgcol[2], .6)
    textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
    textcol = (textcol[0], textcol[1], textcol[2], 1)
    textcol_mild = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
    textcol_strong = (textcol[0] * 1.3, textcol[1] * 1.3, textcol[2] * 1.3, 1)
    white = (1, 1, 1, .1)

    # background
    ui_bgl.draw_rect(x - ttipmargin,
                     y - 2 * ttipmargin - isizey,
                     isizex + ttipmargin * 2,
                     2 * ttipmargin + isizey,
                     bgcol)
    # main preview image
    ui_bgl.draw_image(x, y - isizey - ttipmargin, isizex, isizey, img, 1)

    # text overlay background
    ui_bgl.draw_rect(x - ttipmargin,
                     y - 2 * ttipmargin - isizey,
                     isizex + ttipmargin * 2,
                     2 * ttipmargin + texth,
                     bgcol1)
    # draw gravatar
    gsize = 40
    if gravatar is not None:
        # ui_bgl.draw_image(x + isizex - gsize - textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
        #                   gsize, gsize, gravatar, 1)
        ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
                          gsize, gsize, gravatar, 1)

    i = 0
    column_lines = -1  # start minus one for the name
    xtext = x + textmargin
    fsize = name_height
    tcol = textcol

    for l in lines:
        ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
        if i == 0:
            ytext = y - name_height + 5 - isizey + texth - textmargin
        elif i == len(lines) - 1:
            ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
            tcol = textcol
            tsize = font_height
        else:
            if l[:4] == 'Tip:':
                tcol = textcol_strong
            fsize = font_height
        i += 1
        column_lines += 1
        ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
    xtext += int(isizex / ncolumns)

    column_lines = 1
    for l in alines:
        if gravatar is not None:
            if column_lines == 1:
                xtext += gsize + textmargin
            if column_lines == 4:
                xtext -= gsize + textmargin

        ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
        if i == 0:
            ytext = y - name_height + 5 - isizey + texth - textmargin
        elif i == len(lines) - 1:
            ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
            tcol = textcol
            tsize = font_height
        else:
            if l[:4] == 'Tip:':
                tcol = textcol_strong
            fsize = font_height
        i += 1
        column_lines += 1
        ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)

    # for l in lines:
    #     if column_lines >= nlines:
    #         xtext += int(isizex / ncolumns)
    #         column_lines = 0
    #     ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
    #     if i == 0:
    #         ytext = y - name_height + 5 - isizey + texth - textmargin
    #     elif i == len(lines) - 1:
    #         ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
    #         tcol = textcol
    #         tsize = font_height
    #     else:
    #         if l[:4] == 'Tip:':
    #             tcol = textcol_strong
    #         fsize = font_height
    #     i += 1
    #     column_lines += 1
    #     ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)

    t = time.time()


def draw_tooltip_old(x, y, text='', author='', img=None):
    region = bpy.context.region
    scale = bpy.context.preferences.view.ui_scale
    t = time.time()

    ttipmargin = 10

    font_height = int(12 * scale)
    line_height = int(15 * scale)
    nameline_height = int(23 * scale)

    lines = text.split('\n')
    ncolumns = 2
    nlines = math.ceil((len(lines) - 1) / ncolumns)
    texth = line_height * nlines + nameline_height

    isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
    isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))

    estimated_height = texth + 3 * ttipmargin + isizey

    if estimated_height > y:
        scaledown = y / (estimated_height)
        scale *= scaledown
        # we need to scale these down to have correct size if the tooltip wouldn't fit.
        font_height = int(12 * scale)
        line_height = int(15 * scale)
        nameline_height = int(23 * scale)

        lines = text.split('\n')
        ncolumns = 2
        nlines = math.ceil((len(lines) - 1) / ncolumns)
        texth = line_height * nlines + nameline_height

        isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
        isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))

    name_height = int(18 * scale)

    x += 2 * ttipmargin
    y -= 2 * ttipmargin

    width = isizex + 2 * ttipmargin

    properties_width = 0
    for r in bpy.context.area.regions:
        if r.type == 'UI':
            properties_width = r.width

    x = min(x + width, region.width - properties_width) - width

    bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
    textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
    textcol = (textcol[0], textcol[1], textcol[2], 1)
    textcol1 = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
    white = (1, 1, 1, .1)

    ui_bgl.draw_rect(x - ttipmargin,
                     y - texth - 2 * ttipmargin - isizey,
                     isizex + ttipmargin * 2,
                     texth + 3 * ttipmargin + isizey,
                     bgcol)

    i = 0
    column_lines = -1  # start minus one for the name
    xtext = x
    fsize = name_height
    tcol = textcol
    for l in lines:
        if column_lines >= nlines:
            xtext += int(isizex / ncolumns)
            column_lines = 0
        ytext = y - column_lines * line_height - nameline_height - ttipmargin
        if i == 0:
            ytext = y - name_height + 5
        elif i == len(lines) - 1:
            ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin
            tcol = textcol
            tsize = font_height
        else:
            if l[:5] == 'tags:':
                tcol = textcol1
            fsize = font_height
        i += 1
        column_lines += 1
        ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
    t = time.time()
    ui_bgl.draw_image(x, y - texth - isizey - ttipmargin, isizex, isizey, img, 1)


def draw_callback_2d(self, context):
    if not utils.guard_from_crash():
        return;

    a = context.area
    w = context.window
    try:
        # self.area might throw error just by itself.
        a1 = self.area
        w1 = self.window
        go = True
        if len(a.spaces[0].region_quadviews) > 0:
            # print(dir(bpy.context.region_data))
            # print('quad', a.spaces[0].region_3d, a.spaces[0].region_quadviews[0])
            if a.spaces[0].region_3d != context.region_data:
                go = False
    except:
        # bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
        # bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        go = False
    if go and a == a1 and w == w1:

        props = context.scene.blenderkitUI
        if props.down_up == 'SEARCH':
            draw_ratings_bgl()
            draw_callback_2d_search(self, context)
        elif props.down_up == 'UPLOAD':
            draw_callback_2d_upload_preview(self, context)


def draw_downloader(x, y, percent=0, img=None):
    if img is not None:
        ui_bgl.draw_image(x, y, 50, 50, img, .5)
    ui_bgl.draw_rect(x, y, 50, int(0.5 * percent), (.2, 1, .2, .3))
    ui_bgl.draw_rect(x - 3, y - 3, 6, 6, (1, 0, 0, .3))


def draw_progress(x, y, text='', percent=None, color=colors.GREEN):
    ui_bgl.draw_rect(x, y, percent, 5, color)
    ui_bgl.draw_text(text, x, y + 8, 16, color)


def draw_callback_3d_progress(self, context):
    # 'star trek' mode gets here, blocked by now ;)
    for threaddata in download.download_threads:
        asset_data = threaddata[1]
        tcom = threaddata[2]
        if tcom.passargs.get('downloaders'):
            for d in tcom.passargs['downloaders']:
                if asset_data['assetType'] == 'model':
                    draw_bbox(d['location'], d['rotation'], asset_data['bbox_min'], asset_data['bbox_max'],
                              progress=tcom.progress)


def draw_callback_2d_progress(self, context):
    green = (.2, 1, .2, .3)
    offset = 0
    row_height = 35

    ui = bpy.context.scene.blenderkitUI

    x = ui.reports_x
    y = ui.reports_y
    index = 0
    for threaddata in download.download_threads:
        asset_data = threaddata[1]
        tcom = threaddata[2]

        directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
        tpath = os.path.join(directory, asset_data['thumbnail_small'])
        img = utils.get_hidden_image(tpath, asset_data['id'])

        if tcom.passargs.get('downloaders'):
            for d in tcom.passargs['downloaders']:

                loc = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d,
                                                            d['location'])
                if loc is not None:
                    if asset_data['assetType'] == 'model':
                        # models now draw with star trek mode, no need to draw percent for the image.
                        draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img)
                    else:
                        draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img)


        else:
            draw_progress(x, y - index * 30, text='downloading %s' % asset_data['name'],
                          percent=tcom.progress)
            index += 1
    for process in bg_blender.bg_processes:
        tcom = process[1]
        draw_progress(x, y - index * 30, '%s' % tcom.lasttext,
                      tcom.progress)
        index += 1
    global reports
    for report in reports:
        report.draw(x, y - index * 30)
        index += 1
        report.fade()


def draw_callback_2d_upload_preview(self, context):
    ui_props = context.scene.blenderkitUI

    props = utils.get_upload_props()
    if props != None and ui_props.draw_tooltip:

        if ui_props.asset_type != 'BRUSH':
            ui_props.thumbnail_image = props.thumbnail
        else:
            b = utils.get_active_brush()
            ui_props.thumbnail_image = b.icon_filepath

        img = utils.get_hidden_image(ui_props.thumbnail_image, 'upload_preview')

        draw_tooltip(ui_props.bar_x, ui_props.bar_y, text=ui_props.tooltip, img=img)


def draw_callback_2d_search(self, context):
    s = bpy.context.scene
    ui_props = context.scene.blenderkitUI
    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences

    r = self.region
    # hc = bpy.context.preferences.themes[0].view_3d.space.header
    # hc = bpy.context.preferences.themes[0].user_interface.wcol_menu_back.inner
    # hc = (hc[0], hc[1], hc[2], .2)
    hc = (1, 1, 1, .07)
    # grey1 = (hc.r * .55, hc.g * .55, hc.b * .55, 1)
    grey2 = (hc[0] * .8, hc[1] * .8, hc[2] * .8, .5)
    # grey1 = (hc.r, hc.g, hc.b, 1)
    white = (1, 1, 1, 0.2)
    green = (.2, 1, .2, .7)
    highlight = bpy.context.preferences.themes[0].user_interface.wcol_menu_item.inner_sel
    highlight = (1, 1, 1, .2)
    # highlight = (1, 1, 1, 0.8)
    # background of asset bar
    if not ui_props.dragging and ui_props.hcount>0:
        search_results = s.get('search results')
        search_results_orig = s.get('search results orig')
        if search_results == None:
            return
        h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))

        if ui_props.wcount > len(search_results):
            bar_width = len(search_results) * (ui_props.thumb_size + ui_props.margin) + ui_props.margin
        else:
            bar_width = ui_props.bar_width
        row_height = ui_props.thumb_size + ui_props.margin
        ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, bar_width,
                         ui_props.bar_height, hc)

        if search_results is not None:
            if ui_props.scrolloffset > 0 or ui_props.wcount * ui_props.hcount < len(search_results):
                ui_props.drawoffset = 35
            else:
                ui_props.drawoffset = 0

            if ui_props.wcount * ui_props.hcount < len(search_results):
                # arrows
                arrow_y = ui_props.bar_y - int((ui_props.bar_height + ui_props.thumb_size) / 2) + ui_props.margin
                if ui_props.scrolloffset > 0:

                    if ui_props.active_index == -2:
                        ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, 25,
                                         ui_props.bar_height, highlight)
                    img = utils.get_thumbnail('arrow_left.png')
                    ui_bgl.draw_image(ui_props.bar_x, arrow_y, 25,
                                      ui_props.thumb_size,
                                      img,
                                      1)

                if search_results_orig['count'] - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount) + 1:
                    if ui_props.active_index == -1:
                        ui_bgl.draw_rect(ui_props.bar_x + ui_props.bar_width - 25,
                                         ui_props.bar_y - ui_props.bar_height, 25,
                                         ui_props.bar_height,
                                         highlight)
                    img1 = utils.get_thumbnail('arrow_right.png')
                    ui_bgl.draw_image(ui_props.bar_x + ui_props.bar_width - 25,
                                      arrow_y, 25,
                                      ui_props.thumb_size, img1, 1)

            for b in range(0, h_draw):
                w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)

                y = ui_props.bar_y - (b + 1) * (row_height)
                for a in range(0, w_draw):
                    x = ui_props.bar_x + a * (
                            ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset

                    #
                    index = a + ui_props.scrolloffset + b * ui_props.wcount
                    iname = utils.previmg_name(index)
                    img = bpy.data.images.get(iname)
                    if img is not None:
                        w = int(ui_props.thumb_size * img.size[0] / max(img.size[0], img.size[1]))
                        h = int(ui_props.thumb_size * img.size[1] / max(img.size[0], img.size[1]))
                        crop = (0, 0, 1, 1)
                        if img.size[0] > img.size[1]:
                            offset = (1 - img.size[1] / img.size[0]) / 2
                            crop = (offset, 0, 1 - offset, 1)

                        ui_bgl.draw_image(x, y, w, w, img, 1,
                                          crop=crop)
                        if index == ui_props.active_index:
                            ui_bgl.draw_rect(x - ui_props.highlight_margin, y - ui_props.highlight_margin,
                                             w + 2 * ui_props.highlight_margin, w + 2 * ui_props.highlight_margin,
                                             highlight)
                        # if index == ui_props.active_index:
                        #     ui_bgl.draw_rect(x - highlight_margin, y - highlight_margin,
                        #               w + 2*highlight_margin, h + 2*highlight_margin , highlight)

                    else:
                        ui_bgl.draw_rect(x, y, ui_props.thumb_size, ui_props.thumb_size, white)

                    result = search_results[index]
                    if result['downloaded'] > 0:
                        ui_bgl.draw_rect(x, y - 2, int(w * result['downloaded'] / 100.0), 2, green)
                    # object type icons - just a test..., adds clutter/ not so userfull:
                    # icons = ('type_finished.png', 'type_template.png', 'type_particle_system.png')

                    if (result.get('canDownload', True)) == 0:
                        img = utils.get_thumbnail('locked.png')
                        ui_bgl.draw_image(x + 2, y + 2, 24, 24, img, 1)

                    v_icon = verification_icons[result.get('verificationStatus', 'validated')]
                    if v_icon is not None:
                        img = utils.get_thumbnail(v_icon)
                        ui_bgl.draw_image(x + ui_props.thumb_size - 26, y + 2, 24, 24, img, 1)

            # if user_preferences.api_key == '':
            #     report = 'Register on BlenderKit website to upload your own assets.'
            #     ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin,
            #                      ui_props.bar_y - 25 - ui_props.margin - ui_props.bar_height, 15)
            # elif len(search_results) == 0:
            #     report = 'BlenderKit - No matching results found.'
            #     ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin,
            #                      ui_props.bar_y - 25 - ui_props.margin, 15)
        s = bpy.context.scene
        props = utils.get_search_props()
        # if props.report != '' and props.is_searching or props.search_error:
        #     ui_bgl.draw_text(props.report, ui_props.bar_x,
        #                      ui_props.bar_y - 15 - ui_props.margin - ui_props.bar_height, 15)

        props = s.blenderkitUI
        if props.draw_tooltip:
            # TODO move this lazy loading into a function and don't duplicate through the code
            iname = utils.previmg_name(ui_props.active_index, fullsize=True)

            directory = paths.get_temp_dir('%s_search' % mappingdict[props.asset_type])
            sr = s.get('search results')
            if sr != None and -1 < ui_props.active_index < len(sr):
                r = sr[ui_props.active_index]
                tpath = os.path.join(directory, r['thumbnail'])

                img = bpy.data.images.get(iname)
                if img == None or img.filepath != tpath:
                    # TODO replace it with a function
                    if os.path.exists(tpath):

                        if img is None:
                            img = bpy.data.images.load(tpath)
                            img.name = iname
                        else:
                            if img.filepath != tpath:
                                # todo replace imgs reloads with a method that forces unpack for thumbs.
                                if img.packed_file is not None:
                                    img.unpack(method='USE_ORIGINAL')
                                img.filepath = tpath
                                img.reload()
                                img.name = iname
                    else:
                        iname = utils.previmg_name(ui_props.active_index)
                        img = bpy.data.images.get(iname)
                    if img:
                        img.colorspace_settings.name = 'sRGB'

                gimg = None
                atip = ''
                if bpy.context.window_manager.get('bkit authors') is not None:
                    a = bpy.context.window_manager['bkit authors'].get(r['author']['id'])
                    if a is not None and a != '':
                        if a.get('gravatarImg') is not None:
                            gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
                        atip = a['tooltip']

                draw_tooltip(ui_props.mouse_x, ui_props.mouse_y, text=ui_props.tooltip, author=atip, img=img,
                             gravatar=gimg)

    if ui_props.dragging and (
            ui_props.draw_drag_image or ui_props.draw_snapped_bounds) and ui_props.active_index > -1:
        iname = utils.previmg_name(ui_props.active_index)
        img = bpy.data.images.get(iname)
        linelength = 35
        ui_bgl.draw_image(ui_props.mouse_x + linelength, ui_props.mouse_y - linelength - ui_props.thumb_size,
                          ui_props.thumb_size, ui_props.thumb_size, img, 1)
        ui_bgl.draw_line2d(ui_props.mouse_x, ui_props.mouse_y, ui_props.mouse_x + linelength,
                           ui_props.mouse_y - linelength, 2, white)


def draw_callback_3d(self, context):
    ''' Draw snapped bbox while dragging and in the future other blenderkit related stuff. '''
    if not utils.guard_from_crash():
        return;

    ui = context.scene.blenderkitUI

    if ui.dragging and ui.asset_type == 'MODEL':
        if ui.draw_snapped_bounds:
            draw_bbox(ui.snapped_location, ui.snapped_rotation, ui.snapped_bbox_min, ui.snapped_bbox_max)


def mouse_raycast(context, mx, my):
    r = context.region
    rv3d = context.region_data
    coord = mx, my

    # get the ray from the viewport and mouse
    view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord)
    ray_target = ray_origin + (view_vector * 1000000000)

    vec = ray_target - ray_origin

    has_hit, snapped_location, snapped_normal, face_index, object, matrix = bpy.context.scene.ray_cast(
        bpy.context.view_layer.depsgraph, ray_origin, vec)

    # rote = mathutils.Euler((0, 0, math.pi))
    randoffset = math.pi
    if has_hit:
        props = bpy.context.scene.blenderkit_models
        up = Vector((0, 0, 1))

        if props.perpendicular_snap:
            if snapped_normal.z > 1 - props.perpendicular_snap_threshold:
                snapped_normal = Vector((0, 0, 1))
            elif snapped_normal.z < -1 + props.perpendicular_snap_threshold:
                snapped_normal = Vector((0, 0, -1))
            elif abs(snapped_normal.z) < props.perpendicular_snap_threshold:
                snapped_normal.z = 0
                snapped_normal.normalize()

        snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()


        if props.randomize_rotation and snapped_normal.angle(up) < math.radians(10.0):
            randoffset = props.offset_rotation_amount + math.pi + (
                    random.random() - 0.5) * props.randomize_rotation_amount
        else:
            randoffset = props.offset_rotation_amount  # we don't rotate this way on walls and ceilings. + math.pi
        # snapped_rotation.z += math.pi + (random.random() - 0.5) * .2

    else:
        snapped_rotation = mathutils.Quaternion((0, 0, 0, 0)).to_euler()

    snapped_rotation.rotate_axis('Z', randoffset)

    return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix


def floor_raycast(context, mx, my):
    r = context.region
    rv3d = context.region_data
    coord = mx, my

    # get the ray from the viewport and mouse
    view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
    ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord)
    ray_target = ray_origin + (view_vector * 1000)

    # various intersection plane normals are needed for corner cases that might actually happen quite often - in front and side view.
    # default plane normal is scene floor.
    plane_normal = (0, 0, 1)
    if math.isclose(view_vector.x, 0, abs_tol=1e-4) and math.isclose(view_vector.z, 0, abs_tol=1e-4):
        plane_normal = (0, 1, 0)
    elif math.isclose(view_vector.z, 0, abs_tol=1e-4):
        plane_normal = (1, 0, 0)

    snapped_location = mathutils.geometry.intersect_line_plane(ray_origin, ray_target, (0, 0, 0), plane_normal,
                                                               False)
    if snapped_location != None:
        has_hit = True
        snapped_normal = Vector((0, 0, 1))
        face_index = None
        object = None
        matrix = None
        snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
        props = bpy.context.scene.blenderkit_models
        if props.randomize_rotation:
            randoffset = props.offset_rotation_amount + math.pi + (
                    random.random() - 0.5) * props.randomize_rotation_amount
        else:
            randoffset = props.offset_rotation_amount + math.pi
        snapped_rotation.rotate_axis('Z', randoffset)

    return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix


def is_rating_possible():
    ao = bpy.context.active_object
    ui = bpy.context.scene.blenderkitUI
    preferences = bpy.context.preferences.addons['blenderkit'].preferences
    #first test if user is logged in.
    if preferences.api_key == '':
        return False, False, None, None
    if bpy.context.scene.get('assets rated') 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'])
                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')
                if ad is not None:

                    s['assets rated'] = s.get('assets rated',{})
                    rated = s['assets rated'].get(ad.get('assetBaseId'))
                    # 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')
                if ad is not None and ad.get('assetBaseId'):
                    rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
                    if rated:
                        return True, rated, m, ad

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

    return False, False, None, None


def interact_rating(r, mx, my, event):
    ui = bpy.context.scene.blenderkitUI
    rating_possible, rated, asset, asset_data = is_rating_possible()
    if rating_possible:
        bkit_ratings = asset.bkit_ratings

        t = time.time() - ui.last_rating_time
        if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
            accept_value = 'PRESS'
        else:
            accept_value = 'RELEASE'

        if ui.rating_button_on and event.type == 'LEFTMOUSE' and event.value == accept_value:
            if mouse_in_area(mx, my,
                             ui.rating_x,
                             ui.rating_y - ui.rating_button_width,
                             ui.rating_button_width * 2,
                             ui.rating_button_width):
                ui.rating_menu_on = True
                ui.rating_button_on = False
                return True
        if ui.rating_menu_on:
            if mouse_in_area(mx, my,
                             ui.rating_x,
                             ui.rating_y - ui.rating_ui_height,
                             ui.rating_ui_width,
                             ui.rating_ui_height + 25):
                rmx = mx - (ui.rating_x)
                rmy = my - (ui.rating_y - ui.rating_ui_height)

                # quality
                upload_rating = False
                if (ui.quality_stars_x < rmx and rmx < ui.quality_stars_x + 10 * ui.star_size and \
                    ui.quality_stars_y < rmy and rmy < ui.quality_stars_y + ui.star_size and event.type == 'LEFTMOUSE' and event.value == 'PRESS') or \
                        ui.dragging_rating_quality:

                    if event.type == 'LEFTMOUSE':
                        if event.value == 'PRESS':
                            ui.dragging_rating = True
                            ui.dragging_rating_quality = True
                        elif event.value == 'RELEASE':
                            ui.dragging_rating = False
                            ui.dragging_rating_quality = False

                    if ui.dragging_rating_quality:
                        q = math.ceil((rmx - ui.quality_stars_x) / (float(ui.star_size)))
                        bkit_ratings.rating_quality = q

                # work hours
                if (
                        ui.workhours_bar_x < rmx and rmx < ui.workhours_bar_x + ui.workhours_bar_x_max + ui.workhours_bar_slider_size and \
                        ui.workhours_bar_y < rmy and rmy < ui.workhours_bar_y + ui.workhours_bar_slider_size and event.type == 'LEFTMOUSE' and event.value == 'PRESS') \
                        or (ui.dragging_rating_work_hours):
                    if event.value == 'PRESS':
                        ui.dragging_rating = True
                        ui.dragging_rating_work_hours = True
                    elif event.value == 'RELEASE':
                        ui.dragging_rating = False
                        ui.dragging_rating_work_hours = False
                    if ui.dragging_rating_work_hours:
                        xv = rmx - ui.workhours_bar_x - ui.workhours_bar_slider_size / 2
                        ratio = xv / ui.workhours_bar_x_max
                        if asset_data['assetType'] == 'model':
                            wh_log2 = ratio * 9 - 1
                            wh = 2 ** wh_log2
                        else:
                            wh = 5 * ratio
                        bkit_ratings.rating_work_hours = wh

                if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
                    ui.last_rating_time = time.time() # this prop seems obsolete now?
                return True
            else:
                ui.rating_button_on = True
                ui.rating_menu_on = False
    return False


def mouse_in_area(mx, my, x, y, w, h):
    if x < mx < x + w and y < my < y + h:
        return True
    else:
        return False


def mouse_in_asset_bar(mx, my):
    s = bpy.context.scene

    ui_props = bpy.context.scene.blenderkitUI
    # search_results = s.get('search results')
    # if search_results == None:
    #     return False
    #
    # w_draw1 = min(ui_props.wcount + 1, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)
    # end = ui_props.bar_x + (w_draw1) * (
    #         ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset + 25

    if ui_props.bar_y - ui_props.bar_height < my < ui_props.bar_y \
            and mx > ui_props.bar_x and mx < ui_props.bar_x + ui_props.bar_width:
        return True
    else:
        return False


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


def update_ui_size(area, region):
    ui = bpy.context.scene.blenderkitUI
    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
    ui_scale = bpy.context.preferences.view.ui_scale

    ui.margin = ui.bl_rna.properties['margin'].default * ui_scale
    ui.thumb_size = user_preferences.thumb_size * ui_scale

    reg_multiplier = 1
    if not bpy.context.preferences.system.use_region_overlap:
        reg_multiplier = 0

    for r in area.regions:
        if r.type == 'TOOLS':
            ui.bar_x = r.width * reg_multiplier + ui.margin + ui.bar_x_offset * ui_scale
        elif r.type == 'UI':
            ui.bar_end = r.width * reg_multiplier + 100 * ui_scale

    ui.bar_width = region.width - ui.bar_x - ui.bar_end
    ui.wcount = math.floor(
        (ui.bar_width - 2 * ui.drawoffset) / (ui.thumb_size + ui.margin))

    search_results = bpy.context.scene.get('search results')
    if search_results != None and ui.wcount > 0:
        ui.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / ui.wcount))
    else:
        ui.hcount = 1
    ui.bar_height = (ui.thumb_size + ui.margin) * ui.hcount + ui.margin
    ui.bar_y = region.height - ui.bar_y_offset * ui_scale
    if ui.down_up == 'UPLOAD':
        ui.reports_y = ui.bar_y - 600
        ui.reports_x = ui.bar_x
    else:
        ui.reports_y = ui.bar_y - ui.bar_height - 100
        ui.reports_x = ui.bar_x

    ui.rating_x = ui.bar_x
    ui.rating_y = ui.bar_y - ui.bar_height



class AssetBarOperator(bpy.types.Operator):
    '''runs search and displays the asset bar at the same time'''
    bl_idname = "view3d.blenderkit_asset_bar"
    bl_label = "BlenderKit Asset Bar UI"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    do_search: BoolProperty(name="Run Search", description='', default=True, options={'SKIP_SAVE'})
    keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'})
    free_only: BoolProperty(name="Free Only", description='', default=False, options={'SKIP_SAVE'})

    category: StringProperty(
        name="Category",
        description="search only subtree of this category",
        default="", options={'SKIP_SAVE'})

    tooltip: bpy.props.StringProperty(default='runs search and displays the asset bar at the same time')

    @classmethod
    def description(cls, context, properties):
        return properties.tooltip

    def search_more(self):
        sro = bpy.context.scene.get('search results orig')
        if sro is not None and sro.get('next') is not None:
            search.search(get_next=True)

    def exit_modal(self):
        try:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
            bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
        except:
            pass;
        ui_props = bpy.context.scene.blenderkitUI

        ui_props.dragging = False
        ui_props.tooltip = ''
        ui_props.active_index = -3
        ui_props.draw_drag_image = False
        ui_props.draw_snapped_bounds = False
        ui_props.has_hit = False
        ui_props.assetbar_on = False

    def modal(self, context, event):
        # This is for case of closing the area or changing type:
        ui_props = context.scene.blenderkitUI
        user_preferences = bpy.context.preferences.addons['blenderkit'].preferences

        areas = []


        #timers testing - seems timers might be causing crashes. testing it this way now.
        if not user_preferences.use_timers:
                search.timer_update()
                download.timer_update()
                tasks_queue.queue_worker()
                bg_blender.bg_update()

        if bpy.context.scene != self.scene:
            self.exit_modal()
            return {'CANCELLED'}

        for w in context.window_manager.windows:
            areas.extend(w.screen.areas)

        if self.area not in areas or self.area.type != 'VIEW_3D' or self.has_quad_views != (
                len(self.area.spaces[0].region_quadviews) > 0):
            # print('search areas')   bpy.context.area.spaces[0].region_quadviews
            # stopping here model by now - because of:
            #   switching layouts or maximizing area now fails to assign new area throwing the bug
            #   internal error: modal gizmo-map handler has invalid area
            self.exit_modal()
            return {'CANCELLED'}

            newarea = None
            for a in context.window.screen.areas:
                if a.type == 'VIEW_3D':
                    self.area = a
                    for r in a.regions:
                        if r.type == 'WINDOW':
                            self.region = r
                    newarea = a
                    break;
                    # context.area = a

            # we check again and quit if things weren't fixed this way.
            if newarea == None:
                self.exit_modal()
                return {'CANCELLED'}

        update_ui_size(self.area, self.region)



        # this was here to check if sculpt stroke is running, but obviously that didn't help,
        #  since the RELEASE event is cought by operator and thus there is no way to detect a stroke has ended...
        if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
            if event.type == 'MOUSEMOVE':  # ASSUME THAT SCULPT OPERATOR ACTUALLY STEALS THESE EVENTS,
                # SO WHEN THERE ARE SOME WE CAN APPEND BRUSH...
                bpy.context.window_manager['appendable'] = True
            if event.type == 'LEFTMOUSE':
                if event.value == 'PRESS':
                    bpy.context.window_manager['appendable'] = False

        self.area.tag_redraw()
        s = context.scene

        if ui_props.turn_off:
            ui_props.turn_off = False
            self.exit_modal()
            ui_props.draw_tooltip = False
            return {'CANCELLED'}

        if context.region != self.region:
            # print(time.time(), 'pass through because of region')
            # print(context.region.type, self.region.type)
            return {'PASS_THROUGH'}

        if ui_props.down_up == 'UPLOAD':

            ui_props.mouse_x = 0
            ui_props.mouse_y = self.region.height

            mx = event.mouse_x
            my = event.mouse_y

            ui_props.draw_tooltip = True

            # only generate tooltip once in a while
            if (
                    event.type == 'LEFTMOUSE' or event.type == 'RIGHTMOUSE') and event.value == 'RELEASE' or event.type == 'ENTER' or ui_props.tooltip == '':
                ao = bpy.context.active_object
                if ui_props.asset_type == 'MODEL' and ao != None \
                        or ui_props.asset_type == 'MATERIAL' and ao != None and ao.active_material != None \
                        or ui_props.asset_type == 'BRUSH' and utils.get_active_brush() is not None:
                    export_data, upload_data, eval_path_computing, eval_path_state, eval_path, props = upload.get_upload_data(
                        self,
                        context,
                        ui_props.asset_type)
                    ui_props.tooltip = search.generate_tooltip(upload_data)

            return {'PASS_THROUGH'}

        # TODO add one more condition here to take less performance.
        r = self.region
        s = bpy.context.scene
        sr = s.get('search results')
        search_results_orig = s.get('search results orig')
        # If there aren't any results, we need no interaction(yet)
        if sr is None:
            return {'PASS_THROUGH'}
        if len(sr) - ui_props.scrolloffset < (ui_props.wcount * ui_props.hcount) + 10:
            self.search_more()
        if event.type == 'WHEELUPMOUSE' or event.type == 'WHEELDOWNMOUSE' or event.type == 'TRACKPADPAN':
            # scrolling
            mx = event.mouse_region_x
            my = event.mouse_region_y

            if ui_props.dragging and not mouse_in_asset_bar(mx, my):  # and my < r.height - ui_props.bar_height \
                # and mx > 0 and mx < r.width and my > 0:
                sprops = bpy.context.scene.blenderkit_models
                if event.type == 'WHEELUPMOUSE':
                    sprops.offset_rotation_amount += sprops.offset_rotation_step
                elif event.type == 'WHEELDOWNMOUSE':
                    sprops.offset_rotation_amount -= sprops.offset_rotation_step

                #### TODO - this snapping code below is 3x in this file.... refactor it.
                ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
                    context, mx, my)

                # MODELS can be dragged on scene floor
                if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
                    ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
                        context,
                        mx, my)

                return {'RUNNING_MODAL'}

            if not mouse_in_asset_bar(mx, my):
                return {'PASS_THROUGH'}

            # note - TRACKPADPAN is unsupported in blender by now.
            # if event.type == 'TRACKPADPAN' :
            #     print(dir(event))
            #     print(event.value, event.oskey, event.)
            if (event.type == 'WHEELDOWNMOUSE') and len(sr) - ui_props.scrolloffset > (
                    ui_props.wcount * ui_props.hcount):
                if ui_props.hcount > 1:
                    ui_props.scrolloffset += ui_props.wcount
                else:
                    ui_props.scrolloffset += 1
                if len(sr) - ui_props.scrolloffset < (ui_props.wcount * ui_props.hcount):
                    ui_props.scrolloffset = len(sr) - (ui_props.wcount * ui_props.hcount)

            if event.type == 'WHEELUPMOUSE' and ui_props.scrolloffset > 0:
                if ui_props.hcount > 1:
                    ui_props.scrolloffset -= ui_props.wcount
                else:
                    ui_props.scrolloffset -= 1
                if ui_props.scrolloffset < 0:
                    ui_props.scrolloffset = 0

            return {'RUNNING_MODAL'}
        if event.type == 'MOUSEMOVE':  # Apply

            r = self.region
            mx = event.mouse_region_x
            my = event.mouse_region_y

            ui_props.mouse_x = mx
            ui_props.mouse_y = my

            if ui_props.dragging_rating or ui_props.rating_menu_on:
                res = interact_rating(r, mx, my, event)
                if res == True:
                    return {'RUNNING_MODAL'}

            if ui_props.drag_init:
                ui_props.drag_length += 1
                if ui_props.drag_length > 0:
                    ui_props.dragging = True
                    ui_props.drag_init = False

            if not (ui_props.dragging and mouse_in_region(r, mx, my)) and not mouse_in_asset_bar(mx, my):  #

                ui_props.dragging = False
                ui_props.has_hit = False
                ui_props.active_index = -3
                ui_props.draw_drag_image = False
                ui_props.draw_snapped_bounds = False
                ui_props.draw_tooltip = False
                bpy.context.window.cursor_set("DEFAULT")
                return {'PASS_THROUGH'}

            sr = bpy.context.scene['search results']

            if not ui_props.dragging:
                bpy.context.window.cursor_set("DEFAULT")

                if sr != None and ui_props.wcount * ui_props.hcount > len(sr) and ui_props.scrolloffset > 0:
                    ui_props.scrolloffset = 0

                asset_search_index = get_asset_under_mouse(mx, my)
                ui_props.active_index = asset_search_index
                if asset_search_index > -1:

                    asset_data = sr[asset_search_index]
                    ui_props.draw_tooltip = True

                    ui_props.tooltip = asset_data['tooltip']
                    # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')

                else:
                    ui_props.draw_tooltip = False

                if mx > ui_props.bar_x + ui_props.bar_width - 50 and search_results_orig[
                    'count'] - ui_props.scrolloffset > (
                        ui_props.wcount * ui_props.hcount) + 1:
                    ui_props.active_index = -1
                    return {'RUNNING_MODAL'}
                if mx < ui_props.bar_x + 50 and ui_props.scrolloffset > 0:
                    ui_props.active_index = -2
                    return {'RUNNING_MODAL'}

            else:
                result = False
                if ui_props.dragging and mouse_in_region(r, mx, my):
                    ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
                        context, mx, my)
                    # MODELS can be dragged on scene floor
                    if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
                        ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
                            context,
                            mx, my)
                if ui_props.has_hit and ui_props.asset_type == 'MODEL':
                    # this condition is here to fix a bug for a scene submitted by a user, so this situation shouldn't
                    # happen anymore, but there might exists scenes which have this problem for some reason.
                    if ui_props.active_index < len(sr) and ui_props.active_index > -1:
                        ui_props.draw_snapped_bounds = True
                        active_mod = sr[ui_props.active_index]
                        ui_props.snapped_bbox_min = Vector(active_mod['bbox_min'])
                        ui_props.snapped_bbox_max = Vector(active_mod['bbox_max'])

                else:
                    ui_props.draw_snapped_bounds = False
                    ui_props.draw_drag_image = True
            return {'RUNNING_MODAL'}

        if event.type == 'RIGHTMOUSE':
            mx = event.mouse_x - r.x
            my = event.mouse_y - r.y

            if event.value == 'PRESS' and mouse_in_asset_bar(mx, my):
                bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
                return {'RUNNING_MODAL'}

        if event.type == 'LEFTMOUSE':

            r = self.region
            mx = event.mouse_x - r.x
            my = event.mouse_y - r.y

            ui_props = context.scene.blenderkitUI
            if event.value == 'PRESS' and ui_props.active_index > -1:
                if ui_props.asset_type == 'MODEL' or ui_props.asset_type == 'MATERIAL':
                    # check if asset is locked and let the user know in that case
                    asset_search_index = ui_props.active_index
                    asset_data = sr[asset_search_index]
                    if not asset_data.get('canDownload'):
                        message = "Let's support asset creators and Blender development."
                        link_text = 'Unlock the asset.'
                        url = paths.get_bkit_url() + '/get-blenderkit/' + asset_data['id'] + '/?from_addon'
                        bpy.ops.wm.blenderkit_url_dialog('INVOKE_REGION_WIN', url=url, message=message,
                                                         link_text=link_text)
                        return {'RUNNING_MODAL'}
                    # go on with drag init
                    ui_props.drag_init = True
                    bpy.context.window.cursor_set("NONE")
                    ui_props.draw_tooltip = False
                    ui_props.drag_length = 0

            if ui_props.rating_on:
                res = interact_rating(r, mx, my, event)
                if res:
                    return {'RUNNING_MODAL'}

            if not ui_props.dragging and not mouse_in_asset_bar(mx, my):
                return {'PASS_THROUGH'}

            # this can happen by switching result asset types - length of search result changes
            if ui_props.scrolloffset > 0 and (ui_props.wcount * ui_props.hcount) > len(sr) - ui_props.scrolloffset:
                ui_props.scrolloffset = len(sr) - (ui_props.wcount * ui_props.hcount)

            if event.value == 'RELEASE':  # Confirm
                ui_props.drag_init = False

                # scroll by a whole page
                if mx > ui_props.bar_x + ui_props.bar_width - 50 and len(
                        sr) - ui_props.scrolloffset > ui_props.wcount * ui_props.hcount:
                    ui_props.scrolloffset = min(
                        ui_props.scrolloffset + (ui_props.wcount * ui_props.hcount),
                        len(sr) - ui_props.wcount * ui_props.hcount)
                    return {'RUNNING_MODAL'}
                if mx < ui_props.bar_x + 50 and ui_props.scrolloffset > 0:
                    ui_props.scrolloffset = max(0, ui_props.scrolloffset - ui_props.wcount * ui_props.hcount)
                    return {'RUNNING_MODAL'}

                # Drag-drop interaction
                if ui_props.dragging and mouse_in_region(r, mx, my):
                    asset_search_index = ui_props.active_index
                    # raycast here
                    ui_props.active_index = -3

                    if ui_props.asset_type == 'MODEL':

                        ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
                            context, mx, my)

                        # MODELS can be dragged on scene floor
                        if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
                            ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
                                context,
                                mx, my)

                        if not ui_props.has_hit:
                            return {'RUNNING_MODAL'}

                        target_object = ''
                        if object is not None:
                            target_object = object.name
                        target_slot = ''

                    if ui_props.asset_type == 'MATERIAL':
                        ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
                            context, mx, my)

                        if not ui_props.has_hit:
                            # this is last attempt to get object under mouse - for curves and other objects than mesh.
                            ui_props.dragging = False
                            sel = utils.selection_get()
                            bpy.ops.view3d.select(location=(event.mouse_region_x, event.mouse_region_y))
                            sel1 = utils.selection_get()
                            if sel[0] != sel1[0] and sel1[0].type != 'MESH':
                                object = sel1[0]
                                target_slot = sel1[0].active_material_index
                                ui_props.has_hit = True
                            utils.selection_set(sel)

                        if not ui_props.has_hit:
                            return {'RUNNING_MODAL'}

                        else:
                            # first, test if object can have material applied.
                            # TODO add other types here if droppable.
                            if object is not None and not object.is_library_indirect and object.type == 'MESH':
                                target_object = object.name
                                # create final mesh to extract correct material slot
                                depsgraph = bpy.context.evaluated_depsgraph_get()
                                object_eval = object.evaluated_get(depsgraph)
                                temp_mesh = object_eval.to_mesh()
                                target_slot = temp_mesh.polygons[face_index].material_index
                                object_eval.to_mesh_clear()
                            else:
                                self.report({'WARNING'}, "Invalid or library object as input:")
                                target_object = ''
                                target_slot = ''

                # Click interaction
                else:
                    asset_search_index = get_asset_under_mouse(mx, my)

                    if ui_props.asset_type in ('MATERIAL',
                                               'MODEL'):  # this was meant for particles, commenting for now or ui_props.asset_type == 'MODEL':
                        ao = bpy.context.active_object
                        if ao != None and not ao.is_library_indirect:
                            target_object = bpy.context.active_object.name
                            target_slot = bpy.context.active_object.active_material_index
                        else:
                            target_object = ''
                            target_slot = ''
                # FIRST START SEARCH

                if asset_search_index == -3:
                    return {'RUNNING_MODAL'}
                if asset_search_index > -3:
                    if ui_props.asset_type == 'MATERIAL':
                        if target_object != '':
                            # position is for downloader:
                            loc = ui_props.snapped_location
                            rotation = (0, 0, 0)

                            asset_data = sr[asset_search_index]
                            utils.automap(target_object, target_slot=target_slot,
                                          tex_size=asset_data.get('texture_size_meters', 1.0))
                            bpy.ops.scene.blenderkit_download(True,
                                                              asset_type=ui_props.asset_type,
                                                              asset_index=asset_search_index,
                                                              model_location=loc,
                                                              model_rotation=rotation,
                                                              target_object=target_object,
                                                              material_target_slot=target_slot)


                    elif ui_props.asset_type == 'MODEL':
                        if ui_props.has_hit and ui_props.dragging:
                            loc = ui_props.snapped_location
                            rotation = ui_props.snapped_rotation
                        else:
                            loc = s.cursor.location
                            rotation = s.cursor.rotation_euler

                        bpy.ops.scene.blenderkit_download(True,
                                                          asset_type=ui_props.asset_type,
                                                          asset_index=asset_search_index,
                                                          model_location=loc,
                                                          model_rotation=rotation,
                                                          target_object=target_object)

                    else:
                        bpy.ops.scene.blenderkit_download(asset_type=ui_props.asset_type,
                                                          asset_index=asset_search_index)

                    ui_props.dragging = False
                    return {'RUNNING_MODAL'}
            else:
                return {'RUNNING_MODAL'}

        if event.type == 'W' and ui_props.active_index > -1:
            sr = bpy.context.scene['search results']
            asset_data = sr[ui_props.active_index]
            a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
            if a is not None:
                utils.p('author:', a)
                if a.get('aboutMeUrl') is not None:
                    bpy.ops.wm.url_open(url=a['aboutMeUrl'])
            return {'RUNNING_MODAL'}
        if event.type == 'A' and ui_props.active_index > -1:
            sr = bpy.context.scene['search results']
            asset_data = sr[ui_props.active_index]
            a = asset_data['author']['id']
            if a is not None:
                sprops = utils.get_search_props()
                sprops.search_keywords = ''
                sprops.search_verification_status = 'ALL'
                utils.p('author:', a)
                search.search(author_id=a)
            return {'RUNNING_MODAL'}

        if event.type == 'X' and ui_props.active_index > -1:
            # delete downloaded files for this asset
            sr = bpy.context.scene['search results']
            asset_data = sr[ui_props.active_index]
            print(asset_data['name'])
            print('delete')
            paths.delete_asset_debug(asset_data)
            asset_data['downloaded'] = 0
            return {'RUNNING_MODAL'}
        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        # FIRST START SEARCH
        ui_props = context.scene.blenderkitUI

        if self.do_search:
            # we erase search keywords for cateogry search now, since these combinations usually return nothing now.
            # when the db gets bigger, this can be deleted.
            if self.category != '':
                sprops = utils.get_search_props()
                sprops.search_keywords = ''
            search.search(category=self.category)

        if ui_props.assetbar_on:
            # we don't want to run the assetbar many times, that's why it has a switch on/off behaviour,
            # unless being called with 'keep_running' prop.
            if not self.keep_running:
                # this sends message to the originally running operator, so it quits, and then it ends this one too.
                # If it initiated a search, the search will finish in a thread. The switch off procedure is run
                # by the 'original' operator, since if we get here, it means
                # same operator is already running.
                ui_props.turn_off = True
                # if there was an error, we need to turn off these props so we can restart after 2 clicks
                ui_props.assetbar_on = False

            else:
                pass
            return {'FINISHED'}

        ui_props.dragging = False  # only for cases where assetbar ended with an error.
        ui_props.assetbar_on = True
        ui_props.turn_off = False

        sr = bpy.context.scene.get('search results')
        if sr is None:
            bpy.context.scene['search results'] = []

        if context.area.type != 'VIEW_3D':
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

        # the arguments we pass the the callback
        args = (self, context)

        self.window = context.window
        self.area = context.area
        self.scene = bpy.context.scene

        self.has_quad_views = len(bpy.context.area.spaces[0].region_quadviews) > 0

        for r in self.area.regions:
            if r.type == 'WINDOW':
                self.region = r

        global active_window, active_area, active_region
        active_window = self.window
        active_area = self.area
        active_region = self.region

        update_ui_size(self.area, self.region)

        self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')
        self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')

        context.window_manager.modal_handler_add(self)
        ui_props.assetbar_on = True
        return {'RUNNING_MODAL'}

    def execute(self, context):
        return {'RUNNING_MODAL'}


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.keys():
                    target_ob[k] = source_ob[k]
        source_ob.property_unset('blenderkit')
        return {'FINISHED'}


class UndoWithContext(bpy.types.Operator):
    """Regenerate cobweb"""
    bl_idname = "wm.undo_push_context"
    bl_label = "BlnenderKit undo push"
    bl_description = "BlenderKit undo push with fixed context"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

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

    message: StringProperty('Undo Message', default='BlenderKit operation')

    def execute(self, context):
        C_dict = utils.get_fake_context(context)
        #w, a, r = get_largest_area(area_type=area_type)
        # wm = bpy.context.window_manager#bpy.data.window_managers[0]
        # w = wm.windows[0]
        #
        # C_dict = {'window': w, 'screen': w.screen}
        # bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message)
        bpy.ops.ed.undo_push( 'INVOKE_REGION_WIN', message=self.message)
        return {'FINISHED'}



class RunAssetBarWithContext(bpy.types.Operator):
    """Regenerate cobweb"""
    bl_idname = "object.run_assetbar_fix_context"
    bl_label = "BlnenderKit assetbar with fixed context"
    bl_description = "Run assetbar with fixed context"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

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

    def execute(self, context):
        C_dict = utils.get_fake_context(context)
        bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False)
        return {'FINISHED'}


classess = (
    AssetBarOperator,
    RunAssetBarWithContext,
    TransferBlenderkitData,
    UndoWithContext
)

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


# @persistent
def pre_load(context):
    ui_props = bpy.context.scene.blenderkitUI
    ui_props.assetbar_on = False
    ui_props.turn_off = True
    preferences = bpy.context.preferences.addons['blenderkit'].preferences
    preferences.login_attempt = False


def register_ui():
    global handler_2d, handler_3d

    for c in classess:
        bpy.utils.register_class(c)

    args = (None, bpy.context)

    handler_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d_progress, args, 'WINDOW', 'POST_PIXEL')
    handler_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_progress, args, 'WINDOW', 'POST_VIEW')

    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(AssetBarOperator.bl_idname, '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(ratings.FastRateMenu.bl_idname, 'F', 'PRESS', ctrl=False, shift=False)
    addon_keymapitems.append(kmi)


def unregister_ui():
    global handler_2d, handler_3d
    pre_load(bpy.context)

    bpy.types.SpaceView3D.draw_handler_remove(handler_2d, 'WINDOW')
    bpy.types.SpaceView3D.draw_handler_remove(handler_3d, 'WINDOW')

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

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

    km = wm.keyconfigs.addon.keymaps['Window']
    for kmi in addon_keymapitems:
        km.keymap_items.remove(kmi)
    del addon_keymapitems[:]