import java.awt.AWTException;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

import org.apache.log4j.Logger;

/**
 * Contain macro to access to the PDF
 * 
 * @author JPR
 * 
 */
public class MacroMethods
{
	static Logger logger = Logger.getLogger(MacroMethods.class.getName());

	private static Robot robot; // to send macros

	/**
	 * Constructor
	 * 
	 * @throws AWTException
	 */
	public MacroMethods() throws ErrorException
	{
		try
		{
			robot = new Robot();
		}
		catch (AWTException e)
		{
			throw new ErrorException("01 : " + e.getMessage(), 1);
		}
	}

	/**
	 * send a key char to the PDF
	 * 
	 * @param shift
	 *            true to send shift is pressed
	 * @param ctrl
	 *            true to send control is pressed
	 * @param key
	 *            the key char to send
	 */
	public void sendKeys(boolean alt, boolean shift, boolean ctrl, char key, boolean fast) throws ErrorException
	{
		logger.info("sendKeys:" + key);
		if (alt)
		{
			robot.keyPress(KeyEvent.VK_ALT);
		}
		if (shift)
		{
			robot.keyPress(KeyEvent.VK_SHIFT);
		}
		if (ctrl)
		{
			robot.keyPress(KeyEvent.VK_CONTROL);
		}
		int ascii = (int) key;
		robot.keyPress(ascii);
		waitLowProcessor(true);
		robot.keyRelease(ascii);
		if (ctrl)
		{
			robot.keyRelease(KeyEvent.VK_CONTROL);
		}
		if (shift)
		{
			robot.keyRelease(KeyEvent.VK_SHIFT);
		}
		if (alt)
		{
			robot.keyRelease(KeyEvent.VK_ALT);
		}
		waitLowProcessor(fast);
	}

	/**
	 * send a key int to the PDF
	 * 
	 * @param shift
	 *            true to send shift is pressed
	 * @param ctrl
	 *            true to send control is pressed
	 * @param key
	 *            the key char to send
	 */
	public void sendKeys(boolean alt, boolean shift, boolean ctrl, int key, boolean fast) throws ErrorException
	{
		logger.info("sendKeys:" + Integer.toString(key));
		if (alt)
		{
			robot.keyPress(KeyEvent.VK_ALT);
		}
		if (shift)
		{
			robot.keyPress(KeyEvent.VK_SHIFT);
		}
		if (ctrl)
		{
			robot.keyPress(KeyEvent.VK_CONTROL);
		}

		robot.keyPress(key);
		waitLowProcessor(true);
		robot.keyRelease(key);
		if (ctrl)
		{
			robot.keyRelease(KeyEvent.VK_CONTROL);
		}
		if (shift)
		{
			robot.keyRelease(KeyEvent.VK_SHIFT);
		}
		if (alt)
		{
			robot.keyRelease(KeyEvent.VK_ALT);
		}
		waitLowProcessor(fast);
	}

	/**
	 * send the keys to put the PDF in full screen
	 */
	public void sendFullScreen() throws ErrorException
	{
		logger.info("sendFullScreen");
		// maximize
		sendKeys(false, false, true, 'L', false);
		// zoom 50%
		sendKeys(false, false, true, 'Y', false);
		sendString("50");
		sendEnter();
		// single page view
		sendKeys(true, false, false, 'V', false);
		sendKeys(false, false, false, 'P', false);
		sendKeys(false, false, false, 'S', false);
	}

	/**
	 * send keys to open the "Goto page" popup
	 */
	public void sendGotoPage() throws ErrorException
	{
		logger.info("sendGotoPage");
		sendKeys(false, true, true, 'N', false);
	}

	/**
	 * send keys to go to last page
	 */
	public void sendGotoLastPage() throws ErrorException
	{
		logger.info("sendGotoLastPage");
		sendKeys(false, false, false, KeyEvent.VK_END, false);
	}

	/**
	 * send keys to Escape a popup
	 */
	public void sendEsc() throws ErrorException
	{
		logger.info("sendEsc");
		sendKeys(false, false, false, KeyEvent.VK_ESCAPE, false);
	}

	/**
	 * send keys to do a paste
	 */
	public void sendPaste() throws ErrorException
	{
		logger.info("sendPaste");
		sendKeys(false, false, true, 'V', false);
	}

	/**
	 * send keys to do an undo
	 */
	public void sendUndo() throws ErrorException
	{
		logger.info("sendUndo");
		sendKeys(false, false, true, 'Z', false);
	}

	/**
	 * send keys to do a left rotation
	 */
	public void sendLeftRotation() throws ErrorException
	{
		logger.info("sendLeftRotation");
		sendKeys(true, false, false, 'V', false);
		sendKeys(false, false, false, 'V', false);
		sendKeys(false, false, false, 'c', false);
	}

	/**
	 * send keys to do a right rotation
	 */
	public void sendRightRotation() throws ErrorException
	{
		logger.info("sendRightRotation");
		sendKeys(true, false, false, 'V', false);
		sendKeys(false, false, false, 'V', false);
		sendKeys(false, false, false, 'w', false);
	}

	/**
	 * send keys to do a select all
	 */
	public void sendSelectAll() throws ErrorException
	{
		logger.info("sendSelectAll");
		sendKeys(false, false, true, 'A', false);
	}

	/**
	 * send keys to close a window
	 */
	public void sendCloseWindow() throws ErrorException
	{
		logger.info("");
		sendKeys(true, false, false, KeyEvent.VK_F4, false);
	}

	/**
	 * send tab key
	 */
	public void sendTab() throws ErrorException
	{
		logger.info("");
		sendKeys(false, false, false, KeyEvent.VK_TAB, false);
	}

	/**
	 * minimize
	 */
	public void setApplicationToMinimized(Frame frame)
	{
		frame.setState(Frame.ICONIFIED);
	}

	/**
	 * normal
	 */
	public void setApplicationToNormal(Frame frame)
	{
		frame.setState(Frame.NORMAL);
	}

	/**
	 * send keys to switch to form mode
	 */
	public void sendFormMode() throws ErrorException
	{
		logger.info("sendFormMode");
		sendKeys(false, true, true, KeyEvent.VK_NUMPAD7, false);
		sendEsc();
	}

	/**
	 * send keys to save
	 */
	public void sendSave() throws ErrorException
	{
		logger.info("sendSave");
		sendKeys(false, false, true, 'S', false);
		waitLowProcessorWithDelay();
	}

	/**
	 * send keys to do a copy
	 */
	public void sendCopy() throws ErrorException
	{
		logger.info("sendCopy");
		sendKeys(false, false, true, 'C', false);
	}

	/**
	 * 1
	 * 
	 * save a capture of the screen
	 * 
	 * @throws AWTException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public BufferedImage screenCapture(Rectangle dim, File file) throws ErrorException
	{
		logger.info("screenCapture");
		try
		{
			BufferedImage screencapture;
			if (dim == null)
			{
				screencapture = new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
			}
			else
			{
				screencapture = new Robot().createScreenCapture(dim);
			}
			ImageIO.write(screencapture, "jpg", file);
			return screencapture;
		}
		catch (AWTException | IOException e)
		{
			throw new ErrorException("02 : " + e.getMessage(), 1);
		}
	}

	/**
	 * used to send the number of a page
	 * 
	 * @param string
	 *            number of the page
	 */
	public void sendString(String string) throws ErrorException
	{
		logger.info("sendString:" + string);
		for (int i = 0; i < string.length(); i++)
		{
			switch (string.charAt(i))
			{
			case '0':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD0, true);
				break;

			case '1':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD1, true);
				break;

			case '2':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD2, true);
				break;

			case '3':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD3, true);
				break;

			case '4':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD4, true);
				break;

			case '5':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD5, true);
				break;

			case '6':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD6, true);
				break;

			case '7':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD7, true);
				break;

			case '8':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD8, true);
				break;

			case '9':
				sendKeys(false, false, false, KeyEvent.VK_NUMPAD9, true);
				break;
			}
		}
		waitLowProcessor(false);
	}

	/**
	 * send the key enter
	 */
	public void sendEnter() throws ErrorException
	{
		logger.info("sendEnter");
		robot.keyPress(10);
		waitLowProcessor(true);
		robot.keyRelease(10);
		waitLowProcessor(false);
	}

	/**
	 * change to the page number passed in parameter
	 * 
	 * @param pageNumber
	 * @throws AWTException
	 */
	public void goToPageNumber(Integer pageNumber) throws ErrorException
	{
		logger.info("gotoPageNumber:" + Integer.toString(pageNumber));
		sendGotoPage();
		sendString(Integer.toString(pageNumber));
		sendEnter();
	}

	/**
	 * go to last page, and get it's page number
	 * 
	 * @throws AWTException
	 * @throws IOException
	 * @throws UnsupportedFlavorException
	 */
	public String getLastPageNumber() throws ErrorException
	{
		logger.info("getLastPageNumber");
		sendGotoLastPage();
		return getCurrentPageNumber();
	}

	/**
	 * go to last page, and get it's page number
	 * 
	 * @throws AWTException
	 * @throws IOException
	 * @throws UnsupportedFlavorException
	 */
	public String getCurrentPageNumber() throws ErrorException
	{
		logger.info("getPageNumber");
		ServiceClipboard serviceClipboard = new ServiceClipboard();
		serviceClipboard.setClipboardContents("0");
		String result;
		int nbRead = 0;
		do
		{
			sendGotoPage();
			sendCopy();
			result = (String) serviceClipboard.getClipboardContents();
			sendEsc();
			nbRead++;
		}
		while ((result == null || result.equalsIgnoreCase("0")) && nbRead < 5);
		if (nbRead == 5)
		{
			result = "1";
			throw new ErrorException("79 : can't read clipboard content", 1);
		}
		return result;
	}

	/**
	 * get text inside stample
	 * 
	 * @throws AWTException
	 * @throws IOException
	 * @throws UnsupportedFlavorException
	 */
	public String getTextInsideStample(int pageNumber) throws ErrorException
	{
		logger.info("getTextInsideStample");
		ServiceClipboard serviceClipboard = new ServiceClipboard();
		serviceClipboard.setClipboardContents("-------");
		goToPageNumber(pageNumber);
		sendTab();
		sendSelectAll();
		String result;
		int nbRead = 0;
		do
		{
			sendCopy();
			result = (String) serviceClipboard.getClipboardContents();
			nbRead++;
		}
		while ((result == null || result.equalsIgnoreCase("-------")) && nbRead < 5);
		if (nbRead == 5)
		{
			throw new ErrorException("80 : can't read clipboard content to get text inside stamp", 1);
		}
		return result;
	}

	/**
	 * get text inside stample
	 * 
	 * @throws AWTException
	 * @throws IOException
	 * @throws UnsupportedFlavorException
	 */
	public void setTextInsideStample(int pageNumber, String text) throws ErrorException
	{
		logger.info("setTextInsideStample");
		goToPageNumber(pageNumber);
		sendTab();
		sendSelectAll();
		ServiceClipboard serviceClipboard = new ServiceClipboard();
		serviceClipboard.setClipboardContents(text);
		waitLowProcessor(false);
		sendPaste();
	}

	public void killAcrobat() throws ErrorException
	{

		logger.info("killAcrobat");
		try
		{
			String[] cmd = new String[4];
			cmd[0] = "taskkill";
			cmd[1] = "/F";
			cmd[2] = "/IM";
			cmd[3] = "Acrobat.exe";

			Runtime rt = Runtime.getRuntime();
			Process proc = rt.exec(cmd);
			proc.waitFor();
			waitLowProcessorWithDelay();
		}
		catch (Exception e)
		{
			throw new ErrorException("12+ : kill pdf :" + e.getMessage(), 1);
		}
	}

	/**
	 * open the PDF corresponding to the File in parameter
	 * 
	 * @param job
	 */
	public Process openPdf(File file) throws ErrorException
	{
		logger.info("openPdf: " + file.getAbsolutePath());
		Process p = null;

		String fileName = file.getAbsolutePath();
		if (!(new File(fileName)).exists())
		{
			throw new ErrorException("120 : open pdf file not exists:", 1);
		}

		try
		{
			p = Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + fileName);
		}
		catch (Exception e)
		{
			throw new ErrorException("120 : open pdf :" + e.getMessage(), 1);
		}

		int acrobatNotRuningCount = 0;
		CPUController cpuController = new CPUController();
		do
		{
			acrobatNotRuningCount++;
			sleep(30);
			if ((acrobatNotRuningCount % 5) == 0)
			{
				robot.keyPress(KeyEvent.VK_CAPS_LOCK);
				robot.keyRelease(KeyEvent.VK_CAPS_LOCK);
				sleep(50);
				robot.keyPress(KeyEvent.VK_CAPS_LOCK);
				robot.keyRelease(KeyEvent.VK_CAPS_LOCK);
			}
			if (cpuController.getAcrobatPID() == 200)
			{
				throw new ErrorException("123 : open pdf too slow:", 1);
			}
		}
		while (cpuController.getAcrobatPID() == 0);
		waitLowProcessor(false);

		// send enter in case of a recovery error
		sendEnter();

		return p;
	}

	/**
	 * destroy the process
	 * 
	 * @param process
	 */
	public void destroyProcess(Process process)
	{
		process.destroy();
	}

	/**
	 * wait until the pdf is opened or saved
	 */
	public void waitLowProcessorWithDelay() throws ErrorException
	{
		waitLowProcessor(false, 20);
	}

	/**
	 * wait while the page is not loaded
	 */
	public void waitLowProcessor(boolean fastWait) throws ErrorException
	{
		waitLowProcessor(fastWait, 5);
	}

	/**
	 * wait while the page is not loaded
	 */
	public void waitLowProcessor(boolean fastWait, int testLenght) throws ErrorException
	{
		if (fastWait)
		{
			sleep(30);
		}
		else
		{

			logger.info("waitLowProcessor");
			int cpttry = 0;
			int processorNotWorkingCount = 0;

			do
			{

				sleep(10);
				cpttry++;
				if (cpttry == 250)
				{
					throw new ErrorException("04 : processor is too busy", 1);
				}

				CPUController cpuController = new CPUController();
				if (cpuController.getCpu() > 0)
				{
					processorNotWorkingCount = 0;
					sleep(30);
					if ((cpttry % 5) == 0)
					{
						robot.keyPress(KeyEvent.VK_CAPS_LOCK);
						robot.keyRelease(KeyEvent.VK_CAPS_LOCK);
						sleep(50);
						robot.keyPress(KeyEvent.VK_CAPS_LOCK);
						robot.keyRelease(KeyEvent.VK_CAPS_LOCK);
					}
				}
				else
				{
					processorNotWorkingCount++;
				}
			}
			while (processorNotWorkingCount < testLenght);

		}

	}

	public void sleep(Integer delay) throws ErrorException
	{
		try
		{
			Thread.sleep(delay);
		}
		catch (InterruptedException e)
		{
			throw new ErrorException("77 : " + e.getMessage(), 1);
		}
	}

	/**
	 * move an object to the given position or select a zone
	 */
	public void move(int x1, int y1, int x2, int y2) throws ErrorException
	{
		logger.info("moveMouse");
		robot.mouseMove(x1, y1);
		waitLowProcessor(true);
		robot.mousePress(16);
		waitLowProcessor(false);
		robot.mouseMove(x2, y2);
		waitLowProcessor(true);
		robot.mouseRelease(16);
		waitLowProcessor(false);
	}

	/**
	 * Select an object at the given position
	 */
	public void select(int x, int y) throws ErrorException
	{
		logger.info("selectMouse");
		robot.mouseMove(x, y);
		waitLowProcessor(true);
		robot.mousePress(16);
		waitLowProcessor(false);
		robot.mouseRelease(16);
		waitLowProcessor(false);
	}

}