package gov.va.med.vss;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintException;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.SimpleDoc;
import javax.print.attribute.HashDocAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Copies;
import javax.print.attribute.standard.PrinterIsAcceptingJobs;
import javax.print.attribute.standard.PrinterName;
import javax.print.event.PrintJobAdapter;
import javax.print.event.PrintJobEvent;

public class PrintUtil {

	private String printerNameSubstring;

	public PrintUtil(String printerNameSubstring) {
		this.printerNameSubstring = printerNameSubstring;
	}

	public static enum LookupMode {
		PRINT_TO_ALL_MATCHES, PRINT_TO_FIRST_MATCH, THROW_EXCEPTION;
		// if print to all matches, may need to set this up as a scheduled job
		// to purge the inactive device print queue:
		// Runtime.getRuntime().exec() net stop spooler del
		// C:\WINDOWS\system32\spool\PRINTERS*.* /q net start spooler
	}

	public void printPlainText(String text) throws PrintException, IOException {
		List<PrintService> services = getServicesToPrintTo(LookupMode.PRINT_TO_ALL_MATCHES, printerNameSubstring);

		for (PrintService service : services) {
			InputStream is = new SequenceInputStream(new ByteArrayInputStream(text.getBytes("UTF8")),
					/*
					 * activate cutter per
					 * http://www.touchpos.net/docs/star/sp700sm.pdf - CPB
					 */
					new ByteArrayInputStream(new byte[] { 0x1B, 0x64, 0x32 }));

			PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
			pras.add(new Copies(1));

			DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
			Doc doc = new SimpleDoc(is, flavor, null);
			DocPrintJob job = service.createPrintJob();

			PrintJobWatcher pjw = new PrintJobWatcher(job);
			job.print(doc, pras);
			pjw.waitForDone();
			is.close();
		}
	}

	private List<PrintService> getServicesToPrintTo(LookupMode mode, String printerNameSubstring) {
		List<PrintService> services = new ArrayList<>();
		PrintService[] availableServices = PrintServiceLookup.lookupPrintServices(DocFlavor.INPUT_STREAM.AUTOSENSE,
				new HashDocAttributeSet());

		for (PrintService availableService : availableServices) {
			PrinterName name = availableService.getAttribute(PrinterName.class);
			PrinterIsAcceptingJobs isAcceptingJobs = availableService.getAttribute(PrinterIsAcceptingJobs.class);

			if (name.toString().contains(printerNameSubstring)
					&& isAcceptingJobs == PrinterIsAcceptingJobs.ACCEPTING_JOBS) {
				services.add(availableService);
				if (mode == LookupMode.PRINT_TO_FIRST_MATCH)
					break;
				if (mode == LookupMode.THROW_EXCEPTION && services.size() > 1)
					throw new RuntimeException("Multiple SP700 printers found, unsure which to print to...");
			}
		}

		return services;
	}
}

class PrintJobWatcher {
	boolean done = false;

	PrintJobWatcher(DocPrintJob job) {
		job.addPrintJobListener(new PrintJobAdapter() {
			public void printJobCanceled(PrintJobEvent pje) {
				allDone();
			}

			public void printJobCompleted(PrintJobEvent pje) {
				allDone();
			}

			public void printJobFailed(PrintJobEvent pje) {
				allDone();
			}

			public void printJobNoMoreEvents(PrintJobEvent pje) {
				allDone();
			}

			void allDone() {
				synchronized (PrintJobWatcher.this) {
					done = true;
					// log.debug("Printing done...");
					PrintJobWatcher.this.notify();
				}
			}
		});
	}

	public synchronized void waitForDone() {
		try {
			while (!done) {
				wait();
			}
		} catch (InterruptedException e) {
		}
	}
}