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

namespace MikuMikuDance.XNA.Accessory
{
    /// <summary>
    /// MikuMikuDanceアクセサリ用プロセッサ
    /// </summary>
    /// <remarks>普通のモデルプロセッサを使うこともできるが、その場合、スフィアマップとスクリーン機能は使われない</remarks>
    [ContentProcessor(DisplayName = "MikuMikuDanceアクセサリ : MikuMikuDance for XNA")]
    public class AccessoryProcessor : ModelProcessor
    {
        /// <summary>
        /// アクセサリの処理
        /// </summary>
        /// <param name="input">アクセサリデータ</param>
        /// <param name="context">プロセッサコンテキスト</param>
        /// <returns>処理済みアクセサリデータ</returns>
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            return base.Process(input, context);
        }
        /// <summary>
        /// 特殊テクスチャの処理
        /// </summary>
        /// <param name="material">マテリアル</param>
        /// <param name="context">プロセッサコンテキスト</param>
        /// <returns>処理済みマテリアルデータ</returns>
        protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
        {
            BasicMaterialContent basicMaterial = material as BasicMaterialContent;
            if (basicMaterial != null)
            {
                List<string> SpecialTexKeys = new List<string>();
                foreach (var i in basicMaterial.Textures)
                {
                    if ((!File.Exists(i.Value.Filename) && Path.GetFileName(i.Value.Filename) == "screen.bmp") ||
                        Path.GetExtension(i.Value.Filename).ToLower() == ".sph" || Path.GetExtension(i.Value.Filename).ToLower() == ".spa")
                    {//screen.bmp及び.sph発見
                        SpecialTexKeys.Add(i.Key);
                    }
                }
                //テクスチャ座標無し対策のためにデータ突っ込んでるので引っ張り出し
                string sphName = "";
                foreach (var i in basicMaterial.OpaqueData)
                {
                    if (i.Key == "sphTex")
                        sphName = basicMaterial.Name;
                }
                if (sphName != "")
                    basicMaterial.OpaqueData.Remove("sphTex");
                if (SpecialTexKeys.Count > 0 || sphName != "")
                {
                    string spherePath, texPath = "";
                    if (SpecialTexKeys.Count > 0)
                        spherePath = basicMaterial.Textures[SpecialTexKeys[0]].Filename;
                    else
                        spherePath = sphName;
                    foreach (var tex in basicMaterial.Textures)
                    {
                        if (tex.Value.Filename != spherePath)
                        {
                            if (texPath != "")
                                throw new InvalidContentException(string.Format(
                                        "AccessoryProcessorはscreen.bmpが指定されているマテリアルにおいて合計テクスチャー1枚のみ、" +
                                        "スフィアマップを指定されているマテリアルにおいてテクスチャを合計2枚までサポートします" +
                                        "入力メッシュはテクスチャー{0}枚を使用しています。", basicMaterial.Textures.Count));
                            texPath = sphName;
                        }
                    }
                    if (Path.GetFileName(spherePath) == "screen.bmp")
                    {
                        if (basicMaterial.Textures.Count >= 2)
                            throw new InvalidContentException(string.Format(
                                "AccessoryProcessorはscreen.bmpが指定されているマテリアルにおいてテクスチャー1枚のみをサポートします" +
                                "入力メッシュはテクスチャー{0}枚を使用しています。", basicMaterial.Textures.Count));
                        EffectMaterialContent effectMaterial = new EffectMaterialContent();
                        //スクリーンのエフェクト挿入
                        //リソースからファイルを作成して読み込むという超セコイ方法……
                        if (!Directory.Exists("ext"))
                            Directory.CreateDirectory("ext");
                        FileStream fs = new FileStream(Path.Combine("ext", "ScreenEffect.fx"), FileMode.Create);
                        BinaryWriter bw = new BinaryWriter(fs);
                        bw.Write(Resource1.ScreenEffect);
                        bw.Close();
                        effectMaterial.Effect = new ExternalReference<EffectContent>(Path.Combine("ext", "ScreenEffect.fx"));
                        //データのコピー
                        foreach (var i in basicMaterial.OpaqueData)
                        {
                            effectMaterial.OpaqueData.Add(i.Key, i.Value);
                        }
                        effectMaterial.Name = "mmd-screen";
                        effectMaterial.Identity = basicMaterial.Identity;
                        //エフェクトマテリアルを代わりに変換
                        return base.ConvertMaterial(effectMaterial, context);
                    }
                    else if (Path.GetExtension(spherePath).ToLower() == ".sph" || Path.GetExtension(spherePath).ToLower() == ".spa")
                    {
                        //スフィアマップのエフェクト作成
                        EffectMaterialContent effectMaterial = new EffectMaterialContent();
                        //スフィアマップのディレクトリ作成
                        string IntDir = Path.Combine(Path.GetDirectoryName(spherePath), "ext");
                        if (!Directory.Exists(IntDir))
                            Directory.CreateDirectory(IntDir);
                        //エフェクトをリソースから作成
                        FileStream fs = new FileStream(Path.Combine(IntDir, "SphereEffect.fx"), FileMode.Create);
                        BinaryWriter bw = new BinaryWriter(fs);
                        bw.Write(Resource1.SphereEffect);
                        bw.Close();
                        effectMaterial.Effect = new ExternalReference<EffectContent>(Path.Combine(IntDir, "SphereEffect.fx"));
                        //データのコピー
                        foreach (var i in basicMaterial.OpaqueData)
                        {
                            effectMaterial.OpaqueData.Add(i.Key, i.Value);
                        }
                        effectMaterial.Name = basicMaterial.Name + "-sphere";
                        effectMaterial.Identity = basicMaterial.Identity;
                        //テクスチャの存在チェック
                        if (!File.Exists(spherePath))
                            throw new InvalidContentException(
                                "Cannot find sphere file:" + spherePath);
                        if (!string.IsNullOrEmpty(texPath) && !File.Exists(texPath))
                            throw new InvalidContentException(
                                "Cannot find sphere file:" + texPath);
                        //スフィアマップの拡張子判別
                        string intFilePath = Path.Combine(IntDir, Path.GetFileName(spherePath));
                        Image sphere = null;
                        try
                        {
                            sphere = new Bitmap(spherePath);
                        }
                        catch (OutOfMemoryException)
                        {
                            //ここで読めないのはtga型
                            intFilePath += ".tga";
                            File.Copy(spherePath, intFilePath, true);//上書きありでコピー
                        }
                        //型判別
                        if (sphere != null)
                        {
                            string Extention;
                            ImageExtAnalyzer.Analyze(sphere, out Extention);
                            intFilePath += Extention;
                            File.Copy(spherePath, intFilePath, true);
                        }
                        //スフィアマップの貼付け
                        effectMaterial.Textures.Add("SphereTexture", new ExternalReference<TextureContent>(intFilePath));
                        if (Path.GetExtension(spherePath).ToLower() == ".spa")
                            effectMaterial.OpaqueData.Add("SphereType", 2);
                        else
                            effectMaterial.OpaqueData.Add("SphereType", 1);
                        //通常テクスチャの貼付け
                        if (!string.IsNullOrEmpty(texPath))
                        {
                            effectMaterial.Textures.Add("Texture", new ExternalReference<TextureContent>(texPath));
                            effectMaterial.OpaqueData.Add("UseTexture", 1);
                        }
                        else
                        {
                            effectMaterial.OpaqueData.Add("UseTexture", 0);
                        }

                        //エフェクトマテリアルを代わりに変換
                        return base.ConvertMaterial(effectMaterial, context);
                    }
                    else//なんか実装忘れ？
                        throw new NotImplementedException();
                }
                else
                {
                    return base.ConvertMaterial(basicMaterial, context);
                }
            }
            else
            {//EffectMaterialContentが来る場合ってあるの？
                throw new InvalidContentException(string.Format(
                    "AccessoryProcessorはBasicMaterialContentのみをサポートします" +
                    "入力メッシュは{0}を使用しています。", material.GetType()));
                
            }
            
        }
        /// <summary>
        /// テクスチャ座標設定サボリ対策用に継承
        /// </summary>
        /// <param name="material">マテリアル</param>
        /// <param name="geometryCollection">ジオメトリコレクション</param>
        /// <param name="context">コンテキスト</param>
        protected override void ProcessGeometryUsingMaterial(MaterialContent material, IEnumerable<GeometryContent> geometryCollection, ContentProcessorContext context)
        {
            //スフィアマップでテクスチャ座標設定サボリ対策
            if (material.Textures.Count > 0)
            {
                foreach (var i in geometryCollection)
                {
                    bool flag = false;
                    int count = 0;
                    foreach (var channel in i.Vertices.Channels)
                    {
                        count = channel.Count;
                        if (channel.Name == VertexChannelNames.TextureCoordinate(0))
                        {
                            flag = true;
                            break;
                        }
                    }
                    if (!flag)
                    {//設定されてないのでテクスチャを退避
                        if (material.OpaqueData.ContainsKey("sphTex"))
                            throw new InvalidContentException("スフィアマップ用のテクスチャを2つ検知しました");
                        material.OpaqueData.Add("sphTex", 0);
                        material.Name = material.Textures["Texture"].Filename;
                        material.Textures.Clear();    
                    }
                }
            }
            base.ProcessGeometryUsingMaterial(material, geometryCollection, context);
        }
        
    }
}