using DeepNestLib;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using PCUT.Extensions;
using PCUT.DeepNestApi;
using PCUT.ViewModels;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using System.ComponentModel;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238

namespace PCUT.Pages
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class SvgNestPage : Page
    {
        private SvgData _svgData => SvgData.Instance;
        private SvgNestSettings _settings => SvgNestSettings.Instance;
        private DesignCenterViewModel _designCenterModel;
        public ImageViewModel PreviewModel { get; set; }
        public NestingProgressModel ProgressModel { get; set; }
        private ConcurrentQueue<(XDocument Svg, double Width, double Height, double MaterialUtilization)> NestedResult { get; set; }
            = new ConcurrentQueue<(XDocument, double, double, double)>();
        public SvgNestPage()
        {
            this.InitializeComponent();
            PreviewModel = new ImageViewModel();
            ProgressModel = new NestingProgressModel();
            DeepNestApi.Background.UseParallel = true;
            _ = _settings;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (_svgData.Data == null)
                return;

            _designCenterModel = e.Parameter as DesignCenterViewModel;
            _designCenterModel.PropertyChanged += UpdateSizeChanged;
            DeepNestApi.Background.displayProgress = (progress) => DispatchAsync(DisplayNestingProgress, progress);
            _ = Initialize();
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            //if (e.SourcePageType != typeof(EditPage))
            //    _svgData.SetData(null);
            StopNesting();
            _designCenterModel.PropertyChanged -= UpdateSizeChanged;
            DeepNestApi.Background.displayProgress = null;
        }

        private static readonly object _boundingSizeLock = new object();
        private void UpdateSizeChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(DesignCenterViewModel.Width))
            {
                var model = sender as DesignCenterViewModel;
                StopNesting();
                _svgData.SetResult(null);
                _designCenterModel.MinSize = 0;
                _ = HandleSizeChangedAsync(width: model.Width);
            }
            else if (e.PropertyName == nameof(DesignCenterViewModel.Height))
            {
                var model = sender as DesignCenterViewModel;
                StopNesting();
                _svgData.SetResult(null);
                _designCenterModel.MinSize = 0;
                _ = HandleSizeChangedAsync(height: model.Height);
            }
        }

        private async Task HandleSizeChangedAsync(double? width = null, double? height = null)
        {
            if (Monitor.TryEnter(_boundingSizeLock, 100))
            {
                try
                {
                    var bound = _svgData.Data.GetDescendantById("PCUT_BOUNDING");
                    if (width != null)
                    {
                        bound.SetAttributeValue("width", width.Value);
                    }
                    if (height != null)
                    {
                        bound.SetAttributeValue("height", height.Value);
                    }
                    PreviewModel.ShowDefault();
                    _currentResult = null;
                    _svgData.CleanUpContext();
                    await PreviewModel.Source.LoadSvgAsync(_svgData.Data);
                }
                finally
                {
                    Monitor.Exit(_boundingSizeLock);
                }
            }
        }

        private void MainScroll_Loading(FrameworkElement sender, object args)
        {
            var borderRatio = PreviewColumnDefinition.ActualWidth / PreviewRowDefinition.ActualHeight;
            var ratio = _svgData.OriginWidth.Value / _svgData.OriginHeight.Value;
            if (ratio < borderRatio)
                ((ComponentStyle)Resources["previewStyle"]).SetSize(PreviewRowDefinition, ratio);
            else
                ((ComponentStyle)Resources["previewStyle"]).SetSize(PreviewColumnDefinition, ratio);
        }

        private void MainPreview_ImageOpened(object sender, RoutedEventArgs e)
        {
            HideProgressRing();
        }

        private void MainScroll_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
        {
            var (offsetX, offsetY) = e.Delta.CalculateTranslation(e.Velocities);
            var scrollViewer = sender as ScrollViewer;
            _ = scrollViewer.ChangeView(scrollViewer.HorizontalOffset - offsetX, scrollViewer.VerticalOffset - offsetY, scrollViewer.ZoomFactor);
        }

        private void MainPreviewRefresh_Click(object sender, RoutedEventArgs e)
        {
            MainScroll.ChangeView(0, 0, 1);
        }

        private void MainPreviewDefault_Click(object sender, RoutedEventArgs e)
        {
            PreviewModel.ShowDefault();
            MainScroll.ChangeView(0, 0, 1);
            _designCenterModel.UseOrigin = true;
        }

        private void NestStart_Click(object sender, RoutedEventArgs e)
        {
            _ = StartAsync(StartNesting);
        }

        private void NestStop_Click(object sender, RoutedEventArgs e)
        {
            StopNesting();
        }

        private void NestResult_Click(object sender, RoutedEventArgs e)
        {
            ShowNestedResult();
        }

        private async void NestSettings_Click(object sender, RoutedEventArgs e)
        {
            var nestSettingDialog = new SvgNestSettingDialog();
            await nestSettingDialog.ShowAsync();
        }

        // private nesting logic
        //private double _maxOfMinSize;
        //private double _totalArea;
        private async Task Initialize()
        {
            await StartAsync(LoadDetail);
            _svgData.ComputeDetailBounds();
            PreviewModel.SetSource(_svgData.Data);
        }

        private void LoadDetail()
        {
            if (_svgData.NestingContext == null)
            {
                if (_svgData.ComponentDetails == null || !_svgData.ComponentDetails.Any())
                {
                    var doc = new XDocument(_svgData.Data);
                    doc.SetSize(_svgData.OriginWidth.Value, _svgData.OriginHeight.Value);
                    doc.RemoveHidden();
                    var (details, scale) = SvgParser.LoadSvg(doc, true);
                    foreach (var detail in details)
                        _svgData.AddDetail(detail);
                    _svgData.DetailScaleFactor = scale;
                }
            }
            if (_svgData.NestedData != null)
            {
                DispatchAsync(UpdateCurrentSource);
            }
        }

        private List<NFP> _sheets => _svgData.NestingContext.Sheets;
        private List<NFP> _polygons => _svgData.NestingContext.Polygons;
        private void StartNesting()
        {
            DispatchAsync(ShowProgressRing);

            var requireStartNest = _svgData.NestingContext == null;
            if (requireStartNest)
            {
                var totalArea = 0d;
                var maxOfMinSize = 0d;
                foreach (var (cWidth, cHeight) in _svgData.DetailBounds)
                {
                    var minSize = Math.Ceiling(Math.Min(cWidth, cHeight));
                    if (maxOfMinSize < minSize)
                        maxOfMinSize = minSize;
                    totalArea += cWidth * cHeight;
                }

                var width = _designCenterModel.Width;
                var height = _designCenterModel.Height;

                // check if width/height (which one is smaller) can accomodate the largest component, based on its smaller dimension.
                if (width < height)
                {
                    if (width < maxOfMinSize)
                        width = maxOfMinSize;

                    if (totalArea > width * height)
                        height = Math.Ceiling(totalArea / width);
                }
                else
                {
                    if (height < maxOfMinSize)
                        height = maxOfMinSize;

                    if (totalArea > width * height)
                        width = Math.Ceiling(totalArea / height);
                }

                _svgData.NewSheetInfo(width, height);
                _svgData.NestingContext = new NestingContext();
                int src = 0;
                if (_svgData.SheetInfo != null)
                {
                    src = _svgData.NestingContext.GetNextSheetSource();
                    for (int i = 0; i < _svgData.SheetInfo.Quantity; i++)
                    {
                        var ns = DeepNestApi.Background.clone(_svgData.SheetInfo.Nfp);
                        Sheet sheet = new Sheet();
                        sheet.Points = ns.Points;
                        sheet.children = ns.children;
                        _sheets.Add(sheet);
                        sheet.Width = sheet.WidthCalculated;
                        sheet.Height = sheet.HeightCalculated;
                        sheet.source = src;
                    }
                }

                _svgData.NestingContext.ReorderSheets();
                _svgData.DxfCache.Clear();

                src = 0;
                if (_svgData.ComponentDetails != null && _svgData.ComponentDetails.Any())
                {
                    foreach (var detail in _svgData.ComponentDetails)
                    {
                        var t1 = detail.Outers.Where(z => z.Tag is object[]).Select(z => z.Tag as object[]).SelectMany(z => z).ToArray();
                        foreach (var outter in detail.Outers)
                        {
                            t1 = t1.Union(outter.Childrens.Where(z => z.Tag is object[]).Select(z => z.Tag as object[]).SelectMany(z => z).ToArray()).ToArray();
                        }
                        _svgData.DxfCache.Add(detail.Name, t1);
                    }
                    foreach (var detail in _svgData.ComponentDetails)
                    {
                        _svgData.NestingContext.ImportFromRawDetail(detail, src);
                        src++;
                    }
                }
            }

            if (_sheets.Count == 0 || _polygons.Count == 0)
            {
                DispatchAsync(HideProgressRing);
                DispatchAsync(StopNesting);
                return;
            }

            //menu.SetTab(menu.NestTab);
            _svgData.NestingContext.ReorderSheets();
            RunDeepnest(_settings.MaxNumberOfRun, requireStartNest);
        }

        private ImageSource _currentResult;

        private readonly ConcurrentQueue<object> _runQueue = new ConcurrentQueue<object>();
        private static readonly object _runCommand = new object();
        private void RunDeepnest(int maxNumberOfRun, bool requiredStartNest)
        {
            while (maxNumberOfRun > 0)
            {
                _runQueue.Enqueue(_runCommand);
                maxNumberOfRun--;
            }
            DispatchAsync(HideProgressRing);
            DispatchAsync(() => ProgressModel.Running = true);
            if (requiredStartNest)
                _svgData.NestingContext.StartNest();

            var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(_settings.MaxRuntimeInMinute));
            while (!cancellationTokenSource.Token.IsCancellationRequested)
            {
                if (!_runQueue.TryDequeue(out _))
                    break;

                double materialUtilization = _svgData.MaterialUtilization;
                DispatchAsync(DisplayNestingProgress, 0.0d);
                _svgData.NestingContext.NestIterate();
                DispatchAsync(DisplayNestingProgress, 1.0d);

                if (_polygons.Count(z => z.fitted) == _svgData.DetailCount && _svgData.NestingContext.MaterialUtilization <= materialUtilization)
                {
                    materialUtilization = _svgData.NestingContext.MaterialUtilization;
                    var (doc, width, height) = Export();
                    NestedResult.Enqueue((doc, width, height, materialUtilization));
                    DispatchAsync(UpdatePreview);
                }
            }

            if (ProgressModel?.Running ?? false)
                DispatchAsync(StopNesting);
        }

        private void DisplayNestingProgress(double progress)
        {
            if (ProgressModel != null)
                ProgressModel.Progress = Math.Floor(progress * 100);
        }

        private void UpdatePreview()
        {
            if (NestedResult.TryDequeue(out var result))
            {
                _svgData.SetResult(result.Svg, result.Width, result.Height, result.MaterialUtilization);
                if (ProgressModel?.Running ?? false)
                {
                    var minSize = Math.Min(_svgData.SheetInfo.Width, _svgData.SheetInfo.Height);
                    if (minSize > Math.Min(_designCenterModel.Width, _designCenterModel.Height))
                        _designCenterModel.MinSize = Math.Ceiling(minSize);
                    else
                        _designCenterModel.MinSize = 0;

                    var switchToResult = _currentResult == null && PreviewModel.DefaultMode;
                    UpdateCurrentSource();
                    if (switchToResult)
                        ShowNestedResult();
                }
            }
        }

        private void UpdateCurrentSource()
        {
            if (_currentResult == null)
                _currentResult = new SvgImageSource();
            _ = _currentResult.LoadSvgAsync(_svgData.NestedData);
        }

        private void ShowNestedResult()
        {
            PreviewModel.SetSource(_currentResult);
            _designCenterModel.UseOrigin = false;
        }

        private void StopNesting()
        {
            if (ProgressModel != null)
                ProgressModel.Running = false;
            _runQueue.Clear();
        }

        private void ShowProgressRing()
        {
            LoadingRing.IsActive = true;
            LoadingRing.Visibility = Visibility.Visible;
        }

        private void HideProgressRing()
        {
            LoadingRing.IsActive = false;
            LoadingRing.Visibility = Visibility.Collapsed;
        }

        private (XDocument NestedDoc, double Width, double Height) Export()
        {
            var polygons = _polygons.Where(x => x.fitted).ToArray();
            foreach (var item in polygons)
            {
                item.Tag = _svgData.DxfCache[item.Name];
            }
            var (nestedDoc, nestedWidth, nestedHeight) = SvgParser.Export(polygons);
            var width = _svgData.SheetInfo.Width;
            var height = _svgData.SheetInfo.Height;
            var viewBox = _svgData.OriginViewBox.GetViewBoxData();
            nestedDoc.Root.SetAttributeValue("viewBox", $"0 0 {nestedWidth} {nestedHeight}");
            nestedDoc.Root.Add(XElement.Parse($"<rect id=\"PCUT_BOUNDING\" x=\"0\" y=\"0\" width=\"{_designCenterModel.Width}\" height=\"{_designCenterModel.Height}\" fill=\"none\" stroke=\"blue\" stroke-dasharray=\"{_designCenterModel.Width / 200}\" stroke-width=\"{_designCenterModel.Height / 2000}\" />"));
            nestedDoc.Root.Add(XElement.Parse($"<rect id=\"PCUT_BOUNDING_NESTED\" x=\"0\" y=\"0\" width=\"{nestedWidth}\" height=\"{nestedHeight}\" fill=\"none\" stroke=\"blue\" stroke-dasharray=\"{width / 200}\" stroke-width=\"{width / 2000}\" />"));
            return (nestedDoc, nestedWidth, nestedHeight);
        }

        private Task StartAsync(Action action)
        {
            return Task.Factory.StartNew(action, TaskCreationOptions.LongRunning);
        }

        private async void DispatchAsync(Action action)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => action.Invoke());
        }

        private async void DispatchAsync<T>(Action<T> action, T param)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => action.Invoke(param));
        }
    }
}
