/*******************************************************************************
 * Copyright (c) 2005, 2010 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.texteditor.templates;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnWeightData;

/**
 * Layout for tables, adapted from <code>TableLayoutComposite</code>.
 * <p>
 * XXX: Should switch to use {@link org.eclipse.jface.layout.TableColumnLayout}.
 * </p>
 *
 * @since 3.2
 */
final class ColumnLayout extends Layout {

	private static final String RECALCULATE_LAYOUT= "recalculateKey"; //$NON-NLS-1$

	/**
	 * The number of extra pixels taken as horizontal trim by the table column.
	 * To ensure there are N pixels available for the content of the column,
	 * assign N+COLUMN_TRIM for the column width.
	 * <p>
	 * XXX: Should either switch to use
	 * {@link org.eclipse.jface.layout.TableColumnLayout} or get API from JFace
	 * or SWT, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=218483
	 * </p>
	 *
	 * @since 3.1
	 */
	private static int COLUMN_TRIM;
	static {
		String platform= SWT.getPlatform();
		switch (platform) {
		case "win32": //$NON-NLS-1$
			COLUMN_TRIM= 4;
			break;
		case "carbon": //$NON-NLS-1$
			COLUMN_TRIM= 24;
			break;
		default:
			COLUMN_TRIM= 3;
			break;
		}
	}

	private List<ColumnLayoutData> columns= new ArrayList<>();

	/**
	 * Adds a new column of data to this table layout.
	 *
	 * @param data the column layout data
	 */
	public void addColumnData(ColumnLayoutData data) {
		columns.add(data);
	}

	private Point computeTableSize(Table table, int wHint, int hHint) {
		Point result= table.computeSize(wHint, hHint);

		int width= 0;
		int size= columns.size();
		for (int i= 0; i < size; ++i) {
			ColumnLayoutData layoutData= columns.get(i);
			if (layoutData instanceof ColumnPixelData) {
				ColumnPixelData col= (ColumnPixelData) layoutData;
				width += col.width;
				if (col.addTrim) {
					width += COLUMN_TRIM;
				}
			} else if (layoutData instanceof ColumnWeightData) {
				ColumnWeightData col= (ColumnWeightData) layoutData;
				width += col.minimumWidth;
			} else {
				Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$
			}
		}
		if (width > result.x)
			result.x= width;
		return result;
	}

	private void layoutTable(final Table table, final int width, final Rectangle area, final boolean increase) {
		final TableColumn[] tableColumns= table.getColumns();
		final int size= Math.min(columns.size(), tableColumns.length);
		final int[] widths= new int[size];

		final int[] weightIteration= new int[size];
		int numberOfWeightColumns= 0;

		int fixedWidth= 0;
		int minWeightWidth= 0;
		int totalWeight= 0;

		// First calc space occupied by fixed columns
		for (int i= 0; i < size; i++) {
			ColumnLayoutData col= columns.get(i);
			if (col instanceof ColumnPixelData) {
				ColumnPixelData cpd= (ColumnPixelData) col;
				int pixels= cpd.width;
				if (cpd.addTrim) {
					pixels += COLUMN_TRIM;
				}
				widths[i]= pixels;
				fixedWidth += pixels;
			} else if (col instanceof ColumnWeightData) {
				ColumnWeightData cw= (ColumnWeightData) col;
				weightIteration[numberOfWeightColumns]= i;
				numberOfWeightColumns++;
				totalWeight += cw.weight;
				minWeightWidth += cw.minimumWidth;
				widths[i]= cw.minimumWidth;
			} else {
				Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$
			}
		}


		// Do we have columns that have a weight?
		final int restIncludingMinWidths= width - fixedWidth;
		final int rest= restIncludingMinWidths - minWeightWidth;
		if (numberOfWeightColumns > 0 && rest > 0) {

			// Modify the weights to reflect what each column already
			// has due to its minimum. Otherwise, columns with low
			// minimums get discriminated.
			int totalWantedPixels= 0;
			final int[] wantedPixels= new int[numberOfWeightColumns];
			for (int i= 0; i < numberOfWeightColumns; i++) {
				ColumnWeightData cw= (ColumnWeightData) columns.get(weightIteration[i]);
				wantedPixels[i]= totalWeight == 0 ? 0 : cw.weight * restIncludingMinWidths / totalWeight;
				totalWantedPixels+= wantedPixels[i];
			}

			// Now distribute the rest to the columns with weight.
			int totalDistributed= 0;
			for (int i= 0; i < numberOfWeightColumns; ++i) {
				int pixels= totalWantedPixels == 0 ? 0 : wantedPixels[i] * rest / totalWantedPixels;
				totalDistributed += pixels;
				widths[weightIteration[i]] += pixels;
			}

			// Distribute any remaining pixels to columns with weight.
			int diff= rest - totalDistributed;
			for (int i= 0; diff > 0; i= ((i + 1) % numberOfWeightColumns)) {
				++widths[weightIteration[i]];
				--diff;
			}
		}

		if (increase) {
			table.setBounds(0, 0, area.width, area.height);
		}
		for (int i= 0; i < size; i++) {
			tableColumns[i].setWidth(widths[i]);
		}
		if (!increase) {
			table.setBounds(0, 0, area.width, area.height);
		}
	}

	@Override
	protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
		return computeTableSize(getTable(composite), wHint, hHint);
	}

	@Override
	protected void layout(Composite composite, boolean flushCache) {
		Rectangle area= composite.getClientArea();
		Table table= getTable(composite);
		int tableWidth= table.getSize().x;
		int trim= computeTrim(area, table, tableWidth);
		int width= Math.max(0, area.width - trim);

		if (width > 1)
			layoutTable(table, width, area, tableWidth < area.width);

		if( composite.getData(RECALCULATE_LAYOUT) == null ) {
			composite.setData(RECALCULATE_LAYOUT, Boolean.FALSE);
			composite.layout();
		}
	}

	private int computeTrim(Rectangle area, Table table, int tableWidth) {
		Point preferredSize= computeTableSize(table, area.width, area.height);
		int trim;
		if (tableWidth > 1) {
			trim= tableWidth - table.getClientArea().width;
		} else {
			// initially, the table has no extend and no client area - use the border with
			// plus some padding as educated guess
			trim= 2 * table.getBorderWidth() + 1 ;
		}
		if (preferredSize.y > area.height) {
			// Subtract the scrollbar width from the total column width
			// if a vertical scrollbar will be required, but is not currently showing
			// (in which case it is already subtracted above)
			ScrollBar vBar= table.getVerticalBar();
			if (!vBar.isVisible()) {
				Point vBarSize= vBar.getSize();
				trim += vBarSize.x;
			}
		}
		return trim;
	}

	private Table getTable(Composite composite) {
		return (Table) composite.getChildren()[0];
	}

}
