#  ***** BEGIN GPL LICENSE BLOCK *****
#
#   Copyright (C) 2021-2024 Robossembler LLC
#
#   Created by Ilia Kurochkin (brothermechanic)
#   contact:    brothermechanic@yandex.com
#
#   This file is part of Robossembler Framework
#   project repo:    https://gitlab.com/robossembler/framework
#
#   Robossembler Framework 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 3
#   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, see <https://www.gnu.org/licenses/>.
#
#  ***** END GPL LICENSE BLOCK *****
#
# coding: utf-8
'''
DESCRIPTION.
Generate render asset from assembly tree and CG libs database.
'''

__version__ = '1.0'

import logging
import json
import os
import random
import shutil

import bpy
from mathutils import Vector, Matrix

from .utils.collection_tools import remove_collections_with_objects
from .utils.cleanup_orphan_data import cleanup_orphan_data
from .utils.object_relations import unparenting, parenting
from .utils.object_transforms import round_transforms

logger = logging.getLogger(__name__)


def recursive_layer_collection(layer_coll, coll_name):
    found = None
    if layer_coll.name == coll_name:
        return layer_coll
    for layer in layer_coll.children:
        found = recursive_layer_collection(layer, coll_name)
        if found:
            return found
    return False


def assembly_builder(item, libs_data, libs_data_dir, collection=None, parent=None):
    '''
    '''
    if not collection:
        collection = bpy.context.scene.collection

    item_obj = None

    if item['type'] == 'LOCATOR':
        item_obj = bpy.data.objects.new(item['name'], None)
        item_obj.empty_display_type = 'CUBE'
        item_obj.empty_display_size = 0.0005
        collection.objects.link(item_obj)

    elif item['type'] == 'LCS':
        item_obj = bpy.data.objects.new(item['name'], None)
        item_obj.empty_display_type = 'ARROWS'
        item_obj.empty_display_size = round(random.uniform(0.05, 0.15), 3)
        item_obj.show_in_front = True
        collection.objects.link(item_obj)

    elif item['type'] == 'PART':
        item_data = [
            data for data in libs_data
            if data['type'] == 'OBJECT'
            if data['name'] == item['base_name']]
        if not item_data:
            logger.error('No %s in database!', item['base_name'])
            return False
        # if there is local base_named object in scene -> rename it
        local_obj = None
        # for clones
        if item['base_name'] != item['name']:
            local_obj = [
                loc for loc in bpy.data.objects
                if loc.name == item['base_name']
                if not loc.library]
            if local_obj:
                local_obj[0].name += '_loc'
        item_file_path = os.path.join(libs_data_dir, item_data[0]['path'])
        # TODO already linked
        bpy.ops.wm.link(
            filepath=item_file_path,
            directory=os.path.join(item_file_path, 'Collection'),
            filename=item['base_name'],
            relative_path=True,
            do_reuse_local_id=True,
            active_collection=True,
        )
        item_obj = bpy.data.objects[item['base_name']]
        # for clones
        if item['base_name'] != item['name']:
            item_obj.name = item['name']
            # rename local back
            if local_obj:
                local_obj[0].name = item['base_name']
        item_obj.empty_display_type = 'PLAIN_AXES'
        item_obj.empty_display_size = 0.0005

    else:
        logger.error('Unknown object type %s of %s', item['type'], item['name'])
        return False

    item_obj.location = Vector(item['pose'][0]['loc_xyz']) * 0.001
    item_obj.rotation_mode = 'QUATERNION'
    item_obj.rotation_quaternion = [
        item['pose'][1]['rot_xyzw'][3]] + item['pose'][1]['rot_xyzw'][:3]

    if item.get('attributes'):
        for attr in item['attributes']:
            item_obj[list(attr)[0]] = attr[list(attr)[0]]

    item_obj.parent = parent

    if item.get('children'):
        for child_item in item.get('children'):
            assembly_builder(
                child_item, libs_data, libs_data_dir, collection, parent=item_obj)

    return True


def assembly_rebuilder():
    ''' Restructure assembling hierarchy. '''
    default_origin = [
        lcs for lcs in bpy.data.objects
        if lcs.type == 'EMPTY'
        if lcs.get('Robossembler_SocketFlow') == 'inlet'
        if lcs.get('Robossembler_DefaultOrigin')]
    if not default_origin:
        raise Exception('Default Origin not found!')
    if len(default_origin) > 1:
        raise Exception('Several Default Origins do not supported!')
    default_origin = default_origin[0]

    root_locator = [
        obj for obj in bpy.data.objects
        if not obj.parent
        if not obj.library]
    for i in root_locator:
        print(i.name)
    if len(root_locator) > 1:
        raise Exception('Render asset should consist of only one hierarchy!')
    root_locator = root_locator[0]

    # retree_by lcs
    for lcs in bpy.data.objects:
        if lcs.type == 'EMPTY':
            if lcs.get('Robossembler_SocketFlow'):
                unparenting(lcs)
                round_transforms(lcs)

    parenting(default_origin, root_locator)

    for lcs in bpy.data.objects:
        if lcs.type == 'EMPTY':
            if lcs.get('Robossembler_SocketFlow'):
                if lcs != default_origin:
                    parenting(default_origin, lcs)

    default_origin.matrix_world = Matrix()

    logger.info('Restructuring assembling hierarchy finished!')

    return default_origin


def build_render_assets(project_dir):
    '''
    '''
    assets_data = []

    libs_data_path = os.path.join(project_dir, 'libs.json')
    if not os.path.exists(libs_data_path):
        raise Exception('No libs database found! Check %s directory' % project_dir)
    with open(libs_data_path, encoding='utf-8') as data:
        libs_data = json.load(data)
    trees_path = os.path.join(project_dir, 'trees.json')
    if not os.path.exists(trees_path):
        raise Exception('No trees database found! Check %s directory' % project_dir)
    with open(trees_path, encoding='utf-8') as data:
        tree_item_list = json.load(data)

    # render assets dir
    render_assets_dir = os.path.join(project_dir, 'assets', 'render')
    if os.path.exists(render_assets_dir):
        shutil.rmtree(render_assets_dir)
        os.makedirs(render_assets_dir)
    else:
        os.makedirs(render_assets_dir)

    for tree_item in tree_item_list:
        # start from stratch
        bpy.ops.wm.read_homefile()
        remove_collections_with_objects()
        cleanup_orphan_data()

        # create redner collection
        render_collection = bpy.data.collections.new(tree_item['name'])
        bpy.context.scene.collection.children.link(render_collection)
        active_collection = recursive_layer_collection(
            bpy.context.view_layer.layer_collection,
            render_collection.name)
        bpy.context.view_layer.active_layer_collection = active_collection

        # build original hierarchy
        assembly_builder(tree_item, libs_data, project_dir, render_collection)
        # rebuild to LCS hierarchy
        assembly_rebuilder()

        # mark as asset
        render_collection.asset_mark()
        # TODO collection thumbnail

        blend_path = os.path.join(render_assets_dir, tree_item['name'] + '.blend')
        bpy.ops.wm.save_as_mainfile(filepath=blend_path)
        logger.info('Render asset %s generated!', tree_item['name'])

        assets_data.append(
            {
                'type': 'RENDER',
                'name': tree_item['name'],
                'path': os.path.relpath(blend_path, project_dir),
                'thumbnail': ''
            }
        )
    logger.info('%s Render Assets was generated!', len(tree_item_list))
    # write db file
    assets_data_path = os.path.join(project_dir, 'assets.json')
    with open(assets_data_path, 'w', encoding='utf-8') as assets_data_file:
        json.dump(assets_data, assets_data_file, ensure_ascii=False, indent=4)
    logger.info('Database saved successfully to %s!', assets_data_path)

    return render_assets_dir
