import { MeshGeo } from './meshGeo';
import { BufferReader,BufferWriter } from './buffer';
import {allfiles } from './util'
import * as fs from 'fs';
import * as path from 'path';


class Laya_SubMesh{
    attribs='POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;'
    _vertexBuffer:Float32Array;
    _indexBuffer:Uint16Array;
    _boneindicesBuffer:Uint8Array;//uint8
}

class Laya_Mesh{
    version='LAYASKINANI:01';
    name='MESH';
    materials:string[]=[];    //只用字符串表示对应的材质文件或者材质id。有几个就表示有几个submesh
    _bindPoses:Float32Array;//Matrix44[]
    _inverseBindPoses:Float32Array;//Matrix44[]
    submeshes:Laya_SubMesh[]=[];
}


class _submeshInfo{
    iboff=0;
    ibsize=0;
    vboff=0;
    vbsize=0;
    boneidxoff=0;
    boneidxsize=0;
}

export class Laya_Mesh_W extends BufferWriter{
    mesh:Laya_Mesh;
    _strmap:string[]=['BLOCK', 'DATA', 'STRINGS'];

    dataOff=0;
    dataSize=0;
    strsOff=0;
    strsNum=0; //
    bindPoseOff=0;
    bindPoseSize=0;
    invGBindePoseOff=0;
    invGBindePoseSize=0;
    submeshInfo:_submeshInfo[]=[];

    constructor(){
        super();
    }
    /**
     * 返回写了多大。
     */
    outobj(obj,members:string,buff:ArrayBuffer,off:number):number{
        if(!obj._memtype){
            throw '对象中必须有_memtype 描述';
        }
        var mems = members.split(',');
        var dview = new DataView(buff,off);
        var moff=0;
        mems.forEach((mname)=>{
            var tp = obj._memtype[mname];
            if(!tp)
                throw 'no type info of member '+mname;
            var value = obj[mname];
            if( value==undefined)
                throw 'no this member :'+mname;
            var isarr = value instanceof Array;
            var len = isarr?value.length:0;
            if(isarr)throw 'outobj not implements array';

            switch(tp){
                case 'u8':
                dview.setUint8(moff,value);moff+=1;               break;
                case 'u16':
                dview.setUint16(moff,value,true);moff+=2;         break;
                case 'u32':
                dview.setUint32(moff,value,true);moff+=4;         break;
                case 'f32':
                dview.setFloat32(moff,value,true);moff+=4;        break;
            }
        });
        return moff;
    }

    outStrings(strs:string[]){

    }

    saveAsLm(file:string){
        this.submeshInfo= [];
        this.mesh.submeshes.forEach((v,i)=>{
            this.submeshInfo[i]=new _submeshInfo()
        });

        //先空跑一下，组装字符表，统计大小
        this.nowrite=true;
        this._saveAsLm();
        var sz = this._writePos;
        this.buff = new ArrayBuffer(sz);
        this.datav=new DataView(this.buff);
        //真正写buffer
        this.nowrite=false;
        this._writePos=0;
        this._saveAsLm();
        fs.writeFileSync(file,new Buffer(new Uint8Array(this.buff)));
    }

    _saveAsLm(){
        this.wstr(this.mesh.version)
        .wu16(0)
        .wu16(5+this.mesh.materials.length+this.mesh.submeshes.length);//chunk count

        //0
        //data chunk
        //'DATA'
        this.wstrid('DATA')//data chunk id
        .wu32(this.dataOff)
        .wu32(this.dataSize);

        //var dataC={id:1,dataoff:0,datasize:0,_memtype:{id:'u16',dataoff:'u32',datasize:'u32'}};
        //this.outobj(dataC,'id,dataoff,datasize',this.buff,this._writePos);

        //1
        //strings chunk
        //'STRINGS'
        this.wstrid('STRINGS') //strings chunk id
        .wu16(this.strsOff)
        .wu16(this.strsNum);
        //把所有的string以wstr的方式写到对应区域

        //2
        //material chunk
        //'MATERIAL'
        this.mesh.materials.forEach((mtlname,i)=>{
            this.wstrid('MATERIAL')   //id
            this.wu16(i);   //放到_materials数组的位置
            this.wstrid('SIMPLE')//shader name /没有用
            .wstrid(mtlname);//url
        });

        //其他的material
        //this.wu16(3);//相同块的id相同

        //3
        //mesh chunk
        //'MESH'
        this.wstrid('MESH') //id
        .wstrid(this.mesh.name) //name
        .wu32(this.bindPoseOff)//bindpose start
        .wu32(this.bindPoseSize)//bindpose size
        .wu32(this.invGBindePoseOff)//invGBindPose start
        .wu32(this.invGBindePoseSize);//invGBindPose size
        //写bindpose和invgbindpos到相应的地方

        //4
        //sub mesh
        //"SUBMESH"
        var materialid=0;
        this.mesh.submeshes.forEach((sm,i)=>{
            var sminfo=this.submeshInfo[i];
            this.wstrid('SUBMESH')
            .wstrid('SUBMESH')
            .wu8(materialid);
            this.wstrid(sm.attribs)
            .wu32(sminfo.iboff)//ibofs    都是相对于data的
            .wu32(sminfo.ibsize)//ibsize
            .wu32(0)//vbIndicesofs 不知道干什么的
            .wu32(0)//vbIndicessize 不知道干什么的
            .wu32(sminfo.vboff)//vbofs
            .wu32(sminfo.vbsize)//vbsize
            .wu32(sminfo.boneidxoff)//bonedicofs
            .wu32(sminfo.boneidxsize);//bonedicsize
        });
        //写vb，ib，bonedic：int8[]

        //5
        //DATAAREA
        this.wstrid('DATAAREA');//=6

        this.dataOff=this._writePos;
        this.strsOff=this._writePos-this.dataOff;
        this.strsNum=this._strmap.length;

        //strings
        this._strmap.forEach((v)=>{
            this.wstr(v);
        });

        //pose
        this.bindPoseOff=this._writePos-this.dataOff;
        this.bindPoseSize = this.mesh._bindPoses.byteLength;
        this.wab(this.mesh._bindPoses.buffer,this.mesh._bindPoses.byteLength);
        //invpose
        this.invGBindePoseOff=this._writePos-this.dataOff;
        this.invGBindePoseSize=this.mesh._inverseBindPoses.byteLength;
        this.wab(this.mesh._inverseBindPoses.buffer,this.mesh._inverseBindPoses.byteLength);

        this.mesh.submeshes.forEach((sm,i)=>{
            var sminfo = this.submeshInfo[i];
            //vb
            sminfo.vboff=this._writePos-this.dataOff;
            sminfo.vbsize=sm._vertexBuffer.byteLength;
            this.wab(sm._vertexBuffer.buffer,sm._vertexBuffer.byteLength);
            //ib
            sminfo.iboff=this._writePos-this.dataOff;
            sminfo.ibsize=sm._indexBuffer.byteLength;
            this.wab(sm._indexBuffer.buffer,sm._indexBuffer.byteLength);
            //bone index       
            sminfo.boneidxoff=this._writePos-this.dataOff;
            sminfo.boneidxsize=sm._boneindicesBuffer.byteLength;
            this.wab(sm._boneindicesBuffer.buffer,sm._boneindicesBuffer.byteLength);
        });
    }

    wstrid(str:string):Laya_Mesh_W{//先转成id
        var i = this._strmap.indexOf(str);
        if(i==-1){
            i=this._strmap.length;
            this._strmap.push(str);
        }
        this.wu16(i);
        return this;
    }
}

/**
 * 问题：现在的格式无法略过不认识的块。
 */
export class loader_lh extends BufferReader{
    _strings = ['BLOCK', 'DATA', 'STRINGS'];    //初始值。read_STRINGS后会被替换
    _funcs = [null,this.read_DATA.bind(this),this.read_STRINGS.bind(this)];
    blockCount=0;
    dataoffset=0;
    datasize=0;
    _attrReg = new RegExp("(\\w+)|([:,;])", "g");
    _shaderAttributes:string[];
    mesh:Laya_Mesh;
    _cursubmeshid=0;
    constructor(){
        super();
    }
    load(str:string,mesh:Laya_Mesh){
        this.mesh=mesh;
    }
    parse(data:ArrayBuffer):any{
        this.init(data);
        this.mesh.version=this.readString();
        this.read_BLOCK();
        for( var i=0; i<this.blockCount; i++){
            var idx = this.readU16();
            var blcokname = this._strings[idx];
            var func = this['read_'+blcokname];
            if(func){
                func.call(this);
            }else{
                console.error('no function to read chunk '+blcokname);
            }
            if(idx==2){//strings 特殊
                var lmaturls = this._strings.filter((v)=>{ return v.indexOf('.lmat')>0;});
            }
        }
    }
    read_BLOCK(){
        this.readpos+=2;
        this.blockCount = this.readU16();
    }
    read_DATA(){
        this.dataoffset = this.readU32();
        this.datasize = this.readU32();
    }
    read_STRINGS(){
        var ret = [];
        var offset = this.readU16();
        var size = this.readU16();
        var oldpos = this.readpos;
        //strings的绝对偏移。注意是相对于data块的
        this.readpos= this.dataoffset+offset;
        for(var i=0; i<size; i++){
            var ss = this.readString();
            ret.push(ss);
        }
        this._strings = ret;
        this.readpos = oldpos;
    }
    read_MATERIAL(){
        var index = this.readU16();
        var stridx = this.readU16();
        var urlidx = this.readU16();
        var shadername = this._strings[stridx];
        var url = this._strings[urlidx];
        // mesh
        if(index<0||index>255)throw 'material index error:'+index;
        this.mesh.materials[index]=url;
    }
    read_MESH(){
        var name = this.getStr(this.readU16());
        switch(this.mesh.version){
            case 'LAYASKINANI:01':
                var bindPoseStart = this.readU32();
                var binPoseLength = this.readU32();
                var ttt = this.buff.slice(bindPoseStart + this.dataoffset);//由于起始位置不是4的倍数，只能重新复制一个
                var bindPoseDatas = new Float32Array(ttt,0, binPoseLength);
                var invBindPoseStart = this.readU32();
                var invBindPoseLength = this.readU32();
                ttt = this.buff.slice(invBindPoseStart+this.dataoffset);
                var invBindPoseDatas = new Float32Array(ttt, 0,invBindPoseLength);
                //mesh
                this.mesh._bindPoses = bindPoseDatas;
                this.mesh._inverseBindPoses = invBindPoseDatas;
            break;
            default:
                throw ('wrong version :'+this.mesh.version);
            //break;
        }
    }

    read_SUBMESH(){
        var className = this.getStr(this.readU16());
        var material = this.readU8();//这个没有用
        var bufferAttribute = this.getStr(this.readU16());
        this._shaderAttributes = bufferAttribute.match(this._attrReg);
        var ibofs = this.readU32();
        var ibsize = this.readU32();
        var vbIndicesofs = this.readU32();  //Not use? 骨骼索引?
        var vbIndicessize = this.readU32(); //not use?
        var vbofs = this.readU32();
        var vbsize = this.readU32();
        var boneDicofs = this.readU32();
        var boneDicsize = this.readU32();
        //vertex decl
        //this._getVertexDeclaration();
        var vbStart = vbofs+this.dataoffset;
        var vbBuff = new Float32Array( this.buff.slice(vbStart,vbStart+vbsize));
        //TODO ib 可能不从0开始
        //现在是相同格式的合并，但是这样可能会导致错误。

        var ibStart = ibofs+this.dataoffset;
        var ibBuff = new Uint16Array( this.buff.slice(ibStart, ibStart+ibsize));

        //bone index
        var bidBuff = new Uint8Array(this.buff.slice(boneDicofs+this.dataoffset,boneDicofs+this.dataoffset+boneDicsize));

        //mesh
        var sm = this.mesh.submeshes[this._cursubmeshid++]=new Laya_SubMesh();
        sm.attribs = bufferAttribute;
        sm._vertexBuffer = vbBuff;
        sm._indexBuffer=ibBuff;
        sm._boneindicesBuffer = bidBuff;
    }

    read_DATAAREA(){}

    getStr(id:number):string{
        return this._strings[id];
    }

    _getVertexDeclaration(){
        var position:boolean, normal:boolean, color:boolean, texcoord0:boolean, texcoord1:boolean, tangent:boolean, blendWeight:boolean, blendIndex:boolean;
        for (var i = 0; i < this._shaderAttributes.length; i += 8) {
            switch (this._shaderAttributes[i]) {
            case "POSITION": 
                position = true;
                break;
            case "NORMAL": 
                normal = true;
                break;
            case "COLOR": 
                color = true;
                break;
            case "UV": 
                texcoord0 = true;
                break;
            case "UV1": 
                texcoord1 = true;
                break;
            case "BLENDWEIGHT": 
                blendWeight = true;
                break;
            case "BLENDINDICES": 
                blendIndex = true;
                break;
            case "TANGENT": 
                tangent = true;
                break;
            }
        }
        //TODO
        this._shaderAttributes;
    }
    
}


var srcPath = 'E:/layaair/layaair/publish/LayaAirPublish/samples/as/3d/bin/h5/threeDimen/models/test/';
class VertexAttrib{
    name='';
    size=0;
    type=0;
    offset=0;
}

class VertexDeclare{
    attribs:VertexAttrib[]=[];
    getTypeLen(t:number):number{
        return 4;
    }
    getStride(){
        if(this.attribs.length<=0)
            return 0;
        var lastattr = this.attribs[this.attribs.length-1];
        return lastattr.offset+this.getTypeLen(lastattr.type)*lastattr.size;
    }
    getOff(name:string):number{
        var att = this.attribs.find((v,i)=>{return v.name==name});
        return att.offset;
    }
}

function parseAttrib(str:string):VertexDeclare{
    //0 name, 1 size, 2 stide, 3 offset
    var R = new VertexDeclare();
    var ret=R.attribs;
    var atts = str.split(';');
    if(atts.length<=0) throw 'parse att string error'+str;
    atts.forEach((v,i)=>{
        if(v.length<=0)
            return;
        var s1 = v.split(':');
        if(s1.length<2) throw 'parse att err1';
        var ca =new VertexAttrib();
        ca.name = s1[0];
        var s2 = s1[1].split(',');
        if(s2.length<3) throw 'parse att err2';
        ca.size=parseInt(s2[0]);
        ca.type=parseInt(s2[1]);
        ca.offset=parseInt(s2[2]);
        ret.push(ca);
    });
    return R;
}

function handlelm(mesh:Laya_Mesh,modelcnfg:Object,swapyz:boolean):boolean{
    if(mesh.version=='LAYAMODEL:02'){
        return false;
    }
    mesh.version = 'LAYAMODEL:02';
    mesh.materials.forEach((mtl,i)=>{
        if(modelcnfg && modelcnfg[mesh.materials[i]]){
            mesh.materials[i]=modelcnfg[mesh.materials[i]];
        }
        else{
            mesh.materials[i]= mesh.materials[i].replace('.lmat','.lpbr');
        }
        console.log(mesh.materials[i]);
    });
    mesh.submeshes.forEach((sm,i)=>{
        var atts = parseAttrib(sm.attribs);
        console.log(atts);
        var oldstride = atts.getStride();
        var oldfstride=oldstride/4;
        if(sm.attribs == 'POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;')
            sm.attribs='POSITION:3,56,0;NORMAL:3,56,12;TANGENT:3,56,24;BINORMAL:3,56,36;UV:2,56,48;';
        if(sm.attribs == 'POSITION:3,64,0;NORMAL:3,64,12;UV:2,64,24;BLENDWEIGHT:4,64,32;BLENDINDICES:4,64,48;')
            sm.attribs = 'POSITION:3,88,0;NORMAL:3,88,12;TANGENT:3,88,24;BINORMAL:3,88,36;UV:2,88,48;BLENDWEIGHT:4,88,56;BLENDINDICES:4,88,72;'
        //sm.attribs='POSITION:3,32,0;NORMAL:3,32,12;UV:2,32,24;';
        var newatts = parseAttrib(sm.attribs);
        var newstride = newatts.getStride();
        var newfstride=newstride/4;
        var oldvb = sm._vertexBuffer;
        var vertnum = sm._vertexBuffer.byteLength/oldstride;
        var mg = new MeshGeo(oldvb,sm._indexBuffer,oldfstride,atts.getOff('UV')/4,'');

        //BBX
        var minx=1e6,miny=1e6,minz=1e6,maxx=-1e6,maxy=-1e6,maxz=-1e6;
        for( var i=0; i<vertnum; i++){
            var stp = i*oldfstride;
            var posx=oldvb[stp++];
            var posy=oldvb[stp++];
            var posz=oldvb[stp++];
            if(posx<minx)minx=posx;if(posx>maxx)maxx=posx;
            if(posy<miny)miny=posy;if(posy>maxy)maxy=posy;
            if(posz<minz)minz=posz;if(posz>maxz)maxz=posz;
            stp=i*oldfstride;
        }
        console.log('submesh size:[',minx,miny,minz,maxx,maxy,maxz,']');

        var coldi=0
        //缩放
        if( modelcnfg && modelcnfg['scale']!=undefined ){
            var scale = modelcnfg['scale']*1.0;
            for( var i=0; i<vertnum; i++){
                var stp = i*oldfstride;
                var posx=oldvb[stp++];
                var posy=oldvb[stp++];
                var posz=oldvb[stp++];
                if(posx<minx)minx=posx;if(posx>maxx)maxx=posx;
                if(posy<miny)miny=posy;if(posy>maxy)maxy=posy;
                if(posz<minz)minz=posz;if(posz>maxz)maxz=posz;
                stp=i*oldfstride;
                oldvb[stp++]=posx*scale;oldvb[stp++]=posy*scale;oldvb[stp++]=posz*scale;
            }            
        }
        //TODO 是否交换yz
        if(swapyz){
            console.log('ATTENTION! swap y z now! ');
            let coldi =0;
            for( var i=0; i<vertnum; i++){
                var stp = i*oldfstride;
                var posx=oldvb[stp++];
                var posy=oldvb[stp++];
                var posz=oldvb[stp++];
                var normx=oldvb[stp++];
                var normy=oldvb[stp++];
                var normz=oldvb[stp++];
                stp=i*oldfstride;
                oldvb[stp++]=posx;oldvb[stp++]=posz;oldvb[stp++]=-posy;
                oldvb[stp++]=normx;oldvb[stp++]=normz;oldvb[stp++]=-normy;
            }            
        }
        //计算tangent
        var tb = mg.calcTangent();
        
        var vb = sm._vertexBuffer = new Float32Array(vertnum*newfstride);
        var cnewi=0;
        for( var i=0; i<vertnum; i++){
            vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];//pos
            vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];vb[cnewi++]=oldvb[coldi++];//normal
            vb[cnewi++]=tb.tan[i*3];vb[cnewi++]=tb.tan[i*3+1];vb[cnewi++]=tb.tan[i*3+2];//tan
            vb[cnewi++]=tb.binor[i*3];vb[cnewi++]=tb.binor[i*3+1];vb[cnewi++]=tb.binor[i*3+2];//bin
            //剩下的
            for(var li=0; li<(oldfstride-6); li++){
                vb[cnewi++]=oldvb[coldi++];
            }
        }
    });
    return true;
}


function dowork(){
    var modelCnfgObj={};
    try{
        var modelcnfg = fs.readFileSync(srcPath+'config.json','utf8');
        if(modelcnfg){
            modelCnfgObj = JSON.parse(modelcnfg);
        }
    }catch(e){
        console.log('no config file:config.json');
    }
        
    allfiles(srcPath,(file)=>{
        if(path.extname(file).toLowerCase()=='.lm'){
            if(path.extname(file.substr(0,file.length-3)).toLowerCase()=='.lm'){
                return;
            }
            var cc = new loader_lh();
            cc.mesh = new Laya_Mesh();
            var data = fs.readFileSync(file);
            try{
                console.log('parse '+file);
                cc.parse(data.buffer);
            }catch(e){
                console.log(e);
                return;
            }
            var rfile = path.basename(file);
            var filepath = path.dirname(file);
            //材质相关
            cc.mesh.materials.forEach((v,i)=>{
                var lmat = filepath+v;
                var lpbr = filepath+v.replace('.lmat','.lpbr');
                var colorfile='';
                var normalfile='';
                var meshname='';
                if(!fs.existsSync(lpbr)){
                    if(fs.existsSync(lmat)){
                        try{
                            var lmatobj = JSON.parse(fs.readFileSync(lmat,'utf8')) as Object;
                            try{
                                colorfile = lmatobj['customProps']['diffuseTexture']['texture2D']
                            }catch(e){}
                            try{
                                normalfile = lmatobj['customProps']['normalTexture']['texture2D']
                            }catch(e){}
                            try{
                                meshname = lmatobj['props']['name'];
                            }catch(e){}
                        }catch(e){}
                    }
                    
                    var pbrfilec=`{
    "version":"LAYAMATERIAL:01",
    "type": "PBRMaterial",
    "props": {
        "name": "${meshname}",
        "renderMode": 1,
        "has_tangent":true,
        "textures":[
            {"name":"diffuseTexture","path":"${colorfile}"},
            {"name":"normalTexture","path":"${normalfile}"}
        ]
    }
    }`;
                    fs.writeFileSync(lpbr,pbrfilec,'utf8');
                }
            });

            //修改mesh
            if(handlelm(cc.mesh,modelCnfgObj[rfile],true)){
                var save = new Laya_Mesh_W();
                save.mesh=cc.mesh;
                save.saveAsLm(file+'.lm');
            }else{
            }
        }
    },false);
}


//dowork();