import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;

import sun.misc.BASE64Decoder;

/**
 * An example class showing connection to DataPaq via a socket. Note that this
 * class writes a lot of information to standard out (System.out) as it is
 * example code. It is recommended that a logging framework be used to replace
 * these System.out calls.
 * 
 * @author Neil Benn
 * @version 1.2
 */
public class DataPaqRemote {

	private BufferedReader in = null;
	private OutputStream out = null;
	private String host;
	private Integer port;

	private static final Integer DATAPAQ_SERVER_PORT = 8888;
	private static final String DATAPAQ_SERVER_HOST = "localhost";

	/**
	 * Initialises and connects to the DataPaq scanner
	 * 
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public DataPaqRemote(String host, Integer port)
			throws UnknownHostException, IOException {
		// create the socket, incoming and outgoing streams
		this.host = host;
		this.port = port;
		Socket clientSocket = new Socket(this.host, this.port);
		out = clientSocket.getOutputStream();
		in = new BufferedReader(new InputStreamReader(clientSocket
				.getInputStream()));
		// this is returned on each connection
		System.out.println(in.readLine());
	}

	/**
	 * Returns the version number of the running DataPaq server.
	 * 
	 * @return
	 * @throws IOException
	 */
	public String getVersion() throws IOException {
		// get the version number
		out.write("VERSION\r\n".getBytes());
		out.flush();
		String version = in.readLine();
		System.out.println("VERSION " + version);
		System.out.println("VERSION ACKNOWLEDGEMENT " + in.readLine());
		return version;
	}

	/**
	 * Returns the status of the scanner
	 * @return the scanner status
	 * @throws IOException if the scanner cannot be communicated with
	 */
	public String getStatus() throws IOException{
		out.write("STATUS\r\n".getBytes());
		out.flush();
		String status = in.readLine();
		System.out.println("STATUS " + status);
		System.out.println("STATUS ACKNOWLEDGEMENT " + in.readLine());
		return status;
	}
	
	/**
	 * Runs a scan in text mode and returns the results of the scan in a string.
	 * 
	 * @param uid
	 *            the unique id of the plate to scan as configured in the
	 *            DataPaq application
	 * @return a string representing the scan result
	 * @throws IOException
	 *             if there is a problem writing to the socket out or reading
	 *             from socket in
	 */
	public String runScan(String uid) throws IOException {
		// perform a scan
		out.write(("SCAN " + uid + " TEXT\r\n").getBytes());
		out.flush();
		System.out.println("SCAN RECEIVED " + in.readLine());

		// read in all the results, appending to the StringBuffer
		StringBuffer scanBuffer = new StringBuffer();
		Boolean scanFirstLine = true;
		while (true) {
			String line = in.readLine();
			if (scanFirstLine) {
				if (line.startsWith("ERR")) {
					throw new RuntimeException("Scanner reported error - "
							+ line + in.readLine());
				}
				scanFirstLine = false;
			}
			if (line.equals("OK")) {
				break;
			}
			scanBuffer.append(line).append("\r\n");
		}
		return scanBuffer.toString();
	}

	/**
	 * Returns the last scanned image from the DataPaq server.
	 * 
	 * @param scanPos
	 *            the position on the scanner to retrieve, starts counting at
	 *            zero
	 * @return a buffered image representing the scanned image
	 * @throws IOException
	 *             if there is a problem writing to the socket out or reading
	 *             from socket in
	 */
	public BufferedImage getLastScanImage(Integer scanPos) throws IOException {
		// get the image of the last scan
		System.out.println("Getting last scanned image");
		out.write("LAST_IMAGE 0\r\n".getBytes());
		out.flush();

		System.out.println("reading image in");
		// reading image in
		StringBuffer buffer = new StringBuffer();
		Boolean imageFirstLine = true;
		while (true) {
			String line = in.readLine();
			buffer.append(line).append("\r\n");
			// the first line may be an error so we check for that
			// and then set first lien to false as the rest of the
			// lines will not be in error
			if (imageFirstLine) {
				imageFirstLine = false;
				if (line.startsWith("ERR")) {
					System.out.println("Error " + line);
					throw new RuntimeException("failed to read image " + line
							+ " " + in.readLine());
				}
			} else {
				if (line.length() == 0) {
					break;
				}
			}

		}

		// we have the image we now need to convert the encoded string to bytes
		// and then write the image to a file
		byte[] decoded = new BASE64Decoder().decodeBuffer(buffer.toString());
		BufferedImage bi = ImageIO.read(new ByteArrayInputStream(decoded));
		return bi;
	}


	/**
	 * Disconnected this client from the DataPaq server. The server remains
	 * operational after this call.
	 * 
	 * @throws IOException
	 *             if there is a problem writing to the socket out or reading
	 *             from socket in
	 */
	public void close() throws IOException {
		out.write("CLOSE\r\n".getBytes());
		out.flush();
		System.out.println(in.readLine());
	}

	/**
	 * Shuts down the DataPaq server if it is not busy. Note that this will
	 * disconnect all other connected clients.
	 * 
	 * @throws IOException
	 *             if there is a problem writing to the socket out or reading
	 *             from socket in
	 */
	public void shutdown() throws IOException {
		out.write("SHUTDOWN\r\n".getBytes());
		out.flush();
		System.out.println(in.readLine());
	}

	/**
	 * Tests execution of the DataPaq server via a socket. Before executing this
	 * ensure that the DataPaq server is running (the port of 8888 is the
	 * default port which will be used), this is assumes that the test is
	 * running on the same machine as the DataPaq server. It also assumes the
	 * server is already running.
	 * 
	 * 
	 * @throws UnknownHostException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public static void main(String args[]) throws UnknownHostException,
			IOException {
		DataPaqRemote dpr = null;
		try {

			System.out.println("Connecting");
			dpr = new DataPaqRemote(DataPaqRemote.DATAPAQ_SERVER_HOST,
					DataPaqRemote.DATAPAQ_SERVER_PORT);
			System.out.println(dpr.getVersion());
			System.out.println(dpr.getStatus());
			System.out.println(dpr.runScan("1"));
			Image i = dpr.getLastScanImage(0);
			i = i.getScaledInstance(i.getWidth(null)/5, 
									i.getHeight(null)/5, 
									Image.SCALE_SMOOTH);
			showImageInFrame(i);
			System.out.println("success");
		} finally {
			System.out.println("closing");
			if (dpr != null){
				dpr.close();
			}
		}
	}
	
	/**
	 * Shows the image specified in the parameter in a frame.
	 * 
	 * @param bi the image to show
	 */
	private static void showImageInFrame(Image bi){
		JFrame frame = new JFrame("Scanned Image");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		JLabel l = new JLabel();
		l.setIcon(new ImageIcon(bi));
		frame.getContentPane().add(l);
		frame.pack();
		frame.setResizable(false);
		frame.setLocation(
				(int)(Toolkit.getDefaultToolkit().getScreenSize().getWidth() - 
						frame.getWidth())/2, 
				(int)(Toolkit.getDefaultToolkit().getScreenSize().getHeight() - 
						frame.getHeight())/2); 
		frame.setVisible(true);

	}
}

