using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using PCUT.Extensions;
using PCUT.DeepNestApi;
using DeepNestLib;

namespace PCUT.ViewModels
{
    public class SvgData
    {
        // singleton implementation
        private SvgData() { }
        private static readonly object _instanceLock = new object();
        private static SvgData _instance;
        public static SvgData Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_instanceLock)
                    {
                        if (_instance == null)
                            _instance = new SvgData();
                    }
                }
                return _instance;
            }
        }

        // Svg Data
        private readonly object _dataLock = new object();
        public XDocument Data { get; private set; }
        public string ItemId { get; set; }
        public string FileId { get; set; }

        private double? _originWidth;
        public double? OriginWidth
        {
            get => _originWidth;
            private set
            {
                _originWidth = value;
            }
        }

        private double? _originHeight;
        public double? OriginHeight
        {
            get => _originHeight;
            private set
            {
                _originHeight = value;
            }
        }

        public string OriginViewBox { get; set; }

        // Only set data once. If required to set data again, need to clear (set to null) first
        public void SetData(XDocument data, double? originalWidth = null, double? originalHeight = null)
        {
            if (Data == null || data == null)
            {
                lock (_dataLock)
                {
                    if (data == null)
                    {
                        if (Data != null)
                        {
                            Data.Changing -= DataChangingHandle;
                            Data.Changed -= DataChangedHandle;
                        }
                        CleanUpData();
                    }
                    else if (Data == null)
                    {
                        Data = data;
                        OriginWidth = originalWidth;
                        OriginHeight = originalHeight;
                        OriginViewBox = data.GetViewBox();
                        data.Changing += DataChangingHandle;
                        data.Changed += DataChangedHandle;
                    }
                }
            }
        }

        private readonly ConcurrentDictionary<string, SvgComponentData> _componentData = new ConcurrentDictionary<string, SvgComponentData>();
        public int ComponentCount => _componentData.Count;

        public IEnumerable<(string Id, bool Deleted, XDocument Data)> ComponentData => _componentData?.Where(x => x.Value.Data != null)
                                                                                                      .OrderBy(x => x.Value.Order)
                                                                                                      .Select(x => (x.Key, x.Value.Hidden, x.Value.Data));

        public IEnumerable<(double Width, double Height)> ComponentBounds => _componentData?.Where(x => x.Value?.Data != null)
                                                                                            .Select(x => (x.Value.Width, x.Value.Height));
        public void AddComponent(string id, bool deleted, XDocument data, double boundWidth, double boundHeight)
        {
            _ = _componentData.AddOrUpdate(
                id,
                (_, param) => new SvgComponentData(param.Data, param.Hidden, boundWidth, boundHeight, _componentData.Count),
                (_, component, param) => { component.UpdateData(param.Data); return component; },
                (Data: data, Hidden: deleted));
        }

        // Svg Nest Data
        private readonly object _resultLock = new object();
        public XDocument NestedData { get; private set; }
        public double? NestedWidth { get; private set; }
        public double? NestedHeight { get; private set; }
        public double MaterialUtilization { get; private set; } = 1;
        public void SetResult(XDocument result, double? nestedWidth = null, double? nestedHeight = null, double materialUtilization = 1)
        {
            lock (_resultLock)
            {
                NestedData = result;
                NestedWidth = nestedWidth;
                NestedHeight = nestedHeight;
                MaterialUtilization = materialUtilization;
            }
        }

        private readonly ConcurrentDictionary<string, SvgDetailData> _detailData = new ConcurrentDictionary<string, SvgDetailData>();
        public int DetailCount => _detailData.Count;

        public IEnumerable<RawDetail> ComponentDetails => _detailData?.Where(x => x.Value?.Detail != null && !x.Value.Hidden)
                                                                      .Select(x => x.Value.Detail);

        public IEnumerable<(double Width, double Height)> DetailBounds => _detailData?.Where(x => x.Value?.Detail != null && !x.Value.Hidden)
                                                                                      .Select(x => (x.Value.Width, x.Value.Height));
        public void AddDetail(RawDetail detail)
        {
            _ = _detailData.AddOrUpdate(
                detail.Name,
                (_, param) => new SvgDetailData(param),
                (_, component, param) => { component.UpdateDetail(param); return component; },
                detail);
        }

        public void ComputeDetailBounds()
        {
            foreach (var data in _detailData.Values.Where(x => x.Detail != null))
            {
                var bbox = data.Detail.BoundingBox();
                data.Width = bbox.Width;
                data.Height = bbox.Height;
            }
        }

        private readonly object _scaleLock = new object();
        private double? _detailScaleFactor;
        public double DetailScaleFactor
        {
            get => _detailScaleFactor ?? 1;
            set
            {
                if (_detailScaleFactor == null)
                {
                    lock(_scaleLock)
                    {
                        if (_detailScaleFactor == null)
                            _detailScaleFactor = value;
                    }
                }
            }
        }

        public NestingContext NestingContext { get; set; }
        public SheetLoadInfo SheetInfo { get; private set; }
        public Dictionary<string, object[]> DxfCache { get; } = new Dictionary<string, object[]>();

        public void NewSheetInfo(double width, double height)
        {
            SheetInfo = new SheetLoadInfo
            {
                Nfp = NewSheet(width, height),
                Width = width,
                Height = height,
                Quantity = 1
            };
        }

        private Sheet NewSheet(double w = 3000, double h = 1500)
        {
            var tt = new RectangleSheet();
            tt.Name = "rectSheet";
            tt.Height = h;
            tt.Width = w;
            tt.Rebuild();
            return tt;
        }

        // Clean up
        private void CleanUpData()
        {
            Data = null;
            FileId = null;
            NestedData = null;
            MaterialUtilization = 1;
            OriginWidth = null;
            OriginHeight = null;
            OriginViewBox = null;
            _detailScaleFactor = null;
            _componentData?.Clear();
            _detailData?.Clear();
            CleanUpContext();
        }

        public void CleanUpContext()
        {
            NestingContext = null;
            SheetInfo = null;
            DxfCache?.Clear();
        }

        // Handler
        private void DataChangingHandle(object sender, XObjectChangeEventArgs e)
        {
            switch (sender)
            {
                case XAttribute attribute:
                    {
                        if (attribute.Name.LocalName == "visibility" && e.ObjectChange == XObjectChange.Remove)
                        {
                            var id = attribute.Parent.GetId();
                            if (_componentData != null && _componentData.ContainsKey(id))
                            {
                                _componentData[id].Hidden = false;
                                _detailData?.Clear();
                            }
                        }
                        NestedData = null;
                        MaterialUtilization = 1;
                        CleanUpContext();
                        break;
                    }
            }
        }

        private void DataChangedHandle(object sender, XObjectChangeEventArgs e)
        {
            switch (sender)
            {
                case XAttribute attribute:
                    {
                        if (attribute.Name.LocalName == "visibility" && e.ObjectChange == XObjectChange.Add && attribute.Value == "hidden")
                        {
                            var id = attribute.Parent.GetId();
                            if (_componentData != null && _componentData.ContainsKey(id))
                            {
                                _componentData[id].Hidden = true;
                                _detailData?.Clear();
                            }    
                        }
                        NestedData = null;
                        MaterialUtilization = 1;
                        CleanUpContext();
                        break;
                    }
            }
        }

        public class SvgComponentData
        {
            public bool Hidden { get; set; }
            public XDocument Data { get; set; }
            public double Width { get; set; }
            public double Height { get; set; }
            public int Order { get; set; }

            public SvgComponentData(XDocument data, bool hidden, double width, double height, int order)
            {
                Data = data;
                Hidden = hidden;
                Width = width;
                Height = height;
                Order = order;
            }

            public void UpdateData(XDocument data)
            {
                Data = data;
            }
        }

        public class SvgDetailData
        {
            public RawDetail Detail { get; set; }
            public double Width { get; set; }
            public double Height { get; set; }
            public bool Hidden { get; set; }

            public SvgDetailData(RawDetail detail)
            {
                Detail = detail;
            }

            public void UpdateDetail(RawDetail detail)
            {
                Detail = detail;
            }
        }

        public class SheetLoadInfo
        {
            public NFP Nfp;

            public string Info { get; set; }
            public double Width { get; set; }
            public double Height { get; set; }
            public int Quantity { get; set; }

            public double BoundScale { get; set; }
        }
    }
}
