// Created with MonoDevelop
//
//    Copyright (C) 2022 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program 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.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.


using System;
using System.IO;
using System.Globalization;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.ComponentModel;

namespace Hiperscan.Common.IO
{
    public partial class XmlObject
    {
        public object Deserialize(string file_name, Type type)
        {
            this.Init();
            this.SetFileName(file_name);
            this.ReadDocument();
            this.CheckRootElement(type);
            this.CreateDeserializationObject(this.Document.DocumentElement, this.DeserializationObject);
            this.SetSerializationCulture();

            object obj = this.CreateInstance(type);
            this.DeserializeProperties(obj, this.DeserializationObject);


            return obj;
        }

        private void ReadDocument()
        {
            this.Document = new XmlDocument();
            using (StreamReader sr = new StreamReader(this.FileName, System.Text.Encoding.UTF8))
            {
                this.Document.Load(sr);
            }
        }

        private void CheckRootElement(Type type)
        {
            if (type.IsClass == false)
                throw new Exception("Deserializing object has to be a class.");

            string root_name = this.Document.DocumentElement.Name;
            if (type.Name == root_name)
                return;

            XmlSerializationAttribute attribute = (XmlSerializationAttribute) type.GetCustomAttribute(typeof(XmlSerializationAttribute));
            if (attribute != null)
            {
                if (attribute.HasAlternativeNames)
                {
                    foreach(string alternative_name in attribute.AlternativeNames)
                    {
                        if (alternative_name == root_name)
                            return;
                    }
                }
            }

            throw new Exception( String.Format("Cannot deserialize object with root element named '{0}'.", root_name));
        }

        private void CreateDeserializationObject(XmlNode node,  Dictionary<string, object> deserialization_object)
        {
            foreach (XmlNode child_node in node.ChildNodes)
            {
                bool is_ienumerable = false;
                try
                {
                    deserialization_object.Add(child_node.Name, null);
                    if (this.XmlNodeHasTrueChildren(child_node) == false)
                    {
                        deserialization_object[child_node.Name] = child_node.InnerText;
                        continue;
                    }
                }
                catch(Exception)
                {
                    deserialization_object.Remove(child_node.Name);
                    is_ienumerable = true;
                }

                if (is_ienumerable)
                    return;

                Dictionary<string, object> child_deserialization_object = new Dictionary<string, object>();
                this.CreateDeserializationObject(child_node, child_deserialization_object);
                deserialization_object[node.Name] = child_deserialization_object;
            }
        }

        private void DeserializeProperties(object instance, Dictionary<string, object> properties)
        {
            foreach (PropertyInfo property_info in instance.GetType().GetProperties(this.BindingFlages))
            {
                XmlSerializationAttribute attr = (XmlSerializationAttribute) property_info.GetCustomAttribute(typeof(XmlSerializationAttribute));
                if (attr == null)
                    continue;

                string property_name = properties.ContainsKey(property_info.Name) ? property_info.Name : String.Empty;
                if (String.IsNullOrEmpty(property_name) && attr.HasAlternativeNames)
                {
                    foreach (string alternative_name in attr.AlternativeNames)
                    {
                        if (properties.ContainsKey(alternative_name))
                        {
                            property_name = alternative_name;
                            break;
                        }
                    }
                }

                if (String.IsNullOrEmpty(property_name))
                    continue;

                object tmp = properties[property_name];

                if (tmp is string)
                {
                    this.SetPropertyValue(instance, property_info, tmp as string);
                }
                else
                {
                    object child_pbject = this.CreateInstance(property_info.PropertyType);
                    Dictionary<string, object> child_properties = tmp as Dictionary<string, object>;
                    this.DeserializeProperties(child_pbject, child_properties);
                }
            }
        }

        private void SetPropertyValue(object instance, PropertyInfo property_info, string value)
        {
            if (String.IsNullOrEmpty(value))
                return;
                
            if (value == XmlObject.NULL)
            {
                property_info.SetValue(instance, null);
                return;
            }

            if (this.IsFlagEnum(property_info.PropertyType))
            {
                this.DeserializeFlagEnum(instance, property_info, value);
                return;
            }


            try
            {
                TypeConverter type_converter = TypeDescriptor.GetConverter(property_info.PropertyType);
                object prop_value = type_converter.ConvertFromString(null, this.Culture, value);
                property_info.SetValue(instance, prop_value);
            }
            catch(Exception)
            {
                Console.WriteLine(property_info.Name);
            }
        }

        private void DeserializeFlagEnum(object instance, PropertyInfo property_info, string value)
        {
            TypeConverter type_converter = TypeDescriptor.GetConverter(property_info.PropertyType);
            string[] data = value.Split(new char[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries);

            int enum_value = 0;
            
            for (int i = 0; i<data.Length; i++)
                enum_value +=  (int) type_converter.ConvertFromString(null, this.Culture, data[i]);

            property_info.SetValue(instance, enum_value);
        }

        private void SetSerializationCulture()
        {
            XmlAttribute attribute = this.Document.DocumentElement.GetAttributeNode(XmlObject.CULTURE_ATTRIBUTE);
            if (attribute != null)
                return;

            if (String.IsNullOrEmpty(this.SerializationCulturePropertyName) == false)
            {
                if (this.DeserializationObject.ContainsKey(this.SerializationCulturePropertyName))
                {
                    this.Culture = new CultureInfo(this.DeserializationObject[this.SerializationCulturePropertyName].ToString());
                    this.DeserializationObject.Remove(this.SerializationCulturePropertyName);
                    return;
                }
            }

            throw new Exception("Could not find any culture for deserialization.");
        }

        private Dictionary<string, object> DeserializationObject { get; set; }
        public string SerializationCulturePropertyName { get; set; }
    }
}
