using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.BuildEngine;
using System.IO;
using System.Runtime.InteropServices;
using MikuMikuDance.XNA.Model;
using MikuMikuDance.XNA.Accessory;
using MikuMikuDance.XNA.Motion;

namespace MikuMikuDance.XNA.Builder.Build
{
    /// <summary>
    /// MikuMikuDanceXNAのアセンブリをプログラムからビルドする用のクラス
    /// ビルド後、出力フォルダにビルド済みファイルが出力される
    /// </summary>
    /// <remarks>MMDXに限らず、それ以外のパイプライン拡張に対応</remarks>
    public class MMDBuilder : IDisposable
    {
        /// <summary>
        /// XNAバージョンキー
        /// </summary>
        public const string xnaVersion = ", Version=3.1.0.0, PublicKeyToken=6d5c3888ef60e27d";
        /// <summary>
        /// MMDXで使うXNAのパイプラインアセンブリ
        /// </summary>
        static string[] DefaultAssemblies =
        {
            "Microsoft.Xna.Framework.Content.Pipeline.FBXImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.XImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.TextureImporter" + xnaVersion,
            "Microsoft.Xna.Framework.Content.Pipeline.EffectImporter" + xnaVersion,
        };
        //動的コンテンツビルドのためのオブジェクト
        Engine m_Engine;
        Project m_Project;
        /// <summary>
        /// エラーロガー
        /// </summary>
        public ErrorLogger ErrLogger { get; private set; }
        /// <summary>
        /// 出力ディレクトリ
        /// </summary>
        public string OutputDirectory
        {
            get { return Path.Combine(TempDirectoryHelper.Instance.BuildDir, "bin/Content"); }
        }
        /// <summary>
        /// 既定のコンストラクタ
        /// </summary>
        /// <param name="mmdImporterPath">MMDImporter.dllのパス</param>
        public MMDBuilder(string mmdImporterPath)
        {
            List<PipelineInfo> asmblies = new List<PipelineInfo>(GetMMDAssemblies(mmdImporterPath));
            CreateBuildProject(asmblies);
        }

        /// <summary>
        /// 別のアセンブリも読み込むコンストラクタ
        /// </summary>
        /// <param name="mmdImporterPath">MMDImporter.dllのパス</param>
        /// <param name="AdditionalPipelineAssemblies">追加インポーター/プロセッサ情報</param>
        public MMDBuilder(string mmdImporterPath, PipelineInfo[] AdditionalPipelineAssemblies)
        {
            List<PipelineInfo> asmblies = new List<PipelineInfo>(GetMMDAssemblies(mmdImporterPath));
            asmblies.AddRange(AdditionalPipelineAssemblies);
            CreateBuildProject(asmblies);
        }
        /// <summary>
        /// MMDXのアセンブリ情報を作成
        /// </summary>
        /// <param name="DllFileName">DLLファイル名</param>
        /// <returns>プロジェクトで使うアセンブリ情報</returns>
        private List<PipelineInfo> GetMMDAssemblies(string DllFileName)
        {
            List<PipelineInfo> result = new List<PipelineInfo>();
            //デフォルトのMMDXパイプラインクラス情報一覧を作成
            result.Add(new PipelineInfo { Name = typeof(MMDImporter).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name =typeof(MMDHiDefProcessor).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name =typeof(AccessoryProcessor).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name =typeof(VACImporter).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name =typeof(MMDBakedMotionProcessor).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name =typeof(MMDMotionImporter).AssemblyQualifiedName, DllFile = DllFileName });
            result.Add(new PipelineInfo { Name = typeof(MMDMotionProcessor).AssemblyQualifiedName, DllFile = DllFileName });
            return result;
        }
        
        //ビルドプロジェクトの作成
        void CreateBuildProject(List<PipelineInfo> PipelineAssemblies)
        {
            //プロジェクトパスの作成
            string projectPath = Path.Combine(TempDirectoryHelper.Instance.BuildDir, "content.contentproj");
            string outputPath = Path.Combine(TempDirectoryHelper.Instance.BuildDir, "bin");
            //ビルドエンジンの作成
            m_Engine = new Engine();
            //カスタムエラーロガーを登録します
            ErrLogger = new ErrorLogger();
            m_Engine.RegisterLogger(ErrLogger);
            //ビルドプロジェクトの作成
            m_Project = new Project(m_Engine);
            m_Project.FullFileName = projectPath;
            m_Project.SetProperty("XnaPlatform", "Windows");
            m_Project.SetProperty("XnaFrameworkVersion", "v3.1");
            m_Project.SetProperty("Configuration", "Release");
            m_Project.SetProperty("OutputPath", outputPath);
            
            //XNAパイプラインアセンブリの追加
            foreach (string defaultAssembly in DefaultAssemblies)
            {
                m_Project.AddNewItem("Reference", defaultAssembly);
            }
            //追加パイプラインアセンブリのうち、XNAの分を追加
            foreach (PipelineInfo pipelineAssembly in PipelineAssemblies)
            {
                if (string.IsNullOrEmpty(pipelineAssembly.DllFile))
                    m_Project.AddNewItem("Reference", pipelineAssembly.Name);
            }
            //MMDXやパイプライン拡張分を参照付きで追加
            foreach (PipelineInfo pipelineAssembly in PipelineAssemblies)
            {
                if (!string.IsNullOrEmpty(pipelineAssembly.DllFile))
                {
                    BuildItem item = m_Project.AddNewItem("Reference", pipelineAssembly.Name);
                    item.SetMetadata("SpecificVersion", "False");
                    item.SetMetadata("HintPath", pipelineAssembly.DllFile);
                }
            }
            // XNA Frameworkのビルド設定読み込み
            m_Project.AddNewImport("$(MSBuildExtensionsPath)\\Microsoft\\XNA " +
                                        "Game Studio\\v3.1\\Microsoft.Xna.GameStudio" +
                                        ".ContentPipeline.targets", null);
        }

        /// <summary>
        /// MMDXコンテンツファイルまたはその他コンテンツファイルをビルド用に追加する。
        /// インポーターおよびプロセッサはnullにより省略可能
        /// </summary>
        /// <param name="filename">入力ファイル</param>
        /// <param name="AssetName">出力アセット名</param>
        /// <param name="importer">インポーター名</param>
        /// <param name="processor">プロセッサ名</param>
        public void Add(string filename, string AssetName, string importer, string processor)
        {
            BuildItem buildItem = m_Project.AddNewItem("Compile", filename);

            buildItem.SetMetadata("Link", Path.GetFileName(filename));
            buildItem.SetMetadata("Name", AssetName);

            if (!string.IsNullOrEmpty(importer))
                buildItem.SetMetadata("Importer", importer);

            if (!string.IsNullOrEmpty(processor))
                buildItem.SetMetadata("Processor", processor);
        }


        /// <summary>
        ///ビルド用のコンテンツファイルを削除する
        /// </summary>
        public void Clear()
        {
            m_Project.RemoveItemsByName("Compile");
        }


        /// <summary>
        /// コンテンツをビルドする。
        /// </summary>
        /// <returns>エラーが出たときには非nullが返る</returns>
        public string Build()
        {
            ErrLogger.Errors.Clear();
            m_Project.Save(Path.Combine(TempDirectoryHelper.Instance.BuildDir, "content.contentproj"));
            //プロジェクトをビルド
            if (!m_Project.Build())
            {
                //ビルド失敗
                return string.Join("\r\n", ErrLogger.Errors.ToArray());
            }

            return null;
        }

        #region IDisposable メンバ
        /// <summary>
        /// プロジェクトを破棄する
        /// </summary>
        public void Dispose()
        {
            TempDirectoryHelper.DisposeStatic();
        }
        
        #endregion
    }
}
