package com.enactor.pos.test;

import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.testng.TestException;

import com.enactor.core.automation.AUT.AUTConfig;
import com.enactor.core.automation.AUT.AUTServer;
import com.enactor.core.automation.AUT.AUTServerPool;
import com.enactor.core.automation.AUT.AutomationException;
import com.enactor.core.automation.AUT.IAUTControllerClient;
import com.enactor.core.automation.actions.ClickComponentAction;
import com.enactor.core.automation.actions.ConnectToAUTAction;
import com.enactor.core.automation.actions.WaitForWindowAction;
import com.enactor.core.automation.api.CommonApiSteps;
import com.enactor.core.automation.component.IComponentAccessor;
import com.enactor.core.automation.component.IWindowAccessor;
import com.enactor.core.utilities.Logger;
import com.enactor.core.utilities.StringUtils;
import com.enactor.pos.IPosStepsProvider;
import com.enactor.pos.PosTestConstants;
import com.enactor.pos.PosTestManager;
import com.enactor.pos.PosUtility;
import com.enactor.pos.TestDataConfig;
import com.enactor.pos.automation.peripherals.PrimaryPrinter;
import com.enactor.pos.swing.ISwingAppStepsProvider;

public abstract class BaseStepsProvider implements IPosStepsProvider {

    private static Logger logger = Logger.getLogger(BaseStepsProvider.class);

    protected static final String PASSWORD_PATTERN = "^([*]{1,})$";
    protected static final String NON_ACCEPTABLE_CHARACTERS_IN_RECEIPTS = "[^ -~,^\\u00A3,^€]";
    protected static final Map<Integer, String> TRANSACTION_IDS = new HashMap<>();
	protected static final String TRANSACTION_BASKET_WINDOW_NAME = "Transaction Basket";
	protected static final String RECOVERY_WINDOW_NAME = "Recovery Window";
    protected static final String ENTER_USER_WINDOW_NAME = "Enter User";
    protected static final String ENT_BUTTON_LABEL = "ENT";

    protected static final int WAITING_TIME_IN_MILLIS_HARD_RESTART = PosTestManager.getWaitingTimeForHardRestartInMillis();
    protected static final long WAIT_DIALOG_PANEL_UPDATE = 7000;
    protected static final int WAIT_WINDOW_DISPLAY = 3000;
    protected static final int WAIT_COMPONENT_DISPLAY = 3000;
    protected static final long WAIT_BUTTON_DISPLAY = 100;

    private static final List<String> POS_BUTTON_LIST = new ArrayList<String>() {{
        add("Button 1");
        add("Button 8");
        add("Enter Button");
        add("Button 2");
        add("Button 3");
        add("Button 4");
        add("Button 5");
        add("Button 6");
        add("Button 7");
        add("Total Button");
        add("Numericpad Enter Button");
        add("Message Prompt OK Button");
        add("Message Prompt No Button");
        add("Message Prompt Yes Button");
        add("Button Panel Button 1");
        add("Button Panel Button 2");
        add("Button Panel Button 3");
        add("Button Panel Button 4");
        add("Button Panel Button 5");
        add("Button Panel Button 6");
        add("Button Panel Button 7");
        add("Button Panel Button 8");
        add("Product Size 2 Button");
        add("Product Add To Bag");
        add("Return Button");
        add("Button Panel CRM Button 1");
        add("Button Panel CRM Button 2");
        add("Button Panel CRM Button 3");
        add("Button Panel CRM Button 4");
        add("Button Panel CRM Button 5");
        add("Button Panel CRM Button 6");
        add("Button Panel CRM Button 7");
        add("Button Panel CRM Button 8");
        add("Page Panel New Customer Tab");
        add("CRM Manager Overview Affiliations Tab");
        add("CRM Manager Overview Record Absence Tab");
        add("CRM Customer Address Select Button");
        add("CRM Message Prompt OK Button");
        add("CRM Diary Previous Month Button");
        add("CRM Diary This Month Button");
        add("CRM Diary Next Month Button");
        add("CRM Diary Day View Button");
        add("CRM Diary Month Back Button");
        add("CRM Diary Week Back Button");
        add("CRM Diary Day Back Button");
        add("CRM Diary Today Button");
        add("CRM Diary Day Forward Button");
        add("CRM Diary Week Forward Button");
        add("CRM Diary Month Forward Button");
        add("CRM Diary Month View Button");
        add("Delivery Option Button 2");
        add("Delivery Option Button 3");
        add("Order Product");
        add("Item 1 Delivery To Home");
        add("Item 1 Collect At Store");
        add("Clear Button");
        add("Product Check Stock Button");
        add("Message Prompt Cancel Button");
    }};

    private static final String PRIMARY_PRINTER_WINDOW_NAME = "Primary Printer";
    private static final String ICC_READER_WINDOW_NAME = "ICCReader";
    private static final String SCANNER_WINDOW_NAME = "Scanner";
    private static final String CASH_DRAWER_WINDOW_NAME = "Cash Drawer";

    protected static final List<String> PERIPHERAL_DEVICES_WINDOW_NAMES_LIST =
            Arrays.asList(PRIMARY_PRINTER_WINDOW_NAME, ICC_READER_WINDOW_NAME,
                    CASH_DRAWER_WINDOW_NAME, SCANNER_WINDOW_NAME);

    private static final SimpleDateFormat TIMESTAMP = new SimpleDateFormat("yyyyMMdd_hhmmss");
    private IAUTControllerClient controller;

    private static final List<String> PERIPHERAL_BUTTON_LIST = new ArrayList<String>() {{
        add("Drawer Close Button");
        add("Test Printer Clear Button");
    }};

    private static final String WINDOW_PREFIX = "WINDOW";
    private static final String COMPONENT_PREFIX = "COMPONENT";
    private static final String SEPARATOR = "_";
    private static final String TEST_PRINTER_RECEIPT_EDITOR_COMPONENT_NAME = "Test Printer Receipt Editor";
    private static final String TEXT_PROPERTY = "text";

    private static final String LAUNCH_BAR_BACK_BUTTON_COMPONENT_NAME = "Back Button";
    private static final String LAUNCH_BAR_HOME_BUTTON_COMPONENT_NAME = "Home Button";
	private static final String LAUNCH_BAR_MENU_BUTTON_COMPONENT_NAME = "Menu Button";
    private static final String BUTTON_PANEL_BACK_BUTTON = "Button Panel Button 8";

    private static List<String> posButtonComponentIdList;

    private static List<String> getComponentIds() throws AutomationException {
        List<String> componentIds = new ArrayList<>();
        for (String name : POS_BUTTON_LIST) {
            componentIds.add(getComponentId(name));
        }
        return componentIds;
    }

    protected static List<String> getPosButtonComponentIdList() throws AutomationException {
        if (posButtonComponentIdList == null) {
            posButtonComponentIdList = getComponentIds();
        }
        return posButtonComponentIdList;
    }

    @Override
    public void beforeTest() throws AutomationException {
    }

    @Override
    public AUTConfig getConfig() {
        return getAUTConfig();
    }

    /**
     * Click on given menu
     *
     * @param menuPath
     * @param windowName
     * @throws AutomationException
     */
    @Override
    public void pressMenu(String menuPath, String windowName) throws AutomationException {
        menuPath = getTestData(menuPath);
        for (String menuItem : menuPath.split(">")) {
            pressButton(menuItem.trim(), windowName);
        }
    }

    @Override
    public void checkListItemDisplay(String item, String list, String windowName) throws AutomationException {
        List<String> lines = null;
        long end = System.currentTimeMillis() + WAIT_DIALOG_PANEL_UPDATE;
        while (System.currentTimeMillis() < end) {
            lines = getTextInListItems(list, windowName);
            for (String line : lines) {
                if (line.contains(getTestData(item))) {
                    return;
                }
            }
            waits(); // wait 1 second
        }
        throw new AutomationException("Item [" + getTestData(item) + "] not display in [" + list + "] list...." + lines);
    }

    @Override
    public void checkListItemNotDisplayed(String item, String list, String windowName) throws AutomationException {
        waits(); // give 1 second to update the list
        List<String> lines = getTextInListItems(list, windowName);
        for (String line : lines) {
            if (line.contains(getTestData(item))) {
                throw new AutomationException("Item [" + getTestData(item) + "] display in [" + list + "] list...." + lines);
            }
        }
    }

    @Override
    public void pressButtonOnTestPeripheral(String buttonLabel, String peripheralName) throws AutomationException {
        IWindowAccessor window = getSwingWindow(peripheralName);
        ClickComponentAction.clickComponent(window, getButtonConfig(window, getTestData(buttonLabel), PERIPHERAL_BUTTON_LIST));
    }

    @Override
    public void clearPrinter() throws AutomationException {
        IWindowAccessor window = getSwingWindow(PRIMARY_PRINTER_WINDOW_NAME);
        ClickComponentAction.clickComponent(window,
                getConfig().getString(PosTestConstants.COMPONENT_TEST_PRINTER_CLEAR_BUTTON));
    }

    @Override
    public void storeTransactionID() throws AutomationException {
        IWindowAccessor window = getSwingWindow(PRIMARY_PRINTER_WINDOW_NAME);
        IComponentAccessor receiptEditor = window.findComponent(
                getConfig().getString(PosTestConstants.COMPONENT_TEST_PRINTER_RECEIPT_EDITOR));
        Object receiptText = receiptEditor.getProperty("text");
        try {
            String tx = PrimaryPrinter.getTransactionId(receiptText.toString());
            TRANSACTION_IDS.put(TRANSACTION_IDS.size() + 1, tx);
            System.out.println("Transaction ID " + tx + " stored");
        } catch (AutomationException e) {
            //receipt with no tx id
            // do nothing
        }
    }

    @Override
    public int checkReceiptItemFlag(List<String> entry, int index) throws AutomationException {
        List<String> receipt = getReceiptLines();
        boolean match = false;
        for (int i = index; i < receipt.size(); i++) {
            String receiptLine = receipt.get(i);
            for (String line : entry) {
                if (line.length() == 1) {
                    match = PosUtility.textMatchingStartWord(getTestData(line), receiptLine);
                } else {
                    match = PosUtility.textMatchingStartsAndEndsWithSpaces(getTestData(line), receiptLine);
                }
                if (!match) {
                    break;
                }
            }
            if (match) {
                return i;
            }
        }
        String item = "";
        for (String line : entry) {
            item = item + " " + getTestData(line);
        }
        throw new AutomationException(
                "Item : [" + item + "] is not found in the printed receipt" + getReceiptLines());

    }

    @Override
    public int checkReceiptItemFlagOnPrinter(List<String> entry, int index, String windowName) throws AutomationException {
        List<String> receipt = getReceiptLinesOnPrinter(windowName);
        boolean match = false;
        for (int i = index; i < receipt.size(); i++) {
            String receiptLine = receipt.get(i);
            for (String line : entry) {
                if (line.length() == 1) {
                    match = PosUtility.textMatchingStartWord(getTestData(line), receiptLine);
                } else {
                    match = PosUtility.textMatchingStartsAndEndsWithSpaces(getTestData(line), receiptLine);
                }
                if (!match) {
                    break;
                }
            }
            if (match) {
                return i;
            }
        }
        String item = "";
        for (String line : entry) {
            item = item + " " + getTestData(line);
        }
        throw new AutomationException(
                "Item : [" + item + "] is not found in the printed receipt" + getReceiptLinesOnPrinter(windowName));

    }
    
    @Override
    public int checkMultipleLineReceiptEntry(List<String> entry, int index) throws AutomationException {
        int matchingIndex = isMultipleLineReceiptEntryDisplay(entry, index);
        if (!(matchingIndex >= 0)) {
            String item = "";
            for (String line : entry) {
                item = item + " " + getTestData(line);
            }
            throw new AutomationException(
                    "Item : [" + item + "] is not found in the printed receipt" + getReceiptLines());
        } else {
            return matchingIndex;
        }
    }
    
    @Override
    public int checkMultipleLineReceiptEntryOnPrinter(List<String> entry, int index, String windowName) throws AutomationException {
        int matchingIndex = isMultipleLineReceiptEntryDisplayOnPrinter(entry, index, windowName);
        if (!(matchingIndex >= 0)) {
            String item = "";
            for (String line : entry) {
                item = item + " " + getTestData(line);
            }
            throw new AutomationException(
                    "Item : [" + item + "] is not found in the printed receipt" + getReceiptLinesOnPrinter(windowName));
        } else {
            return matchingIndex;
        }
    }

    @Override
    public int checkMultipleLineReceiptEntryNotDisplay(List<String> entry, int index) throws AutomationException {
        int matchingIndex = isMultipleLineReceiptEntryDisplay(entry, index);
        if (matchingIndex >= 0) {
            String item = "";
            for (String line : entry) {
                item = item + " " + getTestData(line);
            }
            throw new AutomationException("Item : [" + item + "] is found in the printed receipt" + getReceiptLines());
        } else {
            return matchingIndex;
        }
    }

    @Override
    public int checkReceiptEntry(String text, int index) throws AutomationException {
        int matchingIndex = isReceiptEntryDisplay(text, index);
        if (!(matchingIndex >= 0)) {
            throw new AutomationException(
                    "Text : [" + getTestData(text) + "] is not found in the printed receipt " + getReceiptLines());
        } else {
            return matchingIndex;
        }
    }
    
    @Override
    public int checkReceiptEntryOnPrinter(String text, int index, String windowName) throws AutomationException {
        int matchingIndex = isReceiptEntryDisplayOnPrinter(text, index, windowName);
        if (!(matchingIndex >= 0)) {
            throw new AutomationException(
                    "Text : [" + getTestData(text) + "] is not found in the printed receipt " + getReceiptLinesOnPrinter(windowName));
        } else {
            return matchingIndex;
        }
    }

    @Override
    public int checkReceiptEntryNotDisplay(String text, int index) throws AutomationException {
        int matchingIndex = isReceiptEntryDisplay(text, index);
        if (matchingIndex >= 0) {
            throw new AutomationException("Text : [" + getTestData(text) + "] is found in the printed receipt " + getReceiptLines());
        } else {
            return matchingIndex;
        }
    }

    @Override
    public void blockConnectionsToUri(String uri) throws AutomationException {
        getController().blockConnectionsToUri(getTestData(uri));
    }

    @Override
    public void unblockConnectionsToUri(String uri) throws AutomationException {
        getController().unblockConnectionsToUri(getTestData(uri));
    }

    @Override
    public void readTransactionXmlFromPath(String filePath, String filename) throws AutomationException {
        filePath = filePath.concat(filename);
        CommonApiSteps.setResponse(readFromLocalFile(filePath));
    }

    @Override
    public void readTransactionXmlFromPath(String filePath) throws AutomationException {
        filePath = filePath.concat(getTransactionXMLFileName());
        CommonApiSteps.setResponse(readFromLocalFile(filePath));
    }

    @Override
    public String checkCalculationText(String text) throws AutomationException {
        if (!text.startsWith("#") && !(text.contains("today") || text.contains("Sunday"))) {
            return text;
        }
        Pattern pattern = Pattern.compile("(?<=\\{).*(?=\\})");
        Matcher matcher = pattern.matcher(text);
        if (matcher.find()) {
            String matchText = matcher.group();
            String replacePattern = "\\#.*}";
            text = text.replaceAll(replacePattern, matchText);
        }
        return getCalculatedText(text);
    }

    @Override
    public String getTestData(String value) throws AutomationException {
    	Pattern pattern = Pattern.compile("(?<=\\{)[^}]*(?=\\})");
        Matcher matcher = pattern.matcher(checkCalculationText(value));
        while (matcher.find()) {
            String matchedText = matcher.group();
            if (matchedText.startsWith("retrieveTransaction")) {
                String transactionKey = matchedText.substring(matchedText.indexOf('(') + 1,
                        matchedText.indexOf(')'));
                value = TRANSACTION_IDS.get(Integer.valueOf(transactionKey));

                if (matchedText.startsWith("retrieveTransactionNumber")) {
                    value = extractTransactionNumberFromTransactionId(value);
                }

            } else {
                String text = getTestDataValue(matchedText);
                if (text == null || text.isEmpty()) {
                    throw new AutomationException("No test data value found for [" + value + "]");
                }
                value = value.replaceAll(matchedText, text);
            }
        }
        value = value.replaceAll("[\\$\\{\\}\\#]", "");
        return value;
    }

    /**
     * Returns the transaction number for the given transaction id
     * Assumptions:
     * 1. First 4 digits are the branch number
     * 2. Second 4 digits are the terminal number
     *
     * @param transactionId
     * @return
     */
    private String extractTransactionNumberFromTransactionId(String transactionId) {
        return transactionId.substring(8, 13);
    }

    @Override
    public String getTestDataValue(String name) {
        String key = convertToKeyString(name);
        return TestDataConfig.getInstance().getString(key);
    }

    @Override
    public void waits(long milliseconds) throws AutomationException {
        try {
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
            throw new AutomationException("Failed waiting " + milliseconds + ". " + e);
        }
    }

    @Override
    public void waits() throws AutomationException {
        long milliseconds = 1000;
        waits(milliseconds);
    }

	public String getExtractedProcessIDandPromptIdFromWindowName(String windowName)
			throws AutomationException {
		String pageName = getId(windowName, WINDOW_PREFIX + SEPARATOR);
		Pattern pattern = Pattern.compile("([A-Z][A-Za-z]*/[a-zA-Z/]*)+");
		Matcher matcher = pattern.matcher(pageName);

		String processId = matcher.find() ? matcher.group() : null;
		String promptUrl = matcher.find() ? matcher.group() : null;
		return String.format("%s-%s", processId, promptUrl);
	}

    /**
     * Return the windowId for the given window name
     *
     * @param windowName
     * @return
     * @throws AutomationException
     */
    protected String getWindowId(String windowName) throws AutomationException {
        return getId(windowName, WINDOW_PREFIX + SEPARATOR);
    }

    /**
     * Waits for the button element with the given button label for the specified timeout duration and click on it.
     *
     * @param buttonLabel
     * @param timeout
     * @throws AutomationException
     */
    protected abstract void pressButtonInCurrentWindow(String buttonLabel, long timeout) throws AutomationException;

    /**
     * Waits for the button element with the given component name for the specified timeout duration and click on it.
     *
     * @param componentName
     * @param timeout
     * @throws AutomationException
     */
    protected abstract void pressButtonByComponentInCurrentWindow(String componentName, long timeout) throws AutomationException;

    /**
     * Return a screenshot for the current test case
     *
     * @return
     * @throws AutomationException
     */
    protected abstract byte[] requestCaptureScreenshot() throws AutomationException;

    /**
     * Check whether given window shown during the timout period
     *
     * @param windowName
     * @param timeout
     * @throws AutomationException
     */
    protected abstract void checkWindow(String windowName, long timeout) throws AutomationException;

    /**
     * Get everything displayed in Dialog Panel.
     * Returns all the lines in the Dialog Panel.
     * Each line in the Dialog Panel is one item in the list
     *
     * @param windowName
     * @return List
     * @throws AutomationException
     */
    protected abstract List<String> getTextInListItems(String list, String windowName) throws AutomationException;


    protected static String getComponentId(String componentName) throws AutomationException {
        return getId(componentName, COMPONENT_PREFIX + SEPARATOR);
    }

	protected IAUTControllerClient getController() {

		if (controller == null) {
			try {
				AUTServer autServer = AUTServerPool.getInstance().getAUTServer();
				logger.log(Logger.LOG_INFORMATION, "Getting AUT Server for Current Thread Id: " + Thread.currentThread().getId()
						+ " AUT Server Hostname: " + autServer.getAUThostName() + " Port: " + autServer.getAUTPortName());

				controller = (IAUTControllerClient) ConnectToAUTAction.connectToAUT(autServer.getAUThostName(),
						autServer.getAUTPortName(), autServer.getAUTSimulateMouseMode());
			} catch (AutomationException e) {
				logger.log(Logger.LOG_ERROR, String.format("Failed to Connect to AUT server, Check if the POS/PDP Server is running with AUT Launcher, then verify aut connection properties are correct( %s, %s, %s)", AUTConfig.PROPERTY_AUT_HOST_NAME, AUTConfig.PROPERTY_AUT_PORT_NAME, AUTConfig.PROPERTY_AUT_LIST));
				e.printStackTrace();
			}
		}
		return controller;
	}

    protected IAUTControllerClient resetAndGetController() {
        controller = null;
        return getController();
    }

    /**
     * Try to navigate to transaction basket page
     */
    protected void recoverApplication() {
        boolean isForceRestart = false;
        try {
            isForceRestart = getController().forceSystemExitOnRestart();
        } catch (AutomationException e) {
            logger.log(Logger.LOG_DEBUG, "Invoking AUT Server failed");
        }
        if (!isForceRestart) {
			List<String> launchBarButtonNameList = Arrays.asList(
					LAUNCH_BAR_HOME_BUTTON_COMPONENT_NAME,
					LAUNCH_BAR_MENU_BUTTON_COMPONENT_NAME,
					LAUNCH_BAR_BACK_BUTTON_COMPONENT_NAME,
					BUTTON_PANEL_BACK_BUTTON);
            List<String> buttonNameList = Arrays.asList("Cancel", "Back", "Void", "Complete", "Confirm", "No", "OK");
            int numberOfButtonPresses = 0;

            while (numberOfButtonPresses < 10) {
                try {
					checkWindow(RECOVERY_WINDOW_NAME, WAIT_BUTTON_DISPLAY);
                    break;
                } catch (AutomationException e) {
                    boolean buttonPressed = false;

                    // Recover from launch bar buttons
                    for (String buttonName : launchBarButtonNameList) {
                        try {
                            pressButtonByComponentInCurrentWindow(buttonName, WAIT_BUTTON_DISPLAY);
                            buttonPressed = true;
                            numberOfButtonPresses++;
                            break;
                        } catch (AutomationException e1) {
                            //button name not found. Check next button
                        }
                    }

                    // if launch bar buttons not pressed.
                    // Check other buttons with label Cancel, Back, Void, Complete, Confirm or OK
                    if (!buttonPressed) {
                        for (String buttonName : buttonNameList) {
                            try {
                                pressButtonInCurrentWindow(buttonName, WAIT_BUTTON_DISPLAY);
                                buttonPressed = true;
                                numberOfButtonPresses++;
                                break;
                            } catch (AutomationException e1) {
                                //button name not found. Check next button
                            }
                        }
                    }
                    if (!buttonPressed) {
                        // No buttons pressed. EXIT
                        break;
                    }
                }
            }
        }
    }

    /**
     * Returns Button config from <b>autClientPropertyFileName</b>
     * property file
     *
     * @param window
     * @param label
     * @return
     * @throws AutomationException
     */
    private String getButtonConfig(IWindowAccessor window, String label, List<String> buttonList) throws AutomationException {
        // Check button with label display on the page.
        // Loop through the list twice to find the button because buttons
        // not display as soon as page loads.
        for (int i = 1; i <= 2; i++) {
            for (String button : buttonList) {
                try {
                    String actual = getButtonLabel(window, button);
                    if (label.equals(actual)) {
                        return getComponentId(button);
                    }
                } catch (AutomationException e) {
                    // button text not available. Continue loop
                }
            }
        }
        throw new AutomationException("Button with label [" + label + "] not displayed");
    }

    /**
     * Fail a test, capturing a screenshot if the screenshot path is defined.
     * This will throw an exception that should not be caught by the invoker
     * (i.e. halting the test)
     *
     * @param testName
     * @param cause
     * @throws Exception
     */
    protected void failTest(String testName) {
        String message = "Test failed: " + testName;

        // Only save a screenshot if the base URL has been configured.
        if (StringUtils.notEmpty(PosTestManager.getScreenshotURLBase())) {
            try {
                String screenshotName = testName + "_" + TIMESTAMP.format(new Date()) + ".png";
                String screenshotPath = PosTestManager.getScreenshotURLBase() + "/" + screenshotName;
                byte[] data = requestCaptureScreenshot();

                FileOutputStream fos = new FileOutputStream(screenshotPath);
                fos.write(data);
                fos.close();

                String screenshotLink = String.format("<a href='%s' target='_blank'>%s</a>",
                        screenshotPath, screenshotName);
                message += " - " + screenshotLink;
            } catch (Exception ex) {
                // Just log exceptions caught here. Don't re-throw them otherwise
                // we'll loose the original test failure cause.

                ex.printStackTrace();
            }
        }

        throw new TestException(message);
    }

    /**
     * Returns WindowAccessor of a given window name
     *
     * @param windowName
     * @return
     * @throws AutomationException
     */
    protected IWindowAccessor getSwingWindow(String windowName) throws AutomationException {
        String windowKey = WINDOW_PREFIX + SEPARATOR + convertToKeyString(windowName);
        checkId(windowName, WINDOW_PREFIX + SEPARATOR);
        return getSwingWindowById(getString(windowKey));
    }

    /**
     * Returns WindowAccessor of a given window id
     *
     * @param windowId
     * @return
     * @throws AutomationException
     */
    protected IWindowAccessor getSwingWindowById(String windowId) throws AutomationException {
        return WaitForWindowAction.waitForWindow(getController(), windowId,
                getConfig().getInt(PosTestConstants.WAIT_STARTUP));
    }

    /**
     * Ask the automation server to restart the application.
     *
     * @throws AutomationException
     */
    protected void restartSwingApp(ISwingAppStepsProvider swingAppStepsProvider) throws AutomationException {
        // Give the server a few seconds to restart, otherwise we try to start
        // using the 'old' screen while it is still restarting.
        swingAppStepsProvider.setController(resetAndGetController());
        if (getController().forceSystemExitOnRestart()) {
            getController().restartApplication();
            long start = System.currentTimeMillis();
            // previous controller is not valid after system restart in server
            swingAppStepsProvider.setController(null);
            // Give some time for hard restart to happen
            // If System exit waits too long, client connect with the server before it shuts down
            waits(15000);
            do {
                waits(5000);
                IAUTControllerClient controller = resetAndGetController();
                if (controller != null) {
                    swingAppStepsProvider.setController(controller);
                    logger.log(Logger.LOG_DEBUG,
                            String.format("Connected to AUT Server after hard restart in %d millis",
                                    (System.currentTimeMillis() - start)));
                    break;
                }

            } while (System.currentTimeMillis() < (start + WAITING_TIME_IN_MILLIS_HARD_RESTART));

        } else {
            getController().restartApplication();
            waits(5000);
        }
    }

    protected boolean isPeripheralWindow(String windowName) {
        return PERIPHERAL_DEVICES_WINDOW_NAMES_LIST.contains(windowName);
    }

    private void checkId(String name, String prefix) throws AutomationException {
        getId(name, prefix);

    }

    private static String getId(String property, String prefix) throws AutomationException {
        String key = prefix + convertToKeyString(property);
        String id = getAUTConfig().getString(key);
        if (id != null) {
            return id;
        }
        throw new AutomationException("Property [" + property + "] key [" + key + "] not found in property file");
    }

    private static AUTConfig getAUTConfig() {
        return PosTestManager.getInstance().getConfig();
    }

    /**
     * get the transaction XML file name.
     *
     * @return string
     * @throws AutomationException
     */
    private String getTransactionXMLFileName() throws AutomationException {
        List<String> receipt = getReceiptLines();
        String fileName = "";
        for (String receiptLine : receipt) {
            Pattern pattern = Pattern.compile("\\d{26}");
            Matcher matcher = pattern.matcher(receiptLine);
            if (matcher.find()) {
                fileName = matcher.group(0) + ".xml";
            }
        }
        return fileName;
    }

    /**
     * check given text printed in the receipt.
     *
     * @param text
     * @return
     * @throws AutomationException
     */
    private int isReceiptEntryDisplay(String entry, int index) throws AutomationException {
        List<String> receipt = getReceiptLines();
        int matchingIndex = -1;
        for (int i = index; i < receipt.size(); i++) {
            String receiptLine = receipt.get(i);
            if (PosUtility.textMatchingStartsAndEndsWithSpaces(getTestData(checkCalculationText(entry)), receiptLine)) {
                matchingIndex = i;
                break;
            }
        }
        return matchingIndex;
    }
    
    /**
     * check given text printed in the receipt on the printer.
     *
     * @param text
     * @return
     * @throws AutomationException
     */
    private int isReceiptEntryDisplayOnPrinter(String entry, int index, String windowName)  throws AutomationException {
    	
        String receipt = org.apache.commons.lang.StringUtils.join(getReceiptLinesOnPrinter(windowName), " ");
        receipt= receipt.replaceAll("\\s{2,}", " ").trim().replaceAll(NON_ACCEPTABLE_CHARACTERS_IN_RECEIPTS, "");
        entry = getTestData(entry).replaceAll("\\s{2,}", " ").trim().replaceAll(NON_ACCEPTABLE_CHARACTERS_IN_RECEIPTS, "");
        entry = PosUtility.prepareTextForRegex(entry);
        Pattern pattern = Pattern.compile("\\s" + entry + "\\s");
        receipt = " " + receipt + " ";
    	Matcher matcher = pattern.matcher(receipt);        	
        if (matcher.find()) {
            return 1;
        }
        return -1;
    }

    /**
     * Read the file with given fileName from AUTControllerServer
     *
     * @param filename
     * @return
     * @throws AutomationException
     */
    private String readFromLocalFile(String filename) throws AutomationException {
        return getController().getFileOutput(filename);
    }

    /**
     * Check receipt lines with multiple entries
     * <p>
     * Receipt details extract into an array as bellow. To verify lines with multiple entries such as
     * address, you need to pass address as List<String>
     * eg: [ "Enactor House","1 Bluecoats","Hertford","Hertfordshire","SG14 1PB" ] will match the
     * fist entry of receipt array. All the items you pass must contain in one entry in the
     * receipt array.
     * <p>
     * receipt =
     * [               Enactor House<br>                1 Bluecoats<br>                  Hertford<br>               Hertfordshire<br>                  SG14 1PB<br>             Tel: 01992 500881<br>                  ]
     * ,[<b>Sale</b>]
     * ,[<br>--------------------------------------------<br>]
     * ,[<i>V</i>]
     * ,[ ]
     * ,[<i>HD Complexion Starter Kit</i>]
     * ,[<br>  KAT VON D Eye Liner                 £18.00<br>    £3 off of KAT VON D Eye Liner<br>]
     * ,[<b>Promotion Saving<br></b>]
     * ,[  £3 off of KAT VON D Eye Liner       -£3.00<br>--------------------------------------------<br>  ]
     * ,[<b>TOTAL</b>]
     * ,[                    ]
     * ,[<b>£15.00</b>]
     * ,[<br>  Cash UK                            -£15.00<br>--------------------------------------------<br>           00010001002761804161059439           <br><br>      Thankyou for shopping at Enactor<br>16/04/18 10:59   David Bull   0001 0001 0276<br>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>]
     *
     * @param entry
     * @return
     * @throws AutomationException
     */
    private int isMultipleLineReceiptEntryDisplay(List<String> entry, int index) throws AutomationException {
        List<String> receipt = getReceiptLines();
        int matchingIndex = -1;
        boolean match = false;
        for (int i = index; i < receipt.size(); i++) {
            String receiptLine = receipt.get(i);
            for (String line : entry) {
                if (!PosUtility.textMatchingStartsAndEndsWithSpaces(getTestData(checkCalculationText(line)),
                        receiptLine)) {
                    match = false;
                    break;
                }
                match = true;
            }
            if (match) {
                matchingIndex = i;
                break;
            }
        }
        return matchingIndex;
    }
    
    /**
     * Check receipt lines with multiple entries on printer
     * <p>
     * Receipt details extract into an array as bellow. To verify lines with multiple entries such as
     * address, you need to pass address as List<String>
     * eg: [ "Enactor House","1 Bluecoats","Hertford","Hertfordshire","SG14 1PB" ] will match the
     * fist entry of receipt array. All the items you pass must contain in one entry in the
     * receipt array.
     * <p>
     * receipt =
     * [               Enactor House<br>                1 Bluecoats<br>                  Hertford<br>               Hertfordshire<br>                  SG14 1PB<br>             Tel: 01992 500881<br>                  ]
     * ,[<b>Sale</b>]
     * ,[<br>--------------------------------------------<br>]
     * ,[<i>V</i>]
     * ,[ ]
     * ,[<i>HD Complexion Starter Kit</i>]
     * ,[<br>  KAT VON D Eye Liner                 £18.00<br>    £3 off of KAT VON D Eye Liner<br>]
     * ,[<b>Promotion Saving<br></b>]
     * ,[  £3 off of KAT VON D Eye Liner       -£3.00<br>--------------------------------------------<br>  ]
     * ,[<b>TOTAL</b>]
     * ,[                    ]
     * ,[<b>£15.00</b>]
     * ,[<br>  Cash UK                            -£15.00<br>--------------------------------------------<br>           00010001002761804161059439           <br><br>      Thankyou for shopping at Enactor<br>16/04/18 10:59   David Bull   0001 0001 0276<br>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<br>]
     *
     * @param entry
     * @return
     * @throws AutomationException
     */
    private int isMultipleLineReceiptEntryDisplayOnPrinter(List<String> entry, int index, String windowName) throws AutomationException {
        List<String> receipt = getReceiptLinesOnPrinter(windowName);
        int matchingIndex = -1;
        boolean match = false;
        for (int i = index; i < receipt.size(); i++) {
            String receiptLine = receipt.get(i);
            for (String line : entry) {
                if (!PosUtility.textMatchingStartsAndEndsWithSpaces(getTestData(checkCalculationText(line)),
                        receiptLine)) {
                    match = false;
                    break;
                }
                match = true;
            }
            if (match) {
                matchingIndex = i;
                break;
            }
        }
        return matchingIndex;
    }
    
    

    /**
     * Get calculated text
     * <p>
     * eg:
     * text = #{today-2} will return day before yesterday in dd/mm/yy format
     * text = #{Sunday+1} will return this week's Monday in dd/mm/yy format
     *
     * @param text
     * @return
     * @throws AutomationException
     */
    private String getCalculatedText(String text) throws AutomationException {
        Calendar calendar = Calendar.getInstance();
        String date;
        String period;
        if (text.contains("-")) {
            period = text.substring(0, text.lastIndexOf("-"));
        } else if (text.contains("+")) {
            period = text.substring(0, text.lastIndexOf("+"));
        } else {
            period = text;
        }

        switch (period) {
            case "today":
                if (text.contains("-")) {
                    date = PosUtility.offsetInDays(Integer.parseInt(text.substring(text.lastIndexOf("-"))));
                } else if (text.contains("+")) {
                    date = PosUtility.offsetInDays(Integer.parseInt(text.substring(text.lastIndexOf("+"))));
                } else {
                    date = PosUtility.offsetInDays();
                }
                break;
            case "Sunday":
                int dayOfTheWeek = calendar.get(Calendar.DAY_OF_WEEK);
                calendar.add(Calendar.DAY_OF_WEEK, Calendar.SUNDAY - dayOfTheWeek);
                SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yy");
                if (text.contains("-")) {
                    calendar.add(Calendar.DATE, Integer.parseInt(text.substring(text.lastIndexOf("-"))));
                    date = dateFormat.format(calendar.getTime());
                } else if (text.contains("+")) {
                    calendar.add(Calendar.DATE, Integer.parseInt(text.substring(text.lastIndexOf("+"))));
                    date = dateFormat.format(calendar.getTime());
                } else {
                    date = dateFormat.format(calendar.getTime());
                }
                break;
            default:
                throw new AutomationException("Invalid period : [" + period + "]" + ". Expected [today, Sunday]");
        }
        return date;
    }

    /**
     * Returns the button label of the given button in the given window
     *
     * @param window
     * @param buttonName
     * @return
     * @throws AutomationException
     */
    private String getButtonLabel(IWindowAccessor window, String buttonName) throws AutomationException {
        return window.waitForComponent(getComponentId(buttonName), WAIT_BUTTON_DISPLAY).getText();
    }

    /**
     * get printed receipt.
     * read the test printer and returns
     * every line
     *
     * @return receipt
     * @throws AutomationException
     */
    private List<String> getReceiptLines() throws AutomationException {
        IWindowAccessor window = getSwingWindow(PRIMARY_PRINTER_WINDOW_NAME);
        waits(); //1 second delay. primary printer sometimes takes second or so to display text
        IComponentAccessor receiptEditor = window.findComponent(getComponentId(TEST_PRINTER_RECEIPT_EDITOR_COMPONENT_NAME));
        Object receiptText = receiptEditor.getProperty(TEXT_PROPERTY);
        if (receiptText == null) {
            throw new AutomationException("Failed to get the content of the printed receipt");
        }

        // remove unicode values
        String html = PosUtility.replaceSpecialCharacters(receiptText.toString());

        String htmlBody = html.substring(html.indexOf("<body>") + 6, html.indexOf("</body>"));
        List<String> receipt = Arrays.asList(htmlBody.split("</font>")); // remove end </font> tags

        // Get Body content from the given index
        for (int i = 0; i < receipt.size(); i++) {
            String item = receipt.get(i).replace("--------------------------------------------", ""); // remove line
            // separators
            receipt.set(i, item.substring(item.indexOf(">") + 1)); // remove start <font> tags
        }

        return receipt; //return only <body> of the html
    }
    
    /**
     * get printed receipt.
     * read the test printer and returns
     * every line
     *
     * @return receipt
     * @throws AutomationException
     */
    private List<String> getReceiptLinesOnPrinter(String windowName) throws AutomationException {
        IWindowAccessor window = getSwingWindow(windowName);
        waits(); //1 second delay. primary printer sometimes takes second or so to display text
        IComponentAccessor receiptEditor = window.findComponent(getComponentId(TEST_PRINTER_RECEIPT_EDITOR_COMPONENT_NAME));
        Object receiptText = receiptEditor.getProperty(TEXT_PROPERTY);
        if (receiptText == null) {
            throw new AutomationException("Failed to get the content of the printed receipt");
        }

        // remove unicode values
        String html = PosUtility.replaceSpecialCharacters(receiptText.toString());

        String htmlBody = html.substring(html.indexOf("<body>") + 6, html.indexOf("</body>"));
        List<String> receipt = Arrays.asList(htmlBody.split("</font>")); // remove end </font> tags

        // Get Body content from the given index
        for (int i = 0; i < receipt.size(); i++) {
            String item = receipt.get(i).replace("--------------------------------------------", ""); // remove line
            // separators
            receipt.set(i, item.substring(item.indexOf(">") + 1)); // remove start <font> tags
        }

        return receipt; //return only <body> of the html
    }

    /**
     * Returns the String of the given element.
     *
     * @param elementID
     * @return
     * @throws AutomationException
     */
    private String getString(String elementID) {
        return getConfig().getString(elementID);
    }


    /**
     * Convert a given string to a hyphened upper case word E.g. "My Configurations       Page" = >
     * "MY_CONFIGURATIONS_PAGE"
     *
     * @param label
     * @return
     */
    private static String convertToKeyString(String label) {
        return convertToKeyString(label, true);
    }


    /**
     * Convert a given string to a hyphened upper case word E.g. "My Configurations       Page" = >
     * "my_configurations_page" if toAllCaps is true then Page" = > "MY_CONFIGURATIONS_PAGE"
     *
     * @param label
     * @param toAllCaps
     * @return
     */
    private static String convertToKeyString(String label, boolean toAllCaps) {
        String key = StringUtils.emptyString();
        if (label != null) {
            key = label.replaceAll("\\s+", " ");
            if (toAllCaps) {
                key = key.replaceAll("\\s+", "_");
                key = key.toUpperCase();
            } else {
                key = key.replaceAll("\\s+", "_");
            }
        }
        return key;
    }

}
