/*
 * AutoTester.java		Date created: 13.10.2008
 * Last modified by: $Author$
 * $Revision$	$Date$
 */

package org.richfaces;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.ajax4jsf.autotest.bean.AutoTestBean;
import org.ajax4jsf.template.Template;
import org.testng.Assert;

/**
 * Class provides scope of mehods for automatic test of standard ajax attrs
 * 
 * @author Andrey Markavtsov
 * 
 */
public class AutoTester {

    private static final String AUTOTEST_CONTROLS_FORM_ID = "autoTestControlForm:";

    private static final String AUTOTEST_FORM_ID = "autoTestForm:";

    private static final String INPUT_ID = "_auto_input";

    public static final String STATUS_ID = "_auto_status";

    private static final String TIME_ID = "_auto_time";

    public static final String COMPONENT_ID = "componentId";

    private static final String PARAMS_MAP_VAR_NAME = "requestParamsMap";

    private static final String SIMPLE_SUBMIT = "_auto_simple_submit";

    private static final String AJAX_SUBMIT = "_auto_ajax_submit";

    private static final String AJAX_RESET = "_auto_ajax_reset";

    // /private String componentName;

    SeleniumTestBase base;

    public AutoTester(SeleniumTestBase base) {
        this.base = base;
    }

    public void renderPage(Template template, String resetMethodName) {
        base.renderAutoTestPage(template, resetMethodName);
    }

    public void testAllAjaxAttributes() {
        testRendered();
        testReRender();
        testActionListener();
        testAjaxSingle();
        testImmediate();
        testBypassUpdate();
        testExtrenalValidationFailure();
        testLimitToList();
    }

    public void testRendered() {
        reset();
        setupControl(TestSetupEntry.rendered, Boolean.FALSE);
        clickLoad();

        base.AssertNotPresent(getClientId(COMPONENT_ID), "Rendered attribute does not work");
    }

    public void testReRender() {
        reset();
        setupControl(TestSetupEntry.reRender, STATUS_ID + "," + TIME_ID);
        clickLoad();

        String text = base.getTextById(base.getParentId() + AUTOTEST_FORM_ID + TIME_ID);

        base.sendAjax();
        base.AssertTextNotEquals(base.getParentId() + AUTOTEST_FORM_ID + TIME_ID, text,
                "ReRender attribute does not work");

        String oncomplete = base.runScript("window._ajaxOncomplete");
        Assert.assertEquals(oncomplete,"true", "Oncomplete attribute does not work.");
    }

    public void testActionListener() {
        reset();
        clickLoad();

        base.sendAjax();

        checkActionListener(true);
        checkUpdateModel(true);
    }

    public void testExtrenalValidationFailure() {
        reset();
        clickLoad();

        setExtrenalValidationFailed();
        base.sendAjax();

        checkActionListener(false);
        checkUpdateModel(false);
    }

    public void testAjaxSingle() {
        reset();
        setupControl(TestSetupEntry.ajaxSingle, Boolean.TRUE);
        clickLoad();

        setExtrenalValidationFailed();
        base.sendAjax();

        checkActionListener(true);
        checkUpdateModel(false);

    }

    /**
     * Important! Override
     * {@link SeleniumTestBase#setInternalValidationFailed()} method if you use
     * this method
     */
    public void testAjaxSingleWithInternalValidationFailed() {
        reset();
        setupControl(TestSetupEntry.ajaxSingle, Boolean.TRUE);
        clickLoad();

        setInternalValidationFailed();
        base.sendAjax();

        checkActionListener(false);
        checkUpdateModel(false);
    }

    public void testImmediate() {
        reset();
        setupControl(TestSetupEntry.immediate, Boolean.TRUE);
        clickLoad();

        base.sendAjax();

        checkActionListener(true);
        checkUpdateModel(false);

    }

    public void testImmediateWithExternalValidationFailed() {
        reset();
        setupControl(TestSetupEntry.immediate, Boolean.TRUE);
        clickLoad();

        setExtrenalValidationFailed();
        base.sendAjax();

        checkActionListener(true);
        checkUpdateModel(false);

    }

    public void testBypassUpdate() {
        reset();
        setupControl(TestSetupEntry.bypassUpdate, Boolean.TRUE);
        clickLoad();

        base.sendAjax();

        checkActionListener(true);
        checkUpdateModel(false);
    }

    public void testLimitToList() {
        reset();
        setupControl(TestSetupEntry.limitToList, Boolean.TRUE);
        clickLoad();

        checkComponentReRendered();

        reset();
        setupControl(TestSetupEntry.limitToList, Boolean.TRUE);
        setupControl(TestSetupEntry.reRender, STATUS_ID + "," + TIME_ID);
        clickLoad();

        String text = base.getTextById(base.getParentId() + AUTOTEST_FORM_ID + TIME_ID);

        checkComponentReRendered();

        base.AssertTextNotEquals(base.getParentId() + AUTOTEST_FORM_ID + TIME_ID, text, ""
                + "LimitToList = true does not work. Component in reRender list was not rerendered");

    }

    public void testRequestParameters(Map<String, String> params) {
        reset();
        clickLoad();

        base.sendAjax();

        for (String name : params.keySet()) {
            String value = base.runScript(PARAMS_MAP_VAR_NAME + "." + name);
            if (value == null) {
                Assert.fail("Parameter [" + name + "] is not present in ajax request");
            } else if (!value.equals(params.get(name))) {
                Assert.fail("Parameter [" + name + "] value is invalid. Expected [" + params.get(name) + "]. But was ["
                        + value + "]");
            }
        }
    }

    public void testNestedActionListener() {
        reset();
        clickLoad();

        base.sendAjax();

        checkNestedActionListener(true);
    }

    public void testOncomplete() {
        reset();
        clickLoad();

        base.sendAjax();

        String oncomplete = base.runScript("window._ajaxOncomplete");
        Assert.assertEquals("true", oncomplete, "Oncomplete attribute does not work.");

    }

    public void testStyleAndClasses(String[] classNames, Map<String, String> styleAttr) {
        reset();
        clickLoad();

        String componentId = getClientId(COMPONENT_ID);

        base.assertClassNames(componentId, classNames, "Component's rendering invalid", true);
        base.assertStyleAttributes(componentId, styleAttr);
    }

//
// EditableValueHolder test methods
//

    public void testSubmit() {
        reset();
        clickLoad();

        changeValue();
        clickSubmit();

        checkValueChangeListener(true);
        checkUpdateModel(true);
    }

    public void testSubmitWithExternalValidationFailed() {
        reset();
        clickLoad();

        setExtrenalValidationFailed();
        changeValue();
        clickSubmit();

        checkValueChangeListener(false);
        checkUpdateModel(false);
    }

    public void testSubmitImmediate() {
        reset();
        setupControl(TestSetupEntry.immediate, Boolean.TRUE);
        clickLoad();

        changeValue();
        clickSubmit();

        checkValueChangeListener(true);
        checkUpdateModel(false);

    }

    public void testSubmitImmediateWithExternalValidationFailed() {
        reset();
        setupControl(TestSetupEntry.immediate, Boolean.TRUE);
        clickLoad();

        setExtrenalValidationFailed();
        changeValue();
        clickSubmit();

        checkValueChangeListener(true);
        checkUpdateModel(false);
    }

    public void testHTMLEvents() {
        String componentId = getClientId(COMPONENT_ID);
        base.assertEvents(componentId, SeleniumEvent.STANDARD_HTML_EVENTS);
    }

    private void checkComponentReRendered() {
        if (base.getReRendersId() == null) {
            return;
        }

        List<String> htmlBefore = new ArrayList<String>();
        List<String> htmlAfter = new ArrayList<String>();

        for (String id : base.getReRendersId()) {
            htmlBefore.add(base.getHTMLById(getClientId(id)));
        }

        base.sendAjax();

        for (String id : base.getReRendersId()) {
            htmlAfter.add(base.getHTMLById(getClientId(id)));
        }

        int i = 0;
        for (String html : htmlBefore) {
            Assert.assertEquals(html, htmlAfter.get(i),
                    "LimitToList does not work. Component should not be reRendered if limitToList=true");

            i++;
        }
    }

    public String getClientId(String id) {
        return (base.getParentId() != null ? base.getParentId() : "") + AUTOTEST_FORM_ID + id;
    }

    public String getClientId(String id, Template template) {
        return template.getPrefix() + AUTOTEST_FORM_ID + id;
    }

    public void reset() {
        if (base.getParentId() == null) {
            Assert.fail("Page has not been rendered before test. Invoke 'renderPage' method before !");
        }

        for (TestSetupEntry attr : TestSetupEntry.list) {
            setupControl(attr, attr.defaultValue);
        }
    }

    public void startTracing() {
        String resetId = base.getParentId() + AUTOTEST_FORM_ID + AJAX_RESET;
        base.clickAjaxCommandAndWait(resetId);
    }

    public void clickLoad() {
        String commandButtonId = base.getParentId() + AUTOTEST_CONTROLS_FORM_ID + "_auto_load";
        base.clickCommandAndWait(commandButtonId);
    }

    public void clickAjaxSubmit() {
        String ajaxSubmitId = base.getParentId() + AUTOTEST_FORM_ID + AJAX_SUBMIT;
        base.clickAjaxCommandAndWait(ajaxSubmitId);
    }

    public void clickSubmit() {
        String submitId = base.getParentId() + AUTOTEST_FORM_ID + SIMPLE_SUBMIT;
        base.clickCommandAndWait(submitId);
    }

    public void setExtrenalValidationFailed() {
        base.setValueById(base.getParentId() + AUTOTEST_FORM_ID + INPUT_ID, "");
    }

    private void setInternalValidationFailed() {
        base.setInternalValidationFailed();
    }

    private void changeValue() {
        base.changeValue();
    }

    public void setupControl(TestSetupEntry attr, Object o) {
        final String idPrefix = "_auto_";
        String controlId = base.getParentId() + AUTOTEST_CONTROLS_FORM_ID + idPrefix + attr.name;
        if (attr.type.equals(String.class)) {
            base.runScript("document.getElementById('" + controlId + "').value = '" + o.toString() + "'");
        } else if (attr.type.equals(Boolean.class)) {
            base.runScript("document.getElementById('" + controlId + "').checked = " + o.toString());
        }
    }

    public void checkActionListener(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.ACTION_LISTENER_STATUS) == -1) {
            Assert.fail("ActionListener has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.ACTION_LISTENER_STATUS) != -1) {
            Assert.fail(status);
        }
    }

    public void checkNestedActionListener(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.NESTED_ACTION_LISTENER_STATUS) == -1) {
            Assert.fail("Nested actionListener has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.NESTED_ACTION_LISTENER_STATUS) != -1) {
            Assert.fail("Nested actionListener should be skipped");
        }
    }

    public void checkValueChangeListener(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.VALUE_CHANGE_LISTENER_STATUS) == -1) {
            Assert.fail("ValueChangeListener has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.VALUE_CHANGE_LISTENER_STATUS) != -1) {
            Assert.fail(status);
        }
    }

    public void checkNodeSelectedListener(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.NODE_SELECTED_LISTENER_STATUS) == -1) {
            Assert.fail("NodeSelectedListener has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.NODE_SELECTED_LISTENER_STATUS) != -1) {
            Assert.fail(status);
        }
    }

    public void checkNodeExpandedListener(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.NODE_EXPANDED_LISTENER_STATUS) == -1) {
            Assert.fail("NodeExpandedListener has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.NODE_EXPANDED_LISTENER_STATUS) != -1) {
            Assert.fail(status);
        }
    }

    public void checkUpdateModel(boolean passed) {
        String status = getStatus();
        if (passed && status != null && status.indexOf(AutoTestBean.UPDATE_MODEL_STATUS) == -1) {
            Assert.fail("Update Model phase has been skipped");
        } else if (!passed && status != null && status.indexOf(AutoTestBean.UPDATE_MODEL_STATUS) != -1) {
            Assert.fail("Update Model phase should be skipped");
        }
    }

    private String getStatus() {
        return base.getTextById(base.getParentId() + AUTOTEST_FORM_ID + STATUS_ID);
    }

    public static class TestSetupEntry {

        String name;

        Class<?> type;

        Object defaultValue;

        public TestSetupEntry(String name, Class<?> type, Object defaultValue) {
            super();
            this.name = name;
            this.type = type;
            this.defaultValue = defaultValue;
        }

        public static final TestSetupEntry reRender = new TestSetupEntry("reRender", String.class, AutoTester.STATUS_ID);

        public static final TestSetupEntry validatorId = new TestSetupEntry("validatorId", String.class,
                AutoTestBean.VALIDATOR_DEFAULT_ID);

        public static final TestSetupEntry oncomplete = new TestSetupEntry("oncomplete", String.class,
                AutoTestBean.ONCOMPLETE);

        public static final TestSetupEntry rendered = new TestSetupEntry("rendered", Boolean.class, Boolean.TRUE);

        public static final TestSetupEntry ajaxSingle = new TestSetupEntry("ajaxSingle", Boolean.class, Boolean.FALSE);

        public static final TestSetupEntry immediate = new TestSetupEntry("immediate", Boolean.class, Boolean.FALSE);

        public static final TestSetupEntry bypassUpdate = new TestSetupEntry("bypassUpdate", Boolean.class, Boolean.FALSE);

        public static final TestSetupEntry limitToList = new TestSetupEntry("limitToList", Boolean.class, Boolean.FALSE);

        public static final List<TestSetupEntry> list = new ArrayList<TestSetupEntry>();
        static {
            list.add(reRender);
            list.add(validatorId);
            list.add(rendered);
            list.add(ajaxSingle);
            list.add(immediate);
            list.add(bypassUpdate);
            list.add(limitToList);
            list.add(oncomplete);

        }

    }
}