/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */

package org.apache.poi.hssf.record.aggregates;

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

import org.apache.poi.hssf.model.RecordStream;
import org.apache.poi.hssf.record.BlankRecord;
import org.apache.poi.hssf.record.CellValueRecordInterface;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.MulBlankRecord;
import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StringRecord;
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;

/**
 *
 * Aggregate value records together.  Things are easier to handle that way.
 *
 * @author  andy
 * @author  Glen Stampoultzis (glens at apache.org)
 * @author Jason Height (jheight at chariot dot net dot au)
 */
public final class ValueRecordsAggregate {
	private static final int MAX_ROW_INDEX = 0XFFFF;
	private static final int INDEX_NOT_SET = -1;
	private int firstcell = INDEX_NOT_SET;
	private int lastcell  = INDEX_NOT_SET;
	private CellValueRecordInterface[][] records;

	/** Creates a new instance of ValueRecordsAggregate */

	public ValueRecordsAggregate() {
		this(INDEX_NOT_SET, INDEX_NOT_SET, new CellValueRecordInterface[30][]); // We start with 30 Rows.
	}
	private ValueRecordsAggregate(int firstCellIx, int lastCellIx, CellValueRecordInterface[][] pRecords) {
		firstcell = firstCellIx;
		lastcell = lastCellIx;
		records = pRecords;
	}

	public void insertCell(CellValueRecordInterface cell) {
		short column = cell.getColumn();
		int row = cell.getRow();
		if (row >= records.length) {
			CellValueRecordInterface[][] oldRecords = records;
			int newSize = oldRecords.length * 2;
			if (newSize < row + 1)
				newSize = row + 1;
			records = new CellValueRecordInterface[newSize][];
			System.arraycopy(oldRecords, 0, records, 0, oldRecords.length);
		}
		CellValueRecordInterface[] rowCells = records[row];
		if (rowCells == null) {
			int newSize = column + 1;
			if (newSize < 10)
				newSize = 10;
			rowCells = new CellValueRecordInterface[newSize];
			records[row] = rowCells;
		}
		if (column >= rowCells.length) {
			CellValueRecordInterface[] oldRowCells = rowCells;
			int newSize = oldRowCells.length * 2;
			if (newSize < column + 1)
				newSize = column + 1;
			// if(newSize>257) newSize=257; // activate?
			rowCells = new CellValueRecordInterface[newSize];
			System.arraycopy(oldRowCells, 0, rowCells, 0, oldRowCells.length);
			records[row] = rowCells;
		}
		rowCells[column] = cell;

		if (column < firstcell || firstcell == INDEX_NOT_SET) {
			firstcell = column;
		}
		if (column > lastcell || lastcell == INDEX_NOT_SET) {
			lastcell = column;
		}
	}

	public void removeCell(CellValueRecordInterface cell) {
		if (cell == null) {
			throw new IllegalArgumentException("cell must not be null");
		}
		int row = cell.getRow();
		if (row >= records.length) {
			throw new RuntimeException("cell row is out of range");
		}
		CellValueRecordInterface[] rowCells = records[row];
		if (rowCells == null) {
			throw new RuntimeException("cell row is already empty");
		}
		short column = cell.getColumn();
		if (column >= rowCells.length) {
			throw new RuntimeException("cell column is out of range");
		}
		rowCells[column] = null;
	}

	public void removeAllCellsValuesForRow(int rowIndex) {
		if (rowIndex < 0 || rowIndex > MAX_ROW_INDEX) {
			throw new IllegalArgumentException("Specified rowIndex " + rowIndex
					+ " is outside the allowable range (0.." +MAX_ROW_INDEX + ")");
		}
		if (rowIndex >= records.length) {
			// this can happen when the client code has created a row,
			// and then removes/replaces it before adding any cells. (see bug 46312)
			return;
		}

		records[rowIndex] = null;
	}

	public int getFirstCellNum() {
		return firstcell;
	}

	public int getLastCellNum() {
		return lastcell;
	}

	public void addMultipleBlanks(MulBlankRecord mbr) {
		for (int j = 0; j < mbr.getNumColumns(); j++) {
			BlankRecord br = new BlankRecord();

			br.setColumn(( short ) (j + mbr.getFirstColumn()));
			br.setRow(mbr.getRow());
			br.setXFIndex(mbr.getXFAt(j));
			insertCell(br);
		}
	}

	/**
	 * Processes a single cell value record
	 * @param sfh used to resolve any shared-formulas/arrays/tables for the current sheet
	 */
	public void construct(CellValueRecordInterface rec, RecordStream rs, SharedValueManager sfh) {
		if (rec instanceof FormulaRecord) {
			FormulaRecord formulaRec = (FormulaRecord)rec;
			// read optional cached text value
			StringRecord cachedText;
			Class<? extends Record> nextClass = rs.peekNextClass();
			if (nextClass == StringRecord.class) {
				cachedText = (StringRecord) rs.getNext();
			} else {
				cachedText = null;
			}
			insertCell(new FormulaRecordAggregate(formulaRec, cachedText, sfh));
		} else {
			insertCell(rec);
		}
	}


	/** Returns true if the row has cells attached to it */
	public boolean rowHasCells(int row) {
		if (row >= records.length) {
			return false;
		}
		CellValueRecordInterface[] rowCells=records[row];
		if(rowCells==null) return false;
		for(int col=0;col<rowCells.length;col++) {
			if(rowCells[col]!=null) return true;
		}
		return false;
	}


	public void visitCellsForRow(int rowIndex, RecordVisitor rv) {

		CellValueRecordInterface[] rowCells = records[rowIndex];
		if(rowCells == null) {
			throw new IllegalArgumentException("Row [" + rowIndex + "] is empty");
		}


		for (int i = 0; i < rowCells.length; i++) {
			RecordBase cvr = (RecordBase) rowCells[i];
			if(cvr == null) {
				continue;
			}
			int nBlank = countBlanks(rowCells, i);
			if (nBlank > 1) {
				rv.visitRecord(createMBR(rowCells, i, nBlank));
				i+=nBlank-1;
			} else if (cvr instanceof RecordAggregate) {
				RecordAggregate agg = (RecordAggregate) cvr;
				agg.visitContainedRecords(rv);
			} else {
				rv.visitRecord((Record) cvr);
			}
		}
	}

	/**
	 * @return the number of <em>consecutive</em> {@link BlankRecord}s in the specified row
	 * starting from startIx.
	 */
	private static int countBlanks(CellValueRecordInterface[] rowCellValues, int startIx) {
		int i = startIx;
		while(i < rowCellValues.length) {
			CellValueRecordInterface cvr = rowCellValues[i];
			if (!(cvr instanceof BlankRecord)) {
				break;
			}
			i++;
		}
		return i - startIx;
	}

	private MulBlankRecord createMBR(CellValueRecordInterface[] cellValues, int startIx, int nBlank) {

		short[] xfs = new short[nBlank];
		for (int i = 0; i < xfs.length; i++) {
			xfs[i] = ((BlankRecord)cellValues[startIx + i]).getXFIndex();
		}
		int rowIx = cellValues[startIx].getRow();
		return new MulBlankRecord(rowIx, startIx, xfs);
	}

	/**
	 * Gets all the cell records contained in this aggregate. 
	 * Note {@link BlankRecord}s appear separate (not in {@link MulBlankRecord}s).
	 */
	public CellValueRecordInterface[] getValueRecords() {
		List<CellValueRecordInterface> temp = new ArrayList<>();

		for (int rowIx = 0; rowIx < records.length; rowIx++) {
			CellValueRecordInterface[] rowCells = records[rowIx];
			if (rowCells == null) {
				continue;
			}
			for (int colIx = 0; colIx < rowCells.length; colIx++) {
				CellValueRecordInterface cell = rowCells[colIx];
				if (cell != null) {
					temp.add(cell);
				}
			}
		}

		CellValueRecordInterface[] result = new CellValueRecordInterface[temp.size()];
		temp.toArray(result);
		return result;
	}

	public Object clone() {
		throw new RuntimeException("clone() should not be called.  ValueRecordsAggregate should be copied via Sheet.cloneSheet()");
	}
}
