// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2021 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.


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

namespace Hiperscan.Extensions.ApoIdent
{
    public partial class GuiDesigner
    {
        private const string EXTENDED_COLUMN = "EXTENDED_COLUMN";
        public const uint DEFAULT_COLUMN_SPACE = 5;
        public const uint LAST_COLUMN_SPACING = 5;

        public void FormatTable(Gtk.Table table)
        {
            this.Table = table;
            this.SetTableColumnSpacing(this.Table);
            this.InitializeTableColumns();
            this.CheckTableAlignments(this.Table);
            this.FindTableGtkEntryHeight();
            this.SetTableRowHeight();
            this.AdjustTableComboBoxes(this.Table);
            this.AdjustTableColumnWidths();
        }

        private void SetTableColumnSpacing(Gtk.Widget widget)
        {
            Gtk.Table table = widget as Gtk.Table;
            if (table != null && table != this.Table)
                table.ColumnSpacing = GuiDesigner.DEFAULT_COLUMN_SPACE;

            Gtk.Container container = widget as Gtk.Container;
            if (container != null)
            {
                foreach (Gtk.Widget child in container.Children)
                    this.SetTableColumnSpacing(child);
            }
        }

        private void InitializeTableColumns()
        {
            this.Columns = new Dictionary<uint, List<Gtk.Alignment>>();
            for (uint i = 0; i < this.Table.NColumns; i++)
                this.Columns.Add(i, new List<Gtk.Alignment>());

            foreach (Gtk.Widget widget in this.Table.Children)
            {
                Gtk.Alignment alignment = widget as Gtk.Alignment;
                if (alignment == null)
                    throw new Exception($"Table child {widget.Name} has to be a Gtk.Alignment");

                Gtk.Table.TableChild table_child = (Gtk.Table.TableChild)this.Table[alignment];

                uint column_index = table_child.LeftAttach;
                bool is_extended = table_child.RightAttach - table_child.LeftAttach > 1;

                if (alignment.Data.ContainsKey(GuiDesigner.EXTENDED_COLUMN) == false)
                    alignment.Data.Add(GuiDesigner.EXTENDED_COLUMN, is_extended);

                this.Columns[column_index].Add(alignment);
            }
        }

        private void FindTableGtkEntryHeight()
        {
            this.GtkEntryHeight = this.Columns.Values.SelectMany(item => item).
                                Select(item => this.GetTableGtkEntryHight(item)).
                                Max();
        }

        private void SetTableRowHeight()
        {
            int max_heigt = this.GtkEntryHeight;

            foreach (uint column in this.Columns.Keys)
            {
                if (column == 0)
                    continue;

                foreach (Gtk.Alignment alignment in this.Columns[column])
                {
                    if (alignment.Allocation.Height > max_heigt)
                        continue;

                    alignment.HeightRequest = max_heigt;
                }
            }
        }

        private int GetTableGtkEntryHight(Gtk.Widget widget)
        {
            if (widget is Gtk.Entry)
                return (widget as Gtk.Entry).Allocation.Height + 4;

            Gtk.Container container = widget as Gtk.Container;
            if (container == null)
                return -1;

            foreach(Gtk.Widget child in container.Children)
            {
                int height = this.GetTableGtkEntryHight(child);
                if (height > 0)
                    return height;
            }

            return -1;
        }

        private void AdjustTableComboBoxes(Gtk.Widget widget)
        {
            if (widget is Gtk.ComboBox)
            {
                widget.HeightRequest = this.GtkEntryHeight;
                return;
            }

            Gtk.Container container = widget as Gtk.Container;
            if (container == null)
                return;

            foreach (Gtk.Widget child in container.Children)
                this.AdjustTableComboBoxes(child);
        }

        private bool IsTableAlignmentExtended(Gtk.Alignment alignmnent)
        {
            if (alignmnent.Data.ContainsKey(GuiDesigner.EXTENDED_COLUMN) == false)
                return false;

            return (bool)alignmnent.Data[GuiDesigner.EXTENDED_COLUMN];
        }

        private int FindTableAlignment(Gtk.Alignment alignemt, uint left_attachment)
        {
            Gtk.Table.TableChild table_child = (Gtk.Table.TableChild)(alignemt.Parent as Gtk.Table)[alignemt];

            if (table_child.LeftAttach == left_attachment)
                return (int)table_child.RightAttach;

            return -1;
        }

        private int GetTableGtkItemWidth(Gtk.Widget widget)
        {
            if (widget is Gtk.Button)
                return -1;

            if (widget is Gtk.ToolButton)
                return -1;

            if (widget is Gtk.Label)
                return (widget as Gtk.Label).Allocation.Width;

            if (widget is Gtk.ComboBox)
                return (widget as Gtk.ComboBox).Allocation.Width;

            if (widget is Gtk.Entry)
                return (widget as Gtk.Entry).Allocation.Width;

            Gtk.Container container = widget as Gtk.Container;
            if (container == null)
                return -1;

            foreach (Gtk.Widget child in container.Children)
            {
                int height = this.GetTableGtkItemWidth(child);
                if (height > 0)
                    return height;
            }

            return -1;
        }

        private void AdjustTableColumnWidths()
        {
            int width = this.MeasureColumn(2);

            if (this.MeasureColumn(4) > width)
                width = this.MeasureColumn(4);

            if (this.MeasureColumn(7) > width)
                width = this.MeasureColumn(7);

            if (this.MeasureColumn(9) > width)
                width = this.MeasureColumn(9);

            this.SetColumnWidth(2, width);
            this.SetColumnWidth(4, width);
            this.SetColumnWidth(7, width);
            this.SetColumnWidth(9, width);

            this.SetColumnWidth(3, 10);
            this.SetColumnWidth(8, 10);

            this.SetColumnWidth(5, 15);
        }

        private int MeasureColumn(uint column)
        {
            return this.Columns[column].Where(item => this.IsTableAlignmentExtended(item) == false).
                             Select(item => this.GetTableGtkItemWidth(item)).
                             Max();
        }

        private void SetColumnWidth(uint column, int width)
        {
            foreach (Gtk.Alignment alignment in this.Columns[column].Where(item => this.IsTableAlignmentExtended(item) == false))
                alignment.WidthRequest = width;
        }

        private void CheckTableAlignments(Gtk.Widget widget)
        {
            if (this.Debug == false)
                return;

            if (widget is Gtk.Button)
                return;

            if (widget is Gtk.Alignment)
            {
                Gtk.Alignment alignment = widget as Gtk.Alignment;
                if (alignment.Xalign > 0.001f)
                    Console.WriteLine("Alignment: {0} -> x-Alignment: {1}", alignment.Name, alignment.Xalign);

                if (alignment.Xscale < 0.999f)
                    Console.WriteLine("Alignment: {0} -> x-Scale: {1}", alignment.Name, alignment.Xscale);

                if (alignment.Yalign < 0.499f || alignment.Yalign > 0.5051f)
                    Console.WriteLine("Alignment: {0} -> y-Alignment: {1}", alignment.Name, alignment.Yalign);

                if (alignment.Yscale > 0.001f)
                    Console.WriteLine("Alignment: {0} -> y-Scale: {1}", alignment.Name, alignment.Yscale);
            }

            if ((widget is Gtk.Container) == false)
                return;

            Gtk.Container container = widget as Gtk.Container;
            if (container != null)
            {
                foreach (Gtk.Widget child in container.Children)
                    this.CheckTableAlignments(child);
            }
        }

        private Gtk.Table Table { get; set; }
        Dictionary<uint, List<Gtk.Alignment>> Columns { get; set; }

        private int GtkEntryHeight { get; set; }
        private bool Debug { get; set; } = false;
    }
}
