using FastReport;
using FastReport.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows;
using System.Windows.Forms;
using HorizontalAlignment = System.Windows.HorizontalAlignment;

namespace InABox.Wpf.Reports.CustomObjects
{

    public enum DisplayType
    {
        /// <summary>
        /// Display as a grid of images
        /// </summary>
        Grid,
        /// <summary>
        /// Display as a list, which breaks onto new rows if necessary
        /// </summary>
        List
    }

    public abstract class MultiItemObject : ReportComponentBase
    {

        [Category("Layout")]
        public Padding Padding { get; set; }

        [Category("Layout")]
        public float Gap { get; set; }

        /// <summary>
        /// Sets the number of columns in the object. If it is 0, generates the number automatically. Ignored if <see cref="DisplayType"/> is <see cref="DisplayType.List"/>.
        /// </summary>
        [Category("Layout")]
        public int Columns { get; set; }

        /// <summary>
        /// Sets the number of rows in the object. If it is 0, generates the number automatically. 
        /// Ignored if <see cref="DisplayType"/> is <see cref="DisplayType.Grid"/>
        /// </summary>
        [Category("Layout")]
        public int Rows { get; set; }

        /// <summary>
        /// If <see cref="ReportComponentBase.CanGrow"/> is <c>true</c>, or <see cref="Rows"/> is 0 and not ignored,
        /// sets the height of the row. Otherwise, this property is ignored.
        /// </summary>
        [Category("Layout")]
        public float RowHeight { get; set; }

        /// <summary>
        /// The horizontal alignment of the images within their cells, if their width does not fill the cell. 
        /// Ignored if <see cref="DisplayType"/> is <see cref="DisplayType.List"/>.
        /// </summary>
        [Category("Layout")]
        public HorizontalAlignment HorizontalAlignment { get; set; }

        /// <summary>
        /// The vertical alignment of the images within their cells, if their height does not fill the cell.
        /// Ignored if <see cref="DisplayType"/> is <see cref="DisplayType.List"/>.
        /// </summary>
        [Category("Layout")]
        public VerticalAlignment VerticalAlignment { get; set; }

        /// <summary>
        /// The display type of the image object
        /// </summary>
        [Category("Layout")]
        public DisplayType DisplayType { get; set; }

        protected interface Item
        {
            float Width { get; }
            float Height { get; }
        }

        public MultiItemObject()
        {
            Padding = Padding.Empty;
            Columns = 3;
            Rows = 1;
            RowHeight = 100;
            CanGrow = true;
            HorizontalAlignment = HorizontalAlignment.Center;
            VerticalAlignment = VerticalAlignment.Top;
            DisplayType = DisplayType.Grid;
        }

        #region Serialization

        public override void Serialize(FRWriter writer)
        {
            base.Serialize(writer);
            MultiItemObject c = writer.DiffObject as MultiItemObject;

            if (Padding != c.Padding)
                writer.WriteValue("Padding", Padding);
            if (Gap != c.Gap)
                writer.WriteValue("Gap", Gap);
            if (Columns != c.Columns)
                writer.WriteValue("Columns", Columns);
            if (Rows != c.Rows)
                writer.WriteValue("Rows", Rows);
            if (RowHeight != c.RowHeight)
                writer.WriteValue("RowHeight", RowHeight);
            if (HorizontalAlignment != c.HorizontalAlignment)
                writer.WriteValue("HorizontalAlignment", HorizontalAlignment);
            if (VerticalAlignment != c.VerticalAlignment)
                writer.WriteValue("VerticalAlignment", VerticalAlignment);
            if (DisplayType != c.DisplayType)
                writer.WriteValue("DisplayType", DisplayType);
        }

        #endregion

        public override void Assign(Base source)
        {
            base.Assign(source);

            if (source is MultiItemObject src)
            {
                Padding = src.Padding;
                Gap = src.Gap;
                Columns = src.Columns;
                Rows = src.Rows;
                RowHeight = src.RowHeight;
                HorizontalAlignment = src.HorizontalAlignment;
                VerticalAlignment = src.VerticalAlignment;
                DisplayType = src.DisplayType;
            }
        }

        #region Drawing

        protected abstract IList<Item>? LoadItems();

        protected abstract void DrawItem(FRPaintEventArgs e, Item item, float x, float y, float w, float h);

        public override void Draw(FRPaintEventArgs e)
        {
            base.Draw(e);
            DrawItems(e);
            DrawMarkers(e);
            Border.Draw(e, new RectangleF(AbsLeft, AbsTop, Width, Height));
            //DrawDesign(e);
        }

        private void DrawGrid(FRPaintEventArgs e, IList<Item> items)
        {
            IGraphics g = e.Graphics;

            float drawLeft = AbsLeft + Padding.Left;
            float drawTop = AbsTop + Padding.Top;
            float drawWidth = Width - Padding.Horizontal;
            float drawHeight = Height - Padding.Vertical;

            int numColumns, numRows;
            if (Columns == 0)
            {
                double rows;
                if (CanGrow)
                {
                    rows = Math.Ceiling(Math.Sqrt(items.Count));
                    numRows = Convert.ToInt32(rows);
                    drawHeight = numRows * RowHeight;
                    numColumns = Convert.ToInt32(Math.Ceiling(items.Count / rows));
                }
                else
                {
                    var cols = Math.Ceiling(Math.Sqrt(items.Count));
                    numColumns = Convert.ToInt32(cols);
                    numRows = Convert.ToInt32(Math.Ceiling(Math.Ceiling(items.Count / cols)));
                }
            }
            else
            {
                numColumns = Columns;
                numRows = Convert.ToInt32(Math.Ceiling(items.Count / (double)Columns));
                if (CanGrow)
                {
                    drawHeight = numRows * RowHeight;
                }
            }
            Width = drawWidth + Padding.Horizontal;
            Height = drawHeight + Padding.Vertical;

            RectangleF drawRect = new RectangleF(drawLeft * e.ScaleX, drawTop * e.ScaleY, drawWidth * e.ScaleX, drawHeight * e.ScaleY);

            //if (Config.IsRunningOnMono) // strange behavior of mono - we need to reset clip before we set new one
            g.ResetClip();
            g.SetClip(drawRect);
            Report report = Report;
            if (report != null && report.SmoothGraphics)
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.SmoothingMode = SmoothingMode.AntiAlias;
            }

            var imageWidth = (drawWidth + Gap) / numColumns - Gap;
            var imageHeight = CanGrow ? RowHeight : (drawHeight + Gap) / numRows - Gap;
            int column = 0;
            int row = 0;
            for (int i = 0; i < items.Count; i++)
            {
                var image = items[i];

                var aspectRatio = image.Width / image.Height;
                float width, height;
                if (aspectRatio <= imageWidth / imageHeight)
                {
                    height = imageHeight;
                    width = height * aspectRatio;
                }
                else
                {
                    width = imageWidth;
                    height = width / aspectRatio;
                }

                float x = column * (imageWidth + Gap) + Padding.Left + drawLeft;
                float y = row * (imageHeight + Gap) + Padding.Top + drawTop;

                switch (HorizontalAlignment)
                {
                    case HorizontalAlignment.Center:
                        x += imageWidth / 2 - width / 2;
                        break;
                    case HorizontalAlignment.Right:
                        x += imageWidth - width;
                        break;
                    case HorizontalAlignment.Stretch:
                        width = imageWidth;
                        break;
                }

                switch (VerticalAlignment)
                {
                    case VerticalAlignment.Center:
                        y += imageHeight / 2 - height / 2;
                        break;
                    case VerticalAlignment.Bottom:
                        y += imageHeight - height;
                        break;
                    case VerticalAlignment.Stretch:
                        height = imageHeight;
                        break;
                }

                DrawItem(e, image, x, y, width, height);

                if (++column >= numColumns)
                {
                    column = 0;
                    row++;
                }
            }
        }

        private void DrawList(FRPaintEventArgs e, IList<Item> items)
        {
            IGraphics g = e.Graphics;

            float drawLeft = AbsLeft + Padding.Left;
            float drawTop = AbsTop + Padding.Top;
            float drawWidth = Width - Padding.Horizontal;
            float drawHeight = Height - Padding.Vertical;

            float rowHeight;
            if (Rows == 0)
            {
                rowHeight = RowHeight;
            }
            else
            {
                if (CanGrow)
                {
                    rowHeight = RowHeight;
                }
                else
                {
                    rowHeight = drawHeight / Rows;
                }
            }

            RectangleF drawRect = new RectangleF(drawLeft * e.ScaleX, drawTop * e.ScaleY, drawWidth * e.ScaleX, drawHeight * e.ScaleY);

            //if (Config.IsRunningOnMono) // strange behavior of mono - we need to reset clip before we set new one
            g.ResetClip();
            g.SetClip(drawRect);
            Report report = Report;
            if (report != null && report.SmoothGraphics)
            {
                g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                g.SmoothingMode = SmoothingMode.AntiAlias;
            }

            float pointX = 0f;
            float pointY = 0f;

            for (int i = 0; i < items.Count; i++)
            {
                var image = items[i];
                var aspectRatio = image.Width / image.Height;

                float height = rowHeight;
                float width = rowHeight * aspectRatio;

                float x;
                if (pointX + width > drawWidth)
                {
                    if (pointX > 0.001f)
                    {
                        pointY += rowHeight + Gap;
                    }
                    pointX = 0;
                    x = 0;
                }
                else
                {
                    x = pointX;
                }
                float y = pointY;

                x += Padding.Left + drawLeft;
                y += Padding.Top + drawTop;

                DrawItem(e, image, x * e.ScaleX, y * e.ScaleY, width * e.ScaleX, height * e.ScaleY);

                pointX += width + Gap;
            }
            float totalHeight = pointY + rowHeight + Padding.Vertical;
            if ((CanGrow && totalHeight > Height) || (CanShrink && totalHeight < Height))
            {
                Height = totalHeight;
            }
        }

        protected void DrawItems(FRPaintEventArgs e)
        {
            IGraphics g = e.Graphics;

            var items = LoadItems();
            if (items == null)
            {
                return;
            }

            IGraphicsState state = g.Save();
            try
            {
                switch (DisplayType)
                {
                    case DisplayType.Grid:
                        DrawGrid(e, items);
                        break;
                    case DisplayType.List:
                        DrawList(e, items);
                        break;
                }
            }
            finally
            {
                g.Restore(state);
            }

            if (IsPrinting)
            {
                //DisposeImage();
            }
        }

        #endregion
    }
}
