using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using Microsoft.Xna.Framework;
using MikuMikuDance.Model.Ver1;
using Microsoft.Xna.Framework.Content.Pipeline;

namespace MikuMikuDance.XNA.Model
{
    class MeshConverter
    {
        /// <summary>
        /// メッシュをXNA用に最適化し、MMDModel1モデルデータを最適化にあわせて修正する
        /// </summary>
        /// <param name="model">MMDModel元データ</param>
        /// <param name="FolderPath">モデルファイルがあるフォルダパス</param>
        /// <param name="UseMaterialPalette">マテリアルパレットを使用するかどうか</param>
        /// <returns>最適化済みデータ</returns>
        internal static MMDMeshData ConvertForXNA(MMDModel1 model, string FolderPath, bool UseMaterialPalette)
        {
            MMDMeshData result = new MMDMeshData();
            //モデル名のコピー
            result.ModelName = model.Header.ModelName;
            //トゥーンの情報をコピー
            result.ToonTextures = null;
            if (model.ToonExpantion)
            {
                result.ToonTextures = new string[10];
                Array.Copy(model.ToonFileNames, result.ToonTextures, result.ToonTextures.Length);
            }
            //ボーンの参照をコピー
            result.Bones = model.Bones;
            //頂点データのコピー
            result.Vertexes = new MeshVertex[model.Vertexes.LongLength];
            for (long i = 0; i < model.Vertexes.LongLength; i++)
            {
                result.Vertexes[i].BoneNum=new ushort[model.Vertexes[i].BoneNum.Length];
                Array.Copy(model.Vertexes[i].BoneNum,result.Vertexes[i].BoneNum,result.Vertexes[i].BoneNum.Length);
                result.Vertexes[i].BoneNum = model.Vertexes[i].BoneNum;
                result.Vertexes[i].BoneWeight = model.Vertexes[i].BoneWeight;
                result.Vertexes[i].EdgeFlag = (byte)((model.Vertexes[i].NonEdgeFlag == 1) ? 0 : 1);
                result.Vertexes[i].NormalVector = MMDMath.VectorFromArray(model.Vertexes[i].NormalVector);
                result.Vertexes[i].Pos = MMDMath.VectorFromArray(model.Vertexes[i].Pos);
                result.Vertexes[i].UV = new Vector2(model.Vertexes[i].UV[0], model.Vertexes[i].UV[1]);
            }
            //材質データの変換
            //仮組み
            result.Materials = new MeshMaterial[model.Materials.LongLength];
            long FaceIndex = 0;
            for (long i = 0; i < model.Materials.LongLength; i++)
            {
                result.Materials[i].FaceVertCount = model.Materials[i].FaceVertCount;
                if (string.IsNullOrEmpty(model.Materials[i].TextureFileName))
                    result.Materials[i].TextureFileName = "";
                else
                    result.Materials[i].TextureFileName = model.Materials[i].TextureFileName;
                if (string.IsNullOrEmpty(model.Materials[i].SphereTextureFileName))
                    result.Materials[i].SphereMapFileName = "";
                else
                    result.Materials[i].SphereMapFileName = model.Materials[i].SphereTextureFileName;
                
                //エフェクトにあわせて材質パレットの仮を作成
                result.Materials[i].DiffuseColorPalette =
                    new Vector4[1]{
                        new Vector4(MMDMath.VectorFromArray(model.Materials[i].DiffuseColor), model.Materials[i].Alpha)
                    };
                result.Materials[i].MirrorColorPalette =
                                    new Vector4[1]{
                        new Vector4(MMDMath.VectorFromArray(model.Materials[i].MirrorColor), (model.Materials[i].ToonIndex == 0xFF ? -1 : model.Materials[i].ToonIndex))
                    };
                result.Materials[i].SpecularColorPalette =
                                    new Vector4[1]{
                        new Vector4(MMDMath.VectorFromArray(model.Materials[i].SpecularColor), model.Materials[i].Specularity)
                    };

                for (long j = FaceIndex; j < FaceIndex + result.Materials[i].FaceVertCount; j++)
                {
                    int VecIndex = model.FaceVertexes[j];
                    result.Vertexes[VecIndex].EdgeFlag &= model.Materials[i].EdgeFlag;
                }
                FaceIndex += result.Materials[i].FaceVertCount;
            }
            //同じ種類(テクスチャ無しでアルファも無し)材質を統合し、面の頂点番号入れ替え辞書を作る
            MeshMaterial[] resultMaterial;
            long[] FaceVertGCDic = new long[model.FaceVertexes.LongLength];
            long[] MatGroup = new long[result.Materials.LongLength];
            //まずは同じグループを検出
            for (long i = 0; i < result.Materials.LongLength; i++)
                MatGroup[i] = -1;
            long Counter = 0;
            for (long i = 0; i < result.Materials.LongLength; i++)
            {
                if (MatGroup[i] != -1)
                    continue;
                MatGroup[i] = Counter;
                for (long j = i + 1; j < result.Materials.LongLength; j++)
                {
                    if (result.Materials[i].TextureFileName == result.Materials[j].TextureFileName &&
                        result.Materials[i].SphereMapFileName == result.Materials[j].SphereMapFileName
                        && UseMaterialPalette)
                        MatGroup[j] = MatGroup[i];
                }
                ++Counter;
            }
            //パレットのマックス数をオーバしている場合はグループを分割
            for (long i = 0; i < Counter; i++)
            {
                long NumMat = 0;
                for (long j = 0; j < result.Materials.LongLength; j++)
                {
                    if (MatGroup[j] == i)
                    {
                        NumMat++;
                        if (NumMat > MeshMaterial.NumPalette)
                        {//あふれ分
                            //新グループ
                            MatGroup[j] = Counter;
                        }
                    }
                }
                //あふれが発生したかチェック
                if (NumMat > MeshMaterial.NumPalette)
                {
                    Counter++;//あふれ分をグループ数に加える
                }
            }
            //グループ数(マテリアル数)が判明したので、最終的なメッシュデータを入れる変数を用意
            resultMaterial = new MeshMaterial[Counter];
            //材質を統合し、面番号変換辞書を作成
            long NewFaceIndex = 0;
            int PaletteNum = 0;
            for (long rmIndex = 0; rmIndex < resultMaterial.LongLength; rmIndex++)
            {
                FaceIndex = 0;
                resultMaterial[rmIndex].DiffuseColorPalette = new Vector4[MeshMaterial.NumPalette];
                resultMaterial[rmIndex].MirrorColorPalette = new Vector4[MeshMaterial.NumPalette];
                resultMaterial[rmIndex].SpecularColorPalette = new Vector4[MeshMaterial.NumPalette];
                PaletteNum = 0;
                for (long matIndex = 0; matIndex < result.Materials.LongLength; matIndex++)
                {
                    if (MatGroup[matIndex] == rmIndex)
                    {
                        //マテリアル情報の統合
                        resultMaterial[rmIndex].TextureFileName = result.Materials[matIndex].TextureFileName;
                        resultMaterial[rmIndex].SphereMapFileName = result.Materials[matIndex].SphereMapFileName;
                        resultMaterial[rmIndex].FaceVertCount += result.Materials[matIndex].FaceVertCount;
                        resultMaterial[rmIndex].DiffuseColorPalette[PaletteNum] = result.Materials[matIndex].DiffuseColorPalette[0];
                        resultMaterial[rmIndex].MirrorColorPalette[PaletteNum] = result.Materials[matIndex].MirrorColorPalette[0];
                        resultMaterial[rmIndex].SpecularColorPalette[PaletteNum] = result.Materials[matIndex].SpecularColorPalette[0];
                        //パレット番号の作成と面番号変換辞書を作成
                        for (long i = FaceIndex; i < FaceIndex + result.Materials[matIndex].FaceVertCount; i++)
                        {
                            int VecIndex = model.FaceVertexes[i];
                            result.Vertexes[VecIndex].PaletteNum = PaletteNum;
                            FaceVertGCDic[i] = NewFaceIndex;
                            ++NewFaceIndex;
                        }
                        PaletteNum++;
                    }
                    FaceIndex += result.Materials[matIndex].FaceVertCount;
                }
                //シェーダインデックスの判定
                resultMaterial[rmIndex].ShaderIndex = 0;
                if (!string.IsNullOrEmpty(resultMaterial[rmIndex].TextureFileName))
                    ++resultMaterial[rmIndex].ShaderIndex;
                if (!string.IsNullOrEmpty(resultMaterial[rmIndex].SphereMapFileName))
                    resultMaterial[rmIndex].ShaderIndex += 2;
            }
            //変換済み材質に置き換え
            result.Materials = resultMaterial;
            //面リストの変換
            result.FaceVertexes = new ushort[model.FaceVertexes.LongLength];
            for (long i = 0; i < model.FaceVertexes.LongLength; i++)
            {
                result.FaceVertexes[FaceVertGCDic[i]] = model.FaceVertexes[i];
            }
            //透過チェック
            for (long i = 0; i < result.Materials.LongLength; i++)
            {
                if (string.IsNullOrEmpty(result.Materials[i].TextureFileName))
                {
                    result.Materials[i].IsTranslucent = result.Materials[i].DiffuseColorPalette[0].W < 0.98f;
                }
                else
                {
                    if (Path.GetExtension(result.Materials[i].TextureFileName).ToLower() == ".tga")
                        result.Materials[i].IsTranslucent = true;//透過扱い
                    else
                    {
                        if (!File.Exists(Path.Combine(FolderPath, result.Materials[i].TextureFileName)))
                            throw new InvalidContentException("ファイルが見つかりません:" + Path.Combine(FolderPath, result.Materials[i].TextureFileName));
                        //テクスチャ透過チェック
                        using (Bitmap bitmap = new Bitmap(Path.Combine(FolderPath, result.Materials[i].TextureFileName)))
                        {
                            result.Materials[i].IsTranslucent = ((bitmap.Flags & (int)ImageFlags.HasTranslucent) != 0);
                        }
                    }
                }
            }
            return result;
        }
        internal static MMDMeshData CreateBorderMesh(MMDMeshData input, out long[] VertMaps)
        {
            MMDMeshData result = new MMDMeshData();
            //モデル名とボーンの参照をコピー
            result.ModelName = input.ModelName;
            result.Bones = input.Bones;
            //頂点データのコピー
            MeshVertex[] Vertexes = new MeshVertex[input.Vertexes.LongLength];
            VertMaps = new long[input.Vertexes.LongLength];
            long DelCount = 0;
            Array.Copy(input.Vertexes, Vertexes, Vertexes.LongLength);
            //頂点データの変換
            for (long i = 0; i < Vertexes.LongLength; i++)
            {
                if (Vertexes[i].EdgeFlag != 0)
                {
                    //法線方向に押し出し
                    Vertexes[i].Pos += Vertexes[i].NormalVector * 0.015f;
                    VertMaps[i] = 0;
                }
                else
                {
                    VertMaps[i] = -1;
                    DelCount++;
                    //エッジとして出さないから法線方向に押し出さず、逆方向に押し出す
                    //result.Vertexes[i].Pos -= result.Vertexes[i].NormalVector * 0.03f;
                }
                //法線方向を反転
                Vertexes[i].NormalVector = -Vertexes[i].NormalVector;
            }
            //使用しない頂点データの削除
            result.Vertexes = new MeshVertex[input.Vertexes.LongLength - DelCount];
            long count = 0;
            for (long i = 0; i < Vertexes.LongLength; i++)
            {
                if (VertMaps[i] != -1)
                {
                    result.Vertexes[count] = Vertexes[i];
                    VertMaps[i] = count;
                    count++;
                }
            }

            //面データの変換
            ushort[] FaceVertexes = new ushort[input.FaceVertexes.LongLength];
            bool[] FaceDelFlag = new bool[input.FaceVertexes.LongLength / 3];
            long FaceDelCount = 0;
            for (long i = 0; i < input.FaceVertexes.LongLength; i += 3)
            {
                //ポリゴンの反転
                FaceVertexes[i] = input.FaceVertexes[i];
                FaceVertexes[i + 1] = input.FaceVertexes[i + 2];
                FaceVertexes[i + 2] = input.FaceVertexes[i + 1];
                if (VertMaps[FaceVertexes[i]] == -1 || VertMaps[FaceVertexes[i + 1]] == -1 || VertMaps[FaceVertexes[i + 2]] == -1)
                {
                    FaceDelFlag[i / 3] = true;
                    FaceDelCount++;
                }
                else
                    FaceDelFlag[i / 3] = false;
            }
            //使用しない面データの削除
            result.FaceVertexes = new ushort[input.FaceVertexes.LongLength - FaceDelCount * 3];
            count = 0;
            for (long i = 0; i < input.FaceVertexes.LongLength; i += 3)
            {
                if (!FaceDelFlag[i / 3])
                {
                    result.FaceVertexes[count] = (ushort)VertMaps[FaceVertexes[i]];
                    result.FaceVertexes[count + 1] = (ushort)VertMaps[FaceVertexes[i + 1]];
                    result.FaceVertexes[count + 2] = (ushort)VertMaps[FaceVertexes[i + 2]];
                    count += 3;
                }
            }

            //材質データのコピー
            result.Materials = new MeshMaterial[1];//テクスチャ無しの黒しか無い
            result.Materials[0].TextureFileName = "";//テクスチャ無し
            result.Materials[0].SphereMapFileName = "";
            result.Materials[0].FaceVertCount = (uint)result.FaceVertexes.LongLength;
            /*for (long i = 0; i < input.Materials.LongLength; i++)
            {
                result.Materials[0].FaceVertCount += input.Materials[i].FaceVertCount;//合算
            }*/
            //材質の色を黒にする
            result.Materials[0].DiffuseColorPalette = new Vector4[MeshMaterial.NumPalette];
            result.Materials[0].MirrorColorPalette = new Vector4[MeshMaterial.NumPalette];
            result.Materials[0].SpecularColorPalette = new Vector4[MeshMaterial.NumPalette];
            for (int i = 0; i < result.Materials[0].DiffuseColorPalette.Length; i++)
            {
                result.Materials[0].DiffuseColorPalette[i].W = 1;
                result.Materials[0].MirrorColorPalette[i].W = 1;
                result.Materials[0].SpecularColorPalette[i].W = 1;
            }
            //シェーダ番号調整
            result.Materials[0].ShaderIndex = 0;
            return result;
        }
    }
}
