import java.awt.Image;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.text.NumberFormat;
import java.util.Locale;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;

import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.videoio.VideoCapture;
import org.opencv.imgproc.Imgproc;

import com.sun.speech.freetts.Voice;
import com.sun.speech.freetts.VoiceManager;


public class Webcam {

	static VideoCapture	camera;
	static Voice		voice;

	public static void main(final String args[]) {

		System.out.println("Hello, OpenCV");
		// Load the native library.
		System.loadLibrary("opencv_java411");

		listAllVoices();
		System.setProperty("freetts.voices", "com.sun.speech.freetts.en.us.cmu_us_kal.KevinVoiceDirectory");
		VoiceManager voiceManager = VoiceManager.getInstance();
		voice = voiceManager.getVoice("kevin16");
		voice.allocate();

		camera = new VideoCapture(0);

		if (!camera.isOpened()) {
			System.out.println("Camera Error");
			}
		else {
			System.out.println("Camera OK?");
		}


		while (camera.isOpened()) {
			Mat frame = new Mat();



			// camera.grab();
			// System.out.println("Frame Grabbed");
			// camera.retrieve(frame);
			// System.out.println("Frame Decoded");

			camera.read(frame);
			Core.flip(frame, frame, -1);

			/*
			 * No difference camera.release();
			 */

			// System.out.println("Captured Frame Width " + frame.width());

			doMagic(frame);
			showResult(toBufferedImage(frame));

			/*
			 * try { Thread.sleep(10); } catch (InterruptedException e) { //
			 * TODO Auto-generated catch block e.printStackTrace(); }
			 */
		}
	}

	public static void listAllVoices() {
		System.out.println();
		System.out.println("All voices available:");
		VoiceManager voiceManager = VoiceManager.getInstance();
		Voice[] voices = voiceManager.getVoices();
		for (int i = 0; i < voices.length; i++) {
			System.out.println("    " + voices[i].getName()
					+ " (" + voices[i].getDomain() + " domain)");
		}
	}

	public static Image toBufferedImage(Mat m) {
		int type = BufferedImage.TYPE_BYTE_GRAY;
		if (m.channels() > 1) {
			Mat m2 = new Mat();
			m2 = m;
			// Imgproc.cvtColor(m,m2,Imgproc.COLOR_BGR2RGB);
			type = BufferedImage.TYPE_3BYTE_BGR;
			m = m2;
		}
		byte[] b = new byte[m.channels() * m.cols() * m.rows()];
		m.get(0, 0, b); // get all the pixels
		BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);
		image.getRaster().setDataElements(0, 0, m.cols(), m.rows(), b);
		return image;

	}

	static JLabel	outLabel;

	public static void showResult(final Image img) {

		if (outLabel == null) {
			final JFrame frame = new JFrame();
			frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
			frame.addWindowListener(new WindowAdapter() {
				@Override
				public void windowClosing(final WindowEvent e) {
					int i = JOptionPane.showConfirmDialog(null, "Really quit?");
					if (i == 0)
					{
						frame.dispose();
						voice.deallocate();
						camera.release();
						System.exit(0);// kill aplicacion
					}
				}
			});
			outLabel = new JLabel(new ImageIcon(img));
			frame.getContentPane().add(outLabel);
			frame.pack();
			frame.setVisible(true);
		}
		else {
			outLabel.setIcon(new ImageIcon(img));
		}

	}

	public static Mat doMagic(final Mat image) {

		Imgproc.cvtColor(image, image, Imgproc.COLOR_BGR2RGB);
		// Mat small = image.submat(image.rows()/2-100,
		// image.rows()/2+100,image.cols()/2-200,image.cols()/2+200);
		// Mat small = image.submat(100,image.rows()-100,50,image.cols()-50);
		// Mat small = image;
		Mat small = image.submat(50, image.rows() - 50, 50, image.cols() - 50);

		Scalar mean = Core.mean(small);

		// Scalar lastcolor = new Scalar(0,0,0,0);
		int nums[] = new int[small.cols()];
		double avgy[] = new double[small.cols()];
		// double colors[][] = new double[small.cols()][4];
		for (int x = 0; x < small.cols(); x += 1) {

			int num = 0;
			for (int y = 0; y < small.rows(); y += 1) {
				double res[] = small.get(y, x);
				if (res == null) {
					continue;
				}
				if (res[0] > mean.val[0] * 0.5 && res[1] > mean.val[1] * 0.5 && res[2] > mean.val[2] * 0.5) {
					// small.put(y,x,0,0,0);
				}
				else {
					num++;
					avgy[x] += y;
				}
			}
			if (num > 2) {
				nums[x] = num;
				avgy[x] /= num;
			}
		}

		double avgnum = 0;
		int avgnumnum = 0;
		for (int x = 0; x < small.cols(); x += 1) {
			if (nums[x] > 0) {
				avgnum += nums[x];
				avgnumnum++;
			}
		}
		int minx = -1;
		int maxx = 0;
		if (avgnumnum > 0) {
			avgnum /= avgnumnum * 1.0;

			for (int x = 0; x < small.cols(); x += 1) {
				if (nums[x] > avgnum + 15) {
					if (minx < 0) {
						minx = x;
					}
					maxx = x;

				}
			}
		}

		if (minx >= 0) {
			int miny = (int) avgy[minx];
			int maxy = (int) avgy[maxx];


			int minx2 = minx - 20;
			if (minx2 < 0) {
				minx2 = 0;
			}
			if (minx2 > avgy.length - 1) {
				minx2 = avgy.length - 1;
			}
			int maxx2 = maxx + 20;
			if (maxx2 < 0) {
				maxx2 = 0;
			}
			if (maxx2 > avgy.length - 1) {
				maxx2 = avgy.length - 1;
			}
			double a = (avgy[maxx2] - avgy[minx2]) / ((maxx2) - (minx2));

			miny = (int) (a * (minx - minx2) + avgy[minx2]);
			maxy = (int) (a * (maxx - maxx2) + avgy[maxx2]);

			// Mat tiny = small.submat(y1, y2, minx, maxx);

			int w = maxx - minx;

			if (w > 10 && a < 0.7 && a > -0.7) {

				for (int x = 0; x < w; x++) {
					int y = (int) (a * x) + miny;
					if (true) {// y>40 && y<small.rows()-40){
						double a2 = -1.0 / a;

						double res[] = new double[3];

						int x2 = x + minx;

						int oy = 15;
						int ox = (int) (oy / a2);

						double col1[] = small.get(y + oy, x2 + ox);
						small.put(y + oy, x2 + ox, 0.0, 0.0, 255.0);

						oy = -15;
						ox = (int) (oy / a2);

						double col2[] = small.get(y + oy, x2 + ox);
						small.put(y + oy, x2 + ox, 0.0, 0.0, 255.0);

						oy = 25;
						ox = (int) (oy / a2);

						double col3[] = small.get(y + oy, x2 + ox);
						small.put(y + oy, x2 + ox, 0.0, 0.0, 255.0);

						oy = -25;
						ox = (int) (oy / a2);

						double col4[] = small.get(y + oy, x2 + ox);
						small.put(y + oy, x2 + ox, 0.0, 0.0, 255.0);

						if (col1 != null && col2 != null && col3 != null && col4 != null) {
							res[0] = (col1[0] + col2[0] + col3[0] + col4[0]) / 4;
							res[1] = (col1[1] + col2[1] + col3[1] + col4[1]) / 4;
							res[2] = (col1[2] + col2[2] + col3[2] + col4[2]) / 4;
							image.put(0, x, res);
						}


					}
				}
				Mat res = new Mat();
				Imgproc.resize(image.submat(new Rect(0, 0, w, 1)), res, new Size(image.cols(), 50), 0, 0,
						Imgproc.INTER_NEAREST);
				res.copyTo(image.submat(new Rect(0, 0, res.cols(), res.rows())));

				detect(image, res);

			}

			Imgproc.line(small, new Point(minx, miny), new Point(maxx, maxy), new Scalar(255, 0, 0), 4);

		}

		return image;
	}

	static double	codes[][]		= { { 20, 20, 20 }, // black
			{ 71, 53, 38 }, // brown
			{ 105, 31, 40 }, // red
			{ 160, 90, 50 }, // orange
			{ 157, 123, 39 }, // yellow
			{ 41, 70, 46 }, // green
			{ 40, 73, 86 }, // blue
			{ 75, 55, 75 }, // violet
			{ 73, 65, 62 }, // gray
			{ 200, 200, 200 }		// white
									};


	static double	codesview[][]	= { { 0, 0, 0 }, // black
			{ 139, 69, 19 }, // brown
			{ 255, 0, 0 }, // red
			{ 255, 128, 0 }, // orange
			{ 255, 255, 0 }, // yellow
			{ 0, 255, 0 }, // green
			{ 0, 0, 255 }, // blue
			{ 200, 0, 255 }, // violet
			{ 128, 128, 128 }, // gray
			{ 255, 255, 255 }		// white
									};

	static String	codename[]		= { "black", // black
			"brown", // brown
			"red", // red
			"orange", // orange
			"yellow", // yellow
			"green", // green
			"blue", // blue
			"violet", // violet
			"gray", // gray
			"white" // white
									};


	private static double coldist(final double c1[], final double c2[]) {
		return Math.sqrt(Math.pow(c1[0] - c2[0], 2)
				+ Math.pow(c1[1] - c2[1], 2)
				+ Math.pow(c1[2] - c2[2], 2));
	}

	private static double coldist2(final double c1[], final double c2[]) {
		return Math.pow(c1[0] - c2[0], 2)
				+ Math.pow(c1[1] - c2[1], 2)
				+ Math.pow(c1[2] - c2[2], 2);
	}

	private static int	contfound	= 0;
	private static int	lastresult[];

	private static void detect(final Mat image, final Mat res) {
		double bg[] = new double[3];

		for (int x = res.cols() - 50; x < res.cols(); x++) {
			double val[] = res.get(0, x);
			for (int i = 0; i < 3; i++) {
				bg[i] += val[i] / 50.0;
			}
		}

		double bgdists[] = new double[res.cols()];
		double avgdist = 0;
		for (int x = 0; x < res.cols() - 5; x++) {
			bgdists[x] = coldist2(bg, res.get(0, x)) / 100;
			avgdist += bgdists[x] / res.cols();
			Imgproc.line(image, new Point(x * image.cols() / res.cols(), bgdists[x]), new Point((x + 1) * image.cols()
					/ res.cols(), bgdists[x]), new Scalar(255, 0, 0));
		}
		Imgproc.line(image, new Point(0, avgdist), new Point(image.cols(), avgdist), new Scalar(0, 0, 255));

		int coldet[] = new int[res.cols()];
		for (int x = 0; x < res.cols(); x++) {
			if (bgdists[x] > avgdist * 1.0) {
				double col[] = res.get(0, x);
				double min = 100000;
				int minc = -1;
				for (int c = 0; c < codes.length; c++) {
					double dist = coldist2(col, codes[c]);
					if (dist < min) {
						min = dist;
						minc = c;
					}
				}
				coldet[x] = minc;
				if (minc >= 0) {
					Imgproc.rectangle(image, new Point(x * image.cols() / res.cols(), 50),
							new Point((x + 1) * image.cols() / res.cols(), 100), new Scalar(codes[minc]), -1);
					Imgproc.rectangle(image, new Point(x * image.cols() / res.cols(), 100),
							new Point((x + 1) * image.cols() / res.cols(), 150), new Scalar(codesview[minc]), -1);

				}

			}
			else {
				coldet[x] = -1;
			}
		}

		int numconti = 0;
		int numcodes = 0;
		int sumcodes[] = new int[res.cols()];
		// int sumc[] = new int[codes.length];
		int result[] = new int[3];
		boolean found = false;
		for (int x = 0; x < res.cols(); x++) {
			if (coldet[x] == -1 && numconti > 20) { // 20 continuous at least
				// System.out.println("contiend");
				int sumc[] = new int[codes.length];
				for (int i = 0; i < numconti - 20; i++) {
					sumc[sumcodes[i]]++;
				}


				int maxnum = 0;
				int code = -1;
				for (int i = 0; i < codes.length; i++) {
					if (sumc[i] > maxnum) {
						maxnum = sumc[i];
						code = i;
					}

				}
				if (code != -1) {

					// darstellen

					Imgproc.line(image, new Point(x - 10, 0), new Point(x - 10, 100), new Scalar(0, 255, 0));
					Imgproc.rectangle(image, new Point(numcodes * image.cols() / 4, image.rows() - 50), new Point(
							(numcodes + 1) * image.cols() / 4, image.rows()), new Scalar(codesview[code]), -1);
					Imgproc.putText(image, codename[code], new Point(numcodes * image.cols() / 4, image.rows() - 10),
							Imgproc.FONT_HERSHEY_TRIPLEX, 1.0, new Scalar(200, 200, 200));
					// System.out.printf("found: %d\n", code);
					result[numcodes] = code;
					numcodes++;
					if (numcodes >= 3) {
						found = true;
						break;
					}
				}
				numconti = 0;

				sumcodes = new int[res.cols()];
			}
			else if (coldet[x] >= 0) {
				if (numconti > 10)
				{
					sumcodes[numconti - 10] = coldet[x]; // don't obey the left
															// border
				}
				if (numconti == 10) {
					Imgproc.line(image, new Point(x, 0), new Point(x, 100), new Scalar(0, 255, 0));
				}
				numconti++;
				// System.out.printf("num: %d coldet: %d\n", numconti,
				// coldet[x]);
			}
			else {
				numconti = 0;
			}

		}


		if (found) {
			if (lastresult == null) {
				lastresult = result;
			}
			if (result[0] == lastresult[0] && result[1] == lastresult[1] && result[2] == lastresult[2]) {
				contfound++;
				if (contfound > 10) {
					String speak = String.format("%s %s %s\n", codename[result[0]], codename[result[1]], codename[result[2]]);
					System.out.printf(speak);


					double resistance = calcvalue(result);
					String unit = " Ohm";
					if (resistance >= 1000.0) {
						resistance /= 1000;
						unit = "k";
					}
					if (resistance >= 1000.0) {
						resistance /= 1000;
						unit = "meg";
					}
					String valuestring = String.format(Locale.US, "%.1f%s\n", resistance, unit);
					if (valuestring.split(".")[1].startsWith("0")) {
						valuestring = String.format(Locale.US, "%d%s\n", (int) resistance, unit);
					}
					System.out.println(valuestring);
					// if(voice.getOutputQueue().)
					voice.speak(valuestring);
					// if ((synthesizer1.getEngineState() &
					// Synthesizer.QUEUE_EMPTY) > 0) {
					// synthesizer1.speakPlainText(valuestring, null);
					// }
					contfound = 0;
				}
			}
			else {
				contfound -= 1;
			}

			lastresult = result;
		}
		else {
			contfound -= 2;
			return;
		}
		if (contfound < 0) {
			contfound = 0;
		}

	}

	static double calcvalue(final int rings[]) {

		double result = rings[0] * 10 + rings[1];
		result *= Math.pow(10, rings[2]);

		return result;
	}
}