using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;


namespace lanterna.gui2 {
/**
 * This emulates the behaviour of the GridLayout in SWT (as opposed to the one in AWT/Swing). I originally ported the
 * SWT class itself but due to licensing concerns (the eclipse license is not compatible with LGPL) I was advised not to
 * do that. This is a partial implementation and some of the semantics have changed, but in general it works the same
 * way so the SWT documentation will generally match.
 * <p>
 * You use the {@code GridLayout} by specifying a number of columns you want your grid to have and then when you add
 * components, you assign {@code LayoutData} to these components using the different static methods in this class
 * ({@code createLayoutData(..)}). You can set components to span both rows and columns, as well as defining how to
 * distribute the available space.
 */
    public class GridLayout : LayoutManager {
    /**
     * The enum is used to specify where in a grid cell a component should be placed, in the case that the preferred
     * size of the component is smaller than the space in the cell. This class will generally use two alignments, one
     * for horizontal and one for vertical.
     */
        public enum Alignment {
        /**
         * Place the component at the start of the cell (horizontally or vertically) and leave whatever space is left
         * after the preferred size empty.
         */
            BEGINNING,
        /**
         * Place the component at the middle of the cell (horizontally or vertically) and leave the space before and
         * after empty.
         */
            CENTER,
        /**
         * Place the component at the end of the cell (horizontally or vertically) and leave whatever space is left
         * before the preferred size empty.
         */
            END,
        /**
         * Force the component to be the same size as the table cell
         */
            FILL
        }

        class GridLayoutData : LayoutData {
            internal GridLayout.Alignment horizontalAlignment;
            internal GridLayout.Alignment verticalAlignment;
            internal bool grabExtraHorizontalSpace;
            internal bool grabExtraVerticalSpace;
            internal int horizontalSpan;
            internal int verticalSpan;

            public GridLayoutData(
                GridLayout.Alignment horizontalAlignment,
                GridLayout.Alignment verticalAlignment,
                bool grabExtraHorizontalSpace,
                bool grabExtraVerticalSpace,
                int horizontalSpan,
                int verticalSpan) {

                if (horizontalSpan < 1 || verticalSpan < 1) {
                    throw new ArgumentException("Horizontal/Vertical span must be 1 or greater");
            }

                this.horizontalAlignment = horizontalAlignment;
                this.verticalAlignment = verticalAlignment;
                this.grabExtraHorizontalSpace = grabExtraHorizontalSpace;
                this.grabExtraVerticalSpace = grabExtraVerticalSpace;
                this.horizontalSpan = horizontalSpan;
                this.verticalSpan = verticalSpan;
            }
        }

        private static GridLayout.GridLayoutData DEFAULT = new GridLayoutData(
                horizontalAlignment: GridLayout.Alignment.BEGINNING, 
				verticalAlignment: GridLayout.Alignment.BEGINNING,
                grabExtraHorizontalSpace: false, 
				grabExtraVerticalSpace: false,
                verticalSpan: 1, 
				horizontalSpan: 1);
        
    /**
     * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
     * component in case the cell space is larger than the preferred size of the component
     * @param horizontalAlignment Horizontal alignment strategy
     * @param verticalAlignment Vertical alignment strategy
     * @return The layout data object containing the specified alignments
     */
        public static LayoutData createLayoutData(GridLayout.Alignment horizontalAlignment, GridLayout.Alignment verticalAlignment) {
            return GridLayout.createLayoutData(horizontalAlignment, verticalAlignment, false, false);
        }

    /**
     * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
     * component in case the cell space is larger than the preferred size of the component. This method also has fields
     * for indicating that the component would like to take more space if available to the container. For example, if
     * the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
     * asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
     * space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
     * assigned to the component that asked for it.
     *
     * @param horizontalAlignment Horizontal alignment strategy
     * @param verticalAlignment Vertical alignment strategy
     * @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
     *                                 space if there is any to assign
     * @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
     *                                 space if there is any to assign
     * @return The layout data object containing the specified alignments and size requirements
     */
        public static LayoutData createLayoutData(
				GridLayout.Alignment horizontalAlignment, 
				GridLayout.Alignment verticalAlignment, 
				bool grabExtraHorizontalSpace, 
				bool grabExtraVerticalSpace) {

            return GridLayout.createLayoutData(horizontalAlignment, verticalAlignment, grabExtraHorizontalSpace, grabExtraVerticalSpace, 1, 1);
        }

    /**
     * Creates a layout data object for {@code GridLayout}:s that specify the horizontal and vertical alignment for the
     * component in case the cell space is larger than the preferred size of the component. This method also has fields
     * for indicating that the component would like to take more space if available to the container. For example, if
     * the container is assigned is assigned an area of 50x15, but all the child components in the grid together only
     * asks for 40x10, the remaining 10 columns and 5 rows will be empty. If just a single component asks for extra
     * space horizontally and/or vertically, the grid will expand out to fill the entire area and the text space will be
     * assigned to the component that asked for it. It also puts in data on how many rows and/or columns the component
     * should span.
     *
     * @param horizontalAlignment Horizontal alignment strategy
     * @param verticalAlignment Vertical alignment strategy
     * @param grabExtraHorizontalSpace If set to {@code true}, this component will ask to be assigned extra horizontal
     *                                 space if there is any to assign
     * @param grabExtraVerticalSpace If set to {@code true}, this component will ask to be assigned extra vertical
     *                                 space if there is any to assign
     * @param horizontalSpan How many "cells" this component wants to span horizontally
     * @param verticalSpan How many "cells" this component wants to span vertically
     * @return The layout data object containing the specified alignments, size requirements and cell spanning
     */
        public static LayoutData createLayoutData(
				GridLayout.Alignment horizontalAlignment, 
				GridLayout.Alignment verticalAlignment, 
				bool grabExtraHorizontalSpace, 
				bool grabExtraVerticalSpace, 
				int horizontalSpan, 
				int verticalSpan) {

            return new GridLayout.GridLayoutData(
					horizontalAlignment, 
					verticalAlignment, 
					grabExtraHorizontalSpace, 
					grabExtraVerticalSpace, 
					horizontalSpan, 
					verticalSpan);
        }

    /**
     * This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
     * horizontally and make the component occupy the whole area horizontally and center it vertically
     * @param horizontalSpan How many cells to span horizontally
     * @return Layout data object with the specified span and horizontally expanding as much as it can
     */
    public static LayoutData createHorizontallyFilledLayoutData(int horizontalSpan) {
        return GridLayout.createLayoutData(
				GridLayout.Alignment.FILL, 
				GridLayout.Alignment.CENTER, 
				true, 
				false, 
				horizontalSpan, 
				1);
        }

    /**
     * This is a shortcut method that will create a grid layout data object that will expand its cell as much as is can
     * vertically and make the component occupy the whole area vertically and center it horizontally
     * @param horizontalSpan How many cells to span vertically
     * @return Layout data object with the specified span and vertically expanding as much as it can
     */
    public static LayoutData createHorizontallyEndAlignedLayoutData(int horizontalSpan) {
        return GridLayout.createLayoutData(
				GridLayout.Alignment.END, 
				GridLayout.Alignment.CENTER, 
				true, 
				false, 
				horizontalSpan, 
				1);
    }

        private int numberOfColumns;
        private int horizontalSpacing;
        private int verticalSpacing;
        private int topMarginSize;
        private int bottomMarginSize;
        private int leftMarginSize;
        private int rightMarginSize;

        private bool changed;

    /**
     * Creates a new {@code GridLayout} with the specified number of columns. Initially, this layout will have a
     * horizontal spacing of 1 and vertical spacing of 0, with a left and right margin of 1.
     * @param numberOfColumns Number of columns in this grid
     */
        public GridLayout(int numberOfColumns) {
            this.numberOfColumns = numberOfColumns;
            this.horizontalSpacing = 1;
            this.verticalSpacing = 0;
            this.topMarginSize = 0;
            this.bottomMarginSize = 0;
            this.leftMarginSize = 1;
            this.rightMarginSize = 1;
            this.changed = true;
        }

    /**
     * Returns the horizontal spacing, i.e. the number of empty columns between each cell
     * @return Horizontal spacing
     */
    public virtual int getHorizontalSpacing() {
        return this.horizontalSpacing;
    }

    /**
     * Sets the horizontal spacing, i.e. the number of empty columns between each cell
     * @param horizontalSpacing New horizontal spacing
     * @return Itself
     */
        public virtual GridLayout setHorizontalSpacing(int horizontalSpacing) {
            if (horizontalSpacing < 0) {
                throw new ArgumentException("Horizontal spacing cannot be less than 0");
            }
            this.horizontalSpacing = horizontalSpacing;
            this.changed = true;
            return this;
        }

    /**
     * Returns the vertical spacing, i.e. the number of empty columns between each row
     * @return Vertical spacing
     */
       public virtual int getVerticalSpacing() {
            return this.verticalSpacing;
        }

    /**
     * Sets the vertical spacing, i.e. the number of empty columns between each row
     * @param verticalSpacing New vertical spacing
     * @return Itself
     */
        public virtual GridLayout setVerticalSpacing(int verticalSpacing) {
            if (verticalSpacing < 0) {
                throw new ArgumentException("Vertical spacing cannot be less than 0");
            }
            this.verticalSpacing = verticalSpacing;
            this.changed = true;
            return this;
        }

    /**
     * Returns the top margin, i.e. number of empty rows above the first row in the grid
     * @return Top margin, in number of rows
     */
        public virtual int getTopMarginSize() {
            return this.topMarginSize;
        }

    /**
     * Sets the top margin, i.e. number of empty rows above the first row in the grid
     * @param topMarginSize Top margin, in number of rows
     * @return Itself
     */
        public virtual GridLayout setTopMarginSize(int topMarginSize) {
            if (topMarginSize < 0) {
                throw new ArgumentException("Top margin size cannot be less than 0");
            }
            this.topMarginSize = topMarginSize;
            this.changed = true;
            return this;
        }

    /**
     * Returns the bottom margin, i.e. number of empty rows below the last row in the grid
     * @return Bottom margin, in number of rows
     */
        public virtual int getBottomMarginSize() {
            return this.bottomMarginSize;
        }

    /**
     * Sets the bottom margin, i.e. number of empty rows below the last row in the grid
     * @param bottomMarginSize Bottom margin, in number of rows
     * @return Itself
     */
        public virtual GridLayout setBottomMarginSize(int bottomMarginSize) {
            if (bottomMarginSize < 0) {
                throw new ArgumentException("Bottom margin size cannot be less than 0");
            }
            this.bottomMarginSize = bottomMarginSize;
            this.changed = true;
            return this;
        }

    /**
     * Returns the left margin, i.e. number of empty columns left of the first column in the grid
     * @return Left margin, in number of columns
     */
        public virtual int getLeftMarginSize() {
            return this.leftMarginSize;
        }

    /**
     * Sets the left margin, i.e. number of empty columns left of the first column in the grid
     * @param leftMarginSize Left margin, in number of columns
     * @return Itself
     */
        public virtual GridLayout setLeftMarginSize(int leftMarginSize) {
            if (leftMarginSize < 0) {
                throw new ArgumentException("Left margin size cannot be less than 0");
            }
            this.leftMarginSize = leftMarginSize;
            this.changed = true;
            return this;
        }

    /**
     * Returns the right margin, i.e. number of empty columns right of the last column in the grid
     * @return Right margin, in number of columns
     */
        public virtual int getRightMarginSize() {
            return this.rightMarginSize;
        }

    /**
     * Sets the right margin, i.e. number of empty columns right of the last column in the grid
     * @param rightMarginSize Right margin, in number of columns
     * @return Itself
     */
        public virtual GridLayout setRightMarginSize(int rightMarginSize) {
            if (rightMarginSize < 0) {
                throw new ArgumentException("Right margin size cannot be less than 0");
            }
            this.rightMarginSize = rightMarginSize;
            this.changed = true;
            return this;
        }

//
        public virtual bool hasChanged() {
            return this.changed;
        }

//
        public virtual TerminalSize getPreferredSize(List<Component> components) {
            TerminalSize preferredSize = TerminalSize.ZERO;
            if (components.Count == 0) {
                return preferredSize.withRelative(
						this.leftMarginSize + this.rightMarginSize, 
						this.topMarginSize + this.bottomMarginSize);
            }

            Component[][] table = this.buildTable(components);
            table = this.eliminateUnusedRowsAndColumns(table);

        //Figure out each column first, this can be done independently of the row heights
            int preferredWidth = 0;
            int preferredHeight = 0;
            int[] preferredColumnWidths = this.getPreferredColumnWidths(table);
            foreach(var width in preferredColumnWidths) {
                preferredWidth += width;
            }
            int[] preferredRowHeights = this.getPreferredRowHeights(table);
            foreach(var height in preferredRowHeights) {
                preferredHeight += height;
            }
            preferredSize = preferredSize.withRelative(preferredWidth, preferredHeight);
            preferredSize = preferredSize.withRelativeColumns(this.leftMarginSize + this.rightMarginSize + (table[0].Length - 1) * this.horizontalSpacing);
            preferredSize = preferredSize.withRelativeRows(this.topMarginSize + this.bottomMarginSize + (table.Length - 1) * this.verticalSpacing);
        	return preferredSize;
        }

//
        public virtual void doLayout(TerminalSize area, List<Component> components) {
        //Sanity check, if the area is way too small, just return 
           Component[][] table = this.buildTable(components);
            table = this.eliminateUnusedRowsAndColumns(table);

            if (area.Equals(TerminalSize.ZERO)
                || table.Length == 0
                || area.getColumns() <= this.leftMarginSize + this.rightMarginSize + (table[0].Length - 1) * this.horizontalSpacing
                || area.getRows() <= this.bottomMarginSize + this.topMarginSize + (table.Length - 1) * this.verticalSpacing) {
                return;
            }

        //Adjust area to the margins
            area = area.withRelative(-this.leftMarginSize - this.rightMarginSize, -this.topMarginSize - this.bottomMarginSize);
  
            var sizeMap = new Dictionary<Component, TerminalSize>();
            var positionMap = new Dictionary<Component, TerminalPosition>();
  
        //Figure out each column first, this can be done independently of the row heights
            int[] columnWidths = this.getPreferredColumnWidths(table);

        //Take notes of which columns we can expand if the usable area is larger than what the components want
            var expandableColumns = this.getExpandableColumns(table);

        //Next, start shrinking to make sure it fits the size of the area we are trying to lay out on.
        //Notice we subtract the horizontalSpacing to take the space between components into account
            TerminalSize areaWithoutHorizontalSpacing = area.withRelativeColumns(-this.horizontalSpacing * (table[0].Length - 1));
            int totalWidth = this.shrinkWidthToFitArea(areaWithoutHorizontalSpacing, columnWidths);

        //Finally, if there is extra space, make the expandable columns larger
            while (areaWithoutHorizontalSpacing.getColumns() > totalWidth && expandableColumns.Count > 0) {
                totalWidth = this.grabExtraHorizontalSpace(areaWithoutHorizontalSpacing, columnWidths, expandableColumns, totalWidth);
            }

        //Now repeat for rows
            int[] rowHeights = this.getPreferredRowHeights(table);
            var expandableRows = this.getExpandableRows(table);
            TerminalSize areaWithoutVerticalSpacing = area.withRelativeRows(-this.verticalSpacing * (table.Length - 1));
            int totalHeight = this.shrinkHeightToFitArea(areaWithoutVerticalSpacing, rowHeights);
            while (areaWithoutVerticalSpacing.getRows() > totalHeight && expandableRows.Count > 0) {
                totalHeight = this.grabExtraVerticalSpace(areaWithoutVerticalSpacing, rowHeights, expandableRows, totalHeight);
            }

        //Ok, all constraints are in place, we can start placing out components. To simplify, do it horizontally first
        //and vertically after
            TerminalPosition tableCellTopLeft = TerminalPosition.TOP_LEFT_CORNER;
            for (int y = 0; y < table.Length; y++) {
                tableCellTopLeft = tableCellTopLeft.withColumn(0);
                for (int x = 0; x < table[y].Length; x++) {
                    Component component = table[y][x];
                    if (component != null && !positionMap.ContainsKey(component)) {
                        GridLayout.GridLayoutData layoutData = this.getLayoutData(component);
                        TerminalSize size = component.getPreferredSize();
                        TerminalPosition position = tableCellTopLeft;

                        int availableHorizontalSpace = 0;
                        int availableVerticalSpace = 0;
                        for (int i = 0; i < layoutData.horizontalSpan; i++) {
                            availableHorizontalSpace += columnWidths[x + i] + ((i <= 0) ? 0 : this.horizontalSpacing);
                        }
                        for (int j = 0; j < layoutData.verticalSpan; j++) {
                            availableVerticalSpace += rowHeights[y + j] + ((j <= 0) ? 0 : this.verticalSpacing);
                        }

                    //Make sure to obey the size restrictions
                        size = size.withColumns(Math.Min(size.getColumns(), availableHorizontalSpace));
                        size = size.withRows(Math.Min(size.getRows(), availableVerticalSpace));

                        switch (layoutData.horizontalAlignment) {
                            case Alignment.CENTER:
                                position = position.withRelativeColumn((availableHorizontalSpace - size.getColumns()) / 2);
                                break;
                            case Alignment.BEGINNING:
                                position = position.withRelativeColumn(availableHorizontalSpace - size.getColumns());
                                break;
                            case Alignment.END:
                                size = size.withColumns(availableHorizontalSpace);
                                break;
                        default:
                            break;
                        }
                        switch (layoutData.verticalAlignment) {
                            case Alignment.CENTER:
                                position = position.withRelativeRow((availableVerticalSpace - size.getRows()) / 2);
                                break;
                            case Alignment.BEGINNING:
                                position = position.withRelativeRow(availableVerticalSpace - size.getRows());
                                break;
                            case Alignment.END:
                                size = size.withRows(availableVerticalSpace);
                                break;
                        default:
                            break;
                        }

                        sizeMap.Add(component, size);
                        positionMap.Add(component, position);
                    }
                    tableCellTopLeft = tableCellTopLeft.withRelativeColumn(columnWidths[x] + this.horizontalSpacing);
                }
                tableCellTopLeft = tableCellTopLeft.withRelativeRow(rowHeights[y] + this.verticalSpacing);
            }

        //Apply the margins here
            foreach (var component2 in components) {
                component2.setPosition((positionMap[component2]).withRelative(this.leftMarginSize, this.topMarginSize));
                component2.setSize(sizeMap[component2]);
            }
            this.changed = false;
        }

        private int[] getPreferredColumnWidths(Component[][] table) {
        //actualNumberOfColumns may be different from this.numberOfColumns since some columns may have been eliminated
            int actualNumberOfColumns = table[0].Length;
            int[] columnWidths = new int[actualNumberOfColumns];

        //Start by letting all span = 1 columns take what they need
            foreach (Component[] row in table) {
                for (int i = 0; i < actualNumberOfColumns; i++) {
                    Component component = row[i];
                    if (component != null) {


                        GridLayout.GridLayoutData layoutData = this.getLayoutData(component);
                        if (layoutData.horizontalSpan == 1) {
                            columnWidths[i] = Math.Max(columnWidths[i], component.getPreferredSize().getColumns());
                        }
					}
                }
            }

        //Next, do span > 1 and enlarge if necessary
            foreach (Component[] row2 in table) {
                int j = 0;
                while (j < actualNumberOfColumns) {
                    Component component2 = row2[j];
                    if (component2 == null) {
                        j++;
                    } else {
                        GridLayout.GridLayoutData layoutData2 = this.getLayoutData(component2);
                        if (layoutData2.horizontalSpan > 1) {
                            int accumWidth = 0;
                            for (int k = j; k < j + layoutData2.horizontalSpan; k++) {
                                accumWidth += columnWidths[k];
                            }

                            int preferredWidth = component2.getPreferredSize().getColumns();
                            if (preferredWidth > accumWidth) {
                                int columnOffset = 0;
                                do {
                                    columnWidths[j + columnOffset]++;
                                    columnOffset++;
                                    accumWidth++;
                                    if (columnOffset == layoutData2.horizontalSpan) {
                                        columnOffset = 0;
                                    }
                                }
                                while (preferredWidth > accumWidth);
                            }
                        }
                        j += layoutData2.horizontalSpan;
                    }
                }
            }
            return columnWidths;
        }

        private int[] getPreferredRowHeights(Component[][] table) {
            int numberOfRows = table.Length;
            int[] rowHeights = new int[numberOfRows];

        //Start by letting all span = 1 rows take what they need
            int rowIndex = 0;
            foreach (Component[] row in table) {
                for (int i = 0; i < row.Length; i++) {
                    Component component = row[i];
                    if (component != null) {


                        GridLayout.GridLayoutData layoutData = this.getLayoutData(component);
                        if (layoutData.verticalSpan == 1) {
                            rowHeights[rowIndex] = Math.Max(rowHeights[rowIndex], component.getPreferredSize().getRows());
                        }
                    }
                }
                rowIndex++;
            }

        //Next, do span > 1 and enlarge if necessary
            for (int x = 0; x < this.numberOfColumns; x++) {
                int y = 0;
                while (y < numberOfRows && y < table.Length) {
                    if (x >= table[y].Length) {
                        y++;

                    } else {
                        Component component2 = table[y][x];
                        if (component2 == null) {
                            y++;

                        } else {
                            GridLayout.GridLayoutData layoutData2 = this.getLayoutData(component2);
                            if (layoutData2.verticalSpan > 1) {
                                int accumulatedHeight = 0;
                                for (int j = y; j < y + layoutData2.verticalSpan; j++) {
                                    accumulatedHeight += rowHeights[j];
                                }

                                int preferredHeight = component2.getPreferredSize().getRows();
                                if (preferredHeight > accumulatedHeight) {
                                    int rowOffset = 0;
                                    do {
                                        rowHeights[y + rowOffset]++;
                                        rowOffset++;
                                        accumulatedHeight++;
                                        if (rowOffset == layoutData2.verticalSpan) {
                                            rowOffset = 0;
                                        }
                                    }
                                    while (preferredHeight > accumulatedHeight);
                                }
                            }
                            y += layoutData2.verticalSpan;
                        }
                    }
                }
            }
            return rowHeights;
        }

        private List<int> getExpandableColumns(Component[][] table) {
            var expandableColumns = new List<int>();
            foreach (var row in table) {
                for (int i = 0; i < row.Length; i++) {
                    if (row[i] != null) {


                        GridLayout.GridLayoutData layoutData = this.getLayoutData(row[i]);
                        if (layoutData.grabExtraHorizontalSpace) {
                            expandableColumns.Add(i);
                        }
                    }
                }
            }
            return expandableColumns;
        }

        private List<int> getExpandableRows(Component[][] table) {
            List<int> expandableRows = new List<int>();
            for (int rowIndex = 0; rowIndex < table.Length; rowIndex++) {
                Component[] row = table[rowIndex];
                for (int columnIndex = 0; columnIndex < row.Length; columnIndex++) {
                    if (row[columnIndex] != null) {


                        GridLayout.GridLayoutData layoutData = this.getLayoutData(row[columnIndex]);
                        if (layoutData.grabExtraVerticalSpace) {
                            expandableRows.Add(rowIndex);
                        }
                    }
                }
            }
            return expandableRows;
        }

        private int shrinkWidthToFitArea(TerminalSize area, int[] columnWidths) {
            int totalWidth = 0;
            foreach (int width in columnWidths) {
                totalWidth += width;
            }
            if (totalWidth > area.getColumns()) {
                int columnOffset = 0;
                do {
                    if (columnWidths[columnOffset] > 0) {
                        columnWidths[columnOffset]--;
                        totalWidth += -1;
                    }
                    columnOffset++;
                    if (columnOffset == this.numberOfColumns) {
                        columnOffset = 0;
                    }
                }
                while (totalWidth > area.getColumns());
            }
            return totalWidth;
        }

        private int shrinkHeightToFitArea(TerminalSize area, int[] rowHeights) {
            int totalHeight = 0;
            foreach (int height in rowHeights) {
                totalHeight += height;
            }
            if (totalHeight > area.getRows()) {
                int rowOffset = 0;
                do {
                    if (rowHeights[rowOffset] > 0) {
                        rowHeights[rowOffset]--;
                        totalHeight += -1;
                    }
                    rowOffset++;
                    if (rowOffset == rowHeights.Length) {
                        rowOffset = 0;
                    }
                }
                while (totalHeight > area.getRows());
            }
            return totalHeight;
        }

        private int grabExtraHorizontalSpace(TerminalSize area, int[] columnWidths, List<int> expandableColumns, int totalWidth) {
            foreach (int columnIndex in expandableColumns) {
                columnWidths[columnIndex]++;
                totalWidth++;
                if (area.getColumns() == totalWidth) {
                    break;
                }
            }
            return totalWidth;
        }

        private int grabExtraVerticalSpace(TerminalSize area, int[] rowHeights, List<int> expandableRows, int totalHeight) {
            foreach (int rowIndex in expandableRows) {
                rowHeights[rowIndex]++;
                totalHeight++;
                if (area.getColumns() == totalHeight) {
                    break;
                }
            }
            return totalHeight;
        }

        private Component[][] buildTable(List<Component> components) {
            var rows = new List<Component[]>();
            var hspans = new List<int[]>();
            var vspans = new List<int[]>();

            int rowCount = 0;
            int rowsExtent = 1;
            var toBePlaced = new LinkedList<Component>(components);
            while (toBePlaced.Count > 0 || rowCount < rowsExtent) {
            //Start new row
                Component[] row = new Component[this.numberOfColumns];
                int[] hspan = new int[this.numberOfColumns];
                int[] vspan = new int[this.numberOfColumns];

                for (int i = 0; i < this.numberOfColumns; i++) {
                    if (i > 0 && hspan[i - 1] > 1) {
                        row[i] = row[i - 1];
                        hspan[i] = hspan[i - 1] - 1;
                        vspan[i] = vspan[i - 1];
                    } 
					else if (rowCount > 0 && ((int[])vspans[rowCount - 1])[i] > 1) {
                        row[i] = ((Component[])rows[rowCount - 1])[i];
                        hspan[i] = ((int[])hspans[rowCount - 1])[i];
                        vspan[i] = ((int[])vspans[rowCount - 1])[i] - 1;
                    } 
					else if (toBePlaced.Count > 0) {
                        Component component = toBePlaced.First();//.poll();
                        toBePlaced.RemoveFirst();
                        GridLayout.GridLayoutData gridLayoutData = this.getLayoutData(component);

                        row[i] = component;
                        hspan[i] = gridLayoutData.horizontalSpan;
                        vspan[i] = gridLayoutData.verticalSpan;
                        rowsExtent = Math.Max(rowsExtent, rowCount + gridLayoutData.verticalSpan);
                    }
					else {
                        row[i] = null;
                        hspan[i] = 1;
                        vspan[i] = 1;
                    }
                }

                rows.Add(row);
                hspans.Add(hspan);
                vspans.Add(vspan);
                rowCount++;
            }
            return (Component[][])rows.ToArray();
        }

        private Component[][] eliminateUnusedRowsAndColumns(Component[][] table) {
            if (table.Length == 0) {
                return table;
            }
        //Could make this into a Set, but I doubt there will be any real gain in performance as these are probably going
        //to be very small.
            var rowsToRemove = new List<int>();
            var columnsToRemove = new List<int>();

            int tableRows = table.Length;
            int tableColumns = table[0].Length;

            //Scan for unnecessary columns
            for (int column = tableColumns - 1; column > 0; column--) {
                foreach (Component[] row in table) {
                    if (row[column] != row[column - 1]) {
                        goto columnLoopEnd;//GLEE! I USED A GOTO TO BREAK THE DOUBLE LOOP!!!
                    }
                }
                columnsToRemove.Add(column);
            }
			columnLoopEnd:

            //Scan for unnecessary rows
            for (int row = tableRows - 1; row > 0; row--) {
                for (int column = 0; column < tableColumns; column++) {
                    if (table[row][column] != table[row - 1][column]) {
                        goto rowLoopEnd;//GLEE! I USED A GOTO TO BREAK THE DOUBLE LOOP!!!
                    }
                }
                rowsToRemove.Add(row);
            }
			rowLoopEnd:

        //If there's nothing to remove, just return the same
            if (rowsToRemove.Count == 0 && columnsToRemove.Count == 0) {
                return table;
            }

        //Build a new table with rows & columns eliminated
            Component[][] newTable = new Component[tableRows - rowsToRemove.Count][];
            int insertedRowCounter = 0;
            for (int row3 = 0; row3 < tableRows; row3++) {
                Component[] newColumn = new Component[tableColumns - columnsToRemove.Count];
                int insertedColumnCounter = 0;
                for (int column3 = 0; column3 < tableColumns; column3++) {
                    if (!columnsToRemove.Contains(column3)) {
                        newColumn[insertedColumnCounter] = table[row3][column3];
                        insertedColumnCounter++;
                    }
                }
                newTable[insertedRowCounter] = newColumn;
                insertedRowCounter++;
            }
            return newTable;
        }

        private GridLayout.GridLayoutData getLayoutData(Component component) {
            LayoutData layoutData = component.getLayoutData();
            if (layoutData == null || !(layoutData is GridLayout.GridLayoutData)) {
                return GridLayout.DEFAULT;
            }
            return (GridLayout.GridLayoutData)layoutData;
        }
    }
}
