package webcam;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.media.Buffer;
import javax.media.CannotRealizeException;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoDataSourceException;
import javax.media.NoPlayerException;
import javax.media.Player;
import javax.media.control.FrameGrabbingControl;
import javax.media.format.RGBFormat;
import javax.media.format.VideoFormat;
import javax.media.protocol.CaptureDevice;
import javax.media.protocol.DataSource;
import javax.media.util.BufferToImage;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

import jp.ac.kyutech.ai.ylab.shiva.nyatools.utils.DeviceDetectionUtil;
import jp.ac.kyutech.ai.ylab.shiva.utils.nativecapable.NativeCapableModule;

public class WebcamBroadcaster implements NativeCapableModule {

	private JPanel rootPanel;

	private JPanel imagePanel;

	private Image currentImage;

	private JButton connectButton;

	private JTextField portField;

	private double fps;

	@SuppressWarnings("unchecked")
	private Player createPlayer(int width, int height) {
		try {

			DeviceDetectionUtil.doDetection();

			if (!DeviceDetectionUtil.isDeviceAvailable(320, 240, 30f, "RGB")) {
				JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
						"Couldn't find any capturing device.",
						"No Capturing Device Error", JOptionPane.ERROR_MESSAGE);
				System.exit(0);
			}
			// ---------------> DeviceDetection(End)
			VideoFormat vf = new VideoFormat("RGB", new Dimension(320, 240),
					Format.NOT_SPECIFIED, null, 30f);

			Vector<CaptureDeviceInfo> devices = CaptureDeviceManager
					.getDeviceList(vf);
			for (CaptureDeviceInfo info : devices) {

				Format[] formats = info.getFormats();
				for (Format format : formats) {
					if (!(format instanceof RGBFormat))
						continue;
					RGBFormat rgb = (RGBFormat) format;
					Dimension size = rgb.getSize();
					if (size.width != width || size.height != height)
						continue;
					if (rgb.getPixelStride() != 3)
						continue;
					if (rgb.getBitsPerPixel() != 24)
						continue;
					if (rgb.getLineStride() != width * 3)
						continue;
					MediaLocator locator = info.getLocator();
					source = Manager.createDataSource(locator);
					source.connect();
					System.out.println();
					((CaptureDevice) source).getFormatControls()[0]
							.setFormat(rgb);
					return Manager.createRealizedPlayer(source);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (NoPlayerException e) {
			e.printStackTrace();
		} catch (CannotRealizeException e) {
			e.printStackTrace();
		} catch (NoDataSourceException e) {
			e.printStackTrace();
		}
		return null;
	}

	// public static void main(String[] args) {
	// int[] values = new int[args.length];
	// for (int i = 0; i < values.length; i++) {
	// values[i] = Integer.parseInt(args[i]);
	// }
	//
	// if (values.length == 0) {
	// new WebcamBroadcaster();
	// } else if (values.length == 1) {
	// new WebcamBroadcaster(values[0]);
	// } else if (values.length == 2) {
	// new WebcamBroadcaster(values[0], values[1]);
	// } else {
	// new WebcamBroadcaster(values[0], values[1], values[2]);
	// }
	// }

	public static final int DEFAULT_PORT = 9889;
	public static final int DEFAULT_WIDTH = 320;
	public static final int DEFAULT_HEIGHT = 240;

	private final Object lock = new Object();

	private final int width;
	private final int height;
	private int port;

	private boolean running;

	private Player player;
	private DataSource source;
	private FrameGrabbingControl control;
	private boolean stopping;
	private Worker worker;

	public WebcamBroadcaster(int width, int height, int port) {
		this.width = width;
		this.height = height;
		this.port = port;

		rootPanel = new JPanel();
		imagePanel = new JPanel() {
			private static final long serialVersionUID = 103732197582672962L;

			@Override
			protected void paintComponent(Graphics g) {
				super.paintComponent(g);
				if (currentImage != null) {
					g.drawImage(currentImage, 0, 0, getWidth(), getHeight(),
							this);
					g.setColor(Color.RED);
					String txt = String.format("%f fps", fps);
					g.drawString(txt, 20, 20);
				}
			}
		};

		portField = new JTextField();
		portField.setText("" + port);

		connectButton = new JButton("Connect");
		connectButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				String txt = portField.getText();
				int p = 0;
				try {
					p = Integer.parseInt(txt);
				} catch (Exception ex) {
					return;
				}
				if (p < 1 || p > 65535) {
					return;
				}

				connectButton.setEnabled(false);
				WebcamBroadcaster.this.port = p;
				new Thread() {
					@Override
					public void run() {
						if (!running) {
							WebcamBroadcaster.this.start();
							connectButton.setText("Disconnect");
							connectButton.setEnabled(true);

						} else {
							try {
								WebcamBroadcaster.this.stop();
							} catch (InterruptedException e1) {
								e1.printStackTrace();
							}
							connectButton.setText("Connect");
							connectButton.setEnabled(true);
						}
					}
				}.start();
			}
		});
		JPanel p = new JPanel(new BorderLayout());
		p.add(new JLabel(" Port : "), BorderLayout.WEST);
		p.add(portField, BorderLayout.CENTER);
		p.add(connectButton, BorderLayout.EAST);

		rootPanel.setLayout(new BorderLayout());
		rootPanel.add(imagePanel, BorderLayout.CENTER);
		rootPanel.add(p, BorderLayout.SOUTH);

	}

	public WebcamBroadcaster(int width, int height) {
		this(width, height, DEFAULT_PORT);
	}

	public WebcamBroadcaster(int port) {
		this(DEFAULT_WIDTH, DEFAULT_HEIGHT, port);
	}

	public WebcamBroadcaster() {
		this(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_PORT);

		System.out.println("called");
	}

	public void start() {
		synchronized (lock) {
			if (running) {
				return;
			}
			player = createPlayer(width, height);
			if (player == null) {
				System.err.println("Unable to find a suitable player");
				return;
			}
			player.start();
			control = (FrameGrabbingControl) player
					.getControl("javax.media.control.FrameGrabbingControl");
			worker = new Worker();
			worker.start();
			running = true;
		}
	}

	public void stop() throws InterruptedException {
		synchronized (lock) {
			if (!running)
				return;
			if (player != null) {
				control = null;
				player.stop();
				source.disconnect();
				System.out.println("stop player");
				player = null;
			}
			stopping = true;
			running = false;
		}
		try {
			worker.join();
			worker = null;
		} finally {
			stopping = false;
		}
	}

	private class Worker extends Thread {

		@Override
		public void run() {
			ServerSocket ss = null;

			try {
				ss = new ServerSocket(port);
				ss.setSoTimeout(5000);
			} catch (IOException e) {
				e.printStackTrace();
				return;
			}

			long t = System.currentTimeMillis();

			while (true) {
				FrameGrabbingControl c;
				synchronized (lock) {
					if (stopping)
						break;
					c = control;
				}
				Socket socket = null;
				try {
					// System.out.println("Waiting for connection on port : "
					// + port);
					try {
						socket = ss.accept();
					} catch (SocketTimeoutException ex) {
						System.out.println("Soeckt time out");
						continue;
					}
					long t2 = System.currentTimeMillis();
					// System.out.println("Duration from previous connection : "
					// + (t2 - t) + " ms");
					fps = 1.0 / (t2 - t) * 1000;
					// System.out.println("Connected at " + t);
					t = t2;

					Buffer buffer = c.grabFrame();
					BufferToImage btoi = new BufferToImage((VideoFormat) buffer
							.getFormat());
					BufferedImage image = (BufferedImage) btoi
							.createImage(buffer);

					currentImage = image;
					imagePanel.repaint();

					if (image != null) {
						OutputStream out = socket.getOutputStream();

						ImageIO.write(image, "JPEG", out);
					}

					socket.close();
					socket = null;
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					if (socket != null)
						try {
							socket.close();
						} catch (IOException e) {
							/* ignore */
						}
				}

			}

			try {
				ss.close();
			} catch (IOException e) {
				/* ignore */
			}
		}

	}

	@Override
	public boolean destroyModule() {
		try {
			stop();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return true;
	}

	@Override
	public Component getRootComponent() {
		return rootPanel;
	}

	@Override
	public boolean initModule() {
		return true;
	}

	@Override
	public boolean startModule() {
		return true;
	}

}
