// == LICENSE INFORMATION ==
/*
 * First author tiritomato 2013.
 * This program is distributed under the GNU General Public License(GPL).
 * support blog (Japanese only) http://d.hatena.ne.jp/tiri_tomato/
 */
// == LICENSE INFORMATION ==

namespace UVTexIntegra.Scripting
{
    //! @addtogroup UVTexIntegra-Scripting名前空間
    //! @{

    //! @brief ソースコードを元にコンパイルされたアセンブリを管理する情報クラスです
	[System.ComponentModel.TypeConverter(typeof(Scripting.PropertyCustomTypeConverter))]
    public partial class CompiledAssembly : LoadedAssembly
    {
        // public properties //

		//! @brief 読み込み時の自動コンパイル設定
		[Scripting.PropertyCustomTypeConverter.DisplayNameAttribute("自動コンパイル")]
		[System.ComponentModel.Description("読込時に自動的にコンパイルします。"), System.ComponentModel.DefaultValue(false)]
        public bool AutoCompileOnOpenDialog { get; set; }

        //! @brief ロード時に解決された絶対ロードパス
        [System.ComponentModel.Browsable(false)]
        public override System.String AbsPath { get { return m_lastCompiledAbsPath; } }

        //! @brief ファイルパスを設定、取得します。
        [System.ComponentModel.Description("コンパイルするスクリプトファイル"), System.ComponentModel.ReadOnly(false)]
        [System.ComponentModel.Editor(typeof(System.Windows.Forms.Design.FileNameEditor), typeof(System.Drawing.Design.UITypeEditor))]
        [Scripting.PropertyCustomTypeConverter.DisplayNameAttribute("FilePath")]
        public System.String CodePath { get; set; }

        //! @brief ロード時に指定されたパス。（Browsableを非表示に変更：その代わりR/WなCodePathプロパティを追加）
        [System.ComponentModel.Browsable(false)]
        public override System.String LoadPath { get { return base.LoadPath; } }

        //! @brief コンパイル時にリンクするdllを取得、設定します。
		[System.ComponentModel.Description(@"DLL参照リストを指定します。""mscorlib.dll,System.dll""のように参照するdllをカンマで区切ってリスト設定してください。")]
        [System.ComponentModel.DefaultValue("mscorlib.dll,System.dll,../UVTexIntegra.dll,lib/OpenTK.dll,lib/DotNetEx.dll,lib/UVTexIntegra.Scripting.dll")]
        [Scripting.PropertyCustomTypeConverter.DisplayNameAttribute("参照するDLL")]
		public System.String ReferencedAssemblies
        {
			get { return m_referencedAssemblies; }
            set
            {
			    if ( value != null ) {
				    System.Char[] invalidPathChars = System.IO.Path.GetInvalidPathChars();
				    foreach ( System.Char ch in invalidPathChars ) if ( ch != ',' ) value = value.Replace( new System.String( ch, 1 ), System.String.Empty );
				    System.String[] splits = value.Split( new System.Char[]{','}, System.StringSplitOptions.RemoveEmptyEntries );
				    System.Collections.Generic.List<System.String> list = new System.Collections.Generic.List<System.String>( splits.Length );
				    foreach ( System.String term in splits ) {
					    System.String trimmed = term.Trim();
					    if ( (0 < trimmed.Length) && (list.Contains(trimmed) == false) ) list.Add(trimmed);
				    }
				    m_referencedAssemblies = System.String.Join(",", list);
			    }
			    else m_referencedAssemblies = null;
            }
		}

		//! @brief 文字列のコレクションとして、指定された参照dll名のリストを取得します。例えば、"mscorlib.dll" や "System.dll" が配列要素として取得されます。
		[System.ComponentModel.BrowsableAttribute(false)]
        public System.String[] ReferencedAssemblyCollection {
			 get {
				if ( m_referencedAssemblies == null ) return new System.String[0];
				return m_referencedAssemblies.Split( new System.Char[]{','} );
			}
		}

		//! @brief シリアライズするための情報を返します。
		[System.ComponentModel.BrowsableAttribute(false)]
        public override LoadedAssembly.SerializeInformation SerializeInfo {
			get { return new SerializeInformation(this); }
		}
		
        // public methods //
        //! @brief ファイル名からコンパイル可能か推測します(ファイルの内容がコンパイル可能かはチェックしません)。
        public static bool IsSupportedFilePath(System.String filePath) { return GetCompiler(filePath) != null; }

        /*! @brief ファイル（*.dll/*.cs/*.cpp/*.c/*.vb/*.js）を指定してアセンブリ情報を新規生成します。
        @details @par *.dll を受け付けた時
        単純にアセンブリをファイルから読み込みます（CompiledAssemblyではなくLoadedAssemblyが返ります）。
        @details @par *.dll以外の場合（ソースコードとみなす場合）
        CompiledAssemblyインスタンスを格納したLoadedAssemblyを返します。
        SerializeInformation.autoCompileOnOpenDialog に true を設定すると、コンパイル情報の生成と同時に、アセンブリのコンパイルも実行します（通常、なにも指定しなければコンパイル情報だけ生成して明示的なRebuild()メソッドの呼出を待機します）。
        @details Assemblyのロードまたはコンパイルに成功すると、Assembly内を総検索して、デフォルトコンストラクタにアクセス可能かつpublicに公開されたScriptMain継承クラスのインスタンスを生成し、ScriptMainインスタンスとして列挙します。
        指定されたAssemblyに有効なScriptMainオブジェクトが見つからない場合、Objectsは空の配列を返します。
        @return 生成された、LoadedAssemblyまたはCompiledAssemblyインスタンス。ファイルが見つからない時はnull
        @exception FileNotFoundException infoまたはinfo.filePathがnullです。
        @exception LoadException ロードエラーが生じています。詳細はInnerExceptionを取得してください。この例外はDLLを指定してロードに失敗すると発生します。
        @exception CompileException コンパイルエラーが生じています。詳細はInnerExceptionを取得してください。この例外はDLL以外を指定して、かつautoCompileOnOpenDialogフラグがtrueの時にコンパイルがトライされ、失敗したケースで発生します。 */
        public static LoadedAssembly From(SerializeInformation info)
        {
			if ( (info == null) || ( info.FilePath == null ) ) throw new FileNotFoundException("file is null");
		
			System.String fileExtension = System.IO.Path.GetExtension( info.FilePath );
			if ( System.String.Equals( fileExtension, ".dll", System.StringComparison.OrdinalIgnoreCase ) ) return LoadedAssembly.From( info );

			CompiledAssembly ret = new CompiledAssembly();
			AttributeExtensions.ApplyDefeultValueAttributeToProperties( ret, false );
			ret.AutoCompileOnOpenDialog = info.AutoCompileOnOpenDialog;
			ret.ReferencedAssemblies = info.ReferencedAssemblies;
			ret.CodePath = info.FilePath;
			System.Reflection.Assembly assembly = null;
            System.String fullPath = null;
            if (info.AutoCompileOnOpenDialog)
            {
                try { fullPath = System.IO.Path.GetFullPath(info.FilePath); }
                catch (System.Exception) { fullPath = info.FilePath; }
                assembly = ret.BuildedAssemblyFromPath(fullPath);
                if (assembly == null) throw new FileNotSupportedException(info.FilePath);
            }
			ret.Initialize(info.FilePath, assembly, info.SerializedCollection);
            ret.m_lastCompiledAbsPath = fullPath;
			return ret;
        }

        //! @brief パスを指定して新規にアセンブリを読み込みます。
        //! @details From(new SerializeInformation(path))を実行します。
        //! DLLのロードは即実行されますが、コンパイルの場合はコンパイル情報の生成のみが行われ、明示的なコンパイル操作を待機します。
        //! @exception FileNotFoundException pathがnullです。
        //! @exception LoadException ロードエラーが生じています。詳細はInnerExceptionを取得してください。この例外はDLLを指定してロードに失敗すると発生します。
        public static new LoadedAssembly From(System.String path) { return From(new SerializeInformation(path)); }

        /*! @brief リビルドを行います。
        @return 成功するとtrueを返し、メンバのデータが更新されます。ファイルパスまたは非対応の形式ではfalseを返し、メンバのデータは更新されません。ただし、コンパイルの失敗は例外で返ることに注意してください。
        @details 再コンパイル成功時、現在のインスタンスを元にバックアップを実行します。
        @exception FileNotSupportedException ファイルパスは非対応の形式です。拡張子から適切なコンパイラを取得できません。
        @exception CompileException ファイルのコンパイル中にエラーが生じました。詳細はInnerExceptionをチェックしてください。*/
        public bool Rebuild()
        {
            System.String fullPath = null;
            try { fullPath = System.IO.Path.GetFullPath(CodePath); }
            catch (System.Exception) { fullPath = CodePath; }
            System.Reflection.Assembly assembly = BuildedAssemblyFromPath(fullPath);
            if (assembly == null) return false;
            base.Initialize(CodePath, assembly, ObjectsToSerializedByteArray());
            m_lastCompiledAbsPath = fullPath;
            return true;
        }

        // protected implements //

        //! @brief ファイルパスからコンパイルされたアセンブリを取得します。
        //! @exception FileNotSupportedException ファイルパスは非対応の形式です。拡張子から適切なコンパイラを取得できません。
        //! @exception CompileException ファイルのコンパイル中にエラーが生じました。詳細はInnerExceptionをチェックしてください。
        //! @return コンパイルされたアセンブリ。
        protected System.Reflection.Assembly BuildedAssemblyFromPath(System.String filePath)
		{
			System.CodeDom.Compiler.CodeDomProvider provider = GetCompiler(filePath);
			if (provider == null) throw new FileNotSupportedException(filePath);

            System.String registedCurrentDirectory = System.IO.Directory.GetCurrentDirectory();
            try
            {
                try { System.IO.Directory.SetCurrentDirectory(System.IO.Path.GetDirectoryName(filePath)); }
                catch (System.Exception ex) { throw new FileNotFoundException(filePath, ex); }
                System.CodeDom.Compiler.CompilerResults results = null;
                System.CodeDom.Compiler.CompilerParameters compile_params = new System.CodeDom.Compiler.CompilerParameters();
                compile_params.GenerateInMemory = true;
                foreach (System.String ref_asm in ReferencedAssemblyCollection) compile_params.ReferencedAssemblies.Add(ref_asm);
                try { results = provider.CompileAssemblyFromFile(compile_params, filePath); }
                catch (System.Exception ex) { throw new CompileException("コンパイルに失敗", ex, results); }
                if (results.NativeCompilerReturnValue != 0) throw new CompileException(System.String.Format("コンパイルに失敗({0})", results.NativeCompilerReturnValue), null, results);
                try { return results.CompiledAssembly; }
                catch (System.Exception ex) { throw new CompileException("プラグインのロードに失敗", ex, results); }
            }
            finally { System.IO.Directory.SetCurrentDirectory(registedCurrentDirectory); }
		}

		//! @brief ファイルパスに適合するコンパイラを取得します。適合するコンパイラが見つからない時はnullを返します。
        protected static System.CodeDom.Compiler.CodeDomProvider GetCompiler(System.String filePath)
		{
			if ( filePath == null ) return null;
			System.String fileExt = System.IO.Path.GetExtension( filePath );
			System.CodeDom.Compiler.CodeDomProvider provider = null;
			System.String language = null;
			if ( System.CodeDom.Compiler.CodeDomProvider.IsDefinedExtension( fileExt ) )
				language = System.CodeDom.Compiler.CodeDomProvider.GetLanguageFromExtension( fileExt );
			if ( (language != null) && System.CodeDom.Compiler.CodeDomProvider.IsDefinedLanguage( language ) )
				provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider( language );
			return provider;
		}

        // private fields
        private System.String m_lastCompiledAbsPath;
		private System.String m_referencedAssemblies;
    }

    //! @}
}
