﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace AsmodeeDigital.Common.Plugin.Utils
{
    public static class Reflection
    {
        public static Hashtable HashtableFromObject(object obj, HashSet<string> excludedFields = null, uint maxDepth = 30)
        {
            const uint depth = 1;

            object parsed = ParseObject(obj, "root", excludedFields, depth, maxDepth);
            Hashtable result = parsed as Hashtable;

            if (parsed != null && result == null)
            {
                result = new Hashtable() { { "array", parsed } };
            }

            return result;
        }

        private static object ParseObject(object obj, string varPath, HashSet<string> excludedFields, uint depth, uint maxDepth)
        {
            if (depth > maxDepth) return null;

            if (CanBeLogged(obj))
            {
                return obj;
            }

            if (IsCollection(obj))
            {
                return ParseCollection(obj as ICollection, depth, maxDepth, varPath, excludedFields);
            }

            return ParseUnknownObject(obj, depth, maxDepth, varPath, excludedFields);
        }

        private static object ParseCollection(ICollection collec, uint depth, uint maxDepth, string varPath, HashSet<string> excludedFields)
        {
            if (depth > maxDepth) return null;
            if (collec == null) return null;

            var result = new Hashtable();

            int i = 0;
            foreach (object obj in collec)
            {
                object subObject = ParseObject(obj, varPath + "." + i.ToString(), excludedFields, depth + 1, maxDepth);
                result.Add(i, subObject);
                i++;
            }

            return result;
        }

        private static object ParseUnknownObject(object obj, uint depth, uint maxDepth, string varPath, HashSet<string> excludedFields)
        {
            if (depth > maxDepth) return null;
            if (obj == null) return null;

            Hashtable result = new Hashtable();
            Type objType = obj.GetType();
            FieldInfo[] fields = objType.GetFields();

            foreach (FieldInfo f in fields)
            {
                object objValue = objType.GetField(f.Name).GetValue(obj);
                string subObjectName = varPath + "." + f.Name;

                if (PathMatchesExclusion(subObjectName, excludedFields)) continue;

                object subObject = ParseObject(objValue, subObjectName, excludedFields, depth + 1, maxDepth);
                result.Add(f.Name, subObject);
            }

            PropertyInfo[] properties = objType.GetProperties();
            foreach (PropertyInfo p in properties)
            {
                object objValue = objType.GetProperty(p.Name).GetValue(obj, null);
                string subObjectName = varPath + "." + p.Name;

                if (PathMatchesExclusion(subObjectName, excludedFields)) continue;

                object subObject = ParseObject(objValue, subObjectName, excludedFields, depth + 1, maxDepth);
                result.Add(p.Name, subObject);
            }

            return result;
        }

        public static bool CanBeLogged(object obj)
        {
            if (obj == null) return false;
            return CanLogType(obj.GetType());
        }

        public static bool CanLogType(System.Type type)
        {
            if (type.IsPrimitive || (type == typeof(string)) || type.IsEnum)
            {
                return true;
            }

            if (type.IsArray)
            {
                System.Type elementType = type.GetElementType();
                return CanLogType(elementType);
            }

            if (typeof(ICollection).IsAssignableFrom(type))
            {
                System.Type[] types = type.GetGenericArguments();
                bool ok = true;
                foreach (Type t in types)
                {
                    if (!CanLogType(t))
                    {
                        ok = false;
                    }
                }

                return ok;
            }

            return false;
        }

        public static bool IsCollection(object obj)
        {
            if (obj == null) return false;
            return typeof(ICollection).IsAssignableFrom(obj.GetType());
        }

        private static bool PathMatchesExclusion(string path, HashSet<string> excludedFields)
        {
            if (excludedFields == null)
            {
                return false;
            }

            foreach (string field in excludedFields)
            {
                if (path.Contains(field))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
