using MainShell.Common;
using MainShell.Models;
using MainShell.Models.Wafer;
using MainShell.Recipe.BaseBoard.Model;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MainShell.Process
{
    public class DieTransferPlanningContext
    {
        public DieTransferPlanningContext()
        {
            PadRows = new List<PadTransferRow>();
            DieRows = new List<DieTransferRow>();
            TransPathType = TransPathType.Sequence;
            PadRowDirectionStrategy = DieTransferRowTraversalStrategy.AllPositive;
            DieRowDirectionStrategy = DieTransferRowTraversalStrategy.Serpentine;
            SkipNgDie = true;
        }

        public DieTransferRegion DieRegion { get; set; }

        public DieTransferRegion SubstrateRegion { get; set; }

        public TransPathType TransPathType { get; set; }

        public DieTransferRowTraversalStrategy PadRowDirectionStrategy { get; set; }

        public DieTransferRowTraversalStrategy DieRowDirectionStrategy { get; set; }

        public bool SkipNgDie { get; set; }

        public List<PadTransferRow> PadRows { get; set; }

        public List<DieTransferRow> DieRows { get; set; }

        public int AvailablePadCount
        {
            get
            {
                return PadRows == null ? 0 : PadRows.Sum(row => row.AvailableCount);
            }
        }

        public int AvailableDieCount
        {
            get
            {
                return DieRows == null ? 0 : DieRows.Sum(row => row.AvailableCount);
            }
        }

        public List<Pad> GetOrderedPads()
        {
            List<Pad> pads = new List<Pad>();
            if (PadRows == null)
            {
                return pads;
            }

            foreach (PadTransferRow row in PadRows.OrderBy(item => item.RowIndex))
            {
                pads.AddRange(row.GetAvailablePads());
            }

            return pads;
        }

        public List<Die> GetOrderedDies()
        {
            List<Die> dies = new List<Die>();
            if (DieRows == null)
            {
                return dies;
            }

            foreach (DieTransferRow row in DieRows.OrderBy(item => item.RowIndex))
            {
                dies.AddRange(row.GetAvailableDies());
            }

            return dies;
        }

        public static DieTransferPlanningContext Create(DieTransferPathRequest request)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            DieTransferPlanningContext context = new DieTransferPlanningContext();
            context.DieRegion = DieTransferRegion.FromRegionModel(request.DieRegion);
            context.SubstrateRegion = DieTransferRegion.FromRegionModel(request.SubstrateRegion);
            context.TransPathType = request.TransPathType;
            context.PadRowDirectionStrategy = request.PadRowDirectionStrategy;
            context.DieRowDirectionStrategy = request.DieRowDirectionStrategy;
            context.SkipNgDie = request.SkipNgDie;
            context.PadRows = CreatePadRows(request.PadCandidates, context.SubstrateRegion, context.PadRowDirectionStrategy);
            context.DieRows = CreateDieRows(request.DieCandidates, context.DieRegion, request.SkipNgDie, context.DieRowDirectionStrategy);
            return context;
        }

        public static List<PadTransferRow> CreatePadRows(IReadOnlyCollection<Pad> padCandidates, DieTransferRegion region, DieTransferRowTraversalStrategy rowTraversalStrategy)
        {
            IEnumerable<Pad> filteredPads = (padCandidates ?? Array.Empty<Pad>())
                .Where(pad => pad != null && IsInRegion(pad.Row, pad.Column, region));

            List<PadTransferRow> rows = new List<PadTransferRow>();
            foreach (IGrouping<int, Pad> rowGroup in filteredPads.GroupBy(pad => pad.Row).OrderBy(group => group.Key))
            {
                PadTransferRow row = new PadTransferRow();
                row.RowIndex = rowGroup.Key;
                row.Direction = ResolveRowDirection(rowGroup.Key, rowTraversalStrategy);
                row.Pads = rowGroup.OrderBy(pad => pad.Column).ToList();
                rows.Add(row);
            }

            return rows;
        }

        public static List<DieTransferRow> CreateDieRows(IReadOnlyCollection<Die> dieCandidates, DieTransferRegion region, bool skipNgDie, DieTransferRowTraversalStrategy rowTraversalStrategy)
        {
            IEnumerable<Die> filteredDies = (dieCandidates ?? Array.Empty<Die>())
                .Where(die => die != null && IsInRegion(die.Row, die.Column, region));

            List<DieTransferRow> rows = new List<DieTransferRow>();
            foreach (IGrouping<int, Die> rowGroup in filteredDies.GroupBy(die => die.Row).OrderBy(group => group.Key))
            {
                DieTransferRow row = new DieTransferRow();
                row.RowIndex = rowGroup.Key;
                row.Direction = ResolveRowDirection(rowGroup.Key, rowTraversalStrategy);
                row.SkipNgDie = skipNgDie;
                row.Dies = rowGroup.OrderBy(die => die.Column).ToList();
                rows.Add(row);
            }

            return rows;
        }

        public static List<PadTransferRow> TrimPadRows(IReadOnlyCollection<PadTransferRow> sourceRows, int targetCount, out List<PadTransferRow> remainingRows)
        {
            List<PadTransferRow> activeRows = new List<PadTransferRow>();
            remainingRows = new List<PadTransferRow>();
            int remainingCount = Math.Max(0, targetCount);

            foreach (PadTransferRow sourceRow in (sourceRows ?? Array.Empty<PadTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
            {
                List<Pad> availablePads = sourceRow.GetAvailablePads().ToList();
                if (availablePads.Count == 0)
                {
                    continue;
                }

                if (remainingCount <= 0)
                {
                    remainingRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads));
                    continue;
                }

                if (availablePads.Count <= remainingCount)
                {
                    activeRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads));
                    remainingCount -= availablePads.Count;
                    continue;
                }

                activeRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads.Take(remainingCount)));
                remainingRows.Add(CreatePadRow(sourceRow.RowIndex, sourceRow.Direction, availablePads.Skip(remainingCount)));
                remainingCount = 0;
            }

            return activeRows;
        }

        public static List<DieTransferRow> TrimDieRows(IReadOnlyCollection<DieTransferRow> sourceRows, int targetCount, out List<DieTransferRow> remainingRows)
        {
            List<DieTransferRow> activeRows = new List<DieTransferRow>();
            remainingRows = new List<DieTransferRow>();
            int remainingCount = Math.Max(0, targetCount);

            foreach (DieTransferRow sourceRow in (sourceRows ?? Array.Empty<DieTransferRow>()).Where(row => row != null).OrderBy(row => row.RowIndex))
            {
                List<Die> availableDies = sourceRow.GetAvailableDies().ToList();
                if (availableDies.Count == 0)
                {
                    continue;
                }

                if (remainingCount <= 0)
                {
                    remainingRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies));
                    continue;
                }

                if (availableDies.Count <= remainingCount)
                {
                    activeRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies));
                    remainingCount -= availableDies.Count;
                    continue;
                }

                activeRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies.Take(remainingCount)));
                remainingRows.Add(CreateDieRow(sourceRow.RowIndex, sourceRow.Direction, sourceRow.SkipNgDie, availableDies.Skip(remainingCount)));
                remainingCount = 0;
            }

            return activeRows;
        }

        private static bool IsInRegion(int row, int column, DieTransferRegion region)
        {
            if (region == null)
            {
                return true;
            }

            return region.Contains(row, column);
        }

        private static DieTransferRowDirection ResolveRowDirection(int rowIndex, DieTransferRowTraversalStrategy rowTraversalStrategy)
        {
            switch (rowTraversalStrategy)
            {
                case DieTransferRowTraversalStrategy.AllNegative:
                    return DieTransferRowDirection.Negative;

                case DieTransferRowTraversalStrategy.Serpentine:
                    return rowIndex % 2 == 0 ? DieTransferRowDirection.Negative : DieTransferRowDirection.Positive;

                case DieTransferRowTraversalStrategy.AllPositive:
                default:
                    return DieTransferRowDirection.Positive;
            }
        }

        private static PadTransferRow CreatePadRow(int rowIndex, DieTransferRowDirection direction, IEnumerable<Pad> pads)
        {
            PadTransferRow row = new PadTransferRow();
            row.RowIndex = rowIndex;
            row.Direction = direction;
            row.Pads = (pads ?? Array.Empty<Pad>()).Where(pad => pad != null).ToList();
            return row;
        }

        private static DieTransferRow CreateDieRow(int rowIndex, DieTransferRowDirection direction, bool skipNgDie, IEnumerable<Die> dies)
        {
            DieTransferRow row = new DieTransferRow();
            row.RowIndex = rowIndex;
            row.Direction = direction;
            row.SkipNgDie = skipNgDie;
            row.Dies = (dies ?? Array.Empty<Die>()).Where(die => die != null).ToList();
            return row;
        }
    }
}
