# ##### 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 version 2
#  as published by the Free Software Foundation.
#
#  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.


"""
This script imports a MSH2 files to Blender.

Usage:
Run this script from "File->Import" menu and then load the desired MSH2 file.
Note, This loads mesh objects and materials only, nurbs and curves are not supported.

https://schlechtwetterfront.github.io/ze_filetypes/msh.html
"""

import bpy
from .msh2 import msh2
msh_handler = msh2.MSH2(None)



from .msh2 import msh2_unpack
from .msh2 import msh2_crc



class MshImportError(Exception):

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return str(self.msg)


# class MaterialBuilder(object):
#
#     def __init__(self, imp, msh):
#         self.msh = msh
#         self.imp = imp
#         # self.xsi = self.imp.xsi
#         # self.pb = self.imp.pb
#
#     def build(self):
#         logging.info('Building {0} materials.'.format(len(self.msh.materials)))
#         coll = self.msh.materials
#         if len(coll) < 1:
#             logging.debug('No materials in collection.')
#             return ()
#         matlib = self.xsi.ActiveProject.ActiveScene.ActiveMaterialLibrary
#         materials = {}
#         for mat in coll:
#             logging.info('Building Material {0}.'.format(mat.name))
#             simat = matlib.CreateMaterial('Phong', mat.name)
#             # Colors.
#             shader = simat.Shaders(0)
#             col = shader.Parameters('diffuse').Value
#             col.Red = mat.diff_color.red
#             col.Green = mat.diff_color.green
#             col.Blue = mat.diff_color.blue
#             col.Alpha = mat.diff_color.alpha
#
#             col = shader.Parameters('ambient').Value
#             col.Red = mat.ambt_color.red
#             col.Green = mat.ambt_color.green
#             col.Blue = mat.ambt_color.blue
#             col.Alpha = mat.ambt_color.alpha
#
#             col = shader.Parameters('specular').Value
#             col.Red = mat.spec_color.red
#             col.Green = mat.spec_color.green
#             col.Blue = mat.spec_color.blue
#             col.Alpha = mat.spec_color.alpha
#
#             shader.Parameters('shiny').Value = mat.gloss
#
#             # Image.
#             if mat.tex0:
#                 if self.imp.config.get('btexpath'):
#                     imgfolder = self.imp.config.get('texpath')
#                 else:
#                     imgfolder = os.path.dirname(self.imp.config.get('path'))
#                 imgshader = self.xsi.CreateShaderFromPreset('$XSI_DSPRESETS\\Shaders\\Texture\\Image.Preset', simat)
#                 imgpath = os.path.join(imgfolder, mat.tex0)
#                 img_clip = self.xsi.SICreateImageClip2(imgpath)
#                 self.xsi.SIConnectShaderToCnxPoint(img_clip, imgshader.tex, False)
#                 self.xsi.SIConnectShaderToCnxPoint(imgshader, simat.Shaders(0).Parameters('diffuse'), False)
#             # ZEFlags.
#             simat2 = self.get_si_mat(mat)
#             if simat2:
#                 self.add_flag_prop(simat2, mat)
#             materials[mat.name] = simat
#             logging.info('Finished building {0}.'.format(mat.name))
#         logging.info('Finished building materials.')
#         return materials
#
#     def get_si_mat(self, mat):
#         name = mat.name
#         mats = self.imp.material_name_dict()
#         try:
#             mat = mats[name]
#             return mat
#         except KeyError:
#             logging.exception('Couldnt find material {0}.'.format(name))
#             return None
#
#     def add_flag_prop(self, simat, mat):
#         pset = simat.AddProperty('CustomProperty', False, 'ZeroEngine Flags')
#         pset.AddParameter3('tex1', const.siString, mat.tex1)
#         pset.AddParameter3('tex2', const.siString, mat.tex2)
#         pset.AddParameter3('tex3', const.siString, mat.tex3)
#
#         if mat.flags[4][1]:
#             transp = 2
#         elif mat.flags[5][1]:
#             transp = 1
#         else:
#             transp = 0
#         pset.AddParameter3('emissive', const.siBool, mat.flags[7][1], '', '', 0)
#         pset.AddParameter3('glow', const.siBool, mat.flags[6][1], '', '', 0)
#         pset.AddParameter3('transparency', const.siInt4, transp, 0, 2, 0)
#         pset.AddParameter3('hardedged', const.siBool, mat.flags[3][1], '', '', 0)
#         pset.AddParameter3('perpixel', const.siBool, mat.flags[2][1], '', '', 0)
#         pset.AddParameter3('additive', const.siBool, mat.flags[1][1], '', '', 0)
#         pset.AddParameter3('specular', const.siBool, mat.flags[0][1], '', '', 0)
#         pset.AddParameter3('rendertype', const.siInt4, mat.render_type, 0, 31, 0)
#         pset.AddParameter3('data0', const.siInt4, mat.data0, 0, 255, 0)
#         pset.AddParameter3('data1', const.siInt4, mat.data1, 0, 255, 0)
#
#         lay = pset.PPGLayout
#
#         lay.AddGroup('ZeroEngine Material Flags', 1)
#         lay.AddGroup('Additional Textures', 1)
#         lay.AddItem('tex1', 'Texture 1')
#         lay.AddItem('tex2', 'Texture 2')
#         lay.AddItem('tex3', 'Texture 3')
#         lay.EndGroup()
#         lay.AddGroup('Flags', 1)
#         lay.AddItem('emissive', 'Emissive')
#         lay.AddItem('glow', 'Glow')
#         lay.AddItem('transparency', 'Transparency')
#         lay.AddItem('hardedged', 'Hardedged Transparency')
#         lay.AddItem('perpixel', 'Per-Pixel Lighting')
#         lay.AddItem('additive', 'Additive Transparency')
#         lay.AddItem('specular', 'Specular')
#         lay.AddItem('rendertype', 'RenderType')
#         lay.AddItem('data0', 'Data0')
#         lay.AddItem('data1', 'Data1')
#         lay.EndGroup()
#         lay.EndGroup()















class ChainItemBuilder:
    prim_types = {0: 'Sphere',
                  1: 'Sphere',
                  2: 'Cylinder',
                  4: 'Cube'}
    cloth_prim_types = {0: 'Sphere',
                        1: 'Cylinder',
                        2: 'Cube'}

    def __init__(self, model, chainbuilder):
        self.model = model
        # Will be set in build() after the model is built.
        self.si_model = None
        self.chainbuilder = chainbuilder
        self.geo = None

    def build(self):
        '''Builds an object depending on the model type.'''
        if 'geo' in self.model.model_type:
            if self.model.collprim:
                pass
                # self.build_prim()
            elif self.model.name.startswith(b'c_'): # and self.get_c_prim_data():
                pass
                # self.build_c_prim()
            else:
                self.build_geo()

        # if self.model.model_type == 'null' or self.model.model_type == 'bone':
        #     self.build_null()
        # elif self.model.model_type == 'geoshadow':
        #     self.build_shadow()
        # elif 'geo' in self.model.model_type:
        #     if self.model.collprim:
        #         self.build_prim()
        #     elif self.model.name.startswith('c_') and self.get_c_prim_data():
        #         self.build_c_prim()
        #     else:
        #         self.build_geo()
        # elif self.model.model_type == 'cloth':
        #     self.build_cloth()
        # else:
        #     raise MshImportError('Unsupported model type {0} on {1}.'.format(self.model.model_type,
        #                                                                      self.model.name))
        return self.si_model


    def build_geo(self):
        # logging.info('Building {0} as polymesh(originally {1}).'.format(self.model.name, self.model.model_type))
        # verts = self.get_vertex_positions()
        vertices = self.get_vertex_positions()
        faces = self.get_faces()
        verts = []
        edges = []
        name  = self.model.name.decode("utf-8")

        # print(verts)
        # print(faces)
        # print("")

        mesh2 = bpy.data.meshes.new("{}_obj".format(name))

        # Add the vertices to the mesh
        mesh2.from_pydata(verts, edges, faces)

        # Create an object that uses the mesh data
        myobj = bpy.data.objects.new("{}".format(name), mesh2)

        # Link the object to the scene
        sceen.objects.link(myobj)


        # logging.info('Building {0} as polymesh(originally {1}).'.format(self.model.name, self.model.model_type))

        # vertex_positions = self.get_vertex_positions()
        # faces = self.get_faces()

        # if self.model.parent_name:
        #     parent = self.chainbuilder.name_dict[self.model.parent_name]
        # else:
        #     parent = self.xsi.ActiveSceneRoot
        # if not parent:
        #     logging.error('Cant find parent {0} for {1}.'.format(self.model.parent_name,
        #                                                          self.model.name))
        # if len(vertex_positions) == 0:
        #     logging.info('Building {0} as null (originally {1}) because no geometry information was found.'.format(self.model.name, self.model.model_type))
        #     self.build_null()
        #     return
        # try:
        #     with zetcore.Timer('Built polygon mesh in %ss %sms.'):
        #         self.si_model = parent.AddPolygonMesh(vertex_positions,
        #                                               faces,
        #                                               self.model.name)
        # except com_error:
        #     logging.exception('verts: {0}, faces: {1}, name: {2}.'.format(vertex_positions,
        #                                                                   faces, self.model.name))
        #     self.imp.abort_checklog()
        # self.geo = self.si_model.ActivePrimitive.GetGeometry2(0)
        #
        # # if self.model.vis == 1:
        # #     self.xsi.ToggleVisibility(self.si_model, None, None)
        #
        # self.process_normals()
        # self.process_uvs()
        # self.process_colors()
        #
        # with zetcore.Timer('Creating segments in %ss %sms.'):
        #     if len(self.model.segments) > 1:
        #         logging.debug('Model {0} has {1} segments, creating poly clusters.'.format(self.model.name, len(self.model.segments)))
        #         self.create_poly_clusters()
        #     else:
        #         self.xsi.SIAssignMaterial(self.si_model,
        #                                   self.chainbuilder.materials[self.model.segments[0].material.name])
        #
        # self.set_transform()
        # self.set_vis()


    def get_vertex_positions(self):
        v_pos = []
        for segment in self.model.segments:
            for vert in segment.vertices:
                v_pos.append(vert.pos)
        return v_pos

    def get_faces(self):
        faces    = []
        offset   = 0
        numfaces = 0

        for segment in self.model.segments:
            for face in segment.faces:
                numfaces += 1
                # faces.append(face.sides)
                sii = face.SIindices()
                print(sii)
                if len(sii) == 4:
                    sii = (sii[0] + offset, sii[1] + offset,
                           sii[2] + offset, sii[3] + offset)
                else:
                    sii = (sii[0] + offset, sii[1] + offset, sii[2] + offset)

                print("")
                print(sii)
                faces.append(sii)
            offset += len(segment.vertices)

        return faces



















class ChainBuilder(object):
    def __init__(self, imp, msh, materials=None):
        self.msh = msh
        self.imp = imp
        self.materials = materials
        self.scn = bpy.context.scene
        self.name_dict = {}

    def build(self):
        coll = self.msh.models
        lencoll = len(coll)
        chain = []
        for ind, model in enumerate(coll):
            builder = ChainItemBuilder(model, self)
            item = builder.build()
            self.name_dict[model.name] = item
            chain.append(item)
        return chain


class Import:
    def __init__(self, config = None):
        self.msh = None
        self.config = config

    def do_import(self, path):
        '''Actual import function.'''
        unpacker = msh2_unpack.MSHUnpack(path)

        try:
            self.msh = unpacker.unpack()
        except (msh2_crc.CRCError, msh2_unpack.UnpackError):
            pass




        ignoregeo  = False
        ignoreanim = True
        if not ignoregeo:
            # matbuilder = MaterialBuilder(self, self.msh)
            # materials = matbuilder.build()
            materials = {}
            builder   = ChainBuilder(self, self.msh, materials)
        else:
            builder   = ChainBuilder(self, self.msh)

        try:
            self.chain = builder.build()
        except MshImportError:
            enveloper  = Enveloper(self, self.msh, self.chain)
            enveloper.envelope()

        if not self.msh.animation.empty and not ignoreanim:
            anim = AnimationImport(self, self.chain, self.msh.animation.bones)
            anim.import_()


def load(context,
         filepath,
         *,
         files,
         directory,
         global_clamp_size=0.0,
         use_smooth_groups=True,
         use_edges=True,
         use_split_objects=True,
         use_split_groups=True,
         use_image_search=True,
         use_groups_as_vgroups=False,
         use_cycles=True,
         relpath=None,
         global_matrix=None
         ):
    """
    Called by the user interface or another script.
    load_msh2(path) - should give acceptable results.
    This function passes the file and sends the data off
        to be split into objects and then converted into mesh objects
    """

    # ZEROEDIT IMPORTER CONVERSION TO BLENDER
    # importer = Import()
    # importer.do_import(filepath)
    # return {'FINISHED'}


    # TEST IMPORT TO BLENDER
    msh_handler.import_file(filepath)
    msh   = msh_handler.get_mesh_obj()
    sceen = bpy.context.scene

    # Test adding mesh2 to blender
    for model in msh.models:
        name = model.name.decode("utf-8")
        type = model.model_type
        if 'geo' in type:
            if model.collprim:
                continue
                # self.build_prim()
            elif name.startswith('c_'): # and self.get_c_prim_data():
                continue
                # self.build_c_prim()
            else:
                # print(model)
                # print(model.name)
                # print(model.index)
                # print(model.collection)
                # print("")
                # Create a mesh data block
                mesh2 = bpy.data.meshes.new("{}_obj".format(name))

                verts = []
                edges = []
                faces = []
                offset   = 0
                segments = model.segments
                for segment in segments:
                    if segment.classname == "SegmentGeometry":
                        vertices = segment.vertices
                        for vert in vertices:
                            x = vert.x
                            y = vert.y
                            z = vert.z
                            verts.append((x, y, z))

                        sfaces = segment.faces
                        for face in sfaces:
                            '''Using CCW order for importing.'''
                            sii = face.SIindices()
                            if len(sii) == 4:
                                sii = (sii[0] + offset, sii[1] + offset,
                                       sii[2] + offset, sii[3] + offset)
                            else:
                                sii = (sii[0] + offset, sii[1] + offset, sii[2] + offset)

                            faces.append(sii)

                        offset += len(segment.vertices)

                # Add the vertices to the mesh
                mesh2.from_pydata(verts, edges, faces)

                # Create an object that uses the mesh data
                myobj = bpy.data.objects.new("{}".format(name), mesh2)

                # Link the object to the scene
                sceen.objects.link(myobj)

    return {'FINISHED'}
