using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using TechnologyAssembler.Core.Assets;

namespace HashCalculator.GUI
{
    public class AssetEntry : IEquatable<AssetEntry>, IComparable<AssetEntry>
    {
        public string Type { get; }
        public string Name { get; }
        public string NameString { get; }
        public IReadOnlyList<string> DisplayLabels { get; }
        public uint InstanceId => SageHash.CalculateLowercaseHash(Name);

        public string InstanceIdString => $"{InstanceId:x8} ({InstanceId,10})";

        public string LocalizedNames
        {
            get
            {
                if (!Translator.HasProvider)
                {
                    return string.Empty;
                }
                if (!DisplayLabels.Any())
                {
                    return "N/A";
                }
                return DisplayLabels.Select(Translator.Translate).Aggregate((x, y) => $"{x} {y}");
            }
        }

        public static AssetEntry? TryParse(XElement element)
        {
            if (element == null)
            {
                return null;
            }

            if (element.Name.Namespace != ModXml.EalaAsset)
            {
                TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}");
            }

            try
            {
                return new AssetEntry(element);
            }
            catch (Exception e)
            {
                TracerListener.WriteLine($"Failed to parse element: {e}");
            }

            return null;
        }

        public AssetEntry(XElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException(nameof(element));
            }

            if (element.Name.Namespace != ModXml.EalaAsset)
            {
                throw new NotSupportedException();
            }

            Type = element.Name.LocalName;
            var id = element.Attribute("id")?.Value;
            Name = id ?? throw new NotSupportedException();
            NameString = $"{Type}:{Name}";

            var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
                         select name.Value;
            var transformLabels = from name in element.Elements(ModXml.EalaAsset + "DisplayNameTransformed")
                                  select name.Value;
            DisplayLabels = labels.Concat(transformLabels).ToArray();
        }

        public AssetEntry(Asset asset)
        {
            if (asset is null)
            {
                throw new ArgumentNullException(nameof(asset));
            }
            Type = asset.TypeName;
            Name = asset.InstanceName;
            NameString = $"{Type}:{Name}";
            DisplayLabels = Array.Empty<string>();
            if (InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
            {
                throw new InvalidDataException();
            }
        }

        public bool Equals(AssetEntry? entry)
        {
            return this == entry;
        }

        // override object.Equals
        public override bool Equals(object? obj)
        {
            //       
            // See the full list of guidelines at
            //   http://go.microsoft.com/fwlink/?LinkID=85237  
            // and also the guidance for operator== at
            //   http://go.microsoft.com/fwlink/?LinkId=85238
            //

            if (!(obj is AssetEntry entry))
            {
                return false;
            }

            return this == entry;
        }

        public static bool operator ==(AssetEntry? a, AssetEntry? b)
        {
            if (a is null || b is null)
            {
                return a is null == b is null;
            }

            if (ReferenceEquals(a, b))
            {
                return true;
            }

            return a.Type == b.Type && a.InstanceId == b.InstanceId;
        }

        public static bool operator !=(AssetEntry? a, AssetEntry? b)
        {
            return !(a == b);
        }

        // override object.GetHashCode
        public override int GetHashCode()
        {
            return HashCode.Combine(Type, InstanceId);
        }

        public int CompareTo(AssetEntry? other)
        {
            if (other is null)
            {
                throw new ArgumentNullException(nameof(other));
            }
            return string.CompareOrdinal(NameString, other.NameString);
        }

        public static bool operator <(AssetEntry? left, AssetEntry? right)
        {
            return left is null ? right is object : left.CompareTo(right) < 0;
        }

        public static bool operator <=(AssetEntry? left, AssetEntry? right)
        {
            return left is null || left.CompareTo(right) <= 0;
        }

        public static bool operator >(AssetEntry? left, AssetEntry? right)
        {
            return left is object && left.CompareTo(right) > 0;
        }

        public static bool operator >=(AssetEntry? left, AssetEntry? right)
        {
            return left is null ? right is null : left.CompareTo(right) >= 0;
        }
    }
}