package gov.va.med.vss;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinReg;
import com.sun.jna.platform.win32.WinReg.HKEY;

public class VssPrintMonitor {
	static Log log = LogFactory.getLog(VssPrintMonitor.class);

	static String printerSubstring = null;
	static boolean disableHostnameVerification = false;
	static String urlRoot = null;
	static String registryPath = null;

	long kioskId = -1;
	PrintUtil printUtil;

	public final static void main(String[] args) throws Exception {
		List<String> lines = FileUtils.readLines(new File("config.properties"), "UTF-8");

		Boolean myDisableHostnameVerification = null;

		for (String line : lines) {
			if (line.startsWith("printerSubstring=")) {
				printerSubstring = line.substring("printerSubstring=".length());
			} else if (line.startsWith("disableHostnameVerification=")) {
				myDisableHostnameVerification = Boolean
						.valueOf(line.substring("disableHostnameVerification=".length()));
			} else if (line.startsWith("urlRoot=")) {
				urlRoot = line.substring("urlRoot=".length());
			} else if (line.startsWith("registryPath=")) {
				registryPath = line.substring("registryPath=".length());
			}
		}

		if (printerSubstring == null) {
			System.err.println("Missing required property 'printerSubstring'");
			return;
		}

		if (myDisableHostnameVerification == null) {
			System.err.println("Missing required property 'disableHostnameVerification'");
			return;
		}
		disableHostnameVerification = myDisableHostnameVerification;

		if (urlRoot == null) {
			System.err.println("Missing required property 'urlRoot'");
			return;
		}

		if (registryPath == null) {
			System.err.println("Missing required property 'registryPath'");
			return;
		}

		new VssPrintMonitor().go();
	}

	public VssPrintMonitor() {
		this.printUtil = new PrintUtil(printerSubstring);
	}

	public void go() throws InterruptedException {
		new Thread(new KioskIdMonitor()).start();
		new Thread(new RequestGenerator()).start();

		while (true) {
			Thread.sleep(60000);
		}
	}

	class KioskIdMonitor implements Runnable {
		@Override
		public void run() {
			String[] tokens = registryPath.split("\\\\", 2);

			HKEY root = null;
			if ("HKEY_CURRENT_USER".equals(tokens[0])) {
				root = WinReg.HKEY_CURRENT_USER;
			} else if ("HKEY_LOCAL_MACHINE".equals(tokens[0])) {
				root = WinReg.HKEY_LOCAL_MACHINE;
			} else if ("HKEY_USERS".equals(tokens[0])) {
				root = WinReg.HKEY_USERS;
			}

			if (root == null)
				throw new RuntimeException("Invalid registry root item " + tokens[0]
						+ " - must be one of {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS}");

			try {
				while (true) {
					String startPage = null;
					try {
						try {
							String test = Advapi32Util.registryGetStringValue(root, tokens[1],
									"Start Page");
							if (test.contains("kioskId=")) {
								startPage = test;
							} else {
								String[] secondaryPages = Advapi32Util.registryGetStringArray(root,
										tokens[1], "Secondary Start Pages");
								for (String secondaryPage : secondaryPages)
									if (secondaryPage.contains("kioskId=")) {
										startPage = secondaryPage;
										break;
									}
							}
						} catch (Win32Exception ignored) {

						}

						if (startPage != null) {
							List<NameValuePair> params = URLEncodedUtils.parse(new URI(startPage), "UTF-8");
							for (NameValuePair param : params) {
								if (param.getName().equals("kioskId")) {
									kioskId = Long.parseLong(param.getValue());
									log.info("Discovered kiosk ID " + kioskId);
								}
							}
						} else {
							kioskId = -1;
							log.error("Couldn't determine kiosk ID from IE homepage in registry...");
						}
					} catch (URISyntaxException e) {
						log.error("Invalid URI syntax of homepage: " + startPage);
					} catch (Exception e) {
						if (e instanceof InterruptedException)
							throw e;
						log.error("Unexpected error", e);
					} finally {
						Thread.sleep(60000);
					}
				}
			} catch (InterruptedException e) {
				log.debug("Interrupted, exiting...", e);
			}
		}
	}

	public CloseableHttpClient createHttpClient() {
		HttpClientBuilder b = HttpClientBuilder.create();

		try {
			if (disableHostnameVerification) {
				HttpsURLConnection.setDefaultHostnameVerifier(new NoopHostnameVerifier());

				SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy())
						.build();
				SSLContext.setDefault(sslContext);

				SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(
						SSLContexts.createSystemDefault(), new NoopHostnameVerifier());
				b.setSSLSocketFactory(sslConnectionFactory);
				Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
						.register("https", sslConnectionFactory)
						// .register("http", new PlainConnectionSocketFactory())
						.build();
				HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry);
				b.setConnectionManager(ccm);
			}

			return b.build();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	class RequestGenerator implements Runnable {
		@Override
		public void run() {
			RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(-1).setConnectTimeout(-1)
					.setConnectionRequestTimeout(-1).build();
			try {
				Thread.sleep(5000);

				while (true) {
					if (kioskId == -1) {
						Thread.sleep(20000);
						continue;
					}

					try (CloseableHttpClient httpclient = createHttpClient()) {
						HttpGet httpget = new HttpGet(urlRoot + kioskId);
						httpget.setConfig(requestConfig);

						// Create a custom response handler
						ResponseHandler<List<String>> responseHandler = new ResponseHandler<List<String>>() {
							@Override
							public List<String> handleResponse(final HttpResponse response)
									throws ClientProtocolException, IOException {
								int status = response.getStatusLine().getStatusCode();
								if (status < 200 || status >= 300)
									throw new ClientProtocolException("Unexpected response status: " + status);

								HttpEntity entity = response.getEntity();
								if (entity == null)
									return null;

								try (InputStream instream = entity.getContent();) {
									ObjectMapper mapper = new ObjectMapper();
									String instreamStr = IOUtils.toString(instream, Charset.defaultCharset());

									@SuppressWarnings("unchecked")
									List<String> items = mapper.readValue(instreamStr, List.class);
									for (int i = 0; i < items.size(); i++) {
										/*
										 * Remove any non-printable characters
										 * just to be safe
										 */
										items.set(i, items.get(i).replaceAll("[^\\p{Space}}\\p{Print}}]", ""));
									}
									return items;
								}
							}
						};
						List<String> itemsToPrint = httpclient.execute(httpget, responseHandler);
						for (String item : itemsToPrint) {
							printUtil.printPlainText(item);
						}
					} catch (Throwable e) {
						if (e instanceof InterruptedException)
							throw ((InterruptedException) e);

						log.error("Encountered error generating request", e);
						Thread.sleep(30000);
					}
				}
			} catch (InterruptedException e) {
				log.debug("Thread interrupted, exiting...", e);
			}
		}
	}

}
