import * as fs from 'fs';
import { stringToUTF8Array } from '../util';


type GLTF_TYPE = {

    buffers: { byteLength: number }[],
    bufferViews: { buffer: number, byteOffset: number, byteLength: number, target: number }[]
    accessors: { bufferView: number, byteOffset: number, type: string, componentType: number, count: number }[]
    meshes: { primitives: { attributes: { POSITION: number, NORMAL: number, TEXCOORD_0: number }, indices: number, material: number }[] }[]
}

type PARSED_OBJ_TYPE = {
    v: number[][];
    vn: number[][];
    vt: number[][];
    vbo: number[]
    ibo: number[]
    materialLib: string[]
    materialUse: string[]
    flags: number[]
}



export function obj_to_egret_gltf(objFilename: string, outputFiename: string) {
    let obj1 = readObj(objFilename);
    writeBin(obj1, outputFiename);
}




function getLines(textContent: string) {
    let lines: string[];
    if (textContent.indexOf("\r\n", 0) == -1) {
        lines = textContent.split("\n")
    } else {
        lines = textContent.split("\r\n")
    }
    return lines;
}

function readObj(objFilePath: string) {
    const parsedObj: PARSED_OBJ_TYPE = {
        v: [],
        vn: [],
        vt: [],
        vbo: [],
        ibo: [],
        materialLib: [],
        materialUse: [],
        flags: []
    }


    let usemtl_count = 0;

    let vertices: number[][] = []
    let vertexNormals: number[][] = []
    let textureCoords: number[][] = []
    let faces: any[] = []
    let faces2: string[][] = []
    let faces3: any[] = []


    let ibo_temp: any[] = [];
    let indexs_temp: any[] = [];

    const { v, vn, vt, vbo, ibo, materialLib, materialUse, flags } = parsedObj;

    let iboIndex = 0

    const objFileContent = fs.readFileSync(objFilePath, 'utf-8');
    const tokensWithLines = getLines(objFileContent).map(line => line.split(" ").filter(item => !!item))


    for (let tokensInLine of tokensWithLines) {

        const flag = tokensInLine[0];
        switch (flag) {
            case 'v':
                const x = parseFloat(tokensInLine[1]);
                const y = parseFloat(tokensInLine[2]);
                const z = -tokensInLine[3];
                vertices.push([x, y, z]);
                break;
            case "vt":
                const u = parseFloat(tokensInLine[1]);
                const v = 1 - parseFloat(tokensInLine[2]);
                textureCoords.push([u, v])
                break;
            case "vn":
                const normalX = parseFloat(tokensInLine[1]);
                const normalY = parseFloat(tokensInLine[2]);
                const normalZ = -tokensInLine[3];
                vertexNormals.push([normalX, normalY, normalZ]);
                break;
            case "usemtl":
                usemtl_count++;
                faces2.push(tokensInLine)
                materialUse.push(tokensInLine[1]);
                break;
            case "f":
                //"f 顶点索引/uv点索引/法线索引"。
                let itemf: string[] = tokensInLine.slice(1)
                faces2.push(itemf)
                break;

        }
    }



    if (usemtl_count == 0) {
        let a = ['usemtl', 'default']
        faces2.unshift(a)
        materialUse.push('default')
    }
    for (let item of materialUse) {
        if (materialLib.indexOf(item) == -1) {
            materialLib.push(item)
        }
    }



    for (let face of faces2) {
        let array5: any[] = [];
        if (face[0] == 'usemtl') {
            array5 = face;
        }
        else {
            for (let item2 of face) {
                array5.push(item2.split("/"));
            }
        }
        faces3.push(array5)
    }


    for (let item of faces3) {
        if (item[0] != 'usemtl') {
            for (let i of item) {
                v.push(vertices[i[0] - 1])
            }
        }


    }
    for (let item of faces3) {
        if (item[0] != 'usemtl') {
            for (let i of item) {
                vn.push(vertexNormals[i[2] - 1])
            }
        }


    }
    for (let item of faces3) {
        if (item[0] != 'usemtl') {
            for (let i of item) {
                vt.push(textureCoords[i[1] - 1])
            }
        }


    }


    for (let x of faces3) {
        let array6: any[] = []
        if (x[0] == 'usemtl') {
            array6 = x;
        } else {
            for (let i = 0; i < x.length; i++) {
                array6.push(iboIndex)
                iboIndex++;
            }
        }
        faces.push(array6)
    }
    for (let item of faces) {
        if (item[0] == 'usemtl') {
            let u = item;
            ibo_temp.push(u);
        } else {
            for (let i = 0; i < item.length - 1; i++) {
                let item1: any[] = [item[0], item[i], item[i + 1]]
                ibo_temp.push(item1)
            }
        }

    }

    for (let item of ibo_temp) {
        if (item[0] == 'usemtl') {
            let n = ibo_temp.indexOf(item)
            indexs_temp.push(n);
        }
    }

    for (let i = 0; i < indexs_temp.length; i++) {
        let arr: any[] = [];
        if (i != indexs_temp.length - 1) {
            for (let j = indexs_temp[i] + 1; j < indexs_temp[i + 1]; j++) {
                arr.push(ibo_temp[j]);
            }
        } else {
            for (let j = indexs_temp[i] + 1; j < ibo_temp.length; j++) {
                arr.push(ibo_temp[j]);
            }
        }
        flags.push(arr.length * 3)
    }


    for (let item of ibo_temp) {
        if (item[0] != 'usemtl') {
            for (let num of item) {
                ibo.push(num)
            }
        }
    }


    for (let item of v) {
        for (let i of item) {
            vbo.push(i)
        }
    }

    for (let item of vn) {
        for (let i of item) {
            vbo.push(i)
        }
    }

    for (let item of vt) {
        for (let i of item) {
            vbo.push(i)
        }
    }

    return parsedObj;
}

function convertTo_GLTF_JSON(parseobj: PARSED_OBJ_TYPE): GLTF_TYPE {


    const { v, vn, vbo, ibo, materialUse, materialLib } = parseobj;


    let flags = parseobj.flags;

    let vboByteLength = vbo.length * 4;

    const json: GLTF_TYPE = {
        "buffers": [
            {
                "byteLength": 32
            }
        ],
        "bufferViews": [
            {
                "buffer": 0,
                "byteOffset": 0,
                "byteLength": 10,
                "target": 34962
            },
            {
                "buffer": 0,
                "byteOffset": 14,
                "byteLength": 10,
                "target": 34963
            }
        ],
        "accessors": [
            {
                "bufferView": 0,
                "byteOffset": 0,
                "type": "VEC3",
                "componentType": 5126,
                "count": 2
            },
            {
                "bufferView": 0,
                "byteOffset": 4,
                "type": "VEC3",
                "componentType": 5126,
                "count": 2
            },
            {
                "bufferView": 0,
                "byteOffset": 4,
                "type": "VEC2",
                "componentType": 5126,
                "count": 2
            }
        ],
        "meshes": [
            {
                "primitives": [

                ]
            }
        ]
    }


    json.buffers[0].byteLength = vboByteLength + ibo.length * 2;
    json.bufferViews[0].byteLength = vboByteLength;
    json.bufferViews[1].byteOffset = vboByteLength;
    json.bufferViews[1].byteLength = ibo.length * 2;
    json.accessors[0].count = v.length;
    json.accessors[1].byteOffset = v.length * 3 * 4;
    json.accessors[1].count = vn.length;
    json.accessors[2].byteOffset = v.length * 3 * 4 + vn.length * 3 * 4;
    json.accessors[2].count = vn.length;



    let byteoffset = 0
    for (let item of flags) {
        let a = {
            "bufferView": 1,
            "byteOffset": byteoffset,
            "type": "SCALAR",
            "componentType": 5123,
            "count": item
        }
        json.accessors.push(a);
        byteoffset = byteoffset + item * 2;
    }

    for (let i = 0; i < flags.length; i++) {
        let mname = materialUse[i];
        let mnumber = materialLib.indexOf(mname);
        let p = {
            "attributes": {
                "POSITION": 0,
                "NORMAL": 1,
                "TEXCOORD_0": 2,
            },
            "indices": 3 + i,
            "material": mnumber
        }
        json.meshes[0].primitives.push(p);
    }


    return json;
}
function writeBin(parseobj: PARSED_OBJ_TYPE, filename: string) {

    let vbo1 = parseobj.vbo;
    let ibo1 = parseobj.ibo;


    let vboLength = vbo1.length;

    let vboByteLength = vbo1.length * 4;

    let json = convertTo_GLTF_JSON(parseobj);

    let jsonString = JSON.stringify(json)

    let jsonArray = stringToUTF8Array(jsonString);

    let jsonArrayByteLength = jsonArray.length;

    let chunk0DataByteLength: number;


    if (jsonArrayByteLength % 4 == 0) {
        chunk0DataByteLength = jsonArrayByteLength;
    } else {
        let a = jsonArrayByteLength % 4;
        chunk0DataByteLength = jsonArrayByteLength + (4 - a);
    }

    let iboLength: number;
    if (ibo1.length % 2 == 0) {
        iboLength = ibo1.length;
    } else {

        iboLength = ibo1.length + 1;
    }


    let allbyteLength = 12 + 8 + chunk0DataByteLength + 8 + vboByteLength + iboLength * 2;
    let binary = new ArrayBuffer(allbyteLength);

    let header = new Uint32Array(binary);
    header[0] = 0x46546C67;
    header[1] = 2;
    header[2] = allbyteLength;



    let chunk0header = new Uint32Array(binary, 12, 2);
    chunk0header[0] = chunk0DataByteLength;
    chunk0header[1] = 0x4E4F534A;


    let chunk0Data = new Uint8Array(binary, 20, chunk0DataByteLength)
    addToTypedArray(chunk0Data, jsonArray);


    let chunk1header = new Uint32Array(binary, 20 + chunk0DataByteLength, 2)
    chunk1header[0] = vboByteLength + iboLength * 2;
    chunk1header[1] = 0x004E4942;
    let chunk1VboData = new Float32Array(binary, 20 + chunk0DataByteLength + 8, vboLength)
    addToTypedArray(chunk1VboData, vbo1);


    let chunk1IboData = new Uint16Array(binary, 20 + chunk0DataByteLength + 8 + vboByteLength, iboLength);
    addToTypedArray(chunk1IboData, ibo1);


    function addToTypedArray(typedArray: Uint16Array | Float32Array | Uint8Array, data: number[]) {
        let index = 0;
        for (let item of data) {
            typedArray[index] = item;
            index++;
        }
    }

    fs.writeFileSync(filename, new Buffer(binary))
}

