using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FizzyLauncher.Models;
using RyzStudio.Windows.Forms;

namespace FizzyLauncher.Windows.Forms
{
    public partial class TilePanelLayout : RyzStudio.Windows.Forms.TC1UserControl
    {
        public static int CalcWidth(int tileCount) => (tileCount * tileSize) + ((tileCount - 1) * margin);


        public class Item
        {
            public TilePanel 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 TileGroupModel groupModel = null;
        protected List<Item> items = new List<Item>();

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

        protected bool isAnimating = false;
        protected bool isChecked = true;
        protected Point lastMousePosition = new Point();
        protected Point gridSize = new Point();


        public TilePanelLayout(TileGroupModel model) : base()
        {
            InitializeComponent();

            this.AllowDrop = true;
            this.BackColor = Color.Transparent;

            this.LoadModel(model);

            label1.Location = new Point(0, 4);
            label1.Margin = new Padding(0);
            label1.Padding = new Padding(0);
        }

        protected override void OnDragDrop(DragEventArgs e)
        {
            string[] fileList = e.Data.GetData(DataFormats.FileDrop) as string[];

            this.DropFileList(fileList);
        }

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

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            this.Margin = new Padding(0);
            this.Padding = new Padding(0, 0, 0, 10);
        }

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

            await this.InvalidateContainer();
        }

        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)
                {
                    contextMenuStrip2.Show(this, e.Location);
                }
                else
                {
                    contextMenuStrip1.Show(this, e.Location);
                }
            }
        }

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

        public Point GridSize
        {
            get => gridSize;
        }

        public bool EnableAnimation
        {
            get
            {
                MainForm mainForm = this.MainForm;
                if (mainForm == null)
                {
                    return false;
                }

                if (mainForm.CurrentSession == null)
                {
                    return false;
                }

                return mainForm.CurrentSession.EnableAnimation;
            }
        }

        public int CollapseHeight => labelHeight + collapseHeight;

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

        public void DropFileList(string[] fileList)
        {
            if (fileList == null)
            {
                return;
            }

            if (fileList.Length <= 0)
            {
                return;
            }

            if (string.IsNullOrWhiteSpace(fileList[0]))
            {
                return;
            }

            TileModel model = new TileModel()
            {
                ProcessFilename = fileList[0],
                Title = Path.GetFileName(fileList[0])
            };

            // exe
            if (Path.GetExtension(fileList[0]).Equals(".exe", StringComparison.CurrentCultureIgnoreCase))
            {
                if (File.Exists(fileList[0]))
                {
                    try
                    {
                        FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(fileList[0]);
                        if (fvi != null)
                        {
                            model.Title = fvi.ProductName;
                        }
                    }
                    catch
                    {
                        // do nothing
                    }
                }

                if (string.IsNullOrWhiteSpace(model.Title))
                {
                    model.Title = Path.GetFileNameWithoutExtension(fileList[0]);
                }
            }

            this.AddTile(model);
        }

        public TileGroupModel Model
        {
            get
            {
                TileGroupModel rs = new TileGroupModel()
                {
                    Title = groupModel.Title,
                    GridSize = new Size(this.GridSize.X, this.GridSize.Y),
                    IsExpanded = isChecked,
                    IsExclusive = groupModel.IsExclusive,
                    Items = this.Tiles
                };

                return rs;
            }
        }

        public FlowLayoutPanel FlowLayoutPanel
        {
            get
            {
                Control parentControl = this.Parent;
                while (true)
                {
                    if (parentControl == null)
                    {
                        return null;
                    }

                    if (parentControl.GetType() == typeof(FlowLayoutPanel))
                    {
                        return parentControl as FlowLayoutPanel;
                    }

                    parentControl = parentControl.Parent;
                }
            }
        }

        public MainForm MainForm
        {
            get
            {
                Control parentControl = this.FlowLayoutPanel;
                while (true)
                {
                    if (parentControl == null)
                    {
                        return null;
                    }

                    if (parentControl.GetType() == typeof(MainForm))
                    {
                        return parentControl as MainForm;
                    }

                    parentControl = parentControl.Parent;
                }
            }
        }

        public List<TileModel> Tiles
        {
            get
            {
                List<TileModel> rs = new List<TileModel>();
                foreach (Item item in items)
                {
                    TileModel model = item.Tile.ModelInfo;
                    model.Position = item.Coord;

                    rs.Add(model);
                }

                return rs;
            }
        }

        public int TileSize => (tileSize + margin);

        public void AddTile(TileModel tile)
        {
            Point gridSize = this.GridSize;

            if (items.Count >= (gridSize.X * gridSize.Y))
            {
                this.SetGridSize(gridSize.X, (gridSize.Y + 1));
            }

            Point? newCoord = tile.Position;
            if ((newCoord == null) || hasTile(tile.Position))
            {
                newCoord = findLastFreeCoord();
            }

            if (newCoord == null)
            {
                return;
            }

            tile.Position = newCoord.Value;

            TilePanel panel = new TilePanel();
            panel.LoadInfo(tile);
            panel.Location = convertCoordToLocation(tile.Position);

            items.Add(new Item()
            {
                Tile = panel,
                Coord = tile.Position
            });

            this.Controls.Add(panel);
        }

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

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

                isAnimating = true;
                isChecked = 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;
                isChecked = 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 Point GetTilePosition(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));
        }

        public async Task InvalidateContainer()
        {
            if (isAnimating)
            {
                return;
            }

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

        public void AddGroup()
        {
            if (this.FlowLayoutPanel == null)
            {
                return;
            }

            this.FlowLayoutPanel.Controls.Add(new TilePanelLayout(new TileGroupModel()
            {
                Title = "New Group",
                GridSize = new Size(8, 1)
            }));
        }

        public void AddRow() => this.SetGridSize(gridSize.X, (gridSize.Y + 1));

        public void EditGroup() => EditGroupForm.ShowDialog(this);

        public void LoadModel(TileGroupModel model)
        {
            groupModel = model;

            isChecked = groupModel.IsExpanded;

            label1.Text = "        " + groupModel?.Title;
            label1.Image = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);

            this.SetGridSize(groupModel.GridSize.Width, groupModel.GridSize.Height);
            this.LoadTiles(model.Items);
            this.SetGridSize(groupModel.GridSize.Width, groupModel.GridSize.Height);

            this.Invalidate();
        }

        public void UpdateModel(TileGroupModel model)
        {
            groupModel = model;
            isChecked = groupModel.IsExpanded;

            label1.Text = "        " + groupModel?.Title;

            this.Invalidate();
        }

        public void LoadTiles(List<TileModel> tiles)
        {
            if (tiles == null)
            {
                return;
            }

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

            foreach (TileModel item in tiles)
            {
                // resolve final grid position
                Point? confirmedPosition = resolveCoord(item.Position);
                if (confirmedPosition == null)
                {
                    continue;
                }

                // place control
                TilePanel panel = new TilePanel();
                panel.LoadInfo(item);
                panel.Location = convertCoordToLocation(confirmedPosition.Value);

                items.Add(new Item()
                {
                    Tile = panel,
                    Coord = confirmedPosition.Value
                });

                this.Controls.Add(panel);
            }
        }

        public void MoveTile(TilePanel panel, int posX, int posY)
        {
            Item item = items.Where(x => x.Tile.Equals(panel)).FirstOrDefault();
            if (item == null)
            {
                return;
            }

            Point newPosition = convertLocationToCoord_Nearest(posX, posY);
            if (!isTileInBounds(newPosition))
            {
                return;
            }

            if (hasTile(newPosition))
            {
                Item swapItem = items.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 MoveTop()
        {
            if (this.FlowLayoutPanel == null)
            {
                return;
            }

            this.FlowLayoutPanel.Controls.SetChildIndex(this, 0);
        }

        public void MoveUp()
        {
            if (this.FlowLayoutPanel == null)
            {
                return;
            }

            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)
            {
                return;
            }

            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)
            {
                return;
            }

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

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

            this.FlowLayoutPanel.Controls.Remove(this);
        }

        public void Remove(TilePanel panel)
        {
            Item m = items.Where(x => x.Tile.Equals(panel)).FirstOrDefault();
            if (m != null)
            {
                items.Remove(m);
            }

            this.Controls.Remove(panel);
        }

        public void SetGridSize(int width, int height)
        {
            gridSize = new Point(width, height);

            groupModel.GridSize = new Size(groupModel.GridSize.Width, height);

            expandedHeight = (this.TileSize * height) + labelHeight;

            int w = CalcWidth(gridSize.X);

            this.Size = new Size(w, (isChecked ? this.ExpandedHeight : this.CollapseHeight));
        }

        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.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 convertLocationToCoord_Nearest(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 Point? findFirstFreeCoord()
        {
            Point gridSize = this.GridSize;

            for (int y = 0; y < gridSize.Y; y++)
            {
                for (int x = 0; x < 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 (items.Count >= (gridSize.X * gridSize.Y))
            {
                return null;
            }

            if (items.Count <= 0)
            {
                return findFirstFreeCoord();
            }

            // only one available
            if (items.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 bool hasTile(Point position)
        {
            if (items == null)
            {
                return false;
            }

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

            return (items.Count(x => x.Coord.Equals(position)) > 0);
        }

        protected bool isTileInBounds(Point position)
        {
            Point gridSize = this.GridSize;

            if (position.X >= gridSize.X)
            {
                return false;
            }

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

            return true;
        }

        protected Point? resolveCoord(Point coord)
        {
            if (!isTileInBounds(coord))
            {
                return null;
            }

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

            return resolveNextCoord(coord);
        }

        protected Point? resolveNextCoord(Point coord)
        {
            Point gridSize = this.GridSize;
            Point newCoord = coord;

            while (true)
            {
                newCoord.X++;

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

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

                if (hasTile(newCoord))
                {
                    continue;
                }

                return newCoord;
            }
        }

        private async void label1_MouseClick(object sender, MouseEventArgs e)
        {
            if (isAnimating) return;

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

                label1.Image = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);

                this.Invalidate();

                await this.InvalidateContainer();

                // exclusivity
                if (isChecked)
                {
                    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 = (isChecked ? AppResource.toggle_right_ea_16 : AppResource.toggle_left_ea_16);
            }
            else if (e.Button == MouseButtons.Right)
            {
                contextMenuStrip2.Show(this, e.Location);
            }
        }

        #region tile context menu

        /// <summary>
        /// Add tile
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addTileMenuItem_Click(object sender, EventArgs e)
        {
            Point coord = convertLocationToCoord(lastMousePosition.X, lastMousePosition.Y);

            EditTileForm.ShowAddDialog(this, coord);
        }

        /// <summary>
        /// Add folder
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addListTileMenuItem_Click(object sender, EventArgs e)
        {
            Point coord = convertLocationToCoord(lastMousePosition.X, lastMousePosition.Y);

            EditTileFolderForm.ShowAddDialog(this, coord);
        }

        #endregion

        #region group context menu

        /// <summary>
        /// Add group
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void addGroupMenuItem_Click(object sender, EventArgs e)
        {
            this.AddGroup();
        }

        /// <summary>
        /// Edit group
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void editGroupMenuItem_Click(object sender, EventArgs e)
        {
            this.EditGroup();
        }

        /// <summary>
        /// Add row
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void toolStripMenuItem5_Click(object sender, EventArgs e)
        {
            this.AddRow();
        }

        /// <summary>
        /// Remove row
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void removeRowToolStripMenuItem_Click_1(object sender, EventArgs e)
        {
            if (gridSize.Y <= 1)
            {
                return;
            }

            bool rs = items.Exists(x => x.Coord.Y.Equals(gridSize.Y - 1));
            if (rs)
            {
                return;
            }

            this.SetGridSize(gridSize.X, (gridSize.Y - 1));
        }

        /// <summary>
        /// Move to top
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void moveTopMenuItem_Click(object sender, EventArgs e)
        {
            this.MoveTop();
        }

        /// <summary>
        /// Move up
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void moveUpMenuItem_Click(object sender, EventArgs e)
        {
            this.MoveUp();
        }

        /// <summary>
        /// Move down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void moveDownMenuItem_Click(object sender, EventArgs e)
        {
            this.MoveDown();
        }

        /// <summary>
        /// Move to bottom
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void moveBottomMenuItem_Click(object sender, EventArgs e)
        {
            this.MoveBottom();
        }

        /// <summary>
        /// Remove group
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void removeGroupMenuItem3_Click(object sender, EventArgs e)
        {
            this.Remove();
        }

        #endregion

    }
}
