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;
using System.ComponentModel;

namespace MikuMikuDance.XNA.Accessory
{
    /// <summary>
    /// MikuMikuDanceアクセサリ用プロセッサ
    /// </summary>
    /// <remarks>普通のモデルプロセッサを使うこともできるが、その場合、スフィアマップとスクリーン機能は使われない</remarks>
    [ContentProcessor(DisplayName = "MikuMikuDanceアクセサリ : MikuMikuDance for XNA")]
    public class AccessoryProcessor : ModelProcessor
    {
        float m_Scale = MMDImporterGlobal.ScaleFactor;
        /// <summary>
        /// モデルのスケール
        /// </summary>
        /// <remarks>MMDの基準が違うため、拡張して調整してある</remarks>
        [DefaultValue(MMDImporterGlobal.ScaleFactor)]
        [DisplayName("スケール")]
        [Description("3軸に沿ってモデルのサイズを均一に変更します")]
        public override float Scale
        {
            get
            {
                return m_Scale;
            }
            set
            {
                m_Scale = value;
            }
        }

        /// <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)
            {
                EffectMaterialContent effectMaterial = new EffectMaterialContent();
                //extディレクトリ作成
                string IntDir = "ext";
                if (!Directory.Exists(IntDir))
                    Directory.CreateDirectory(IntDir);
                //エフェクトをリソースから作成
                FileStream fs = new FileStream(Path.Combine(IntDir, "AccessoryEffect.fx"), FileMode.Create);
                BinaryWriter bw = new BinaryWriter(fs);
                bw.Write(Resource1.AccessoryEffect);
                bw.Close();
                effectMaterial.Effect = new ExternalReference<EffectContent>(Path.Combine(IntDir, "AccessoryEffect.fx"));
                //データのコピー
                string Spheremap = "";
                foreach (var i in basicMaterial.OpaqueData)
                {
                    if (i.Key == "SphereTexture")
                        Spheremap = (string)i.Value;
                    else
                        effectMaterial.OpaqueData.Add(i.Key, i.Value);
                }
                //スフィアマップ処理
                if (Spheremap != "")
                {
                    string spherePath = Path.GetFullPath(Spheremap);
                    if (!File.Exists(spherePath))
                        throw new InvalidContentException(
                            "Cannot find sphere file:" + spherePath);
                    //スフィアマップの拡張子判別
                    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));
                }
                effectMaterial.Name = basicMaterial.Name;
                effectMaterial.Identity = basicMaterial.Identity;
                foreach (var i in basicMaterial.Textures)
                {
                    effectMaterial.Textures.Add(i.Key, i.Value);
                }
                return base.ConvertMaterial(effectMaterial, 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)
        {
            //どのエフェクトを使うのかの種別判定
            foreach (var i in geometryCollection)
            {
                bool Color = false;
                bool UseTexture = false;
                bool Normal = false;
                int Special = 0;
                foreach (var channel in i.Vertices.Channels)
                {
                    if (channel.Name == VertexChannelNames.TextureCoordinate(0)
                        && i.Material.Textures.Count > 0)
                    {
                        UseTexture = true;
                        foreach (var j in i.Material.Textures)
                        {
                            if (Path.GetFileName(j.Value.Filename) == "screen.bmp" &&
                                !File.Exists(j.Value.Filename))
                            {
                                if (Special == 0)
                                    Special = 1;
                                else
                                    throw new InvalidContentException("特殊テクスチャを1マテリアルに2種類以上貼り付けることはできません");
                            }
                        }
                    }
                    else if (channel.Name == VertexChannelNames.Color(0))
                        Color = true;
                    else if (channel.Name == VertexChannelNames.Normal(0))
                    {
                        Normal = true;
                        foreach (var j in i.Material.Textures)
                        {
                            if (Path.GetExtension(j.Value.Filename) == ".sph")
                            {
                                if (Special == 0)
                                    Special = 2;
                                else
                                    throw new InvalidContentException("特殊テクスチャを1マテリアルに2種類以上貼り付けることはできません");
                            }
                            else if (Path.GetExtension(j.Value.Filename) == ".spa")
                            {
                                if (Special == 0)
                                    Special = 3;
                                else
                                    throw new InvalidContentException("特殊テクスチャを1マテリアルに2種類以上貼り付けることはできません");
                            }
                        }
                    }
                }
                int ShaderIndex=0;
                switch (Special)
                {
                    case 0:
                        {//通常部
                            if (Normal)
                                ShaderIndex += 4;
                            if (Color)
                                ++ShaderIndex;
                            if (UseTexture)
                                ShaderIndex += 2;
                        }
                        break;
                    case 1:
                        {//スクリーン
                            //スクリーンはテクスチャを消しておく
                            if(i.Material.Textures.Count>1)
                                throw new InvalidContentException(string.Format(
                                        "AccessoryProcessorはスフィアマップが指定されているマテリアルにおいて合計テクスチャー2枚まで、" +
                                        "それ以外のマテリアルにおいてテクスチャを合計2枚までサポートします" +
                                        "入力メッシュはテクスチャー{0}枚を使用しています。", i.Material.Textures.Count));
                            i.Material.Textures.Clear();
                            ShaderIndex = 20;
                            if (Normal)
                                ShaderIndex += 2;
                            if (Color)
                                ++ShaderIndex;
                        }
                        break;
                    case 2:
                    case 3:
                        {//スフィアマップ
                            if (i.Material.Textures.Count > 2)
                                throw new InvalidContentException(string.Format(
                                        "AccessoryProcessorはスフィアマップが指定されているマテリアルにおいて合計テクスチャー2枚まで、" +
                                        "それ以外のマテリアルにおいてテクスチャを合計2枚までサポートします" +
                                        "入力メッシュはテクスチャー{0}枚を使用しています。", i.Material.Textures.Count));
                            //スフィアマップを不透明データに退避
                            string SphKey = "";
                            foreach (var j in i.Material.Textures)
                            {
                                if (Path.GetExtension(j.Value.Filename) == ".sph" ||
                                    Path.GetExtension(j.Value.Filename) == ".spa")
                                {
                                    i.Material.OpaqueData.Add("SphereTexture", j.Value.Filename);
                                    if (SphKey != "")
                                        throw new InvalidContentException("スフィアマップを1つのマテリアルに二つ以上指定することはできません");
                                    SphKey = j.Key;
                                }
                            }
                            //スフィアマップを削除
                            i.Material.Textures.Remove(SphKey);
                            //スフィアマップの種別を登録
                            i.Material.OpaqueData.Add("SphereType", Special - 1);
                            ShaderIndex = 12;
                            if (Color)
                                ++ShaderIndex;
                            if (UseTexture)
                                ShaderIndex += 2;

                        }
                        break;
                    default:
                        throw new NotImplementedException();
                }
                //スフィアマップの処理残しのチェック
                foreach (var j in i.Material.Textures)
                {
                    if (Path.GetExtension(j.Value.Filename) == ".sph" ||
                        Path.GetExtension(j.Value.Filename) == ".spa")
                        throw new InvalidContentException("スフィアマップを処理できませんでした。アクセサリに法線が無い可能性があります");
                }
                i.Material.OpaqueData.Add("ShaderIndex", ShaderIndex);
            }
            
            base.ProcessGeometryUsingMaterial(material, geometryCollection, context);
        }
        
    }
}