using System;
using System.CodeDom;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FizzyLauncher;
using FizzyLauncher.Windows.Forms;

namespace RyzStudio.Windows.Forms
{
    public partial class TileGridPanelLayout<T> : RyzStudio.Windows.Forms.TUserControl where T : Control
    {
        public static int CalcWidth(int tileCount)
            => (tileCount * tileSize) + ((tileCount - 1) * margin);


        public class GridTileItem
        {
            public T Tile { get; set; }
            public Point Coord { get; set; } = new Point(0, 0);
        }


        protected const int tileSize = 70;
        protected const int margin = 3;
        protected const int labelHeight = 20;
        protected const int collapseIncrement = 6;
        protected const int expandIncrement = 8;

        protected int collapseHeight = 0;
        protected int expandedHeight = 0;

        protected bool isAnimating = false;
        protected bool isExpanded = true;
        protected Point lastMousePosition = new Point();
        protected Point gridSize = new Point(0, 0);


        public TileGridPanelLayout() : base()
        {
            InitializeComponent();

            this.AllowDrop = true;
            this.BackColor = Color.Transparent;
            this.Margin = new Padding(0);
            this.Padding = new Padding(0, 0, 0, 10);

        }

        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);

            e.Control.MouseMove += (sender, e) =>
            {
                this.MoveTile(sender as T, e.X, e.Y);
            };
        }

        private void tilePanel_Move(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        protected override void OnDragOver(DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Link;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);

            lastMousePosition = e.Location;

            bool isLabel = ((e.Location.X >= 0) && (e.Location.X <= this.Width) && (e.Location.Y >= 0) && (e.Location.Y <= 20));

            if (e.Button == MouseButtons.Left)
            {
                // do nothing
            }
            else if (e.Button == MouseButtons.Right)
            {
                if (isLabel)
                {
                    this.TitleContextMenuStrip?.Show(this, e.Location);
                }
                else
                {
                    this.ContainerContextMenuStrip?.Show(this, e.Location);
                }
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
            => base.OnMouseClick(e);

        protected override async void OnResize(EventArgs e)
        {
            base.OnResize(e);

            await this.Invalidate();
        }

        protected virtual async void label1_MouseClick(object sender, MouseEventArgs e)
        {
            if (isAnimating)
            {
                return;
            }

            if (e.Button == MouseButtons.Left)
            {
                this.IsExpanded = !this.IsExpanded;

                //label1.Image = (this.IsExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);

                await this.Invalidate();

                // exclusivity
                //if (this.IsExpanded)
                //{
                    //if (this.Model.IsExclusive)
                    //{
                    //    if (this.FlowLayoutPanel != null)
                    //    {
                    //        foreach (TilePanelLayout item in this.FlowLayoutPanel.Controls.OfType<TilePanelLayout>())
                    //        {
                    //            if (item.Equals(this))
                    //            {
                    //                continue;
                    //            }

                    //            await item.Collapse();
                    //        }
                    //    }
                    //}
                //}

                //label1.Image = (this.IsExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);
            }
            else if (e.Button == MouseButtons.Right)
            {
                this.TitleContextMenuStrip?.Show(this, e.Location);
            }
        }

        #region encapsulation

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Padding Margin { get => base.Margin; set => base.Margin = new Padding(0); }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new Padding Padding { get => base.Padding; set => base.Padding = new Padding(0, 0, 0, 10); }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new ContextMenuStrip ContextMenuStrip { get; set; } = null;

        #endregion

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Point GridSize
        {
            get => gridSize;
            protected set
            {
                gridSize = value;
                expandedHeight = (this.TileSize * value.Y) + labelHeight;

                int newWidth = CalcWidth(gridSize.X);

                this.Size = new Size(newWidth, (this.IsExpanded ? this.ExpandedHeight : this.CollapseHeight));
            }
        }

        [Category("Appearance")]
        public ContextMenuStrip TitleContextMenuStrip { get; set; } = null;

        [Category("Appearance")]
        public ContextMenuStrip ContainerContextMenuStrip { get; set; } = null;

        [Category("Appearance")]
        public bool EnableAnimation { get; set; } = false;

        [Category("Appearance")]
        public string Title { get => label1.Text?.Trim(); set => label1.Text = "        " + value; }


        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public List<GridTileItem> GridTileItems { get; protected set; } = new List<GridTileItem>();

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public FlowLayoutPanel FlowLayoutPanel { get => UIControl.GetParentsUntil<FlowLayoutPanel>(this.Parent); }

        protected int CollapseHeight
            => labelHeight + collapseHeight;

        protected int ExpandedHeight
            => expandedHeight + this.Padding.Bottom;

        protected int TileSize => (tileSize + margin);

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public bool IsExpanded
        {
            get => isExpanded;
            protected set
            {
                isExpanded = value;

                //label1.Image = (isExpanded ? UIResource1.toggle_right_16 : UIResource1.toggle_left_16);
                label1.Image = (isExpanded ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);
            }
        }


        public void Clear()
        {
            this.Controls.Clear();
        }

        public async Task Collapse()
        {
            await Task.Run(() =>
            {
                if (isAnimating) return;

                isAnimating = true;
                this.IsExpanded = false;

                if (this.EnableAnimation)
                {
                    while (this.Height > this.CollapseHeight)
                    {
                        UIControl.SetHeight(this, (this.Height - collapseIncrement));

                        Thread.Sleep(10);
                    }
                }

                UIControl.SetHeight(this, this.CollapseHeight);

                isAnimating = false;

                this.Invalidate(this.DisplayRectangle, false);
            });
        }

        public async Task Expand()
        {
            await Task.Run(() =>
            {
                if (isAnimating) return;

                isAnimating = true;
                this.IsExpanded = true;

                if (this.EnableAnimation)
                {
                    while (this.Height < this.ExpandedHeight)
                    {
                        UIControl.SetHeight(this, (this.Height + expandIncrement));
                        Thread.Sleep(10);
                    }
                }

                UIControl.SetHeight(this, this.ExpandedHeight);

                isAnimating = false;

                this.Invalidate(this.DisplayRectangle, false);
            });
        }

        public new async Task Invalidate()
        {
            base.Invalidate();

            if (isAnimating)
            {
                return;
            }

            if (this.IsExpanded)
            {
                await this.Expand();
            }
            else
            {
                await this.Collapse();
            }
        }

        public void MoveTop()
        {
            if (this.FlowLayoutPanel != null)
            {
                this.FlowLayoutPanel.Controls.SetChildIndex(this, 0);
            }
        }

        public void MoveUp()
        {
            if (this.FlowLayoutPanel != null)
            {
                int pos = this.FlowLayoutPanel.Controls.GetChildIndex(this);
                if (pos <= 0)
                {
                    return;
                }

                this.FlowLayoutPanel.Controls.SetChildIndex(this, (pos - 1));
            }
        }

        public void MoveDown()
        {
            if (this.FlowLayoutPanel != null)
            {
                int pos = this.FlowLayoutPanel.Controls.GetChildIndex(this);
                if (pos >= (this.FlowLayoutPanel.Controls.Count - 1))
                {
                    return;
                }

                this.FlowLayoutPanel.Controls.SetChildIndex(this, (pos + 1));
            }
        }

        public void MoveBottom()
        {
            if (this.FlowLayoutPanel != null)
            {
                this.FlowLayoutPanel.Controls.SetChildIndex(this, (this.FlowLayoutPanel.Controls.Count - 1));
            }
        }

        public void MoveTile(T panel, int posX, int posY)
        {
            GridTileItem item = this.GridTileItems.Where(x => x.Tile.Equals(panel)).FirstOrDefault();
            if (item == null)
            {
                return;
            }

            //Point newPosition = ConvertLocationToNearestCoord(posX, posY);
            //Point newPosition = ConvertLocationToCoord(posX, posY);
            Point newPosition = ConvertLocationToCoord(panel.Location.X, panel.Location.Y);
            if (!IsCoordInBounds(newPosition))
            {
                return;
            }

            if (HasTile(newPosition))
            {
                GridTileItem swapItem = this.GridTileItems.Where(x => x.Coord.Equals(newPosition)).FirstOrDefault();
                if (swapItem != null)
                {
                    swapItem.Coord = item.Coord;
                    swapItem.Tile.Location = ConvertCoordToLocation(item.Coord);
                }

                item.Coord = newPosition;
                panel.Location = ConvertCoordToLocation(newPosition);
            }
            else
            {
                item.Coord = newPosition;
                panel.Location = ConvertCoordToLocation(newPosition);
            }
        }

        public void Remove()
        {
            if (this.FlowLayoutPanel != null)
            {
                this.FlowLayoutPanel.Controls.Remove(this);
            }
        }

        public void Remove(T panel)
        {
            GridTileItem m = this.GridTileItems.Where(x => x.Tile.Equals(panel)).FirstOrDefault();
            if (m != null)
            {
                this.GridTileItems.Remove(m);
            }

            this.Controls.Remove(panel);
        }

        //protected Point CalcCoordFromPosition(int posX, int posY)
        //{
        //    int x = (int)Math.Round(decimal.Divide(posX, this.TileSize));
        //    int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize));

        //    if (x < 0) x = 0;
        //    if (y < 0) y = 0;

        //    return new Point((x * this.TileSize), ((y * this.TileSize) + labelHeight));
        //}

        protected Point ConvertCoordToLocation(Point position)
            => new Point((position.X * this.TileSize), ((position.Y * this.TileSize) + labelHeight));

        protected Point ConvertLocationToCoord(int posX, int posY)
        {
            int x = (int)Math.Round(decimal.Divide(posX, this.TileSize));
            int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize));

            //int x = (int)Math.Ceiling(decimal.Divide(posX, this.TileSize));
            //int y = (int)Math.Ceiling(decimal.Divide((posY - labelHeight), this.TileSize));

            //x--;
            //y--;

            if (x < 0) x = 0;
            if (y < 0) y = 0;

            return new Point(x, y);
        }

        //protected Point ConvertLocationToNearestCoord(int posX, int posY)
        //{
        //    int x = (int)Math.Round(decimal.Divide(posX, this.TileSize));
        //    int y = (int)Math.Round(decimal.Divide((posY - labelHeight), this.TileSize));

        //    if (x < 0) x = 0;
        //    if (y < 0) y = 0;

        //    return new Point(x, y);
        //}

        protected bool HasTile(Point position)
        {
            if (GridTileItems == null)
            {
                return false;
            }

            if (GridTileItems.Count <= 0)
            {
                return false;
            }

            return GridTileItems.Any(x => x.Coord.Equals(position));
        }

        protected bool IsCoordInBounds(Point position)
        {
            if (position.X >= this.GridSize.X)
            {
                return false;
            }

            if (position.Y >= this.GridSize.Y)
            {
                return false;
            }

            return true;
        }

        protected Point? FindFirstFreeCoord()
        {
            for (int y = 0; y < this.GridSize.Y; y++)
            {
                for (int x = 0; x < this.GridSize.X; x++)
                {
                    if (HasTile(new Point(x, y)))
                    {
                        continue;
                    }

                    return new Point(x, y);
                }
            }

            return null;
        }

        protected Point? FindLastFreeCoord()
        {
            Point gridSize = this.GridSize;

            // none available
            if (GridTileItems.Count >= (gridSize.X * gridSize.Y))
            {
                return null;
            }

            if (GridTileItems.Count <= 0)
            {
                return FindFirstFreeCoord();
            }

            // only one available
            if (GridTileItems.Count >= ((gridSize.X * gridSize.Y) - 1))
            {
                return FindFirstFreeCoord();
            }

            Point? rv = null;

            for (int y = (gridSize.Y - 1); y >= 0; y--)
            {
                for (int x = (gridSize.X - 1); x >= 0; x--)
                {
                    if (HasTile(new Point(x, y)))
                    {
                        if (rv.HasValue)
                        {
                            return rv;
                        }
                    }
                    else
                    {
                        rv = new Point(x, y);
                    }
                }
            }

            return null;
        }

        protected Point? FindNextFreeCoord(Point coord)
        {
            Point newCoord = coord;

            while (true)
            {
                newCoord.X++;

                if (newCoord.X >= this.GridSize.X)
                {
                    newCoord.Y++;
                    newCoord.X = 0;
                }

                if (!IsCoordInBounds(newCoord))
                {
                    return null;
                }

                if (HasTile(newCoord))
                {
                    continue;
                }

                return newCoord;
            }
        }

        protected Point? ResolveCoord(Point coord)
        {
            if (!IsCoordInBounds(coord))
            {
                return null;
            }

            if (!HasTile(coord))
            {
                return coord;
            }

            return FindNextFreeCoord(coord);
        }

    }
}
