import { CopyObjectAttributes } from '../core/core.js';
import { AddCoord3D, Coord3D, CoordIsEqual3D } from '../geometry/coord3d.js';
import { RGBColor } from './color.js';
import { PhongMaterial } from './material.js';
import { CalculateTriangleNormal, GetMeshType, MeshType } from './meshutils.js';

class ModelFinalizer
{
    constructor (params)
    {
        this.params = {
            getDefaultMaterialColor : () => {
                return new RGBColor (0, 0, 0);
            }
        };
        CopyObjectAttributes (params, this.params);
        this.defaultMaterialIndex = null;
    }

    Finalize (model)
    {
        this.Reset ();

        this.FinalizeMeshes (model);
        this.FinalizeMaterials (model);
        this.FinalizeNodes (model);
    }

    FinalizeMaterials (model)
    {
        if (model.VertexColorCount () === 0) {
            return;
        }

        let materialHasVertexColors = new Map ();
        for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
            let mesh = model.GetMesh (meshIndex);
            for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
                let triangle = mesh.GetTriangle (triangleIndex);
                let hasVertexColors = triangle.HasVertexColors ();
                if (!materialHasVertexColors.has (triangle.mat)) {
                    materialHasVertexColors.set (triangle.mat, hasVertexColors);
                } else if (!hasVertexColors) {
                    materialHasVertexColors.set (triangle.mat, false);
                }
            }
        }

        for (let [materialIndex, hasVertexColors] of materialHasVertexColors) {
            let material = model.GetMaterial (materialIndex);
            material.vertexColors = hasVertexColors;
        }
    }

    FinalizeMeshes (model)
    {
        for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
            let mesh = model.GetMesh (meshIndex);
            let type = GetMeshType (mesh);
            if (type === MeshType.Empty) {
                model.RemoveMesh (meshIndex);
                meshIndex = meshIndex - 1;
                continue;
            }
            this.FinalizeMesh (model, mesh);
        }
    }

    FinalizeMesh (model, mesh)
    {
        function CalculateCurveNormals (mesh)
        {
            function AddAverageNormal (mesh, triangle, vertexIndex, triangleNormals, vertexToTriangles)
            {
                function IsNormalInArray (array, normal)
                {
                    for (let i = 0; i < array.length; i++) {
                        let current = array[i];
                        if (CoordIsEqual3D (current, normal)) {
                            return true;
                        }
                    }
                    return false;
                }

                let averageNormals = [];
                let neigTriangles = vertexToTriangles.get (vertexIndex);
                for (let i = 0; i < neigTriangles.length; i++) {
                    let neigIndex = neigTriangles[i];
                    let neigTriangle = mesh.GetTriangle (neigIndex);
                    if (triangle.curve === neigTriangle.curve) {
                        let triangleNormal = triangleNormals[neigIndex];
                        if (!IsNormalInArray (averageNormals, triangleNormal)) {
                            averageNormals.push (triangleNormal);
                        }
                    }
                }

                let averageNormal = new Coord3D (0.0, 0.0, 0.0);
                for (let i = 0; i < averageNormals.length; i++) {
                    averageNormal = AddCoord3D (averageNormal, averageNormals[i]);
                }
                averageNormal.MultiplyScalar (1.0 / averageNormals.length);
                averageNormal.Normalize ();
                return mesh.AddNormal (averageNormal);
            }

            let triangleNormals = [];
            let vertexToTriangles = new Map ();

            for (let vertexIndex = 0; vertexIndex < mesh.VertexCount (); vertexIndex++) {
                vertexToTriangles.set (vertexIndex, []);
            }

            for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
                let triangle = mesh.GetTriangle (triangleIndex);
                let v0 = mesh.GetVertex (triangle.v0);
                let v1 = mesh.GetVertex (triangle.v1);
                let v2 = mesh.GetVertex (triangle.v2);
                let normal = CalculateTriangleNormal (v0, v1, v2);
                triangleNormals.push (normal);
                vertexToTriangles.get (triangle.v0).push (triangleIndex);
                vertexToTriangles.get (triangle.v1).push (triangleIndex);
                vertexToTriangles.get (triangle.v2).push (triangleIndex);
            }

            for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
                let triangle = mesh.GetTriangle (triangleIndex);
                if (!triangle.HasNormals ()) {
                    let n0 = AddAverageNormal (mesh, triangle, triangle.v0, triangleNormals, vertexToTriangles);
                    let n1 = AddAverageNormal (mesh, triangle, triangle.v1, triangleNormals, vertexToTriangles);
                    let n2 = AddAverageNormal (mesh, triangle, triangle.v2, triangleNormals, vertexToTriangles);
                    triangle.SetNormals (n0, n1, n2);
                }
            }
        }

        let meshStatus = {
            calculateCurveNormals : false
        };

        for (let i = 0; i < mesh.TriangleCount (); i++) {
            let triangle = mesh.GetTriangle (i);
            this.FinalizeTriangle (mesh, triangle, meshStatus);

            if (triangle.mat === null) {
                triangle.mat = this.GetDefaultMaterialIndex (model);
            }
        }

        if (meshStatus.calculateCurveNormals) {
            CalculateCurveNormals (mesh);
        }
    }

    FinalizeTriangle (mesh, triangle, meshStatus)
    {
        if (!triangle.HasNormals ()) {
            if (triangle.curve === null || triangle.curve === 0) {
                let v0 = mesh.GetVertex (triangle.v0);
                let v1 = mesh.GetVertex (triangle.v1);
                let v2 = mesh.GetVertex (triangle.v2);
                let normal = CalculateTriangleNormal (v0, v1, v2);
                let normalIndex = mesh.AddNormal (normal);
                triangle.SetNormals (normalIndex, normalIndex, normalIndex);
            } else {
                meshStatus.calculateCurveNormals = true;
            }
        }

        if (triangle.curve === null) {
            triangle.curve = 0;
        }
    }

    FinalizeNodes (model)
    {
        let rootNode = model.GetRootNode ();

        let emptyNodes = [];
        rootNode.EnumerateChildren ((node) => {
            if (node.IsEmpty ()) {
                emptyNodes.push (node);
            }
        });

        for (let nodeIndex = 0; nodeIndex < emptyNodes.length; nodeIndex++) {
            let node = emptyNodes[nodeIndex];
            let parentNode = node.GetParent ();
            if (parentNode === null) {
                continue;
            }
            parentNode.RemoveChildNode (node);
            if (parentNode.IsEmpty ()) {
                emptyNodes.push (parentNode);
            }
        }
    }

    GetDefaultMaterialIndex (model)
    {
        if (this.defaultMaterialIndex === null) {
            let defaultMaterialColor = this.params.getDefaultMaterialColor ();
            let defaultMaterial = new PhongMaterial ();
            defaultMaterial.color = defaultMaterialColor;
            defaultMaterial.isDefault = true;
            this.defaultMaterialIndex = model.AddMaterial (defaultMaterial);
        }
        return this.defaultMaterialIndex;
    }

    Reset ()
    {
        this.defaultMaterialIndex = null;
    }
}

export function FinalizeModel (model, params)
{
    let finalizer = new ModelFinalizer (params);
    finalizer.Finalize (model);
}

export function CheckModel (model)
{
    function IsCorrectValue (val)
    {
        if (val === undefined || val === null) {
            return false;
        }
        return true;
    }

    function IsCorrectNumber (val)
    {
        if (!IsCorrectValue (val)) {
            return false;
        }
        if (isNaN (val)) {
            return false;
        }
        return true;
    }

    function IsCorrectIndex (val, count)
    {
        if (!IsCorrectNumber (val)) {
            return false;
        }
        if (val < 0 || val >= count) {
            return false;
        }
        return true;
    }

    function CheckMesh (model, mesh)
    {
        function CheckTriangle (model, mesh, triangle)
        {
            if (!IsCorrectIndex (triangle.v0, mesh.VertexCount ())) {
                return false;
            }
            if (!IsCorrectIndex (triangle.v1, mesh.VertexCount ())) {
                return false;
            }
            if (!IsCorrectIndex (triangle.v2, mesh.VertexCount ())) {
                return false;
            }
            if (triangle.HasVertexColors ()) {
                if (!IsCorrectIndex (triangle.c0, mesh.VertexColorCount ())) {
                    return false;
                }
                if (!IsCorrectIndex (triangle.c1, mesh.VertexColorCount ())) {
                    return false;
                }
                if (!IsCorrectIndex (triangle.c2, mesh.VertexColorCount ())) {
                    return false;
                }
            }
            if (!IsCorrectIndex (triangle.n0, mesh.NormalCount ())) {
                return false;
            }
            if (!IsCorrectIndex (triangle.n1, mesh.NormalCount ())) {
                return false;
            }
            if (!IsCorrectIndex (triangle.n2, mesh.NormalCount ())) {
                return false;
            }
            if (triangle.HasTextureUVs ()) {
                if (!IsCorrectIndex (triangle.u0, mesh.TextureUVCount ())) {
                    return false;
                }
                    if (!IsCorrectIndex (triangle.u1, mesh.TextureUVCount ())) {
                    return false;
                }
                if (!IsCorrectIndex (triangle.u2, mesh.TextureUVCount ())) {
                    return false;
                }
            }
            if (!IsCorrectIndex (triangle.mat, model.MaterialCount ())) {
                return false;
            }
            if (!IsCorrectNumber (triangle.curve)) {
                return false;
            }

            return true;
        }

        for (let i = 0; i < mesh.VertexCount (); i++) {
            let vertex = mesh.GetVertex (i);
            if (!IsCorrectNumber (vertex.x)) {
                return false;
            }
            if (!IsCorrectNumber (vertex.y)) {
                return false;
            }
            if (!IsCorrectNumber (vertex.z)) {
                return false;
            }
        }

        for (let i = 0; i < mesh.VertexColorCount (); i++) {
            let color = mesh.GetVertexColor (i);
            if (!IsCorrectNumber (color.r)) {
                return false;
            }
            if (!IsCorrectNumber (color.g)) {
                return false;
            }
            if (!IsCorrectNumber (color.b)) {
                return false;
            }
        }

        for (let i = 0; i < mesh.NormalCount (); i++) {
            let normal = mesh.GetNormal (i);
            if (!IsCorrectNumber (normal.x)) {
                return false;
            }
            if (!IsCorrectNumber (normal.y)) {
                return false;
            }
            if (!IsCorrectNumber (normal.z)) {
                return false;
            }
        }

        for (let i = 0; i < mesh.TextureUVCount (); i++) {
            let uv = mesh.GetTextureUV (i);
            if (!IsCorrectNumber (uv.x)) {
                return false;
            }
            if (!IsCorrectNumber (uv.y)) {
                return false;
            }
        }

        for (let i = 0; i < mesh.TriangleCount (); i++) {
            let triangle = mesh.GetTriangle (i);
            if (!CheckTriangle (model, mesh, triangle)) {
                return false;
            }
        }

        return true;
    }

    for (let i = 0; i < model.MeshCount (); i++) {
        let mesh = model.GetMesh (i);
        if (!CheckMesh (model, mesh)) {
            return false;
        }
    }

    return true;
}
