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


from blenderkit import utils

import bpy
from object_print3d_utils import operators as ops

RENDER_OBTYPES = ['MESH', 'CURVE', 'SURFACE', 'METABALL', 'TEXT']


def check_material(props, mat):
    e = bpy.context.scene.render.engine
    shaders = []
    textures = []
    props.texture_count = 0
    props.node_count = 0
    props.total_megapixels = 0
    props.is_procedural = True

    if e == 'CYCLES':

        if mat.node_tree is not None:
            checknodes = mat.node_tree.nodes[:]
            while len(checknodes) > 0:
                n = checknodes.pop()
                props.node_count += 1
                if n.type == 'GROUP':  # dive deeper here.
                    checknodes.extend(n.node_tree.nodes)
                if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
                    if n.type not in shaders:
                        shaders.append(n.type)
                if n.type == 'TEX_IMAGE':

                    if n.image is not None:
                        mattype = 'image based'
                        props.is_procedural = False
                        if n.image not in textures:
                            textures.append(n.image)
                            props.texture_count += 1
                            props.total_megapixels += (n.image.size[0] * n.image.size[1])

                            maxres = max(n.image.size[0], n.image.size[1])
                            props.texture_resolution_max = max(props.texture_resolution_max, maxres)
                            minres = min(n.image.size[0], n.image.size[1])
                            if props.texture_resolution_min == 0:
                                props.texture_resolution_min = minres
                            else:
                                props.texture_resolution_min = min(props.texture_resolution_min, minres)

    props.shaders = ''
    for s in shaders:
        if s.startswith('BSDF_'):
            s = s[5:]
        s = s.lower().replace('_', ' ')
        props.shaders += (s + ', ')


def check_render_engine(props, obs):
    ob = obs[0]
    m = None

    e = bpy.context.scene.render.engine
    mattype = None
    materials = []
    shaders = []
    textures = []
    props.uv = False
    props.texture_count = 0
    props.total_megapixels = 0
    props.node_count = 0
    for ob in obs:  # TODO , this is duplicated here for other engines, otherwise this should be more clever.
        for ms in ob.material_slots:
            if ms.material is not None:
                m = ms.material
                if m.name not in materials:
                    materials.append(m.name)
        if ob.type == 'MESH' and len(ob.data.uv_layers) > 0:
            props.uv = True

    if e == 'BLENDER_RENDER':
        props.engine = 'BLENDER_INTERNAL'
    elif e == 'CYCLES':

        props.engine = 'CYCLES'

        for mname in materials:
            m = bpy.data.materials[mname]
            if m is not None and m.node_tree is not None:
                checknodes = m.node_tree.nodes[:]
                while len(checknodes) > 0:
                    n = checknodes.pop()
                    props.node_count +=1
                    if n.type == 'GROUP':  # dive deeper here.
                        checknodes.extend(n.node_tree.nodes)
                    if len(n.outputs) == 1 and n.outputs[0].type == 'SHADER' and n.type != 'GROUP':
                        if n.type not in shaders:
                            shaders.append(n.type)
                    if n.type == 'TEX_IMAGE':


                        if n.image is not None and n.image not in textures:
                            props.is_procedural = False
                            mattype = 'image based'

                            textures.append(n.image)
                            props.texture_count += 1
                            props.total_megapixels += (n.image.size[0] * n.image.size[1])

                            maxres = max(n.image.size[0], n.image.size[1])
                            props.texture_resolution_max = max(props.texture_resolution_max, maxres)
                            minres = min(n.image.size[0], n.image.size[1])
                            if props.texture_resolution_min == 0:
                                props.texture_resolution_min = minres
                            else:
                                props.texture_resolution_min = min(props.texture_resolution_min, minres)


        # if mattype == None:
        #    mattype = 'procedural'
        # tags['material type'] = mattype

    elif e == 'BLENDER_GAME':
        props.engine = 'BLENDER_GAME'

    # write to object properties.
    props.materials = ''
    props.shaders = ''
    for m in materials:
        props.materials += (m + ', ')
    for s in shaders:
        if s.startswith('BSDF_'):
            s = s[5:]
        s = s.lower()
        s = s.replace('_', ' ')
        props.shaders += (s + ', ')


def check_printable(props, obs):
    if len(obs) == 1:
        check_cls = (
            ops.Print3DCheckSolid,
            ops.Print3DCheckIntersections,
            ops.Print3DCheckDegenerate,
            ops.Print3DCheckDistorted,
            ops.Print3DCheckThick,
            ops.Print3DCheckSharp,
            # ops.Print3DCheckOverhang,
        )

        ob = obs[0]

        info = []
        for cls in check_cls:
            cls.main_check(ob, info)

        printable = True
        for item in info:
            passed = item[0].endswith(' 0')
            if not passed:
                # print(item[0])
                printable = False

        props.printable_3d = printable


def check_rig(props, obs):
    for ob in obs:
        if ob.type == 'ARMATURE':
            props.rig = True


def check_anim(props, obs):
    animated = False
    for ob in obs:
        if ob.animation_data is not None:
            a = ob.animation_data.action
            if a is not None:
                for c in a.fcurves:
                    if len(c.keyframe_points) > 1:
                        animated = True

                        # c.keyframe_points.remove(c.keyframe_points[0])
    if animated:
        props.animated = True


def check_meshprops(props, obs):
    ''' checks polycount, manifold, mesh parts (not implemented)'''
    fc = 0
    fcr = 0
    tris = 0
    quads = 0
    ngons = 0
    vc = 0

    edges_counts = {}
    manifold = True

    for ob in obs:
        if ob.type == 'MESH' or ob.type == 'CURVE':
            ob_eval = None
            if ob.type == 'CURVE':
                # depsgraph = bpy.context.evaluated_depsgraph_get()
                # object_eval = ob.evaluated_get(depsgraph)
                mesh = ob.to_mesh()
            else:
                mesh = ob.data
            fco = len(mesh.polygons)
            fc += fco
            vc += len(mesh.vertices)
            fcor = fco
            for f in mesh.polygons:
                # face sides counter
                if len(f.vertices) == 3:
                    tris += 1
                elif len(f.vertices) == 4:
                    quads += 1
                elif len(f.vertices) > 4:
                    ngons += 1

                # manifold counter
                for i, v in enumerate(f.vertices):
                    v1 = f.vertices[i - 1]
                    e = (min(v, v1), max(v, v1))
                    edges_counts[e] = edges_counts.get(e, 0) + 1

            # all meshes have to be manifold for this to work.
            manifold = manifold and not any(i in edges_counts.values() for i in [0, 1, 3, 4])

            for m in ob.modifiers:
                if m.type == 'SUBSURF' or m.type == 'MULTIRES':
                    fcor *= 4 ** m.render_levels
                if m.type == 'SOLIDIFY':  # this is rough estimate, not to waste time with evaluating all nonmanifold edges
                    fcor *= 2
                if m.type == 'ARRAY':
                    fcor *= m.count
                if m.type == 'MIRROR':
                    fcor *= 2
                if m.type == 'DECIMATE':
                    fcor *= m.ratio
            fcr += fcor

            if ob_eval:
                ob_eval.to_mesh_clear()

    # write out props
    props.face_count = fc
    props.face_count_render = fcr
    # print(tris, quads, ngons)
    if quads > 0 and tris == 0 and ngons == 0:
        props.mesh_poly_type = 'QUAD'
    elif quads > tris and quads > ngons:
        props.mesh_poly_type = 'QUAD_DOMINANT'
    elif tris > quads and tris > quads:
        props.mesh_poly_type = 'TRI_DOMINANT'
    elif quads == 0 and tris > 0 and ngons == 0:
        props.mesh_poly_type = 'TRI'
    elif ngons > quads and ngons > tris:
        props.mesh_poly_type = 'NGON'
    else:
        props.mesh_poly_type = 'OTHER'

    props.manifold = manifold


def countObs(props, obs):
    ob_types = {}
    count = len(obs)
    for ob in obs:
        otype = ob.type.lower()
        ob_types[otype] = ob_types.get(otype, 0) + 1
    props.object_count = count


def check_modifiers(props, obs):
    # modif_mapping = {
    # }
    modifiers = []
    for ob in obs:
        for m in ob.modifiers:
            mtype = m.type
            mtype = mtype.replace('_', ' ')
            mtype = mtype.lower()
            # mtype = mtype.capitalize()
            if mtype not in modifiers:
                modifiers.append(mtype)
            if m.type == 'SMOKE':
                if m.smoke_type == 'FLOW':
                    smt = m.flow_settings.smoke_flow_type
                    if smt == 'BOTH' or smt == 'FIRE':
                        modifiers.append('fire')

    # for mt in modifiers:
    effectmodifiers = ['soft body', 'fluid simulation', 'particle system', 'collision', 'smoke', 'cloth',
                       'dynamic paint']
    for m in modifiers:
        if m in effectmodifiers:
            props.simulation = True
    if ob.rigid_body is not None:
        props.simulation = True
        modifiers.append('rigid body')
    finalstr = ''
    for m in modifiers:
        finalstr += m
        finalstr += ','
    props.modifiers = finalstr


def get_autotags():
    """ call all analysis functions """
    ui = bpy.context.scene.blenderkitUI
    if ui.asset_type == 'MODEL':
        ob = utils.get_active_model()
        obs = utils.get_hierarchy(ob)
        props = ob.blenderkit
        if props.name == "":
            props.name = ob.name

        # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.
        props.texture_resolution_max = 0
        props.texture_resolution_min = 0
        # disabled printing checking, some 3d print addon bug.
        # check_printable( props, obs)
        check_render_engine(props, obs)

        dim, bbox_min, bbox_max = utils.get_dimensions(obs)
        props.dimensions = dim
        props.bbox_min = bbox_min
        props.bbox_max = bbox_max

        check_rig(props, obs)
        check_anim(props, obs)
        check_meshprops(props, obs)
        check_modifiers(props, obs)
        countObs(props, obs)
    elif ui.asset_type == 'MATERIAL':
        # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.

        mat = utils.get_active_asset()
        props = mat.blenderkit
        props.texture_resolution_max = 0
        props.texture_resolution_min = 0
        check_material(props, mat)
    elif ui.asset_type == 'HDR':
        # reset some properties here, because they might not get re-filled at all when they aren't needed anymore.

        hdr = utils.get_active_asset()
        props = hdr.blenderkit
        props.texture_resolution_max = max(hdr.size[0],hdr.size[1])


class AutoFillTags(bpy.types.Operator):
    """Fill tags for asset. Now run before upload, no need to interact from user side"""
    bl_idname = "object.blenderkit_auto_tags"
    bl_label = "Generate Auto Tags for BlenderKit"
    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}

    @classmethod
    def poll(cls, context):
        return utils.uploadable_asset_poll()

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


def register_asset_inspector():
    bpy.utils.register_class(AutoFillTags)


def unregister_asset_inspector():
    bpy.utils.unregister_class(AutoFillTags)


if __name__ == "__main__":
    register()
