/*
 * 作成日: 2005/10/25
 */
package net.sourceforge.swingx.jspread;

import java.util.ArrayList;
import java.util.Arrays;

public class JSpreadSheet implements JSpreadConstant {
	/** データ.必ずすべての列の行数は同じ。要素にはnullを許す。 */
	Object[][] data;
	/** 列のヘッダ.null不可 */
	JSpreadHeader rowHeader;
	/** 行のヘッダ.null不可 */
	JSpreadHeader colHeader;
	/** コーナーのデータ.null不可 */
	Object cornerData;
	/** シート名(null不可) */
	String name;

	/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
	int focusCol;
	/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
	int focusRow;
	/** カレントのセルの表示位置(viewIndex)　0..,-1のとき行選択 */
	int curCol;
	/** カレントのセルの表示位置(viewIndex)　0..,-1のとき列選択 */
	int curRow;

	/** セルの選択 index側の数 */
	boolean[][] cellSelection;
	/** 列の選択 */
	boolean[] rowSelection;
	/** 行の選択 */
	boolean[] colSelection;
	/** コーナーが選択されているか? */
	boolean cornerSelection;
	/** ヘッダの境界をダブルクリックしてヘッダの幅をあわせるかどうか？*/
	public boolean allowAutoRowHeaderResize = true;
	boolean allowAutoColHeaderResize = true;
	/** TODO:ヘッダの順番が変更可能か？ */
	boolean allowRowHeaderReorder = true;
	boolean allowColHeaderReorder = true;
	/** カラムヘッダを表示するかどうか？ */
	boolean colHeaderVisible = true;
	boolean rowHeaderVisible = true;
	/** TODO:スクロールバーの表示の仕方. */
//	public int horizontalScrollBarPolicy = JSpread.HORIZONTAL_SCROLLBAR_AS_NEEDED;
//	public int verticalScrollBarPolicy = JSpread.VERTICAL_SCROLLBAR_AS_NEEDED;
//	/** 分割を行うか？(NONE,VERTICAL,HORIZONTAL,BOTHのいずれか) */
//	public int allowSplit = JSpread.NONE;

	JSpreadSheet(JSpread spread, String name, int numRows, int numCols) {
		assert name != null;
		assert numRows >= 0: numRows;
		assert numCols >= 0: numCols;
		this.data = new Object[numRows][numCols];
//		this.rowHeader = new JSpreadHeader(spread, JSpread.ROW, numRows, 32, 18);		// Excel97
		this.rowHeader = new JSpreadHeader(spread, JSpread.ROW, numRows, 32, 15);		// Java5 JTable
//		this.colHeader = new JSpreadHeader(spread, JSpread.COLUMN, numCols, 20, 72);	// Excel97
		this.colHeader = new JSpreadHeader(spread, JSpread.COLUMN, numCols, 16, 72);	// Java5 JTable
		this.cornerData = "";
		this.name = name;
		this.focusCol = this.focusRow = 0;
		this.curCol = this.curRow = 0;
		int numViewRows = rowHeader.getViewCount();
		int numViewCols = colHeader.getViewCount();
		this.cellSelection = new boolean[numViewRows][numViewCols];
		this.rowSelection = new boolean[numViewRows];
		this.colSelection = new boolean[numViewCols];
		this.cornerSelection = false;
	}
	public JSpreadHeader getHeader(int orientation) {
		assert orientation == JSpread.ROW || orientation == JSpread.COLUMN: orientation;
		return orientation == JSpread.ROW ? rowHeader : colHeader;
	}
	/**
	 * row、column に位置するセルの値を取得します.
	 * @param	rowDataIndex	列のデータ番号(dataIndex)(0..*)
	 * @param	colDataIndex	行のデータ番号(dataIndex)(0..*)
	 * @return	値(null可)
	 */
	Object getValueAt(int rowDataIndex, int colDataIndex) {
		return this.data[rowDataIndex][colDataIndex];
	}

	/**
	 * row、column に位置するセルの値を設定します.
	 * @param	value	値。null可
	 * @param	rowDataIndex	列のデータ番号(dataIndex)(0..*)
	 * @param	columnDataIndex	行のデータ番号(dataIndex)(0..*)
	 */
	void setValueAt(Object value, int rowDataIndex, int columnDataIndex) {
		this.data[rowDataIndex][columnDataIndex] = value;
	}

	/**
	 * ヘッダの値を取得します.
	 * @param	orientation	ヘッダの種類. ROW/COLUMN/CORNER
	 * @param	dataIndex	データ番号(dataIndex)(0..*).CORNERの場合は無視される。
	 * @return	値(null可)
	 */
	Object getHeaderValueAt(int orientation, int dataIndex) {
		assert orientation == ROW || orientation == COLUMN || orientation == CORNER: orientation;
		switch (orientation) {
		case ROW:
			assert 0 <= dataIndex && dataIndex < this.rowHeader.getCount(): dataIndex;
			return this.rowHeader.getValueAt(dataIndex);
		case COLUMN:
			assert 0 <= dataIndex && dataIndex < this.colHeader.getCount(): dataIndex;
			return this.colHeader.getValueAt(dataIndex);
		case CORNER:
			return this.cornerData;
		default:
			assert false: orientation;
		}
		return null;
	}

	/**
	 * ヘッダの値を設定します.
	 * @param	value	値。null可
	 * @param	orientation		ヘッダの種類. ROW/COLUMN
	 * @param	dataIndex	データ番号(dataIndex)(0..*)
	 */
	void setHeaderValueAt(Object value, int orientation, int dataIndex) {
		assert orientation == ROW || orientation == COLUMN: orientation;
		if (orientation == ROW) {
			assert 0 <= dataIndex && dataIndex < this.rowHeader.getCount(): dataIndex;
			this.rowHeader.setValueAt(dataIndex, value);
		} else {
			assert 0 <= dataIndex && dataIndex < this.colHeader.getCount(): dataIndex;
			this.colHeader.setValueAt(dataIndex, value);
		}
	}
	
	/**
	 * 左上のコーナーの値を取得します。
	 * @return	値(null可)
	 */
	Object getCornerValue() {
		return this.cornerData;
	}

	/**
	 * 左上のコーナーの値を設定します.
	 * @param	value	値。null可
	 */
	void setCornerValue(Object value) {
		this.cornerData = value;
	}
	
	/**
	 * ヘッダの縦横の数を返します。
	 * @param orientation	ヘッダの種類. ROW/COLUMN
	 * @return	データの数(DataIndex)
	 */
	int getCellCount(int orientation) {
		assert orientation == ROW || orientation == COLUMN: orientation;
		return (orientation == ROW) ?
				this.rowHeader.getCount():
				this.colHeader.getCount();
	}
	/**
	 * セルのサイズを設定します。
	 * 通常は、初期処理やビューの切り替えなど、外見が大きく切り替わる場合に使用します。
	 * このメソッドはなるべく多くの情報を残すように努力しますが、
	 * いくつかのデータや表示順番などは無効になります。
	 * サイズが変わらない場合は何も行いません。
	 * @param rowCount	列の数。0以上
	 * @param colCount	行の数。0以上
	 * @param initValue	初期値(null可)
	 */
	void setCellCount(int newRowCount, int newColCount, Object initValue) {
		assert newRowCount >= 0: newRowCount;
		assert newColCount >= 0: newColCount;
		// サイズが変わらないときには何もしない。
		int oldRowCount = this.getCellCount(ROW);
		int oldColCount = this.getCellCount(COLUMN);
		if (newRowCount == oldRowCount && newColCount == oldColCount) {
			return;
		}
		// 新しいデータ領域を用意し、古いデータをコピー。
		Object[][] newData = new Object[newRowCount][newColCount];
		for (int r=0; r<newRowCount; r++) {
			for (int c=0; c<newColCount; c++) {
				if (r < this.getCellCount(ROW) && c < this.getCellCount(COLUMN)) {
					newData[r][c] = this.getValueAt(r, c);
				} else {
					newData[r][c] = initValue;
				}
			}
		}
		this.data = newData;
		// 列のサイズが変わるとき、ヘッダ情報の数も変える。
		if (newRowCount != this.getCellCount(ROW)) {
			ArrayList headerInfos = new ArrayList();
			JSpreadHeader header = this.rowHeader;
			for (int i=0; i<newRowCount; i++) {
				JSpreadHeader.HeaderInfo info = 
					(i < oldRowCount) ? 
							header.getInfo(i): 
							new JSpreadHeader.HeaderInfo(null, header.defaultWidth, i);
				info.viewPosition = i;	// TODO:並び順は残すことができるか？
				headerInfos.add(info);
			}
			header.infos = headerInfos;
			header.makeDataIndexList();
		}
		// 行のサイズが変わるとき、ヘッダ情報の数も変える。
		if (newColCount != this.getCellCount(COLUMN)) {
			ArrayList headerInfos = new ArrayList();
			JSpreadHeader header = this.colHeader;
			for (int i=0; i<newColCount; i++) {
				JSpreadHeader.HeaderInfo info = 
					(i < oldColCount) ?
							header.getInfo(i):
							new JSpreadHeader.HeaderInfo(null, header.defaultWidth, i);
				info.viewPosition = i;	// TODO:並び順は残すことができるか？
				headerInfos.add(info);
			}
			header.infos = headerInfos;
			header.makeDataIndexList();
		}
		// 選択状態もサイズを変更する。
		int numViewRows = rowHeader.getViewCount();
		int numViewCols = colHeader.getViewCount();
		this.cellSelection = new boolean[numViewRows][numViewCols];
		this.rowSelection = new boolean[numViewRows];
		this.colSelection = new boolean[numViewCols];
		this.cornerSelection = false;
		System.out.println(newRowCount+"->"+newRowCount+" viewCount="+rowHeader.getViewCount());
	}

	/**
	 * 指定された場所に列/行を挿入します。dataIndexに-1を指定すると末尾に追加します。
	 * @param value			セルの値。null可
	 * @param header		ヘッダの種類. ROW/COLUMN
	 * @param dataIndex		挿入する場所。-1の場合、末尾に追加されます。
	 * @param count			挿入する行/列の数. 1以上
	 * @param headerValue	ヘッダの値。null可
	 */
	void insertValueAt(Object value, int header, int dataIndex, int count, Object headerValue) {
		assert header != ROW || header != COLUMN: header;
		assert 0 <= dataIndex || dataIndex == -1: dataIndex;
		assert 1 <= count: count;
		int rowCount = this.getCellCount(ROW);
		int colCount = this.getCellCount(COLUMN);
		if (header == ROW) {
			Object[][] newData = new Object[rowCount+count][colCount];
			if (dataIndex != -1) {
				for (int r=0; r<rowCount; r++) {
					if (r < dataIndex) {
						newData[r] = this.data[r];
					} else if (r < dataIndex + count) {
						newData[r] = new Object[colCount];
						Arrays.fill(newData[r], value);
					} else {
						newData[r] = this.data[r-count];
					}
				}
			} else {
				System.arraycopy(this.data, 0, newData, 0, rowCount);
				for (int i=0; i<count; i++) {
					newData[rowCount+i] = new Object[colCount];
					Arrays.fill(newData[rowCount+i], value);
				}
			}
			this.data = newData;
		} else { 
			
		}
		// 選択状態もサイズを変更する。
		int numViewRows = rowHeader.getViewCount();
		int numViewCols = colHeader.getViewCount();
		this.cellSelection = new boolean[numViewRows][numViewCols];
		this.rowSelection = new boolean[numViewRows];
		this.colSelection = new boolean[numViewCols];
		this.cornerSelection = false;
	}
	
	/** 指定された位置を選択する。表示位置で指定 */
	boolean getSelection(int viewRow, int viewCol) {
		assert 0<=viewRow && viewRow<rowHeader.getViewCount();
		assert 0<=viewCol && viewCol<colHeader.getViewCount();
		if (cellSelection[viewRow][viewCol]) return true;
		// (curRow,curCol)と(focusRow,focusCol)の間か？
		if (curRow == -1) {
			// col選択なら、colの範囲に入っているかどうか？
			if ((viewCol < curCol) && (viewCol < focusCol)) return false;
			if ((viewCol > curCol) && (viewCol > focusCol)) return false;
			return true;
		}
		if (curCol == -1) {
			// row選択なら、rowの範囲に入っているかどうか？
			if ((viewRow < curRow) && (viewRow < focusRow)) return false;
			if ((viewRow > curRow) && (viewRow > focusRow)) return false;
			return true;
		}
		if ((viewRow < curRow) && (viewRow < focusRow)) return false;
		if ((viewRow > curRow) && (viewRow > focusRow)) return false;
		if ((viewCol < curCol) && (viewCol < focusCol)) return false;
		if ((viewCol > curCol) && (viewCol > focusCol)) return false;
		return true;
	}
	// TODO:テスト的に作った
	public boolean[] getSelectionArray(int rowcol) {
		assert rowcol == ROW || rowcol == COLUMN: rowcol;
		if (rowcol == ROW) {
			boolean[] result = new boolean[rowHeader.getViewCount()];
			if (curRow == -1) {
				// すべての列が選択
				for (int r=0; r<result.length; r++) {
					result[r] = true;
				}
				return result;
			}
			// 
			for (int r=0; r<result.length; r++) {
				if ((r < curRow && r < focusRow) || (r > curRow && r > focusRow)) {
					for (int c=0; c<colHeader.getViewCount(); c++) {
						if (cellSelection[r][c]) {
							result[r] = true;
							break;
						}
					}
				} else {
					result[r] = true;
				}
			}
			return result;
		} else {
			// TODO:まだ
			return null;
		}
	}
	void setSelection(int viewRow, int viewCol, boolean value) {
		assert 0<=viewRow && viewRow<rowHeader.getViewCount();
		assert 0<=viewCol && viewCol<colHeader.getViewCount();
		cellSelection[viewRow][viewCol] = value;
	}
	public void setSelectionAll(boolean value) {
		for (int r=0; r<cellSelection.length; r++) {
			for (int c=0; c<cellSelection[r].length; c++) {
				cellSelection[r][c] = value;
			}
		}
	}

	void setSelection(int row1, int col1, int row2, int col2, boolean value) {
		assert (0 <= row1 && row1 < rowHeader.getViewCount()) || row1 == -1: row1;
		assert (0 <= col1 && col1 < colHeader.getViewCount()) || col1 == -1: col1;
		assert (0 <= row2 && row2 < rowHeader.getViewCount()) || row2 == -1: row2;
		assert (0 <= col2 && col2 < colHeader.getViewCount()) || col2 == -1: col2;
		int rowMin = Math.min(row1, row2);
		int rowMax = Math.max(row1, row2);
		int colMin = Math.min(col1, col2);
		int colMax = Math.max(col1, col2);
		if (col1 == -1 || col2 == -1) {
			colMin = 0;
			colMax = colHeader.getViewCount()-1;
		}
		if (row1 == -1 || row2 == -1) {
			rowMin = 0;
			rowMax = rowHeader.getViewCount()-1;
		}
		for (int r=rowMin; r<=rowMax; r++) {
			for (int c=colMin; c<=colMax; c++) {
				cellSelection[r][c] = value;
			}
		}
	}
	
	/** 表示位置 */
	boolean getHeaderSelection(int orientation, int index) {
		assert orientation == JSpread.ROW || 
			orientation == JSpread.COLUMN || 
			orientation == JSpread.CORNER:
			orientation;
		int min, max;
		switch (orientation) {
		case JSpread.ROW:
			if (rowSelection[index]) return true;
			if (curCol != -1) return false;	// 行選択されているときには列は選択解除
			min = Math.min(curRow, focusRow);
			max = Math.max(curRow, focusRow);
			return min <= index && index <= max;
		case JSpread.COLUMN:
			if (colSelection[index]) return true;
			if (curRow != -1) return false;
			min = Math.min(curCol, focusCol);
			max = Math.max(curCol, focusCol);
			return min <= index && index <= max;
		case JSpread.CORNER:
			return cornerSelection;
		default: assert false;
		}
		return false;
	}

	void setHeaderSelectionAll(int orientation, boolean value) {
		assert orientation == JSpread.ROW || orientation == JSpread.COLUMN;
		if (orientation == JSpread.ROW) {
			for (int i=0; i<rowSelection.length; i++) {
				rowSelection[i] = value;
			}
		}
		else {
			for (int i=0; i<colSelection.length; i++) {
				colSelection[i] = value;
			}
		}
	}
	void setCornerSelection(boolean value) {
		cornerSelection = value;
	}
	/** 不変条件のチェック */
	boolean inv() {
		assert rowHeader != null;
		//rowHeader.inv();
		assert colHeader != null;
		//colHeader.inv();
		assert name != null;
		/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
		assert 0 <= focusCol && focusCol < colHeader.getViewCount();
		/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
		assert 0 <= focusRow && focusRow < rowHeader.getViewCount();
		return true;
	}
	public CurrentFocus getCurrentFocus() {
		return new CurrentFocus(focusCol, focusRow, curCol, curRow);
	}
	static class CurrentFocus {
		/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
		int focusCol;
		/** フォーカスのあるセルの表示位置(viewIndex) 0..*/
		int focusRow;
		/** カレントのセルの表示位置(viewIndex)　0..,-1のとき行選択 */
		int curCol;
		/** カレントのセルの表示位置(viewIndex)　0..,-1のとき列選択 */
		int curRow;
		CurrentFocus(int focusCol, int focusRow, int curCol, int curRow) {
			this.focusCol = focusCol;
			this.focusRow = focusRow;
			this.curCol = curCol;
			this.curRow = curRow;
		}
		public boolean equals(Object obj) {
			CurrentFocus cf = (CurrentFocus) obj;
			return
			this.focusCol == cf.focusCol &&
			this.focusRow == cf.focusRow &&
			this.curCol == cf.curCol &&
			this.curRow == cf.curRow;
		}
		
	}
}
