using System;
using System.Collections.Generic;

namespace InABox.Core
{

    public abstract class Dimensions<TLink,TUnit> : EnclosedEntity, IDimensions 
        where TLink : DimensionUnitLink<TUnit>, new() 
        where TUnit : DimensionUnit, new()
    {

        public Dimensions() : base() { }
        
        public Dimensions(Func<BaseObject> entity) : base(entity) { }

        [EditorSequence(1)]
        [RequiredColumn]
        [Caption("Sizing", IncludePath = false)]
        public abstract TLink Unit { get; set; }

        public IDimensionUnit GetUnit() => Unit;
        
        [DoubleEditor(Visible = Visible.Hidden)]
        [EditorSequence(2)]
        [Caption("Quantity", IncludePath = false)]
        [RequiredColumn]
        public abstract double Quantity { get; set; }
        
        [DoubleEditor(Visible = Visible.Hidden)]
        [EditorSequence(3)]
        [Caption("Length", IncludePath = false)]
        [RequiredColumn]
        public abstract double Length { get; set; }
        
        [DoubleEditor(Visible = Visible.Hidden)]
        [EditorSequence(4)]
        [Caption("Width", IncludePath = false)]
        [RequiredColumn]
        public abstract double Width { get; set; }
        
        [DoubleEditor(Visible = Visible.Hidden)]
        [EditorSequence(5)]
        [Caption("Height", IncludePath = false)]
        [RequiredColumn]
        public abstract double Height { get; set; }
        
        [DoubleEditor(Visible = Visible.Hidden)]
        [EditorSequence(6)]
        [Caption("Weight", IncludePath = false)]
        [RequiredColumn]
        public abstract double Weight { get; set; }
        
        [DoubleEditor(Visible = Visible.Optional, Editable = Editable.Hidden)]
        [Caption("Value", IncludePath = false)]
        [EditorSequence(7)]
        [RequiredColumn]
        public abstract double Value { get; set; }
        
        [TextBoxEditor(Visible = Visible.Default, Editable=Editable.Hidden)]
        [EditorSequence(8)]
        [Caption("Unit Size", IncludePath = false)]
        [RequiredColumn]
        public abstract String UnitSize { get; set; }
        
        protected override void Init()
        {
            base.Init();
            Unit = Activator.CreateInstance(typeof(TLink), new object[] { new Func<BaseObject>(LinkedEntity) }) as TLink;
            Unit.PropertyChanged += (s, e) =>
            {
                if(e.PropertyName == "ID")
                {
                    DoPropertyChanged("Unit." + e.PropertyName, OriginalValues.GetValueOrDefault("Unit." + e.PropertyName), Unit.ID);
                }
                else if(e.PropertyName == "Formula")
                {
                    DoPropertyChanged("Unit." + e.PropertyName, OriginalValues.GetValueOrDefault("Unit." + e.PropertyName), Unit.Formula);
                }
                else if(e.PropertyName == "Format")
                {
                    DoPropertyChanged("Unit." + e.PropertyName, OriginalValues.GetValueOrDefault("Unit." + e.PropertyName), Unit.Format);
                }
            };
        }
        
        private bool bCalculating = false;

        private static Column<Dimensions<TLink,TUnit>> unitid = new Column<Dimensions<TLink,TUnit>>(x => x.Unit.ID);
        private static Column<Dimensions<TLink,TUnit>> quantity = new Column<Dimensions<TLink,TUnit>>(x => x.Quantity);
        private static Column<Dimensions<TLink,TUnit>> length = new Column<Dimensions<TLink,TUnit>>(x => x.Length);
        private static Column<Dimensions<TLink,TUnit>> width = new Column<Dimensions<TLink,TUnit>>(x => x.Width);
        private static Column<Dimensions<TLink,TUnit>> height = new Column<Dimensions<TLink,TUnit>>(x => x.Height);
        private static Column<Dimensions<TLink,TUnit>> weight = new Column<Dimensions<TLink,TUnit>>(x => x.Weight);
        private static Column<Dimensions<TLink,TUnit>> sizeformula = new Column<Dimensions<TLink,TUnit>>(x => x.Unit.Formula);
        private static Column<Dimensions<TLink,TUnit>> sizeformat = new Column<Dimensions<TLink,TUnit>>(x => x.Unit.Format);
        
        protected override void DoPropertyChanged(string name, object? before, object? after)
        {
            base.DoPropertyChanged(name, before, after);

            if (bCalculating)
                return;
            
            bCalculating = true;
            
            try
            {
                
                if (unitid.IsEqualTo(name))
                    Calculate(Quantity, Length, Width, Height, Weight, Unit.Formula, Unit.Format);
                else if (quantity.IsEqualTo(name))
                    Calculate(after, Length, Width, Height, Weight, Unit.Formula, Unit.Format);
                else if (length.IsEqualTo(name))
                    Calculate(Quantity, after, Width, Height, Weight, Unit.Formula, Unit.Format);
                else if (width.IsEqualTo(name))
                    Calculate(Quantity, Length, after, Height, Weight, Unit.Formula, Unit.Format);
                else if (height.IsEqualTo(name))
                    Calculate(Quantity, Length, Width, after, Weight, Unit.Formula, Unit.Format);
                else if (weight.IsEqualTo(name))
                    Calculate(Quantity, Length, Width, Height, after, Unit.Formula, Unit.Format);
                else if (sizeformula.IsEqualTo(name))
                    Calculate(Quantity, Length, Width, Height, Weight, after as string, Unit.Format);
                else if (sizeformat.IsEqualTo(name))
                    Calculate(Quantity, Length, Width, Height, Weight, Unit.Formula, after as string);
            }
            finally
            {
                bCalculating = false;
            }
        }

        public void Set(IDimensionUnit unit, double quantity, double length, double width, double height, double weight)
        {
            bCalculating = true;
            try
            {
                Unit.ID = unit.ID;
                Unit.HasQuantity = unit.HasQuantity;
                Unit.HasLength = unit.HasLength;
                Unit.HasWidth = unit.HasWidth;
                Unit.HasHeight = unit.HasHeight;
                Unit.HasWeight = unit.HasWeight;
                Unit.Code = unit.Code;
                Unit.Description = unit.Description;
                Unit.Formula = unit.Formula;
                Unit.Format = unit.Format;
                Quantity = quantity;
                Length = length;
                Width = width;
                Height = height;
                Weight = weight;
                Calculate(quantity, length, width, height, weight, unit.Formula, unit.Format);
            }
            finally
            {
                bCalculating = false;                
            }
        }

        
        private void Calculate(object? quantity, object? length, object? width, object? height, object? weight, string? formula, string? format)
        {
            if (Evaluate<double>(formula, quantity, length, width, height, weight, out double value))
                Value = value;
            if (Evaluate<String>(format, quantity, length, width, height, weight, out string unitsize))
                UnitSize = unitsize;
        }

        private bool Evaluate<T>(string? formula, object? quantity, object? length, object? width, object? height, object? weight, out T result)
        {
            
            if (!String.IsNullOrWhiteSpace(formula))
            {
                var variables = new Dictionary<string, object?>()
                {
                    { "Quantity", Convert.ToDouble(quantity) },
                    { "Length", Convert.ToDouble(length) },
                    { "Width", Convert.ToDouble(width) },
                    { "Height", Convert.ToDouble(height) },
                    { "Weight", Convert.ToDouble(weight) }
                };
                try
                {
                    var expr = new CoreExpression(formula);
                    var eval = expr.Evaluate(variables);
                    result = (T)CoreUtils.ChangeType(eval,typeof(T));
                    return true;
                }
                catch (Exception e)
                {
                    Logger.Send(LogType.Information, "", String.Format("Error in Formula: [{0}] ({1})", formula, e.Message));
                    result = default(T);
                    return false;
                }
            }

            result = default(T);
            return true;
        }

        public void CopyFrom(IDimensions source, bool observing = false)
        {
            if (!observing)
                SetObserving(false);
            Unit.ID = source.GetUnit().ID;
            Unit.Synchronise(source.GetUnit());
            Quantity = source.Quantity;
            Length = source.Length;
            Width = source.Width;
            Height = source.Height;
            Weight = source.Weight;
            Value = source.Value;
            UnitSize = source.UnitSize;
            if (!observing)
                SetObserving(true);
        }
    }
}