/*
 * 作成日: 2006/11/24
 */
package net.sourceforge.swingx.jspread;

import javax.swing.JComponent;
import javax.swing.JViewport;

import net.sourceforge.swingx.Logger;

public class JSpreadControllerResize implements JSpreadConstant {
	static Logger logger = Logger.getLogger(JSpread.class);
	
	void doLayout(JSpread spread) {
		logger.debug("doLayout() enter");
		JSpreadSheet sheet = spread.sheet;
		JSpreadCellPane cellPane = (JSpreadCellPane) spread.getComponent("CellPane");
		JComponent viewport = cellPane;
		if (cellPane.getParent() != null && cellPane.getParent() instanceof JViewport) {
			viewport = (JComponent) cellPane.getParent();
		}
		if (sheet != null) {
			doHeaderLayout(viewport, sheet.rowHeader, ROW);
			doHeaderLayout(viewport, sheet.colHeader, COLUMN);
		}
		int w = sheet.colHeader.getTotalWidth();
		int h = sheet.rowHeader.getTotalWidth();
		JSpreadHeaderPane rowHeader = (JSpreadHeaderPane) spread.getComponent("RowHeaderPane");
		if (rowHeader != null) rowHeader.setSize(sheet.rowHeader.thickness, h);
		JSpreadHeaderPane colHeader = (JSpreadHeaderPane) spread.getComponent("ColumnHeaderPane");
		if (colHeader != null) colHeader.setSize(w, sheet.colHeader.thickness);
		cellPane.setSize(w, h);
		//panel.doLayout();
		logger.debug("doLayout() exit");
	}
	private void doHeaderLayout(JComponent viewport, JSpreadHeader header, int orientation) {
		//JViewport viewport = (JViewport) getComponent("CellViewport");
		if (header.autoResizeMode == JSpread.AUTO_RESIZE_OFF) return;
		int index = -1;//header.resizingViewIndex;
		if (index == -1) {
			// 最適なサイズから幅を求める
	        setWidthsFromPreferredWidths(viewport, header, false);
		} else {
			// 差分を求める
			int delta = 
				(orientation == COLUMN ? viewport.getWidth(): viewport.getHeight())  
				- header.getTotalWidth();
			// 差分を調達する
			accommodateDelta(header, index, delta);

			// もし、差分が完全に調達できなければ、サイズ変更しているカラムは残りを持たなければならない。
			delta = 
				(orientation == COLUMN ? viewport.getWidth(): viewport.getHeight())
				- header.getTotalWidth();

			// 
			// If the delta cannot be completely accomodated, then the
			// resizing column will have to take any remainder. This means
			// that the column is not being allowed to take the requested
			// width. This happens under many circumstances: For example,
			// AUTO_RESIZE_NEXT_COLUMN specifies that any delta be distributed
			// to the column after the resizing column. If one were to attempt
			// to resize the last column of the table, there would be no
			// columns after it, and hence nowhere to distribute the delta.
			// It would then be given entirely back to the resizing column,
			// preventing it from changing size.
			if (delta != 0) {
				int dataIndex = header.getDataIndex(index);
				int w = header.getWidth(dataIndex);
				header.setWidth(dataIndex, w + delta);
			}

			// At this point the JTable has to work out what preferred sizes
			// would have resulted in the layout the user has chosen.
			// Thereafter, during window resizing etc. it has to work off
			// the preferred sizes as usual - the idea being that, whatever
			// the user does, everything stays in synch and things don't jump
			// around.
			setWidthsFromPreferredWidths(viewport, header, true);
		}
	}
    private void setWidthsFromPreferredWidths(JComponent targetComponent, final JSpreadHeader header, final boolean inverse) {
		int totalWidth = targetComponent.getWidth();
		int totalPreferred = targetComponent.getPreferredSize().width;
		int target = !inverse ? totalWidth : totalPreferred;

		//final TableColumnModel cm = columnModel;
		Resizable3 r = new Resizable3() {
			public int getElementCount() {
				return header.getViewCount();
				//return cm.getColumnCount();
			}
			public int getLowerBoundAt(int i) {
				return header.getMinWidth(header.getDataIndex(i));
				//return cm.getColumn(i).getMinWidth();
			}
			public int getUpperBoundAt(int i) {
				return header.getMaxWidth(header.getDataIndex(i));
				//return cm.getColumn(i).getMaxWidth();
			}
			public int getMidPointAt(int i) {
				if (!inverse) {
					return header.getPreferredWidth(header.getDataIndex(i));
					//return cm.getColumn(i).getPreferredWidth();
				} else {
					return header.getWidth(header.getDataIndex(i));
					//return cm.getColumn(i).getWidth();
				}
			}
			public void setSizeAt(int s, int i) {
				if (!inverse) {
					//cm.getColumn(i).setWidth(s);
					header.setWidth(header.getDataIndex(i), s);
				} else {
					//cm.getColumn(i).setPreferredWidth(s);
					header.setPreferredWidth(header.getDataIndex(i), s);
				}
			}
		};
		adjustSizes(target, r, inverse);
	}
    /**差分を適応させる */
    private void accommodateDelta(final JSpreadHeader header, int resizingColumnIndex, int delta) {
		int columnCount = header.getViewCount();
		int from = resizingColumnIndex;
		int to = columnCount;

		// どのように変更を吸収するか、モードによって決定する
		switch (header.autoResizeMode) {
		case AUTO_RESIZE_NEXT_COLUMN:
			from = from + 1;
			to = Math.min(from + 1, columnCount);
			break;
//		case AUTO_RESIZE_SUBSEQUENT_COLUMNS:
//			from = from + 1;
//			to = columnCount;
//			break;
		case AUTO_RESIZE_LAST_COLUMN:
			from = columnCount - 1;
			to = from + 1;
			break;
//		case AUTO_RESIZE_ALL_COLUMNS:
//			from = 0;
//			to = columnCount;
//			break;
		default:
			return;
		}

		final int start = from;
		final int end = to;
		//final TableColumnModel cm = columnModel;
		Resizable3 r = new Resizable3() {
			public int getElementCount() {
				return end - start;
			}
			public int getLowerBoundAt(int i) {
				return header.getMinWidth(header.getDataIndex(i+start));
				//return cm.getColumn(i + start).getMinWidth();
			}
			public int getUpperBoundAt(int i) {
				return header.getMaxWidth(header.getDataIndex(i+start));
				//return cm.getColumn(i + start).getMaxWidth();
			}
			public int getMidPointAt(int i) {
				return header.getWidth(header.getDataIndex(i+start));
				//return cm.getColumn(i + start).getWidth();
			}
			public void setSizeAt(int s, int i) {
				header.setWidth(header.getDataIndex(i+start), s);
				//cm.getColumn(i + start).setWidth(s);
			}
		};
		int totalWidth = 0;
		for (int i = from; i < to; i++) {
//			TableColumn aColumn = columnModel.getColumn(i);
//			int input = aColumn.getWidth();
//			totalWidth = totalWidth + input;
			totalWidth += header.getWidth(header.getDataIndex(i));
		}

		adjustSizes(totalWidth + delta, r, false);

		return;
	}	
    private interface Resizable2 {
        public int  getElementCount();
        public int  getLowerBoundAt(int i);
        public int  getUpperBoundAt(int i);
        public void setSizeAt(int newSize, int i);
    }
    private interface Resizable3 extends Resizable2 {
        public int  getMidPointAt(int i);
    }
    /**
     * @param	limitToRange	trueのとき、最小幅から最大幅の間になるように強制的に収める。 
     */
    private void adjustSizes(long target, Resizable2 r, boolean limitToRange) {
		assert target >= 0: target;
		assert r != null;
    	// 最小幅、最大幅を求める
    	long totalLowerBound = 0;
		long totalUpperBound = 0;
		for (int i = 0; i < r.getElementCount(); i++) {
			totalLowerBound += r.getLowerBoundAt(i);
			totalUpperBound += r.getUpperBoundAt(i);
		}
		// 最小幅から最大幅の間になるように強制的に収める。 
		if (limitToRange) {
			target = Math.min(Math.max(totalLowerBound, target),
					totalUpperBound);
		}

		for (int i = 0; i < r.getElementCount(); i++) {
			int lowerBound = r.getLowerBoundAt(i);
			int upperBound = r.getUpperBoundAt(i);
			// Check for zero. This happens when the distribution of the delta
			// finishes early due to a series of "fixed" entries at the end.
			// In this case, lowerBound == upperBound, for all subsequent terms.
			int newSize;
			if (totalLowerBound == totalUpperBound) {
				newSize = lowerBound;
			} else {
				double f = (double) (target - totalLowerBound)
						/ (totalUpperBound - totalLowerBound);
				newSize = (int) Math.round(lowerBound + f
						* (upperBound - lowerBound));
				// We'd need to round manually in an all integer version.
				// size[i] = (int)(((totalUpperBound - target) * lowerBound +
				// (target - totalLowerBound) *
				// upperBound)/(totalUpperBound-totalLowerBound));
			}
			r.setSizeAt(newSize, i);
			target -= newSize;
			totalLowerBound -= lowerBound;
			totalUpperBound -= upperBound;
		}
	}
}
