using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Reflection;
using System.Diagnostics;

namespace nft.framework {
    class FailsafeSurrogate : ISerializationSurrogate, ISurrogateSelector {
        public FailsafeSurrogate() {
        }

        #region ISerializationSurrogate メンバ

        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
            throw new NotImplementedException();
        }

        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
            string fieldName = null;
            Type entityType = obj.GetType();
            Hashtable map = new Hashtable(info.MemberCount);
            Hashtable unproc = new Hashtable(info.MemberCount);
            setMemberMap(entityType, ref map);
            foreach (object key in map.Keys)
                unproc.Add(key, null);
            
            foreach (SerializationEntry entry in info) {
                // for each member that was serialized,
                // get matching member in new type
                fieldName = entry.Name;
                int ps = fieldName.IndexOf('+');
                if (ps != -1) {
                    fieldName = fieldName.Substring(ps + 1);
                }
                object val = entry.Value;
                if (map.ContainsKey(fieldName)) {
                    FieldInfo finfo = map[fieldName] as FieldInfo;
                    if (finfo != null) {
                        if (val==null || finfo.FieldType.IsInstanceOfType(val)) {
                            finfo.SetValue(obj, val);
                            // remove entry from hashtable when deserialized correctly;
                            unproc.Remove(fieldName);
                            // remenver settled value.
                            map[fieldName] = val;
                        } else {
                            unproc[fieldName] = val;
                            Debug.WriteLine("deserialize error: field type mismatch (object=" + entityType.Name + ",fieldname=" + fieldName + ",serializedtype=" + entry.ObjectType.Name);
                        }
                    } else {
                        // called again with the same filed name.
                        object o = map[fieldName];
                        if (o != null) {
                            if (!o.Equals(val)) {
                                Debug.WriteLine("deserialize error: field value deserialized again (object=" + entityType.Name + ",fieldname=" + fieldName);
                                unproc[fieldName] = val;
                            }
                        } else {
                            if (val != null) {
                                Debug.WriteLine("deserialize error: field value deserialized again (object=" + entityType.Name + ",fieldname=" + fieldName);
                                unproc[fieldName] = val;
                            }
                        }
                    }
                } else {
                    unproc[fieldName] = val;
                    Debug.WriteLine("deserialize error: missing field (object=" + entityType.Name + ",fieldname=" + fieldName + ",serializedtype=" + entry.ObjectType.Name);
                }
            } // foreach
            
            if (unproc.Count>0 && obj is IVersionCompatible) {
                ((IVersionCompatible)obj).ProcesseUnmatchedFields(unproc);
            }
            unproc.Clear();
            map.Clear();
            return null;
        }

        #region old code
        /*
        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
            string fieldName = null;
            Type entityType;
            foreach (SerializationEntry entry in info) {
                // for each member that was serialized,
                // get matching member in new type
                fieldName = entry.Name;
                entityType = obj.GetType();
                int ps = fieldName.IndexOf('+');
                if (ps != -1) {
                    string baseType = fieldName.Substring(0, ps);
                    fieldName = fieldName.Substring(ps + 1);
                    // drill into base classes until type found
                    do {
                        entityType = entityType.BaseType;
                    } while (!entityType.Name.Equals(baseType));
                }
                MemberInfo[] fields = entityType.GetMember(fieldName, MemberTypes.Field, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (fields.Length > 0) {
                    //entity has a member matching the serialized info
                    FieldInfo newField = fields[0] as FieldInfo;
                    object val = entry.Value;
                    if (val != null) {
                        // don't bother adding serialized members with null values
                        if (!newField.FieldType.IsInstanceOfType(val)) {
                            // convert type if changed in new member
                            val = Convert.ChangeType(val, newField.FieldType);
                        }
                    }
                    newField.SetValue(obj, val);
                }
            } // foreach
            return null;
        }
        */
        #endregion

        private void setMemberMap(Type type, ref Hashtable hash) {
            FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
            foreach (FieldInfo info in fields) {
                hash.Add(info.Name, info);
            }
            if (type.BaseType != null) {
                setMemberMap(type.BaseType, ref hash);
            }
        }

        #endregion

        #region ISurrogateSelector メンバ

        public ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) {

            if (!type.Assembly.GlobalAssemblyCache) {
                selector = this;
                return this;
            } else {
                selector = null;
                return null;
            }
        }

        public void ChainSelector(ISurrogateSelector selector) {
            throw new NotImplementedException("ChainSelector not supported");
        }

        public ISurrogateSelector GetNextSelector() {
            return null;
        }

        #endregion
    }
}
