/**
 *                    
 * @author greg (at) myrobotlab.org
 *  
 * This file is part of MyRobotLab (http://myrobotlab.org).
 *
 * MyRobotLab is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version (subject to the "Classpath" exception
 * as provided in the LICENSE.txt file that accompanied this code).
 *
 * MyRobotLab is distributed in the hope that it will be useful or fun,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * All libraries in thirdParty bundle are subject to their own license
 * requirements - please refer to http://myrobotlab.org/libraries for 
 * details.
 * 
 * Enjoy !
 * 
 * */

package org.myrobotlab.opencv;

/*
 import static org.bytedeco.javacpp.opencv_imgproc.CV_BGR2HSV;
 import static org.bytedeco.javacpp.opencv_imgproc.CV_HAAR_DO_CANNY_PRUNING;
 import static org.bytedeco.javacpp.opencv_imgproc.cvHaarDetectObjects;
 import static org.bytedeco.javacpp.opencv_core.CV_RGB;
 import static org.bytedeco.javacpp.opencv_core.cvClearMemStorage;
 import static org.bytedeco.javacpp.opencv_core.cvCreateMemStorage;
 import static org.bytedeco.javacpp.opencv_core.cvDrawLine;
 import static org.bytedeco.javacpp.opencv_core.cvGetSeqElem;
 import static org.bytedeco.javacpp.opencv_core.cvLoad;
 import static org.bytedeco.javacpp.opencv_core.cvRectangle;
 import static org.bytedeco.javacpp.opencv_core.cvSize;
 import org.bytedeco.javacpp.opencv_imgproc.CvHaarClassifierCascade;
 import org.bytedeco.javacpp.opencv_core.CvMemStorage;
 import org.bytedeco.javacpp.opencv_core.CvPoint;
 import org.bytedeco.javacpp.opencv_core.CvRect;
 import org.bytedeco.javacpp.opencv_core.CvSeq;
 import org.bytedeco.javacpp.opencv_core.IplImage;
 */

import static org.bytedeco.javacpp.opencv_core.cvClearMemStorage;
import static org.bytedeco.javacpp.opencv_core.cvCreateMemStorage;
import static org.bytedeco.javacpp.opencv_core.cvDrawRect;
import static org.bytedeco.javacpp.opencv_core.cvGetSeqElem;
import static org.bytedeco.javacpp.opencv_core.cvLoad;
import static org.bytedeco.javacpp.opencv_core.cvPoint;
import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_DO_CANNY_PRUNING;
import static org.bytedeco.javacpp.opencv_objdetect.CV_HAAR_FIND_BIGGEST_OBJECT;
import static org.bytedeco.javacpp.opencv_objdetect.cvHaarDetectObjects;

import java.util.ArrayList;

import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.service.data.Rectangle;
import org.slf4j.Logger;

import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacpp.opencv_core.CvMemStorage;
import org.bytedeco.javacpp.opencv_core.CvRect;
import org.bytedeco.javacpp.opencv_core.CvScalar;
import org.bytedeco.javacpp.opencv_core.CvSeq;
import org.bytedeco.javacpp.opencv_core.IplImage;
import org.bytedeco.javacpp.opencv_objdetect;
import org.bytedeco.javacpp.opencv_objdetect.CvHaarClassifierCascade;

public class OpenCVFilterFaceDetect extends OpenCVFilter {

	private static final long serialVersionUID = 1L;

	public final static Logger log = LoggerFactory.getLogger(OpenCVFilterFaceDetect.class.getCanonicalName());

	CvMemStorage storage = null;
	public CvHaarClassifierCascade cascade = null; // TODO - was static
	public String cascadeDir = "haarcascades";
	public String cascadeFile = "haarcascade_frontalface_alt2.xml";
	// public String cascadePath = "haarcascades/haarcascade_mcs_lefteye.xml";
	// public String cascadePath =
	// "haarcascades/haarcascade_mcs_eyepair_big.xml";

	int i;

	// public int stablizedFrameCount = 10;
	public int minFaceFrames = 10;
	public int minEmptyFrames = 10;
	public int firstFaceFrame = 0;
	public int firstEmptyFrame = 0;
	public int faceCnt = 0;
	public int lastFaceCnt = 0;

	public static final String STATE_LOST_TRACKING = "STATE_LOST_TRACKING";
	public static final String STATE_LOSING_TRACKING = "STATE_LOSING_TRACKING";
	public static final String STATE_DETECTING_FACE = "STATE_DETECTING_FACE";
	public static final String STATE_DETECTED_FACE = "STATE_DETECTED_FACE";

	private String state = STATE_LOST_TRACKING;

	int x0, y0, x1, y1;

	public OpenCVFilterFaceDetect() {
		super();
	}

	public OpenCVFilterFaceDetect(String name) {
		super(name);
	}

	@Override
	public IplImage display(IplImage image, OpenCVData data) {

		if (data != null) {
			ArrayList<Rectangle> bb = data.getBoundingBoxArray();
			if (bb != null) {
				for (int i = 0; i < bb.size(); ++i) {
					Rectangle rect = bb.get(i);

					if (useFloatValues) {
						x0 = (int) (rect.x * width);
						y0 = (int) (rect.y * height);
						x1 = x0 + (int) (rect.width * width);
						y1 = y0 + (int) (rect.height * height);
						cvDrawRect(image, cvPoint(x0, y0), cvPoint(x1, y1), CvScalar.RED, 1, 1, 0);
					} else {
						x0 = (int) rect.x;
						y0 = (int) rect.y;
						x1 = x0 + (int) rect.width;
						y1 = y0 + (int) rect.height;
						cvDrawRect(image, cvPoint(x0, y0), cvPoint(x1, y1), CvScalar.RED, 1, 1, 0);
					}
				}

				return image;
			}
		}

		return image;
	}

	@Override
	public void imageChanged(IplImage image) {
		// Allocate the memory storage TODO make this globalData
		if (storage == null) {
			storage = cvCreateMemStorage(0);
		}

		if (cascade == null) {
			// Preload the opencv_objdetect module to work around a known bug.
			Loader.load(opencv_objdetect.class);

			cascade = new CvHaarClassifierCascade(cvLoad(String.format("%s/%s", cascadeDir, cascadeFile)));
			// cascade = new
			// CvHaarClassifierCascade(cvLoad("haarcascades/haarcascade_eye.xml"));

			if (cascade == null) {
				log.error("Could not load classifier cascade");
			}
		}

	}

	@Override
	public IplImage process(IplImage image, OpenCVData data) {

		// Clear the memory storage which was used before
		cvClearMemStorage(storage);

		// Find whether the cascade is loaded, to find the faces. If yes, then:
		if (cascade != null) {

			// CV_HAAR_DO_CANNY_PRUNING - causes flat regions (no lines) to be
			// skipped
			// CV_HAAR_SCALE_IMAGE
			// CV_HAAR_FIND_BIGGEST_OBJECT - tells the detector to return the
			// biggest - hence # of objects will be 1 or none
			// CV_HAAR_DO_ROUGH_SEARCH

			// faces = cvHaarDetectObjects(grayImage, classifier, storage, 1.1,
			// 3, CV_HAAR_DO_ROUGH_SEARCH | CV_HAAR_FIND_BIGGEST_OBJECT);
			// faces = cvHaarDetectObjects(grayImage, classifier_eyes, storage,
			// 1.1, 3, CV_HAAR_DO_CANNY_PRUNING);
			CvSeq faces = cvHaarDetectObjects(image, cascade, storage, 1.1, 1, CV_HAAR_DO_CANNY_PRUNING | CV_HAAR_FIND_BIGGEST_OBJECT);

			if (faces != null) {
				ArrayList<Rectangle> bb = new ArrayList<Rectangle>();
				faceCnt = faces.total();
				// Loop the number of faces found.
				for (i = 0; i < faces.total(); i++) {

					CvRect r = new CvRect(cvGetSeqElem(faces, i));

					Rectangle rect;
					if (useFloatValues) {
						rect = new Rectangle((float) r.x() / width, (float) r.y() / height, (float) r.width() / width, (float) r.height() / height);
					} else {
						rect = new Rectangle(r.x(), r.y(), r.width(), r.height());
					}
					bb.add(rect);
				}

				data.put(bb);
			}
		} else {
			cascade = new CvHaarClassifierCascade(cvLoad(String.format("%s/%s", cascadeDir, cascadeFile)));
		}

		// WOOHOO LOOK AT THAT A STRING SWITCH !!!
		// 16 years later ! :D
		// converted from compiler into 2 stage hash switch :) cool !
		switch (state) {
		case STATE_LOST_TRACKING:
			if (faceCnt > 0) {
				firstFaceFrame = frameIndex;
				state = STATE_DETECTING_FACE;
				broadcastFilterState();
			}
			break;
		case STATE_DETECTING_FACE:
			if (faceCnt > 0 && frameIndex - firstFaceFrame > minFaceFrames) {
				state = STATE_DETECTED_FACE;
				// broadcastFilterState();
			} else if (faceCnt == 0) {
				firstFaceFrame = frameIndex;
			}
			break;
		case STATE_DETECTED_FACE:
			if (faceCnt == 0) {
				state = STATE_LOSING_TRACKING;
				firstFaceFrame = frameIndex;
				broadcastFilterState();
			}
			break;

		case STATE_LOSING_TRACKING:
			if (faceCnt == 0 && frameIndex - firstEmptyFrame > minEmptyFrames) {
				state = STATE_LOST_TRACKING;
				// broadcastFilterState();
			} else if (faceCnt > 0) {
				firstEmptyFrame = frameIndex;
			}
			break;
		default:
			log.error("invalid state");
			break;
		}
		// face detection events
		if (faceCnt > 0 && frameIndex - firstFaceFrame > minFaceFrames) {

		} else {

		}

		lastFaceCnt = faceCnt;
		return image;
	}

}
