/*
 * XmlStaticMemberSerializer.cs
 * Copyright (c) 2009 kbinani
 *
 * This file is part of Boare.Lib.AppUtil.
 *
 * Boare.Lib.AppUtil is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * Boare.Lib.AppUtil is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;

using Microsoft.CSharp;

namespace Boare.Lib.AppUtil {

    /// <summary>
    /// クラスのstaticメンバーのxmlシリアライズ/デシリアライズを行うclass
    /// </summary>
    public class XmlStaticMemberSerializer {
        /// <summary>
        /// ターゲットとなるクラスから，シリアライズするメンバーを抽出する時に使用
        /// </summary>
        private class MemberEntry {
            /// <summary>
            /// プロパティ/フィールドの名前
            /// </summary>
            public string Name;
            /// <summary>
            /// プロパティ/フィールドの型
            /// </summary>
            public Type Type;
            /// <summary>
            /// プロパティ/フィールドのデフォルト値
            /// </summary>
            public object Default;

            public MemberEntry( string name, Type type, object default_ ) {
                Name = name;
                Type = type;
                Default = default_;
            }
        }

        /// <summary>
        /// シリアライズする対象の型．staticメンバーだけなので，インスタンスではなく型を保持
        /// </summary>
        private Type m_item;
        /// <summary>
        /// シリアライズ/デシリアライズするための内部型
        /// </summary>
        private Type m_config_type = null;
        /// <summary>
        /// m_config_typeで初期化されたシリアライザ
        /// </summary>
        private XmlSerializer m_xs = null;

        private XmlStaticMemberSerializer() {
        }

        /// <summary>
        /// 指定された型をシリアライズするための初期化を行います
        /// </summary>
        /// <param name="item"></param>
        public XmlStaticMemberSerializer( Type item ) {
            m_item = item;
        }

        /// <summary>
        /// シリアライズを行い，ストリームに書き込みます
        /// </summary>
        /// <param name="stream"></param>
        public void Serialize( Stream stream ) {
            if ( m_config_type == null ) {
                GenerateConfigType();
            }
            ConstructorInfo ci = m_config_type.GetConstructor( new Type[]{} );
            object config = ci.Invoke( new object[]{} );
            foreach ( FieldInfo target in m_config_type.GetFields() ) {
                foreach ( PropertyInfo pi in m_item.GetProperties( BindingFlags.Public | BindingFlags.Static ) ) {
                    if ( target.Name == pi.Name && target.FieldType.Equals( pi.PropertyType ) && pi.CanRead && pi.CanWrite ) {
                        target.SetValue( config, pi.GetValue( m_item, new object[] { } ) );
                        break;
                    }
                }
                foreach ( FieldInfo fi in m_item.GetFields( BindingFlags.Public | BindingFlags.Static ) ) {
                    if ( target.Name == fi.Name && target.FieldType.Equals( fi.FieldType ) ) {
                        target.SetValue( config, fi.GetValue( m_item ) );
                        break;
                    }
                }
            }
            m_xs.Serialize( stream, config );
        }

        /// <summary>
        /// 指定したストリームを使って，デシリアライズを行います
        /// </summary>
        /// <param name="stream"></param>
        public void Deserialize( Stream stream ) {
            if ( m_config_type == null ) {
                GenerateConfigType();
            }
            object config = m_xs.Deserialize( stream );
            if ( config == null ) {
                throw new ApplicationException( "failed serializing internal config object" );
            }
            foreach ( FieldInfo target in m_config_type.GetFields() ) {
                foreach ( PropertyInfo pi in m_item.GetProperties( BindingFlags.Public | BindingFlags.Static ) ) {
                    if ( target.Name == pi.Name && target.FieldType.Equals( pi.PropertyType ) && pi.CanRead && pi.CanWrite ) {
                        pi.SetValue( m_item, target.GetValue( config ), new object[] { } );
                        break;
                    }
                }
                foreach ( FieldInfo fi in m_item.GetFields( BindingFlags.Public | BindingFlags.Static ) ) {
                    if ( target.Name == fi.Name && target.FieldType.Equals( fi.FieldType ) ) {
                        fi.SetValue( m_item, target.GetValue( config ) );
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// シリアライズ用の内部型をコンパイルし，m_xsが使用できるようにします
        /// </summary>
        private void GenerateConfigType() {
            List<MemberEntry> config_names = CollectScriptConfigEntries( m_item );
            string code = GenerateClassCodeForXmlSerialization( config_names, m_item );
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add( "System.Windows.Forms.dll" );
            parameters.ReferencedAssemblies.Add( "System.dll" );
            parameters.ReferencedAssemblies.Add( "System.Drawing.dll" );
            parameters.ReferencedAssemblies.Add( "System.Xml.dll" );
            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = false;
            parameters.IncludeDebugInformation = true;
            CompilerResults results = provider.CompileAssemblyFromSource( parameters, code );
            Assembly asm = results.CompiledAssembly;
            if ( asm.GetTypes().Length <= 0 ) {
                m_config_type = null;
                m_xs = null;
                throw new ApplicationException( "failed generating internal xml serizlizer" );
            } else {
                m_config_type = (asm.GetTypes())[0];
                m_xs = new XmlSerializer( m_config_type );
            }
        }

        /// <summary>
        /// 設定ファイルから読込むための型情報を蒐集
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private static List<MemberEntry> CollectScriptConfigEntries( Type item ) {
            List<MemberEntry> config_names = new List<MemberEntry>();
            foreach ( PropertyInfo pi in item.GetProperties( BindingFlags.Static | BindingFlags.Public ) ) {
                object[] attrs = pi.GetCustomAttributes( true );
                foreach ( object obj in attrs ) {
                    if ( obj.GetType().Equals( typeof( System.Xml.Serialization.XmlIgnoreAttribute ) ) ) {
                        continue;
                    }
                }
                if ( pi.CanRead && pi.CanWrite ) {
                    config_names.Add( new MemberEntry( pi.Name, pi.PropertyType, pi.GetValue( item, new object[] { } ) ) );
                }
            }
            foreach ( FieldInfo fi in item.GetFields( BindingFlags.Static | BindingFlags.Public ) ) {
                object[] attrs = fi.GetCustomAttributes( true );
                foreach ( object obj in attrs ) {
                    if ( obj.GetType().Equals( typeof( System.Xml.Serialization.XmlIgnoreAttribute ) ) ) {
                        continue;
                    }
                }
                config_names.Add( new MemberEntry( fi.Name, fi.FieldType, fi.GetValue( item ) ) );
            }
            return config_names;
        }

        /// <summary>
        /// 指定した型から、Reflectionを使ってxmlシリアライズ用のクラスをコンパイルするためのC#コードを作成します
        /// </summary>
        /// <param name="implemented"></param>
        /// <returns></returns>
        private static string GenerateClassCodeForXmlSerialization( List<MemberEntry> config_names, Type item ) {
            // XmlSerialization用の型を作成
            string code = "";
            code += "using System;\n";
            code += "namespace Boare.Lib.AppUtil{\n";
            code += "    public class StaticMembersOf" + item.Name + "{\n";
            foreach ( MemberEntry entry in config_names ) {
                code += "        public " + entry.Type.ToString() + " " + entry.Name + ";\n";
            }
            code += "    }\n";
            code += "}\n";
            return code;
        }
    }

}
