package extractors.hog;

/**
 * This class represents a block of cells.
 * 
 */
public class Block {

	// the vector of histogram values
	private double[] values;

	// the Method to normalize the values
	private static NormalizationMethod norm;

	public Block(double[] values) {
		this.values = values;
	}

	/**
	 * Constructs the block from a given cell grid on the position specified by x, y and z.
	 * 
	 * @param cellGrid
	 * @param x
	 * @param y
	 * @param z
	 * @param blockSizeX
	 * @param blockSizeY
	 * @param blockSizeZ
	 */
	public Block(Cell[][][] cellGrid, int x, int y, int z, int blockSizeX, int blockSizeY, int blockSizeZ) {
		values = new double[blockSizeX * blockSizeY * blockSizeZ * Cell.getPhiBins() * Cell.getThetaBins()];
		int cnt = 0;

		for (int xi = x; xi < x + blockSizeX; xi++) {
			for (int yi = y; yi < y + blockSizeY; yi++) {
				for (int zi = z; zi < z + blockSizeZ; zi++) {
					double[][] histogram = cellGrid[xi][yi][zi].getHistogram();
					for (int i = 0; i < histogram.length; i++) {
						for (int j = 0; j < histogram[i].length; j++) {
							values[cnt] = histogram[i][j];
							cnt++;
						}
					}
				}
			}
		}

		normalize();
	}

	/**
	 * Normalizes the values of this block.
	 */
	private void normalize() {
		norm.normalize(values);
	}

	/**
	 * Computes a block grid as a 3D {@link Block} array from a given {@link Cell} grid.
	 * 
	 * @param cellGrid
	 * @param blockSizeX
	 * @param blockSizeY
	 * @param blockSizeZ
	 * @param overlapX
	 * @param overlapY
	 * @param overlapZ
	 * @return the block grid
	 */
	public static Block[][][] computeBlockGrid(Cell[][][] cellGrid, int blockSizeX, int blockSizeY, int blockSizeZ,
			int overlapX, int overlapY, int overlapZ) {

		int stepSizeX = blockSizeX - overlapX;
		int stepSizeY = blockSizeY - overlapY;
		int stepSizeZ = blockSizeZ - overlapZ;

		int blocksX = (cellGrid.length - overlapX) / stepSizeX;
		int blocksY = (cellGrid[0].length - overlapY) / stepSizeY;
		int blocksZ = (cellGrid[0][0].length - overlapZ) / stepSizeZ;
		
		Block[][][] blockGrid = new Block[blocksX][blocksY][blocksZ];

		for (int x = 0; x < blocksX; x++) {
			for (int y = 0; y < blocksY; y++) {
				for (int z = 0; z < blocksZ; z++) {
					blockGrid[x][y][z] = new Block(cellGrid, x * stepSizeX, y * stepSizeY, z * stepSizeZ, blockSizeX,
							blockSizeY, blockSizeZ);
				}
			}
		}
		
		return blockGrid;
	}

	/**
	 * Concatenates the values of the blocks of a given block grid to produce the final feature vector.
	 * 
	 * @param blockGrid
	 * @return the feature vector
	 */
	public static double[] getHogFV(Block[][][] blockGrid) {
		int blocksX = blockGrid.length;
		int blocksY = blockGrid[0].length;
		int blocksZ = blockGrid[0][0].length;
		int numOfValues = blockGrid[0][0][0].getValues().length;
		double[] fv = new double[blocksX * blocksY * blocksZ * numOfValues];

		int cnt = 0;
		for (int x = 0; x < blockGrid.length; x++) {
			for (int y = 0; y < blockGrid[x].length; y++) {
				for (int z = 0; z < blockGrid[x][y].length; z++) {
					double[] values = blockGrid[x][y][z].getValues();
					System.arraycopy(values, 0, fv, cnt, numOfValues);
					cnt += numOfValues;
				}
			}
		}

		return fv;
	}

	/**
	 * @return the values of this block
	 */
	public double[] getValues() {
		return values;
	}

	/**
	 * @param norm the norm to set
	 */
	public static void setNorm(NormalizationMethod norm) {
		Block.norm = norm;
	}

	/**
	 * @return the norm
	 */
	public static NormalizationMethod getNorm() {
		return norm;
	}
}
