#  ***** 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 CG libs database for Robossembler Framework.
'''

__version__ = '1.0'

import logging
import json
import math
import os
import shutil

import bpy

from .utils.collection_tools import remove_collections_with_objects
from .utils.cleanup_orphan_data import cleanup_orphan_data
from .utils.object_transforms import apply_transforms
from .material_generators import material_generator, black_material
from .utils.object_converter import mesh_to_mesh
from .export import dae, fbx, glb, ply

logger = logging.getLogger(__name__)


def generate_libs_database(project_dir):
    ''' Generate Blender LIBS database from PARTs database. '''
    parts_data_path = os.path.join(project_dir, 'parts.json')
    if not os.path.exists(parts_data_path):
        raise Exception('No parts database found! Check %s directory' % project_dir)
    with open(parts_data_path, encoding='utf-8') as data:
        parts_data = json.load(data)

    libs_data = []

    # material libs
    materials_dir = os.path.join(project_dir, 'libs', 'materials')
    if os.path.exists(materials_dir):
        shutil.rmtree(materials_dir)
        os.makedirs(materials_dir)
    else:
        os.makedirs(materials_dir)

    for part in parts_data:
        bpy.ops.wm.read_homefile()
        obj = bpy.data.objects['Cube']
        if not part.get('material_path'):
            continue
        material_name = os.path.splitext(os.path.basename(part['material_path']))[0]
        # do not regenerate exists material
        material_in_libs = False
        for lib in libs_data:
            if lib['type'] == 'MATERIAL' and lib['name'] == material_name:
                material_in_libs = True
                break
        if material_in_libs:
            continue
        # generate material
        mat = material_generator(obj, os.path.join(project_dir, part['material_path']))
        bpy.data.materials[mat.name].use_fake_user = True
        remove_collections_with_objects()
        cleanup_orphan_data()
        bpy.data.materials.remove(bpy.data.materials['Dots Stroke'])
        bpy.ops.wm.previews_ensure()

        img = bpy.data.images.new(mat.name, *mat.preview.image_size, alpha=True)
        img.pixels.foreach_set(mat.preview.image_pixels_float)
        img_path = os.path.join(materials_dir, mat.name + '.png')
        img.save(filepath=img_path)

        blend_path = os.path.join(materials_dir, mat.name + '.blend')
        logger.info('Material stored as libs %s to %s.', mat.name, materials_dir)
        bpy.ops.wm.save_as_mainfile(filepath=blend_path, compress=True)

        libs_data.append(
            {
                'type': mat.rna_type.name.upper(),
                'name': mat.name,
                'path': os.path.relpath(blend_path, project_dir),
                'thumbnail': os.path.relpath(img_path, project_dir),
            }
        )

    # object libs
    objects_dir = os.path.join(project_dir, 'libs', 'objects')
    if os.path.exists(objects_dir):
        shutil.rmtree(objects_dir)
        os.makedirs(objects_dir)
    else:
        os.makedirs(objects_dir)

    for part in parts_data:
        bpy.ops.wm.read_homefile()
        remove_collections_with_objects()
        cleanup_orphan_data()
        obj_collection = bpy.data.collections.new(part['name'])
        bpy.context.scene.collection.children.link(obj_collection)
        # set active to collection
        active_collection = bpy.context.view_layer.layer_collection.children[part['name']]
        bpy.context.view_layer.active_layer_collection = active_collection
        # import stl file
        bpy.ops.wm.stl_import(
            filepath=os.path.join(project_dir, part['part_path']),
            global_scale=0.001,
            use_facet_normal=False,
            forward_axis='Y',
            up_axis='Z')
        obj = bpy.data.objects.get(part['name'])
        if not obj:
            raise Exception('STL Import Fail for %s' % part['name'])
        apply_transforms(obj, scale=True)
        # setup material
        if part.get('material_path'):
            material_name = os.path.splitext(os.path.basename(part['material_path']))[0]
            for lib in libs_data:
                if lib['type'] == 'MATERIAL' and lib['name'] == material_name:
                    lib_path = os.path.join(project_dir, lib['path'])
                    bpy.ops.wm.link(
                        filepath=lib_path,
                        directory=os.path.join(lib_path, 'Material'),
                        filename=lib['name'],
                        relative_path=True,
                        do_reuse_local_id=True)
                    obj.data.materials.append(bpy.data.materials[lib['name']])
        else:
            black_material(obj)

        with bpy.context.temp_override(selected_editable_objects=[obj]):
            bpy.ops.object.shade_smooth_by_angle(angle=math.radians(12))
        obj.modifiers.new(
            type='WEIGHTED_NORMAL', name='weighted_normal').keep_sharp = True
        obj = mesh_to_mesh(obj)

        obj.asset_mark()
        obj.asset_generate_preview()
        img = bpy.data.images.new(part['name'], *obj.preview.image_size, alpha=True)
        img.pixels.foreach_set(obj.preview.image_pixels_float)
        img_path = os.path.join(objects_dir, part['name'] + '.png')
        img.save(filepath=img_path)
        obj.asset_clear()

        blend_path = os.path.join(objects_dir, part['name'] + '.blend')
        logger.info('Object stored as libs %s to %s.', part['name'], objects_dir)
        bpy.ops.wm.save_as_mainfile(filepath=blend_path, compress=True)

        libs_data.append(
            {
                'type': obj.rna_type.name.upper(),
                'name': part['name'],
                'path': os.path.relpath(blend_path, project_dir),
                'dae': os.path.relpath(
                    dae.export(obj_name=part['name'], file_dir=objects_dir),
                    project_dir),
                'fbx': os.path.relpath(
                    fbx.export(obj_name=part['name'], file_dir=objects_dir,
                               axis_forward='-Z', axis_up='Y'),
                    project_dir),
                'glb': os.path.relpath(
                    glb.export(obj_name=part['name'], file_dir=objects_dir),
                    project_dir),
                'ply': os.path.relpath(
                    ply.export(obj_name=part['name'], file_dir=objects_dir),
                    project_dir),
                'thumbnail': os.path.relpath(img_path, project_dir),
            }
        )

    # write db file
    libs_data_path = os.path.join(project_dir, 'libs.json')
    with open(libs_data_path, 'w', encoding='utf-8') as libs_data_file:
        json.dump(libs_data, libs_data_file, ensure_ascii=False, indent=4)
    logger.info('Database saved successfully to %s!', libs_data_path)

    return libs_data_path
