/*
 *  Brno University of Technology
 *
 *  Copyright (C) 2009-2010 by Brno University of Technology and the contributors
 *
 *  Complete list of developers available at our web site:
 *
 *       http://spl.utko.feec.vutbr.cz
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see http://www.gnu.org/licenses/.
 */
package cz.vutbr.feec.utko.opencv.operators.imageTransforms;

import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
import static com.googlecode.javacv.cpp.opencv_highgui.*;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;

import Jama.Matrix;

import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.UserError;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeCategory;
import com.rapidminer.parameter.ParameterTypeDouble;
import com.rapidminer.parameter.ParameterTypeFile;
import com.rapidminer.parameter.ParameterTypeInt;
import com.rapidminer.parameter.ParameterTypeMatrix;
import com.rapidminer.parameter.conditions.EqualTypeCondition;

import cz.vutbr.feec.imageprocessing.op.filters.ConvolutionMatrixes;
import cz.vutbr.feec.utko.opencv.operators.imageProcessing.BaseProcessingOperator;

public class Convolution extends BaseProcessingOperator {
	// FIXME zkontrolova funkcnost jednotlivych filtru
	protected static final String PARAMETER_MATRIX = "Matrix";
	protected static final String PARAMETER_FILE = "File";
	private static final String PARAMETER_MATRIX_TYPE = "Matrix_type";

	private static final String ANCHOR_X = "Anchor X";
	private static final String ANCHOR_Y = "Anchor Y";

	private static final String[] PARAMETER_LIST_MATRIXES = new String[] {
			"Predefined kernel", "EdgeDetection kernel", "Matrix from file",
			"Own matrix", };

	public static final int PREDEFINED_KERNEL = 0;
	public static final int EDGEDETECTION_KERNEL = 1;
	public static final int MATRIX_FROM_FILE = 2;
	public static final int OWN_MATRIX = 3;

	private static final String PARAM_KERNEL_TYPE = "kernel_type";
	private static final String[] KERNELS = ConvolutionMatrixes.Kernels;

	private static final String PARAM_EDGE_DETECTION_KERNEL_TYPE = "edge_detection_kernel_type";
	private static final String[] EDGE_DETECTION_KERNELS = ConvolutionMatrixes.EdgeDetectionKernels;

	private static final String PARAM_BOOST = "boost_constant";
	// private static final String PARAM_DOG_RADIUS1 = "dog_radius_1";
	// private static final String PARAM_DOG_RADIUS2 = "dog_radius_2";
	private static final int HIGH_BOOST_1 = 6;
	private static final int HIGH_BOOST_2 = 7;

	public Convolution(OperatorDescription description) {
		super(description);
	}

	public IplImage doWork(IplImage cvImg) throws OperatorException {

		IplImage out = cvCreateImage(cvGetSize(cvImg), IPL_DEPTH_8U, 3);
		Matrix matrix = null;
		int matrixType = getParameterAsInt(PARAMETER_MATRIX_TYPE);
		switch (matrixType) {
		case PREDEFINED_KERNEL:
			matrix = ConvolutionMatrixes.getKernel(
					getParameterAsInt(PARAM_KERNEL_TYPE),
					getParameterAsDouble(PARAM_BOOST));
			break;
		case EDGEDETECTION_KERNEL:
			matrix = ConvolutionMatrixes
					.getEdgeDetectionKernel(getParameterAsInt(PARAM_EDGE_DETECTION_KERNEL_TYPE));
			break;
		case MATRIX_FROM_FILE:
			String fileName = getParameterAsString(PARAMETER_MATRIX);
			BufferedReader in;
			try {
				in = new BufferedReader(new FileReader(fileName));
			} catch (FileNotFoundException e) {
				throw new UserError(this, 301, fileName);
			}
			try {
				matrix = Matrix.read(in);
				matrix = norm(matrix);
			} catch (IOException e) {
				throw new UserError(this, 136, "Cannot read file " + fileName);
			}
			break;
		case OWN_MATRIX:
			matrix = new Matrix(getParameterAsMatrix(PARAMETER_MATRIX));
			matrix = norm(matrix);
		default:
			break;
		}

		int anchX = getParameterAsInt(ANCHOR_X);
		int anchY = getParameterAsInt(ANCHOR_Y);

		CvMat kernel = createKernel(matrix);
		cvFilter2D(cvImg, out, kernel, cvPoint(anchX, anchY));

		return out;
	}

	private CvMat createKernel(Matrix matrix) {

		CvMat filter = CvMat.create(matrix.getRowDimension(),
				matrix.getColumnDimension(), CV_32FC1);
		filter.put(matrix.getRowPackedCopy());
		return filter;
	}

	private Matrix norm(Matrix matrix) {
		double sum = 0.0;
		for (int x = 0; x < matrix.getRowDimension(); x++) {
			for (int y = 0; y < matrix.getColumnDimension(); y++) {
				sum += matrix.get(x, y);
			}
		}
		return matrix.times(1 / sum);

	}

	@Override
	public List<ParameterType> getParameterTypes() {
		List<ParameterType> types = super.getParameterTypes();

		ParameterType anchX = new ParameterTypeInt(ANCHOR_X,
				"X coordinate of the anchor point.", -1, 9999, -1);
		ParameterType anchY = new ParameterTypeInt(ANCHOR_Y,
				"Y coordinate of the anchor point.", -1, 9999, -1);
		types.add(anchX);
		types.add(anchY);

		ParameterType type = new ParameterTypeCategory(PARAMETER_MATRIX_TYPE,
				"Indicates which type of filtration matrix should be applied.",
				PARAMETER_LIST_MATRIXES, PREDEFINED_KERNEL);
		type.setExpert(false);
		types.add(type);

		ParameterType cat = new ParameterTypeCategory(PARAM_KERNEL_TYPE,
				"kernel type", KERNELS, 1);
		cat.setExpert(false);
		cat.registerDependencyCondition(new EqualTypeCondition(this,
				PARAMETER_MATRIX_TYPE, PARAMETER_LIST_MATRIXES, true,
				PREDEFINED_KERNEL));
		types.add(cat);

		ParameterType cat2 = new ParameterTypeCategory(
				PARAM_EDGE_DETECTION_KERNEL_TYPE, "edge detection kernel type",
				EDGE_DETECTION_KERNELS, 1);
		cat2.setExpert(false);
		cat2.registerDependencyCondition(new EqualTypeCondition(this,
				PARAMETER_MATRIX_TYPE, PARAMETER_LIST_MATRIXES, true,
				EDGEDETECTION_KERNEL));
		types.add(cat2);

		ParameterType fileMatrix = new ParameterTypeFile(PARAMETER_FILE,
				"File to load convolutional mask from.", "txt", "matrix.txt");
		fileMatrix.registerDependencyCondition(new EqualTypeCondition(this,
				PARAMETER_MATRIX_TYPE, PARAMETER_LIST_MATRIXES, true,
				MATRIX_FROM_FILE));
		fileMatrix.setExpert(false);
		types.add(fileMatrix);

		ParameterType matrix = new ParameterTypeMatrix(PARAMETER_MATRIX,
				"Used matrix for filtration.", "filtration_matrix", "row",
				"column", true);
		matrix.registerDependencyCondition(new EqualTypeCondition(this,
				PARAMETER_MATRIX_TYPE, PARAMETER_LIST_MATRIXES, true,
				OWN_MATRIX));
		matrix.setExpert(false);
		types.add(matrix);

		ParameterType boost = new ParameterTypeDouble(PARAM_BOOST,
				"boost constant", Double.MIN_VALUE, Double.MAX_VALUE, 1.0);
		boost.setExpert(false);
		boost.registerDependencyCondition(new EqualTypeCondition(this,
				PARAMETER_MATRIX_TYPE, PARAMETER_LIST_MATRIXES, true,
				PREDEFINED_KERNEL));
		boost.registerDependencyCondition(new EqualTypeCondition(this,
				PARAM_KERNEL_TYPE, KERNELS, true, new int[] { HIGH_BOOST_1,
						HIGH_BOOST_2 }));
		types.add(boost);

		return types;
	}
}
