package cz.vutbr.feec.utko.opencv.operators.lineOperations;
/**
 * Implementation of the RANSAC fit line algorithm
 * 
 * Finding the best model of a line from a input set of the points 
 * 
 * Author: Martin Hasmanda
 */

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

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Vector;

import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.OutputPort;
import com.rapidminer.operator.ports.metadata.GenerateNewMDRule;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.SimplePrecondition;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeDouble;

import cz.vutbr.feec.imageprocessing.IOObject.PointsIOObject;
import cz.vutbr.feec.imageprocessing.IOObject.innerObject.InterrestArea;
import cz.vutbr.feec.utko.opencv.io.OpenCVIOObject;
import cz.vutbr.feec.utko.opencv.operators.imageProcessing.BaseProcessingOperator;
import cz.vutbr.feec.utko.opencv.operators.lineOperations.lineOperationsClasses.InlierParams;
import cz.vutbr.feec.utko.opencv.operators.lineOperations.lineOperationsClasses.RansacFitLine;

public class RansacFitLineOperator extends BaseProcessingOperator{

//	protected InputPort inOpenCV = getInputPorts().createPort("color image 1");
//	protected OutputPort outOpenCV = getOutputPorts().createPort("color image out");
	
	protected InputPort inPoints = getInputPorts().createPort("Pts In");
	protected OutputPort outPoints = getOutputPorts().createPort("Pts Out");
	protected OutputPort outPointsApprox = getOutputPorts().createPort("Pts Out approx");
	protected OutputPort outLine = getOutputPorts().createPort("Line Out");

	private final String DIST = "Dist to line";
	private final String LIKELIHOOD = "Likelihood";
	private final String POLYNOMIAL = "3rd order polynomial";
	
	/**
	 * Constructor
	 * @param description
	 */
	public RansacFitLineOperator(OperatorDescription description) {
		super(description);
//		inOpenCV.addPrecondition(new SimplePrecondition(inOpenCV, new MetaData(OpenCVIOObject.class)));
//		getTransformer().addRule(new GenerateNewMDRule(outOpenCV, OpenCVIOObject.class));
		inPoints.addPrecondition(new SimplePrecondition(inPoints, new MetaData(PointsIOObject.class)));
		getTransformer().addRule(new GenerateNewMDRule(outPoints, PointsIOObject.class));
		getTransformer().addRule(new GenerateNewMDRule(outPointsApprox, PointsIOObject.class));
		getTransformer().addRule(new GenerateNewMDRule(outLine, PointsIOObject.class));
	}

	/**
	 * Finding the best line approximation by RANSAC algorithm
	 */
	public IplImage doWork(IplImage cvImg) throws OperatorException {
		IplImage img = cvCloneImage(cvImg);
		
		
		
		PointsIOObject src = inPoints.getData();
		PointsIOObject points = new PointsIOObject(img.width(), img.height(), 0, 0);
		PointsIOObject line = new PointsIOObject(img.width(), img.height(), 0, 0);
		
		Collection<InterrestArea> pts = src.getPoints();
		ArrayList<InterrestArea> objects = new ArrayList<InterrestArea>(pts);
		
		double likel = getParameterAsDouble(LIKELIHOOD);
		double dist = getParameterAsDouble(DIST);
		boolean polynom = getParameterAsBoolean(POLYNOMIAL);
		
		///////////////////////////////////////////////
		// apply the RANSAC for a fit line algorithms
			
		RansacFitLine ransac = new RansacFitLine();
		ransac.SetupParameters(1000, 100, likel, polynom);
		
		java.awt.Point values[] = new java.awt.Point[objects.size()];
		
		System.out.println("input data");
		for(int i=0; i<values.length; i++)
		{
			values[i] = new java.awt.Point(objects.get(i).x, objects.get(i).y);
			System.out.print(values[i].x + ", " + values[i].y + ", ");
		}
		
		// run
		InlierParams params = ransac.Run(values, 3, dist);

		///////////////////////////////////////////////
		params = null;
		
		// result test image
		IplImage result = null;
		
		if(img.nChannels() == 1)
		{
			result = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3);
			cvCvtColor( img, result, CV_GRAY2BGR );
		}			
		else
			result = cvCloneImage(img);
		
		params = ransac.ApproximateInliers();

		// best points
		
		for (int i = 0; i <values.length; i++)
		{
			if(params.act_inliers.get(i))
				points.addPoint(values[i].x, values[i].y);
		}
		
		
		Vector<Point> pointsOnApproximation = new Vector<Point>();
		// draw line
		if(polynom == false)
			pointsOnApproximation = ransac.DrawLine(result, params, dist);
		else
			pointsOnApproximation = ransac.DrawPolynom(result, params, dist);
		// save result points
		
		Point [] resline = ransac.GetLine(result, params);
		line.addPoint(resline[0].x, resline[0].y);	// Begin Point
		line.addPoint(resline[1].x, resline[1].y);	// End Point
		
		IplImage img1 = cvCloneImage(result);
		
		cvReleaseImage(result);
		
		outLine.deliver(line);
		outPoints.deliver(points);
		
		
		PointsIOObject pointsOnApproximationDeliv = new PointsIOObject(img1.width(), img1.height(), 0, 0);
		
		for (Point p : pointsOnApproximation) {
			pointsOnApproximationDeliv.addPoint(p.x, p.y);
		}
		
		
		outPointsApprox.deliver(pointsOnApproximationDeliv);
		
		return img1;
	}

	/**
	 * List of parameters for the algorithm
	 */
	public List<ParameterType> getParameterTypes() {
		List<ParameterType> types = super.getParameterTypes();
		
		types.add(new ParameterTypeDouble(DIST, "Dist to line [px]", 0.0, 999.0, 20.0));
		types.add(new ParameterTypeDouble(LIKELIHOOD, "Likelhood for inliers [%]", 0.0, 1.0, 0.99));
		types.add(new ParameterTypeBoolean(POLYNOMIAL, "3rd order polynomial", false));
		
		return types;
	}
}
