package aalg.algebra;

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

/**
 * Three dimensional matrix with elements from given field.
 * @author Ing. Petr Ležák
 *
 * @param <T> type of field element
 */
public class Cube<T extends IElement> {
	
	private final IField<T> field;   // Underlying field
	private final int layerCount;   // Number of layers (first dimension)
	private final int rowCount;   // Number of rows (second dimension)
	private final int columnCount;   // Number of columns (third dimension)
	private final List<Matrix<T>> elements;   // Elements of cube stored as layers (matrices)
	
	/**
	 * Creates cube with elements set to zero. Zero is additive identity of given field.
	 * @param field field with cube elements
	 * @param layerCount first dimension
	 * @param rowCount second dimension
	 * @param columnCount third dimension
	 */
	public Cube(final IField<T> field, final int layerCount, final int rowCount, final int columnCount) {
		this.field = field;
		this.layerCount = layerCount;
		this.rowCount = rowCount;
		this.columnCount = columnCount;
		this.elements = new ArrayList<Matrix<T>>(layerCount);
		
		for (int i = 0; i < rowCount; i++) {
			this.elements.add(new Matrix<T>(field, rowCount, columnCount));
		}
	}

	/**
	 * Returns first dimension.
	 * @return first dimension
	 */
	public int getLayerCount() {
		return layerCount;
	}

	/**
	 * Returns second dimension.
	 * @return second dimension
	 */
	public int getRowCount() {
		return rowCount;
	}
	
	/**
	 * Returns third dimension.
	 * @return third dimension
	 */
	public int getColumnCount() {
		return columnCount;
	}
	
	/**
	 * Returns element on given indices.
	 * @param layerIndex first index
	 * @param rowIndex second index
	 * @param columnIndex third index
	 * @return element on given indices
	 */
	public T get (final int layerIndex, final int rowIndex, final int columnIndex) {
		return elements.get(layerIndex).get(rowIndex, columnIndex);
	}
	
	/**
	 * Sets element on given indices.
	 * @param layerIndex first index
	 * @param rowIndex second index
	 * @param columnIndex third index
	 * @param element new value of element on given indices
	 */
	public void set (final int layerIndex, final int rowIndex, final int columnIndex, final T element) {
		elements.get(layerIndex).set(rowIndex, columnIndex, element);
	}
	
	/**
	 * Returns string representation of the cube.
	 * Format is subject of change.
	 * @return string representation of the cube
	 */
	@Override
	public String toString() {
		final StringBuilder builder = new StringBuilder();
		builder.append('[');
		
		for (int i = 0; i < elements.size(); i++) {
			if (i > 0)
				builder.append(", ");
			
			builder.append(elements.get(i).toString());
		}
		
		builder.append(']');
		
		return builder.toString();
	}

	/**
	 * Returns deep copy of the cube.
	 * @return deep copy
	 */
	public Cube<T> copy() {
		final Cube<T> cp = new Cube<T>(field, layerCount, rowCount, columnCount);
		
		for (int i = 0; i < layerCount; i++) {
			for (int j = 0; j < rowCount; j++) {
				for (int k = 0; k < columnCount; k++) {
					cp.set(i, j, k, this.get(i, j, k));
				}
			}
		}
		
		return cp;
	}
	
	/**
	 * Applies given field transformation to elements of cube.
	 * @param transformation field transformation
	 * @return transformed cube
	 * @throws TransformationException transformation is not possible
	 */
	public <S extends IElement> Cube<S> transform (final IFieldTransformation<T, S> transformation) throws TransformationException {
		final Cube<S> result = new Cube<S> (transformation.getOutputField(), layerCount, rowCount, columnCount);
		
		for (int i = 0; i < layerCount; i++) {
			for (int j = 0; j < rowCount; j++) {
				for (int k = 0; k < columnCount; k++) {
					result.set (i, j, k, transformation.transformElement(this.get(i, j, k)));
				}
			}
		}
		
		return result;
	}
	
}
