using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace MikuMikuDance.XNA.Model
{
    class MMDMeshBuilder
    {
        internal static MeshContent BuildMesh(MMDMeshData model, string filename, ContentIdentity Identity, Dictionary<long, long> VertToFaceVert)
        {

            MeshBuilder meshBuilder = MeshBuilder.StartMesh(model.ModelName);
            //頂点や面をチェック
            if (model.Vertexes == null || model.Vertexes.Length == 0)
                return null;
            if (model.FaceVertexes == null || model.FaceVertexes.Length == 0)
                return null;
            meshBuilder.MergeDuplicatePositions = true;

            //ここからメッシュ情報を登録していく

            //頂点を登録
            int[] Positions = new int[model.Vertexes.LongLength];// + border.Vertexes.LongLength];
            for (long i = 0; i < model.Vertexes.LongLength; i++)
            {
                //頂点登録(座標系変換込み)
                Positions[i] = meshBuilder.CreatePosition(model.Vertexes[i].Pos);
            }
            
            //テクスチャー座標および法線用の頂点チャンネルを追加
            int texCoordinateIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));
            int normalDataIndex = meshBuilder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal(0));
            int weightDataIndex = meshBuilder.CreateVertexChannel<BoneWeightCollection>(VertexChannelNames.Weights(0));
            
            //表情用頂点データ(-1なら表情に用いない頂点、0以上ならbaseの頂点番号)
            int FaceDataIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(1));
            long FaceIndex = 0;

            //マテリアル
            for (long i = 0; i < model.Materials.LongLength; i++)
            {
                EffectMaterialContent material = new EffectMaterialContent();
                //BasicMaterialContent material = new BasicMaterialContent();
                //名前設定
                material.Name = "material" + i.ToString();
                
                bool HasTexture = false;
                //テクスチャ読み込み
                if (!string.IsNullOrEmpty(model.Materials[i].TextureFileName))
                {
                    string path = model.Materials[i].TextureFileName;
                    if (!Path.IsPathRooted(path))
                    {
                        path = Path.Combine(Path.GetDirectoryName(filename), path);
                    }
                    if (!File.Exists(path))
                        throw new InvalidContentException(
                            "Cannot find texture file:" + path, Identity);
                    //テクスチャのセット
                    material.Textures.Add("Texture", new ExternalReference<TextureContent>(path));
                    HasTexture = true;
                }
                //スフィアマップ読み込み
                if (!string.IsNullOrEmpty(model.Materials[i].SphereMapFileName))
                {
                    //ファイルチェック
                    string path = model.Materials[i].SphereMapFileName;
                    if (!Path.IsPathRooted(path))
                    {
                        path = Path.Combine(Path.GetDirectoryName(filename), path);
                    }
                    if (!File.Exists(path))
                        throw new InvalidContentException(
                            "Cannot find Spheremap:" + path, Identity);
                    //加算スフィアか乗算スフィアか判定
                    if (Path.GetExtension(path).ToLower() == ".sph")
                    {
                        //シェーダ内処理用のフラグ、UseSphereを1(乗算)にする
                        material.OpaqueData.Add("UseSphere", 1.0f);
                    }
                    else if (Path.GetExtension(path).ToLower() == ".spa")
                    {
                        //シェーダ内処理用のフラグ、UseSphereを2(加算)にする
                        material.OpaqueData.Add("UseSphere", 2.0f);
                    }
                    else
                        throw new InvalidContentException("不明なスフィアマップ: " + path);
                    //スフィアマップは.NETフレームワークを利用して.bmpに変換し、中間ディレクトリに吐き出す
                    string midDir = Path.Combine(Path.GetDirectoryName(path), "ext");
                    if (!Directory.Exists(midDir))
                        Directory.CreateDirectory(midDir);
                    string intFilePath = Path.Combine(midDir, Path.GetFileName(path));
                    //.NETフレームワークを利用して読み込み
                    Image sphere = null;
                    try
                    {
                        sphere = new Bitmap(path);
                    }
                    catch (OutOfMemoryException)
                    {//.tga形式は読み込めないのでこっちで処理
                        intFilePath += ".tga";
                        File.Copy(path, intFilePath, true);
                    }
                    //型判別
                    if (sphere != null)
                    {
                        string Extention;
                        ImageExtAnalyzer.Analyze(sphere, out Extention);
                        intFilePath += Extention;
                        File.Copy(path, intFilePath, true);
                    }
                    //スフィアマップテクスチャのセット
                    //テクスチャのセット
                    material.Textures.Add("SphereTexture", new ExternalReference<TextureContent>(intFilePath));
                }
                else
                {
                    material.OpaqueData.Add("UseSphere", 0.0f);
                }
                //カラーパレット設定
                material.OpaqueData.Add("DiffuseColorPalette", model.Materials[i].DiffuseColorPalette);
                material.OpaqueData.Add("EmissiveColorPalette", model.Materials[i].MirrorColorPalette);
                material.OpaqueData.Add("SpecularColorPalette", model.Materials[i].SpecularColorPalette);
                //シェーダ番号設定
                material.OpaqueData.Add("ShaderIndex", model.Materials[i].ShaderIndex);
                //トゥーンテクスチャ設定
                ToonTextureManager.Process(material, model.ToonTextures, filename);
                for (long k = FaceIndex; k < FaceIndex + model.Materials[i].FaceVertCount; k++)
                {
                    //マテリアルをセット
                    meshBuilder.SetMaterial(material);
                    //頂点index取得
                    int VecIndex = model.FaceVertexes[k];
                    //UV座標を0番にセット
                    if (HasTexture)
                        meshBuilder.SetVertexChannelData(texCoordinateIndex, model.Vertexes[VecIndex].UV);
                    else
                        meshBuilder.SetVertexChannelData(texCoordinateIndex, Vector2.Zero);
                    //法線セット
                    meshBuilder.SetVertexChannelData(normalDataIndex,
                        model.Vertexes[VecIndex].NormalVector);
                    //ボーンウェイト
                    BoneWeightCollection boneWeight = new BoneWeightCollection();
                    int boneNum = model.Vertexes[VecIndex].BoneNum[0];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, model.Vertexes[VecIndex].BoneWeight / 100f));
                    boneNum = model.Vertexes[VecIndex].BoneNum[1];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, 1.0f - model.Vertexes[VecIndex].BoneWeight / 100f));

                    meshBuilder.SetVertexChannelData(weightDataIndex, boneWeight);

                    //表情用の頂点番号(base表情列内の番号)をUV座標の1番にセット
                    if (VertToFaceVert.ContainsKey(VecIndex))
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(VertToFaceVert[VecIndex], model.Vertexes[VecIndex].PaletteNum));
                    else
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(-1, model.Vertexes[VecIndex].PaletteNum));

                    //面を生成
                    meshBuilder.AddTriangleVertex(Positions[VecIndex]);
                }
                FaceIndex += model.Materials[i].FaceVertCount;
            }
            
            //メッシュを作成
            MeshContent meshContent = meshBuilder.FinishMesh();
            //メッシュの設定
            meshContent.Identity = Identity;
            meshContent.Name = model.ModelName;

            return meshContent;
        }

        internal static MeshContent BuildBorderMesh(MMDMeshData model, string filename, ContentIdentity Identity, Dictionary<long, long> VertToFaceVert, long[] InvVertMap)
        {

            MeshBuilder meshBuilder = MeshBuilder.StartMesh(model.ModelName);
            //頂点や面をチェック
            if (model.Vertexes == null || model.Vertexes.Length == 0)
                return null;
            if (model.FaceVertexes == null || model.FaceVertexes.Length == 0)
                return null;
            meshBuilder.MergeDuplicatePositions = true;

            //ここからメッシュ情報を登録していく

            //頂点を登録
            int[] Positions = new int[model.Vertexes.LongLength];
            for (long i = 0; i < model.Vertexes.LongLength; i++)
            {
                //頂点登録(座標系変換込み)
                Positions[i] = meshBuilder.CreatePosition(model.Vertexes[i].Pos);
            }

            //テクスチャー座標および法線用の頂点チャンネルを追加
            int texCoordinateIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(0));
            int normalDataIndex = meshBuilder.CreateVertexChannel<Vector3>(VertexChannelNames.Normal(0));
            int weightDataIndex = meshBuilder.CreateVertexChannel<BoneWeightCollection>(VertexChannelNames.Weights(0));
            //表情用頂点データ(-1なら表情に用いない頂点、0以上ならbaseの頂点番号)
            int FaceDataIndex = meshBuilder.CreateVertexChannel<Vector2>(VertexChannelNames.TextureCoordinate(1));
            long FaceIndex = 0;


            //マテリアル(境界線分) 1個しか無いはずだけど、念のため
            for (long i = 0; i < model.Materials.LongLength; i++)
            {
                EffectMaterialContent material = new EffectMaterialContent();
                //名前設定
                material.Name = "material-border" + i.ToString();
                //テクスチャは無し
                material.OpaqueData.Add("UseSphere", 0.0f);
                //カラーパレット設定
                material.OpaqueData.Add("DiffuseColorPalette", model.Materials[i].DiffuseColorPalette);
                material.OpaqueData.Add("EmissiveColorPalette", model.Materials[i].MirrorColorPalette);
                material.OpaqueData.Add("SpecularColorPalette", model.Materials[i].SpecularColorPalette);
                for (long k = FaceIndex; k < FaceIndex + model.Materials[i].FaceVertCount; k++)
                {
                    //マテリアルをセット
                    meshBuilder.SetMaterial(material);
                    //頂点index取得
                    int VecIndex = model.FaceVertexes[k];
                    //UV座標を0番にセット
                    meshBuilder.SetVertexChannelData(texCoordinateIndex, Vector2.Zero);
                    //法線セット
                    meshBuilder.SetVertexChannelData(normalDataIndex,
                        model.Vertexes[VecIndex].NormalVector);
                    //ボーンウェイト
                    BoneWeightCollection boneWeight = new BoneWeightCollection();
                    int boneNum = model.Vertexes[VecIndex].BoneNum[0];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, model.Vertexes[VecIndex].BoneWeight / 100f));
                    boneNum = model.Vertexes[VecIndex].BoneNum[1];
                    if (boneNum >= 0 && boneNum < model.Bones.Length)
                        boneWeight.Add(new BoneWeight(model.Bones[boneNum].BoneName, 1.0f - model.Vertexes[VecIndex].BoneWeight / 100f));

                    meshBuilder.SetVertexChannelData(weightDataIndex, boneWeight);

                    //表情用の頂点番号(base表情列内の番号)をUV座標の1番にセット
                    if (VertToFaceVert.ContainsKey(InvVertMap[VecIndex]))
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(VertToFaceVert[InvVertMap[VecIndex]], model.Vertexes[VecIndex].PaletteNum));
                    else
                        meshBuilder.SetVertexChannelData(FaceDataIndex, new Vector2(-1, model.Vertexes[VecIndex].PaletteNum));

                    //カラー設定
                    //面を生成
                    meshBuilder.AddTriangleVertex(Positions[VecIndex]);
                }
                FaceIndex += model.Materials[i].FaceVertCount;
            }
            //メッシュを作成
            MeshContent meshContent = meshBuilder.FinishMesh();
            //メッシュの設定
            meshContent.Identity = Identity;
            meshContent.Name = model.ModelName;

            return meshContent;
        }
    }
}
