package extractors.hog;

import java.io.*;
import java.util.Properties;

import extractors.*;

/**
 * This class provides the implementation for the {@link FVExtractor} interface realizing the HOG feature
 * extraction from 3D models.
 * 
 * 
 */
public class HOGExtractor implements FVExtractor {

	// parameters
	private int cellSizeX, cellSizeY, cellSizeZ;
	private int blockSizeX, blockSizeY, blockSizeZ;
	private int blockOverlapX, blockOverlapY, blockOverlapZ;
	private int phiRange, thetaRange, phiBins, thetaBins;

	private DistanceFieldGenerator dfg;
	private GradientFieldGenerator gfg;

	// set default parameters
	private static Properties defaults = new Properties();

	static {
		defaults.setProperty("cellSizeX", "8");
		defaults.setProperty("cellSizeY", "8");
		defaults.setProperty("cellSizeZ", "8");
		defaults.setProperty("blockSizeX", "2");
		defaults.setProperty("blockSizeY", "2");
		defaults.setProperty("blockSizeZ", "2");
		defaults.setProperty("blockOverlapX", "1");
		defaults.setProperty("blockOverlapY", "1");
		defaults.setProperty("blockOverlapZ", "1");
		defaults.setProperty("phiRange", "180");
		defaults.setProperty("phiBins", "9");
		defaults.setProperty("thetaRange", "180");
		defaults.setProperty("thetaBins", "9");
		defaults.setProperty("gradientMethod", "simple");
		defaults.setProperty("norm", "l2");
		defaults.setProperty("epsilon", "120");
	}

	/**
	 * Constructs a {@link HOGExtractor} object with the specified parameters. It is private, because the
	 * method hogExtractor should be used with the property file specifying the parameters.
	 * 
	 * @param cellSizeX
	 * @param cellSizeY
	 * @param cellSizeZ
	 * @param blockSizeX
	 * @param blockSizeY
	 * @param blockSizeZ
	 * @param blockOverlapX
	 * @param blockOverlapY
	 * @param blockOverlapZ
	 * @param phiRange
	 * @param thetaRange
	 * @param phiBins
	 * @param thetaBins
	 * @param dfg
	 * @param gfg
	 * @param nm
	 */
	private HOGExtractor(int cellSizeX, int cellSizeY, int cellSizeZ, int blockSizeX, int blockSizeY, int blockSizeZ,
			int blockOverlapX, int blockOverlapY, int blockOverlapZ, int phiRange, int thetaRange, int phiBins,
			int thetaBins, DistanceFieldGenerator dfg, GradientFieldGenerator gfg, NormalizationMethod nm) {
		this.cellSizeX = cellSizeX;
		this.cellSizeY = cellSizeY;
		this.cellSizeZ = cellSizeZ;
		this.blockSizeX = blockSizeX;
		this.blockSizeY = blockSizeY;
		this.blockSizeZ = blockSizeZ;
		this.blockOverlapX = blockOverlapX;
		this.blockOverlapY = blockOverlapY;
		this.blockOverlapZ = blockOverlapZ;
		this.phiRange = phiRange;
		this.thetaRange = thetaRange;
		this.phiBins = phiBins;
		this.thetaBins = thetaBins;

		Block.setNorm(nm);
		Cell.setParameters(phiRange, phiBins, thetaRange, thetaBins);

		this.dfg = dfg;
		this.gfg = gfg;
	}

	/**
	 * This method constructs a {@link HOGExtractor} with the parameters specified in the given property file.
	 * Not specified parameters are set to default values. The given {@link DistanceFieldGenerator} specifies
	 * the resolution of the distance fields.
	 * 
	 * @param propertyFile
	 * @param dfg
	 * @return
	 * @throws IOException
	 */
	public static HOGExtractor hogExtractor(String propertyFile, DistanceFieldGenerator dfg) throws IOException {
		Properties prop = new Properties(defaults);
		if (propertyFile != null) {
			prop.load(new FileReader(propertyFile));
		}

		int cx = Integer.parseInt(prop.getProperty("cellSizeX"));
		int cy = Integer.parseInt(prop.getProperty("cellSizeY"));
		int cz = Integer.parseInt(prop.getProperty("cellSizeZ"));
		int bx = Integer.parseInt(prop.getProperty("blockSizeX"));
		int by = Integer.parseInt(prop.getProperty("blockSizeY"));
		int bz = Integer.parseInt(prop.getProperty("blockSizeZ"));
		int ox = Integer.parseInt(prop.getProperty("blockOverlapX"));
		int oy = Integer.parseInt(prop.getProperty("blockOverlapY"));
		int oz = Integer.parseInt(prop.getProperty("blockOverlapZ"));
		int pr = Integer.parseInt(prop.getProperty("phiRange"));
		int pb = Integer.parseInt(prop.getProperty("phiBins"));
		int tr = Integer.parseInt(prop.getProperty("thetaRange"));
		int tb = Integer.parseInt(prop.getProperty("thetaBins"));
		double e = Double.parseDouble(prop.getProperty("epsilon"));

		String gfgMethod = prop.getProperty("gradientMethod");
		GradientFieldGenerator gfg;

		if (gfgMethod.equals("diffOfGrads")) {
			gfg = new DifferenceOfGradients();
		} else if (gfgMethod.equals("second")) {
			gfg = new SecondGradients();
		} else {
			gfg = new SimpleGradientsNullBorders();
		}

		String nmString = prop.getProperty("norm");
		NormalizationMethod nm;

		if (nmString.equals("l2hys")) {
			double ehys = Double.parseDouble(prop.getProperty("epsilonHys"));
			double clippingThres = Double.parseDouble(prop.getProperty("clippingThres"));
			nm = new L2HysNormalization(e, ehys, clippingThres);
		} else {
			nm = new L2Normalization(e);
		}

		return new HOGExtractor(cx, cy, cz, bx, by, bz, ox, oy, oz, pr, tr, pb, tb, dfg, gfg, nm);
	}

	@Override
	public int getDimension() {
		int blocksX = (gfg.getResx(dfg) / cellSizeX - blockOverlapX) / (blockSizeX - blockOverlapX);
		int blocksY = (gfg.getResy(dfg) / cellSizeY - blockOverlapY) / (blockSizeY - blockOverlapY);
		int blocksZ = (gfg.getResz(dfg) / cellSizeZ - blockOverlapZ) / (blockSizeZ - blockOverlapZ);

		int numBlocks = blocksX * blocksY * blocksZ;
		int numCellsPerBlock = blockSizeX * blockSizeY * blockSizeZ;
		return numBlocks * numCellsPerBlock * phiBins * thetaBins;
	}

	@Override
	public double[] getFV(File offFile) {
		double[][][] field;
		try {
			field = dfg.readFromConvertedModel(offFile);
			Gradient[][][] gradientField = gfg.generateGradientField(field);
			Cell[][][] cellGrid = Cell.computeCellGrid(gradientField, cellSizeX, cellSizeY, cellSizeZ);
			Block[][][] blockGrid = Block.computeBlockGrid(cellGrid, blockSizeX, blockSizeY, blockSizeZ, blockOverlapX,
				blockOverlapY, blockOverlapZ);
			return Block.getHogFV(blockGrid);
		} catch (IOException e) {
			System.out.println(this + " can't extract from " + offFile.getPath() + ". Writing dummy FV.");
			return new double[getDimension()];
		}
	}

	@Override
	public String toString() {
		return "hog_" + getDimension() + "_" + Block.getNorm();
	}

}
