using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ClosedXML.Excel
{
    internal class XLConditionalFormats : IXLConditionalFormats
    {
        private readonly List<IXLConditionalFormat> _conditionalFormats = new List<IXLConditionalFormat>();
        public void Add(IXLConditionalFormat conditionalFormat)
        {
            _conditionalFormats.Add(conditionalFormat);
        }

        public IEnumerator<IXLConditionalFormat> GetEnumerator()
        {
            return _conditionalFormats.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Remove(Predicate<IXLConditionalFormat> predicate)
        {
            _conditionalFormats.RemoveAll(predicate);
        }

        /// <summary>
        /// The method consolidate the same conditional formats, which are located in adjacent ranges.
        /// </summary>
        internal void Consolidate()
        {
            var formats = _conditionalFormats
                .Where(cf => cf.Ranges.Any())
                .ToList();
            _conditionalFormats.Clear();

            while (formats.Count > 0)
            {
                var item = formats.First();

                var rangesToJoin = item.Ranges;
                var skippedRanges = new XLRanges();
                Func<IXLConditionalFormat, bool> IsSameFormat = f =>
                    f != item && f.Ranges.First().Worksheet.Position == item.Ranges.First().Worksheet.Position &&
                    XLConditionalFormat.NoRangeComparer.Equals(f, item);

                //Get the top left corner of the rectangle covering all the ranges
                var baseAddress = new XLAddress(
                    item.Ranges.Select(r => r.RangeAddress.FirstAddress.RowNumber).Min(),
                    item.Ranges.Select(r => r.RangeAddress.FirstAddress.ColumnNumber).Min(),
                    false, false);
                var baseCell = item.Ranges.First().Worksheet.Cell(baseAddress) as XLCell;

                int i = 1;
                bool stop = false;
                List<IXLConditionalFormat> similarFormats = new List<IXLConditionalFormat>();
                do
                {
                    stop = (i >= formats.Count);

                    if (!stop)
                    {
                        var nextFormat = formats[i];

                        var intersectsSkipped =
                            skippedRanges.Any(left => nextFormat.Ranges.GetIntersectedRanges(left.RangeAddress).Any());

                        if (IsSameFormat(nextFormat) && !intersectsSkipped)
                        {
                            similarFormats.Add(nextFormat);
                            nextFormat.Ranges.ForEach(r => rangesToJoin.Add(r));
                        }
                        else if (rangesToJoin.Any(left => nextFormat.Ranges.GetIntersectedRanges(left.RangeAddress).Any()) ||
                                 intersectsSkipped)
                        {
                            // if we reached the rule intersecting any of captured ranges stop for not breaking the priorities
                            stop = true;
                        }
                        nextFormat.Ranges.ForEach(r => skippedRanges.Add(r));
                    }

                    i++;
                } while (!stop);

                var consRanges = rangesToJoin.Consolidate();
                item.Ranges.RemoveAll();
                consRanges.ForEach(r => item.Ranges.Add(r));

                var targetCell = item.Ranges.First().FirstCell() as XLCell;
                (item as XLConditionalFormat).AdjustFormulas(baseCell, targetCell);

                _conditionalFormats.Add(item);

                similarFormats.ForEach(cf => formats.Remove(cf));
                formats.Remove(item);
            }
        }

        public void RemoveAll()
        {
            _conditionalFormats.Clear();
        }

        /// <summary>
        /// Reorders the according to original priority. Done during load process
        /// </summary>
        public void ReorderAccordingToOriginalPriority()
        {
            var reorderedFormats = _conditionalFormats.OrderBy(cf => (cf as XLConditionalFormat).OriginalPriority).ToList();
            _conditionalFormats.Clear();
            _conditionalFormats.AddRange(reorderedFormats);
        }
    }
}
