// == 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
{
    public partial class ScriptMain
    {
        //! @addtogroup UVTexIntegra-Scripting名前空間
        //! @{

        /// @cond <summary>UVTexIntegraがScriptMainを制御するためのユーティリティクラスです。このクラスは通常スクリプトプログラマが利用するためのものではありません。</summary> @endcond
        //! @brief UVTexIntegraがScriptMainを制御するためのユーティリティクラスです。このクラスは通常スクリプトプログラマが利用するためのものではありません。
        public static class Utility
        {
            //! @brief Assemblyに含まれるScriptMain継承クラスを列挙してインスタンスのリストを生成します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @param [in] serializedCollections デシリアライズ元にするシリアライズデータ配列を渡します。Assemblyにこの配列に含まれないクラスが出現する場合
            //! デフォルトコンストラクタで初期化されます。
            //! @return assemblyがnullの時はnullを返します。それ以外は空または有効な配列が返ります。
            public static ScriptMain[] BuildInstancesFromTypes(System.Reflection.Assembly assembly, System.Byte[][] serializedCollections)
            {
                if (assembly == null) return null;

                System.Collections.Generic.Dictionary<System.String, ScriptMain> preDeserializedItems = null;
                if (serializedCollections == null) preDeserializedItems = new System.Collections.Generic.Dictionary<System.String, ScriptMain>(0);
                else
                {
                    preDeserializedItems = new System.Collections.Generic.Dictionary<System.String, ScriptMain>(serializedCollections.Length);
                    using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
                    {
                        foreach (System.Byte[] item in serializedCollections)
                        {
                            if ((item == null) || (item.Length <= 0)) continue;
                            try
                            {
                                stream.Position = 0;
                                stream.SetLength(0);
                                stream.Write(item, 0, item.Length);
                                stream.Position = 0;
                                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                                bf.Binder = new AssemblySelectedSerializeBinder(assembly);
                                ScriptMain deserialized = bf.Deserialize(stream) as ScriptMain;
                                if (deserialized != null) preDeserializedItems.Add(deserialized.GetType().FullName, deserialized);
                            }
                            catch (System.Exception) { }
                        }
                    }
                }

                System.Type[] types = assembly.GetTypes();

                // create instances
                System.Collections.Generic.Dictionary<System.String, ScriptMain> builtInstances = new System.Collections.Generic.Dictionary<System.String, ScriptMain>(types.Length);
                using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
                    foreach (System.Type type in types)
                    {
                        System.Reflection.ConstructorInfo defConstructor = GetDefaultConstructorFrom(type);
                        if (defConstructor == null) continue;

                        ScriptMain addInstance = null;
                        System.String typeName = type.FullName;

                        if (System.String.IsNullOrWhiteSpace(typeName) || builtInstances.ContainsKey(typeName)) continue;

                        if (preDeserializedItems.ContainsKey(typeName)) addInstance = preDeserializedItems[typeName];
                        else
                        {
                            ApplyDefaultValueAttribute attr = defConstructor.GetPrimaryAttribute<ApplyDefaultValueAttribute>();
                            addInstance = defConstructor.Invoke(null) as ScriptMain;
                            if (attr != null) attr.ApplyDefaultValueTo(addInstance);
                        }

                        // finally, add to return value table
                        if (addInstance == null)
                        {
                            System.Text.StringBuilder sb = new System.Text.StringBuilder();
                            sb.AppendLine("生成エラーが生じています。");
                            sb.AppendLine(typeName);
                            System.Windows.Forms.MessageBox.Show(sb.ToString());
                        }
                        else builtInstances.Add(typeName, addInstance);
                    }

                ScriptMain[] ret = new ScriptMain[builtInstances.Count];
                builtInstances.Values.CopyTo(ret, 0);
                return ret;
            }

            //! @brief シリアライズされた文字列を指定してSriptMain派生クラスをデシリアライズします。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @param [in] assembly 特定のアセンブリから型情報のロードを行う場合に指定します。nullを指定すると通常のリンクが行われます。
            //! @details シリアライズに成功した場合はScriptMain派生インスタンス。失敗した場合はnull。
            public static ScriptMain FromSerializedByteArray(System.Byte[] serialized, System.Reflection.Assembly assembly)
            {
                if (serialized != null)
                {
                    System.IO.MemoryStream stream = new System.IO.MemoryStream();
                    try
                    {
                        stream.Write(serialized, 0, serialized.Length);
                        stream.Position = 0;
                        System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                        if (assembly != null) bf.Binder = new AssemblySelectedSerializeBinder(assembly);
                        return bf.Deserialize(stream) as ScriptMain;
                    }
                    catch (System.Exception) { /* kill exception */ }
                    finally { if (stream != null) stream.Dispose(); }
                }
                return null;
            }

            //! @brief シリアライズされた文字列を指定してSriptMain派生クラスをデシリアライズします。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @param [in] assembly 特定のアセンブリから型情報のロードを行う場合に指定します。nullを指定すると通常のリンクが行われます。
            //! @details シリアライズに成功した場合はScriptMain派生インスタンス。失敗した場合はnull。
            public static ScriptMain FromSerializedString(System.String serialized, System.Reflection.Assembly assembly)
            {
                if (serialized == null) return null;
                return FromSerializedByteArray(System.Convert.FromBase64String(serialized), assembly);
            }

            //! @brief typeが、有効なScriptMain継承クラスである場合デフォルトコンストラクタを取得します。
            //! @return 以下の条件をすべて満たす場合、デフォルトコンストラクタを返します。どれかひとつでも満たさない(またはtypeがnull)の場合nullを返します。
            //! - ScriptMainを継承している。
            //! - 型がパブリックに公開されている
            //! - 引数のないデフォルトコンストラクタを公開している。
            public static System.Reflection.ConstructorInfo GetDefaultConstructorFrom(System.Type type)
            {
                if ((type != null) && (type.IsPublic || type.IsNestedPublic) && type.IsSubclassOf(typeof(ScriptMain)))
                {
                    return type.GetConstructor(System.Type.EmptyTypes);
                }
                return null;
            }

            //! @brief typeが、有効なScriptMain継承クラスである場合デシリアライズ用のコンストラクタを取得します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @details (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext stream)を引数としたパブリックコンストラクタ(デシリアライズ用のコンストラクタ)を取得します。
            //! 有効なScriptMain継承クラスとは、GetDefaultConstructor(type)がnull以外を返すクラスです。
            //! @return コンストラクタを示すConstructorInfo。デシリアライズ用コンストラクタが見つからない、または有効なScriptMainクラスではない場合nullを返します。
            public static System.Reflection.ConstructorInfo GetDeserializeConstructorFrom(System.Type type)
            {
                if (GetDefaultConstructorFrom(type) == null) return null;
                return type.GetConstructor(new System.Type[] { typeof(System.Runtime.Serialization.SerializationInfo), typeof(System.Runtime.Serialization.StreamingContext) });
            }

            //! @brief instanceが、シリアライズ条件を満たす有効なScriptMain継承クラスかどうか判定します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @return typeが以下の条件をすべて満たす場合true。どれかひとつでも満たさない場合false
            //! - Serializable属性が付与されている。
            //! - System.Runtime.Serialization.ISerializableインターフェースを実装している
            //! - デシリアライズコンストラクタを公開している（GetDeserializeConstructorFrom(System.Type)がnull以外を返す）。
            public static bool IsSerializableScriptMain(ScriptMain instance) { if (instance != null) return IsSerializableScriptMain(instance.GetType()); return false; }

            //! @brief typeが、シリアライズ条件を満たす有効なScriptMain継承クラスかどうか判定します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @return typeが以下の条件をすべて満たす場合true。どれかひとつでも満たさない場合false
            //! - Serializable属性が付与されている。
            //! - System.Runtime.Serialization.ISerializableインターフェースを実装している
            //! - デシリアライズコンストラクタを公開している（GetDeserializeConstructorFrom(System.Type)がnull以外を返す）。
            public static bool IsSerializableScriptMain(System.Type type)
            {
                if ((GetDeserializeConstructorFrom(type) != null) && typeof(System.Runtime.Serialization.ISerializable).IsAssignableFrom(type))
                {
                    return (type.Attributes & System.Reflection.TypeAttributes.Serializable) == System.Reflection.TypeAttributes.Serializable;
                }
                return false;
            }

            //! @brief Assemblyに含まれるScriptMain継承クラスを列挙してインスタンスのリストを生成します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @param [in] cleanupSources この関数が処理を返す前にcleanupSourcesに渡されたインスタンスは全て Close() および Dispose(true) されます。
            //! @param [in] tryRestoreFromSerializableSource cleanupSourcesにシリアライズ可能な要素が含まれる時、この関数の返す新しいScriptMain配列へシリアライズによってバックアップされます。
            //! なんらかのシリアライズやバックアップにエラーが生じる場合は、通常のコンストラクトが実施されます。
            //! @return Assemblyがnullの時はnullを返します。それ以外は空または有効な配列が返ります。
            public static ScriptMain[] RebuildScriptMainInstancesFromTypes(System.Reflection.Assembly assembly, ScriptMain[] cleanupSources, bool tryRestoreFromSerializableSource)
            {
                if (assembly == null) return null;

                System.Type[] types = assembly.GetTypes();

                // all source instance Close(), and listup to serializable source table
                System.Collections.Generic.Dictionary<System.String, ScriptMain> serializableSources = null;
                if (cleanupSources != null)
                {
                    serializableSources = new System.Collections.Generic.Dictionary<System.String, ScriptMain>(cleanupSources.Length);
                    foreach (ScriptMain instance in cleanupSources)
                    {
                        if (instance == null) continue;
                        instance.Close();
                        if (instance.IsSerializable())
                        {
                            System.String typeName = instance.GetType().FullName;
                            if ((System.String.IsNullOrEmpty(typeName) == false) && (serializableSources.ContainsKey(typeName) == false))
                            {
                                serializableSources.Add(typeName, instance);
                            }
                        }
                    }
                }
                else serializableSources = new System.Collections.Generic.Dictionary<System.String, ScriptMain>();

                // create instances
                System.Collections.Generic.Dictionary<System.String, ScriptMain> builtInstances = new System.Collections.Generic.Dictionary<System.String, ScriptMain>(types.Length);
                using (System.IO.Stream stream = new System.IO.MemoryStream())
                    foreach (System.Type type in types)
                    {
                        System.Reflection.ConstructorInfo defConstructor = GetDefaultConstructorFrom(type);
                        if (defConstructor == null) continue;

                        ScriptMain addInstance = null;
                        System.String typeName = type.FullName;

                        if (System.String.IsNullOrWhiteSpace(typeName) || builtInstances.ContainsKey(typeName)) continue;

                        // try serialize
                        if (tryRestoreFromSerializableSource && serializableSources.ContainsKey(typeName))
                        {
                            System.Reflection.ConstructorInfo deserial_constructor = GetDeserializeConstructorFrom(type);
                            ScriptMain source_instance = serializableSources[typeName];
                            if ((deserial_constructor != null) && IsSerializableScriptMain(source_instance))
                            {
                                try
                                {
                                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                                    stream.Position = 0;
                                    stream.SetLength(0);
                                    bf.Serialize(stream, source_instance);
                                    stream.Position = 0;
                                    bf.Binder = new TypeCastSerializeBinder(type);
                                    addInstance = bf.Deserialize(stream) as ScriptMain;
                                }
                                catch (System.Exception)
                                {
                                    if (addInstance != null) addInstance.Dispose();
                                    addInstance = null;
                                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                                    sb.AppendLine("バックアップ中のシリアライズエラーが生じています。");
                                    sb.AppendLine(typeName);
                                    System.Windows.Forms.MessageBox.Show(sb.ToString());
                                }
                            }
                        }

                        // on failed backup or non-backup mode, try default construct
                        if (addInstance == null)
                        {
                            ApplyDefaultValueAttribute attr = defConstructor.GetPrimaryAttribute<ApplyDefaultValueAttribute>();
                            addInstance = defConstructor.Invoke(null) as ScriptMain;
                            if (attr != null) attr.ApplyDefaultValueTo(addInstance);
                        }

                        // finally, add to return value table
                        if (addInstance == null)
                        {
                            System.Text.StringBuilder sb = new System.Text.StringBuilder();
                            sb.AppendLine("生成エラーが生じています。");
                            sb.AppendLine(typeName);
                            System.Windows.Forms.MessageBox.Show(sb.ToString());
                        }
                        else builtInstances.Add(typeName, addInstance);
                    }

                // dispose all source instances
                if (cleanupSources != null) foreach (ScriptMain instance in cleanupSources) if (instance != null) instance.Dispose(true);

                ScriptMain[] ret = new ScriptMain[builtInstances.Count];
                builtInstances.Values.CopyTo(ret, 0);
                return ret;
            }

            //! @brief インスタンスをバイト配列にシリアライズして返します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @details このメソッドでは、 IsSerializable プロパティがtrueを返すシリアライズ可能なインスタンスのみ成功します。
            //! @return シリアライズしたバイト配列。シリアライズ不可能な場合null。
            public static System.Byte[] ToSerializedByteArray(ScriptMain src)
            {
                if ((src != null) && src.IsSerializable())
                {
                    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                    System.IO.MemoryStream stream = new System.IO.MemoryStream();
                    try { bf.Serialize(stream, src); return stream.ToArray(); }
                    catch (System.Exception) { }
                    finally { if (stream != null) stream.Dispose(); }
                }
                return null;
            }

            //! @brief インスタンスを文字列にシリアライズして返します。この関数は通常スクリプトプログラマが利用するためのものではありません。
            //! @details このメソッドでは、 IsSerializable プロパティがtrueを返すシリアライズ可能なインスタンスのみ成功します。
            //! @return シリアライズしたSerializedインスタンス。シリアライズ不可能な場合null。
            public static System.String ToSerializedString(ScriptMain src)
            {
                System.Byte[] arry = ToSerializedByteArray(src);
                if (arry != null) return System.Convert.ToBase64String(arry);
                return null;
            }
        }

        //! @}
    }
}
