import { FileFormat, GetFileName } from '../io/fileutils.js';
import { TextWriter } from '../io/textwriter.js';
import { MaterialType } from '../model/material.js';
import { ExportedFile, ExporterBase } from './exporterbase.js';

export class ExporterObj extends ExporterBase
{
    constructor ()
    {
        super ();
    }

    CanExport (format, extension)
    {
        return format === FileFormat.Text && extension === 'obj';
    }

    ExportContent (exporterModel, format, files, onFinish)
    {
        function WriteTexture (mtlWriter, keyword, texture, files)
        {
            if (texture === null || !texture.IsValid ()) {
                return;
            }
            let fileName = GetFileName (texture.name);
            mtlWriter.WriteArrayLine ([keyword, fileName]);

            let fileIndex = files.findIndex ((file) => {
                return file.GetName () === fileName;
            });
            if (fileIndex === -1) {
                let textureFile = new ExportedFile (fileName);
                textureFile.SetBufferContent (texture.buffer);
                files.push (textureFile);
            }
        }

        let mtlFile = new ExportedFile ('model.mtl');
        let objFile = new ExportedFile ('model.obj');

        files.push (mtlFile);
        files.push (objFile);

        let mtlWriter = new TextWriter ();
        mtlWriter.WriteLine (this.GetHeaderText ());
        for (let materialIndex = 0; materialIndex < exporterModel.MaterialCount (); materialIndex++) {
            let material = exporterModel.GetMaterial (materialIndex);
            mtlWriter.WriteArrayLine (['newmtl', this.GetExportedMaterialName (material.name)]);
            mtlWriter.WriteArrayLine (['Kd', material.color.r / 255.0, material.color.g / 255.0, material.color.b / 255.0]);
            mtlWriter.WriteArrayLine (['d', material.opacity]);
            if (material.type === MaterialType.Phong) {
                mtlWriter.WriteArrayLine (['Ka', material.ambient.r / 255.0, material.ambient.g / 255.0, material.ambient.b / 255.0]);
                mtlWriter.WriteArrayLine (['Ks', material.specular.r / 255.0, material.specular.g / 255.0, material.specular.b / 255.0]);
                mtlWriter.WriteArrayLine (['Ns', material.shininess * 1000.0]);
            }
            WriteTexture (mtlWriter, 'map_Kd', material.diffuseMap, files);
            if (material.type === MaterialType.Phong) {
                WriteTexture (mtlWriter, 'map_Ks', material.specularMap, files);
            }
            WriteTexture (mtlWriter, 'bump', material.bumpMap, files);
        }
        mtlFile.SetTextContent (mtlWriter.GetText ());

        let objWriter = new TextWriter ();
        objWriter.WriteLine (this.GetHeaderText ());
        objWriter.WriteArrayLine (['mtllib', mtlFile.GetName ()]);
        let vertexOffset = 0;
        let normalOffset = 0;
        let uvOffset = 0;
        let usedMaterialName = null;
        exporterModel.EnumerateTransformedMeshes ((mesh) => {
            objWriter.WriteArrayLine (['g', this.GetExportedMeshName (mesh.GetName ())]);
            for (let vertexIndex = 0; vertexIndex < mesh.VertexCount (); vertexIndex++) {
                let vertex = mesh.GetVertex (vertexIndex);
                objWriter.WriteArrayLine (['v', vertex.x, vertex.y, vertex.z]);
            }
            for (let normalIndex = 0; normalIndex < mesh.NormalCount (); normalIndex++) {
                let normal = mesh.GetNormal (normalIndex);
                objWriter.WriteArrayLine (['vn', normal.x, normal.y, normal.z]);
            }
            for (let textureUVIndex = 0; textureUVIndex < mesh.TextureUVCount (); textureUVIndex++) {
                let uv = mesh.GetTextureUV (textureUVIndex);
                objWriter.WriteArrayLine (['vt', uv.x, uv.y]);
            }
            for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
                let triangle = mesh.GetTriangle (triangleIndex);
                let v0 = triangle.v0 + vertexOffset + 1;
                let v1 = triangle.v1 + vertexOffset + 1;
                let v2 = triangle.v2 + vertexOffset + 1;
                let n0 = triangle.n0 + normalOffset + 1;
                let n1 = triangle.n1 + normalOffset + 1;
                let n2 = triangle.n2 + normalOffset + 1;
                if (triangle.mat !== null) {
                    let material = exporterModel.GetMaterial (triangle.mat);
                    let materialName = this.GetExportedMaterialName (material.name);
                    if (materialName !== usedMaterialName) {
                        objWriter.WriteArrayLine (['usemtl', materialName]);
                        usedMaterialName = materialName;
                    }
                }
                let u0 = '';
                let u1 = '';
                let u2 = '';
                if (triangle.HasTextureUVs ()) {
                    u0 = triangle.u0 + uvOffset + 1;
                    u1 = triangle.u1 + uvOffset + 1;
                    u2 = triangle.u2 + uvOffset + 1;
                }
                objWriter.WriteArrayLine (['f', [v0, u0, n0].join ('/'), [v1, u1, n1].join ('/'), [v2, u2, n2].join ('/')]);
            }
            vertexOffset += mesh.VertexCount ();
            normalOffset += mesh.NormalCount ();
            uvOffset += mesh.TextureUVCount ();
        });

        objFile.SetTextContent (objWriter.GetText ());
        onFinish ();
    }

    GetHeaderText ()
    {
        return '# exported by https://3dviewer.net';
    }
}
