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

namespace FastReport.AdvMatrix
{
    /// <summary>
    /// Represents a base class for matrix buttons such as expand or sort button.
    /// </summary>
    public class MatrixButton : ReportComponentBase
    {
        /// <summary>
        /// Determines the symbol size, in pixels. 0 indicates the auto size.
        /// </summary>
        [Category("Appearance")]
        public int SymbolSize { get; set; }

        /// <summary>
        /// Determines whether this buttons belongs to column header. For internal use only.
        /// </summary>
        [Browsable(false)]
        public bool IsColumn { get; set; }

        /// <summary>
        /// Gets or set the index of this button. For internal use only.
        /// </summary>
        [Browsable(false)]
        public int Index { get; set; }

        internal AdvMatrixObject Matrix
        {
            get
            {
                Base obj = this;
                while (obj != null && !(obj is AdvMatrixObject))
                    obj = obj.Parent;
                if (obj is AdvMatrixObject)
                    return Report.FindObject(obj.Name) as AdvMatrixObject;
                return null;
            }
        }

        internal TableCell Cell
        {
            get
            {
                Base obj = this;
                while (obj != null && !(obj is TableCell))
                    obj = obj.Parent;
                if (obj is TableCell)
                    return obj as TableCell;
                return null;
            }
        }

        /// <inheritdoc/>
        public override void Assign(Base source)
        {
            base.Assign(source);
            MatrixButton src = source as MatrixButton;
            if (src != null)
            {
                SymbolSize = src.SymbolSize;
                IsColumn = src.IsColumn;
                Index = src.Index;
            }
        }

        /// <inheritdoc/>
        public override void Serialize(FRWriter writer)
        {
            base.Serialize(writer);
            MatrixButton c = writer.DiffObject as MatrixButton;

            if (SymbolSize != c.SymbolSize)
                writer.WriteInt("SymbolSize", SymbolSize);
            if (IsColumn != c.IsColumn)
                writer.WriteBool("IsColumn", IsColumn);
            if (Index != c.Index)
                writer.WriteInt("Index", Index);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MatrixButton"/> class.
        /// </summary>
        public MatrixButton()
        {
            Cursor = System.Windows.Forms.Cursors.Hand;
            Printable = false;
            Exportable = false;
            SymbolSize = 0;
        }
    }

    /// <summary>
    /// Represents the symbol used to display the matrix expand/collapse state.
    /// </summary>
    public enum CollapseSymbol
    {
        /// <summary>
        /// Plus/minus.
        /// </summary>
        PlusMinus,
        /// <summary>
        /// The pointer.
        /// </summary>
        Pointer,
        /// <summary>
        /// The arrow.
        /// </summary>
        Arrow
    }

    /// <summary>
    /// Represents the matrix button used to toggle expand/collapse state of matrix headers.
    /// </summary>
    public partial class MatrixCollapseButton : MatrixButton
    {
        /// <summary>
        /// Determines whether this button has a collapsed state. For internal use only.
        /// </summary>
        [Browsable(false)]
        public bool Collapsed { get; set; }

        /// <summary>
        /// Determines whether to show collapse/expand menu on right click.
        /// </summary>
        public bool ShowCollapseExpandMenu { get; set; }
        /// <summary>
        /// Determines the symbol used to display the button's state.
        /// </summary>
        [Category("Appearance")]
        public CollapseSymbol Symbol { get; set; }

        /// <summary>
        /// Determines if only one button in the group can be expanded.
        /// </summary>
        [Category("Behavior")]
        public bool Exclusive { get; set; }

        /// <inheritdoc/>
        public override void Assign(Base source)
        {
            base.Assign(source);
            MatrixCollapseButton src = source as MatrixCollapseButton;
            if (src != null)
            {
                Collapsed = src.Collapsed;
                Symbol = src.Symbol;
                Exclusive = src.Exclusive;
                ShowCollapseExpandMenu = src.ShowCollapseExpandMenu;
            }
        }

        /// <inheritdoc/>
        public override void Draw(FRPaintEventArgs e)
        {
            if (Parent == null || (Parent as ReportComponentBase).Width == 0 || (Parent as ReportComponentBase).Height == 0)
                return;

            IGraphics g = e.Graphics;
            IGraphicsState state = g.Save();
            g.SmoothingMode = SmoothingMode.AntiAlias;

            PointF[] points = null;
            Pen pen = e.Cache.GetPen(Border.Color, Border.Width, DashStyle.Solid);
            Brush brush = e.Cache.GetBrush(Border.Color);
            // trying to be pixel perfect
            float dx = SymbolSize > 0 ? SymbolSize : (int)Math.Min(Width, Height) - 6;
            if ((int)dx % 2 == 1)
                dx -= 1;
            float dy = dx;

            g.ScaleTransform(e.ScaleX, e.ScaleY);
            g.TranslateTransform((int)Math.Round(AbsLeft + Width / 2 - dx / 2), (int)Math.Round(AbsTop + Height / 2 - dy / 2));

            if (Fill is SolidFill)
                g.FillRectangle(e.Cache.GetBrush((Fill as SolidFill).Color), new RectangleF(-2, -2, dx + 4, dy + 4));
            if (Border.Lines != BorderLines.None)
                g.DrawRectangle(pen, -2, -2, dx + 4, dy + 4);

            if (Collapsed)
            {
                switch (Symbol)
                {
                    case CollapseSymbol.PlusMinus:
                        g.DrawLine(pen, 0, dy / 2, dx, dy / 2);
                        g.DrawLine(pen, dx / 2, 0, dx / 2, dy);
                        break;
                    case CollapseSymbol.Arrow:
                        points = new PointF[] { new PointF(dx / 2, 0), new PointF(dx, dy / 2), new PointF(dx / 2, dy) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        break;
                    case CollapseSymbol.Pointer:
                        g.DrawLines(pen, new PointF[] { new PointF(dx / 2, 0), new PointF(dx, dy / 2), new PointF(dx / 2, dy) });
                        break;
                }
            }
            else
            {
                switch (Symbol)
                {
                    case CollapseSymbol.PlusMinus:
                        g.DrawLine(pen, 0, dy / 2, dx, dy / 2);
                        break;
                    case CollapseSymbol.Arrow:
                        points = new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, dy), new PointF(dx, dy / 2) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        break;
                    case CollapseSymbol.Pointer:
                        g.DrawLines(pen, new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, dy), new PointF(dx, dy / 2) });
                        break;
                }
            }
            g.Restore(state);
        }

        /// <inheritdoc/>
        public override void Serialize(FRWriter writer)
        {
            base.Serialize(writer);
            MatrixCollapseButton c = writer.DiffObject as MatrixCollapseButton;

            if (Collapsed != c.Collapsed)
                writer.WriteBool("Collapsed", Collapsed);
            if (Exclusive != c.Exclusive)
                writer.WriteBool("Exclusive", Exclusive);
            if (Symbol != c.Symbol)
                writer.WriteValue("Symbol", Symbol);
            if (ShowCollapseExpandMenu != c.ShowCollapseExpandMenu)
                writer.WriteBool("ShowCollapseExpandMenu", ShowCollapseExpandMenu);
        }
        /// <summary>
        /// For internal use only,return action click for Advanced Matrix collapse button
        /// </summary>
        public void MatrixCollapseButtonClick()
        {
            if (IsColumn)
                Matrix.ToggleColumnVisible(Index);
            else
                Matrix.ToggleRowVisible(Index);
            Report.Refresh();
        }
    

        internal List<HeaderDataList> GetLinkedItems(HeaderData item)
        {
            List<HeaderDataList> list = new List<HeaderDataList>();
            Action<HeaderData> func = (root) =>
            {
                if (root == null)
                    return;
                foreach (HeaderDataList dl in root.AllItems)
                {
                    if (dl.Descriptor.VisibleToggledBy == Name)
                        list.Add(dl);
                }
            };
            
            func(item);
            if (list.Count == 0 && item.Value == null)
                func(item.Parent);
            return list;
        }

        internal int GetLinkedItemsCount(HeaderData item)
        {
            int result = 0;
            foreach (HeaderDataList dl in GetLinkedItems(item))
                result += dl.Count;
            return result;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MatrixCollapseButton"/> class.
        /// </summary>
        public MatrixCollapseButton()
        {
            BaseName = "CollapseButton";
            Symbol = CollapseSymbol.PlusMinus;
            Border.Lines = BorderLines.All;
        }
    }

    /// <summary>
    /// Represents the symbol used to display the matrix sort order.
    /// </summary>
    public enum SortSymbol
    {
        /// <summary>
        /// The arrow.
        /// </summary>
        Arrow,
        /// <summary>
        /// The pointer.
        /// </summary>
        Pointer
    }

    /// <summary>
    /// Represents the matrix button used to toggle sort order of matrix headers.
    /// </summary>
    public partial class MatrixSortButton : MatrixButton
    {
        /// <summary>
        /// Determines the sort state of this button.
        /// </summary>
        [Category("Behavior")]
        public SortOrder Sort { get; set; }

        /// <summary>
        /// Determines whether "None" sort is allowed when you switch sort states.
        /// </summary>
        [Category("Behavior")]
        public bool AllowInactiveSort { get; set; }

        /// <summary>
        /// Determines the symbol used to display the state of this button.
        /// </summary>
        [Category("Appearance")]
        public SortSymbol Symbol { get; set; }

        /// <summary>
        /// Determines the color used to display button with inactive sort state (Sort = None).
        /// </summary>
        [Category("Appearance")]
        public Color InactiveSortColor { get; set; }

        /// <inheritdoc/>
        public override void Assign(Base source)
        {
            base.Assign(source);
            MatrixSortButton src = source as MatrixSortButton;
            if (src != null)
            {
                Sort = src.Sort;
                AllowInactiveSort = src.AllowInactiveSort;
                Symbol = src.Symbol;
                InactiveSortColor = src.InactiveSortColor;
            }
        }
        /// <summary>
        /// For internal use only,return action click for Advanced Matrix sort button
        /// </summary>
        public void MatrixSortButtonClick()
        {
            SortOrder sort = (Sort == SortOrder.None ? SortOrder.Ascending : (Sort == SortOrder.Ascending ? SortOrder.Descending : SortOrder.None));
            if (Index == 0)
            {
                // this button toggles sort order of descriptors with SortToggledBy = this
                if (!AllowInactiveSort && sort == SortOrder.None)
                    sort = SortOrder.Ascending;
                (Report.FindObject(Name) as MatrixSortButton).Sort = sort;
            }
            else if (Matrix != null)
            {
                if (IsColumn)
                    Matrix.SortRowsByColumn(Index, sort);
                else
                    Matrix.SortColumnsByRow(Index, sort);
            }
              Report.Refresh();
        }
        /// <inheritdoc/>
        public override void Draw(FRPaintEventArgs e)
        {
            IGraphics g = e.Graphics;
            IGraphicsState state = g.Save();
            g.SmoothingMode = SmoothingMode.AntiAlias;

            PointF[] points = null;
            Pen pen = e.Cache.GetPen(Border.Color, Border.Width, DashStyle.Solid);
            Brush brush = e.Cache.GetBrush(pen.Color);
            // trying to be pixel perfect
            float dx = SymbolSize > 0 ? SymbolSize : (int)Math.Min(Width, Height) - 6;
            if ((int)dx % 2 == 1)
                dx -= 1;
            float dy = dx;

            g.ScaleTransform(e.ScaleX, e.ScaleY);
            g.TranslateTransform((int)Math.Round(AbsLeft + Width / 2 - dx / 2), (int)Math.Round(AbsTop + Height / 2 - dy / 2));

            if (Fill is SolidFill)
                g.FillRectangle(e.Cache.GetBrush((Fill as SolidFill).Color), new RectangleF(-2, -2, dx + 4, dy + 4));
            if (Border.Lines != BorderLines.None)
                g.DrawRectangle(pen, -2, -2, dx + 4, dy + 4);

            if (Sort == SortOrder.Ascending)
            {
                switch (Symbol)
                {
                    case SortSymbol.Arrow:
                        points = new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, 0), new PointF(dx, dy / 2) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        break;
                    case SortSymbol.Pointer:
                        g.DrawLines(pen, new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, 0), new PointF(dx, dy / 2) });
                        break;
                }
            }
            else if (Sort == SortOrder.Descending)
            {
                switch (Symbol)
                {
                    case SortSymbol.Arrow:
                        points = new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, dy), new PointF(dx, dy / 2) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        break;
                    case SortSymbol.Pointer:
                        g.DrawLines(pen, new PointF[] { new PointF(0, dy / 2), new PointF(dx / 2, dy), new PointF(dx, dy / 2) });
                        break;
                }

            }
            else if (Sort == SortOrder.None)
            {
                pen = e.Cache.GetPen(InactiveSortColor, pen.Width, DashStyle.Solid);
                brush = e.Cache.GetBrush(pen.Color);
                switch (Symbol)
                {
                    case SortSymbol.Arrow:
                        points = new PointF[] { new PointF(1, dy / 2 - 1), new PointF(dx / 2, 0), new PointF(dx - 1, dy / 2 - 1) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        points = new PointF[] { new PointF(1, dy / 2 + 1), new PointF(dx / 2, dy), new PointF(dx - 1, dy / 2 + 1) };
                        g.FillAndDrawPolygon(pen, brush, points);
                        break;
                    case SortSymbol.Pointer:
                        g.DrawLines(pen, new PointF[] { new PointF(1, dy / 2 - 1), new PointF(dx / 2, 0), new PointF(dx - 1, dy / 2 - 1) });
                        g.DrawLines(pen, new PointF[] { new PointF(1, dy / 2 + 1), new PointF(dx / 2, dy), new PointF(dx - 1, dy / 2 + 1) });
                        break;
                }
            }
            g.Restore(state);
        }

        /// <inheritdoc/>
        public override void Serialize(FRWriter writer)
        {
            base.Serialize(writer);
            MatrixSortButton c = writer.DiffObject as MatrixSortButton;

            if (Sort != c.Sort)
                writer.WriteValue("Sort", Sort);
            if (AllowInactiveSort != c.AllowInactiveSort)
                writer.WriteBool("AllowInactiveSort", AllowInactiveSort);
            if (Symbol != c.Symbol)
                writer.WriteValue("Symbol", Symbol);
            if (InactiveSortColor != c.InactiveSortColor)
                writer.WriteValue("InactiveSortColor", InactiveSortColor);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="MatrixSortButton"/> class.
        /// </summary>
        public MatrixSortButton()
        {
            BaseName = "SortButton";
            Sort = SortOrder.None;
            AllowInactiveSort = true;
            Symbol = SortSymbol.Arrow;
            InactiveSortColor = Color.Gray;
        }
    }
}