using System;
using System.Collections.Generic;

namespace Wayne.Lib.Log
{
    /// <summary>
    /// This class wraps an IIdentifiableEntity and a Category to be used as a
    /// key in e.g. Dictionaries and Lists.
    /// </summary>
    public class EntityCategory
    {
        #region Fields

        private DateTime lastTouched = DateTime.Now;
        private string categoryName;
        private string entityName;
        private string entityAncestryName;
        private string entityCategoryName;
        private string entityAncestryCategoryName;
        private List<IdentifableValues> ancestors;
        private int? hashCode;

        #endregion

        #region Construction

        /// <summary>
        /// Construction.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="category">The category.</param>
        internal EntityCategory(IIdentifiableEntity entity, object category)
        {
            Entity = entity;
            Category = category;
            if (category != null)
                CategoryString = category.ToString();
            else
                CategoryString = string.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The entity.
        /// </summary>
        public IIdentifiableEntity Entity { get; private set; }

        /// <summary>
        /// The category.
        /// </summary>
        public object Category { get; private set; }

        /// <summary>
        /// The category as a string.
        /// </summary>
        public string CategoryString { get; private set; }

        /// <summary>
        /// A date time that specifies when the object was last touched. 
        /// </summary>
        public DateTime LastTouched
        {
            get { return lastTouched; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// ToString.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return GetName(EntityLogKind.Ancestors, false);
        }

        /// <summary>
        /// Equals
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            EntityCategory otherEntityCategory = obj as EntityCategory;
            if (otherEntityCategory != null)
            {
                return IdentifiableEntity.Equals(Entity, otherEntityCategory.Entity) &&
                       CategoryString.Equals(otherEntityCategory.CategoryString);
            }

            return false;
        }

        /// <summary>
        /// Equals
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="category"></param>
        /// <returns></returns>
        public bool Equals(IIdentifiableEntity entity, object category)
        {
            if (entity == null)
                return false;
            return IdentifiableEntity.Equals(Entity, entity) && CategoryString.Equals(category.ToString());
        }

        /// <summary>
        /// GetHashCode
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            if (!hashCode.HasValue)
                hashCode = Entity.GetHashCode() ^ CategoryString.GetHashCode();
            return hashCode.Value;
        }

        /// <summary>
        /// Touch the entity category.
        /// </summary>
        public void Touch()
        {
            lastTouched = DateTime.Now;
        }

        /// <summary>
        /// Get the log-name.
        /// </summary>
        /// <param name="entityLogKind">In which detail the id-entity should be presented.</param>
        /// <param name="suppressCategory">Should the category be suppressed or not.</param>
        /// <returns></returns>
        public string GetName(EntityLogKind entityLogKind, bool suppressCategory)
        {
            switch (entityLogKind)
            {
                ///////////////////////////////////////////////
                case EntityLogKind.None:
                    if (suppressCategory)
                        return string.Empty;
                    if (categoryName == null)
                    {
                        if (string.IsNullOrEmpty(CategoryString))
                            categoryName = string.Empty;
                        else
                            categoryName = "(" + CategoryString + ")";
                    }
                    return categoryName;

                ///////////////////////////////////////////////
                case EntityLogKind.Entity:
                    if (suppressCategory)
                    {
                        if (entityName == null)
                            entityName = IdentifiableEntity.ToString(Entity);
                        return entityName;
                    }
                    if (entityCategoryName == null)
                    {
                        if (string.IsNullOrEmpty(CategoryString))
                            entityCategoryName = IdentifiableEntity.ToString(Entity);
                        else
                            entityCategoryName = IdentifiableEntity.ToString(Entity) + "(" + CategoryString + ")";
                    }
                    return entityCategoryName;

                ///////////////////////////////////////////////
                case EntityLogKind.Ancestors:
                    if (suppressCategory)
                    {
                        if (entityAncestryName == null)
                            entityAncestryName = IdentifiableEntity.ToString(Entity, true);
                        return entityAncestryName;
                    }
                    if (entityAncestryCategoryName == null)
                    {
                        if (string.IsNullOrEmpty(CategoryString))
                            entityAncestryCategoryName = IdentifiableEntity.ToString(Entity, true);
                        else
                            entityAncestryCategoryName = IdentifiableEntity.ToString(Entity, true) + "(" + CategoryString + ")";
                    }
                    return entityAncestryCategoryName;

                ///////////////////////////////////////////////
            }
            return string.Empty;
        }

        internal IdentifableValues[] GetAncestors()
        {
            if (ancestors == null)
            {
                ancestors = new List<IdentifableValues>();
                foreach (var value in IdentifiableEntity.GetAncestorArray(Entity))
                {
                    ancestors.Add(new IdentifableValues(value));
                }
            }
            return ancestors.ToArray();
        }

        #endregion
    }

    internal class IdentifableValues : IIdentifiableEntity
    {
        public int Id { get; private set; }

        public string EntityType { get; private set; }
        public string FullEntityName { get; set; }

        public string EntitySubType { get; private set; }
        public IIdentifiableEntity ParentEntity { get; private set; }

        public IdentifableValues(IIdentifiableEntity entity)
        {
            Id = entity.Id;
            EntityType = entity.EntityType;
            EntitySubType = entity.EntitySubType;
        }
    }
}