package org.jboss.seam.examples.booking.ftest;

import static org.jboss.arquillian.ajocado.Ajocado.waitForXhr;
import static org.jboss.arquillian.ajocado.locator.LocatorFactory.jq;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.jboss.arquillian.ajocado.dom.Event;
import org.jboss.arquillian.ajocado.locator.JQueryLocator;
import org.jboss.arquillian.ajocado.locator.option.OptionLocator;
import org.jboss.arquillian.ajocado.locator.option.OptionValueLocator;
import org.junit.Test;

/**
 * This class tests booking functionality of the example.
 *
 * @author jbalunas
 * @author <a href="http://community.jboss.org/people/jharting">Jozef Hartinger</a>
 */
public class BookingTest extends AbstractBookingTest {

    public static final JQueryLocator BUTTON_BOOK = jq("[id='actions:bookHotel']");
    public static final JQueryLocator BUTTON_PROCEED = jq("[id='bookingForm:proceed']");
    public static final JQueryLocator BUTTON_CONFIRM = jq("[id='confirmForm:confirm']");
    public static final JQueryLocator BUTTON_CANCEL = jq("[id='confirmForm:cancel']");
    public static final JQueryLocator BUTTON_REVISE = jq("[id='confirmForm:revise']");
    public static final String BOOKING_MESSAGE = "You're booked to stay at the";
    public static final String CANCEL_MESSAGE = "has been canceled.";

    public static final JQueryLocator COUNT_HOTEL = jq("[id='hotelSelectionForm:hotels'] tbody tr");
    public static final JQueryLocator COUNT_BOOKING = jq("[id='bookings:bookings'] tbody tr");

    public static final JQueryLocator BOOKING_TABLE_FIRST_ROW_NAME = jq("table[id='bookings:bookings'] tbody tr:first td:first");
    public static final JQueryLocator BOOKING_TABLE_FIRST_ROW_LINK = jq("[id='bookings:bookings:0:cancel']");
    public static final JQueryLocator BOOKING_CANCEL_MESSAGE = jq("[id='messages'] li");
    public static final String BOOKING_CANCEL_MESSAGE_TEXT = "Your booking at the .+? on .+? has been canceled\\.";

    public static final JQueryLocator DETAILS_CARD_TYPE = jq("[id='bookingForm:creditCardType:type']");
    public static final JQueryLocator DETAILS_CARD_NUMBER = jq("[id='bookingForm:creditCardNumber:input']");
    public static final JQueryLocator DETAILS_SMOKING = jq("[id='bookingForm:smokingPreference:input:0']");
    public static final JQueryLocator DETAILS_NONSMOKING = jq("[id='bookingForm:smokingPreference:input:1']");

    public static final JQueryLocator CONFIRM_TEXT = jq("[id='content']");

    public static final JQueryLocator SEARCH_PAGE_SIZE = jq("[id='pageSize']");

    /**
     * Tests the hotel search - with both existing and non-existing queries.
     */
    @Test
    public void testSearch() {
        enterSearchQuery("Marriott");
        assertFalse(selenium.isElementPresent(SEARCH_NO_RESULTS));
        assertEquals(2, selenium.getCount(COUNT_HOTEL));

        enterSearchQuery("nonExistingHotel");
        assertTrue(selenium.isElementPresent(SEARCH_NO_RESULTS));
        assertEquals(0, selenium.getCount(COUNT_HOTEL));
    }

    @Test
    public void testSearchPageSize() {
        int[] values = {20, 10, 5};

        selenium.type(SEARCH_QUERY, "a");
        waitForXhr(selenium).fireEvent(SEARCH_QUERY, Event.KEYUP);

        for (int pageSize : values) {
            waitForXhr(selenium).select(SEARCH_PAGE_SIZE, new OptionValueLocator(String.valueOf(pageSize)));
            assertEquals(pageSize, selenium.getCount(COUNT_HOTEL));
        }
    }

    /**
     * Simply follows the booking wizard without changing anything.
     */
    @Test
    public void testSimpleBooking() {
        String hotelName = "Grand Hyatt";
        int bookingCount = selenium.getCount(COUNT_BOOKING);
        bookHotel(hotelName, CreditCardType.VISA);
        assertEquals(++bookingCount, selenium.getCount(COUNT_BOOKING));
    }

    /**
     * Tests "revise" and "cancel" buttons as well as that changed credit card details are propagated across the booking wizard.
     */
    @Test
    public void testMoreSophisticatedBooking() {
        String hotelName = "Conrad Miami";
        String creditCardNumber1 = "0123456789012347";
        CreditCardType creditCardType1 = CreditCardType.DISCOVER;
        String creditCardNumber2 = "6432109876543210";
        CreditCardType creditCardType2 = CreditCardType.AMEX;
        int bookingCount = selenium.getCount(COUNT_BOOKING);

        enterSearchQuery(hotelName);
        selenium.click(SEARCH_RESULT_TABLE_FIRST_ROW_LINK);
        selenium.waitForPageToLoad();
        // booking page
        selenium.click(BUTTON_BOOK);
        selenium.waitForPageToLoad();
        // booking detail page
        populateBookingFields(creditCardNumber1, creditCardType1);
        selenium.click(BUTTON_PROCEED);
        selenium.waitForPageToLoad();
        // confirmation page
        assertTrue(selenium.getText(CONFIRM_TEXT).contains(creditCardNumber1));
        assertTrue(selenium.getText(CONFIRM_TEXT).contains(creditCardType1.getName()));
        selenium.click(BUTTON_REVISE);
        selenium.waitForPageToLoad();
        // back to booking page
        populateBookingFields(creditCardNumber2, creditCardType2);
        selenium.click(BUTTON_PROCEED);
        selenium.waitForPageToLoad();
        // confirmation page
        assertTrue(selenium.getText(CONFIRM_TEXT).contains(creditCardNumber2));
        assertTrue(selenium.getText(CONFIRM_TEXT).contains(creditCardType2.getName()));
        // cancel booking
        selenium.click(BUTTON_CANCEL);
        selenium.waitForPageToLoad();
        // check that the booking cound remains unchanged
        assertEquals(bookingCount, selenium.getCount(COUNT_BOOKING));
    }

    @Test
    public void testBookingCanceling() {
        String[] hotelNames = new String[]{"Hilton Diagonal Mar", "Parc 55", "Ritz-Carlton Montreal", "Parc 55"};
        int bookingCount = selenium.getCount(COUNT_BOOKING);

        // make 3 bookings
        for (String hotelName : hotelNames) {
            bookHotel(hotelName, CreditCardType.VISA);
        }

        bookingCount += hotelNames.length;
        assertEquals(bookingCount, selenium.getCount(COUNT_BOOKING));

        for (int i = 0; i < hotelNames.length; i++) {
            String hotelName = selenium.getText(BOOKING_TABLE_FIRST_ROW_NAME).trim();
            selenium.click(BOOKING_TABLE_FIRST_ROW_LINK);
            selenium.waitForPageToLoad();
            String message = selenium.getText(BOOKING_CANCEL_MESSAGE);
            assertTrue("Unexpected canceling message: " + message, message.matches(BOOKING_CANCEL_MESSAGE_TEXT));
            assertTrue("Unexpected hotel name.", message.contains(hotelName));
            assertEquals("Unexpected number of bookings", --bookingCount, selenium.getCount(COUNT_BOOKING));
        }
    }

    protected void bookHotel(String hotelName, CreditCardType creditCardType) {
        if (!isLoggedIn()) {
            fail();
        }
        if (!selenium.isElementPresent(SEARCH_QUERY)) {
            selenium.open(contextPath);
            selenium.waitForPageToLoad();
            selenium.click(MENU_FIND);
            selenium.waitForPageToLoad();
        }
        enterSearchQuery(hotelName);
        selenium.click(SEARCH_RESULT_TABLE_FIRST_ROW_LINK);
        selenium.waitForPageToLoad();
        // booking page
        selenium.click(BUTTON_BOOK);
        selenium.waitForPageToLoad();
        // hotel page
        populateBookingFields(creditCardType);
        selenium.click(BUTTON_PROCEED);
        selenium.waitForPageToLoad();
        // confirm page
        selenium.click(BUTTON_CONFIRM);
        selenium.waitForPageToLoad();
        // main page
        assertTrue("Booking failed.", selenium.isTextPresent(BOOKING_MESSAGE));
    }

    protected void populateBookingFields(CreditCardType creditCardType) {
        selenium.select(DETAILS_CARD_TYPE, creditCardType.getLocator());
    }

    protected void populateBookingFields(String creditCardNumber, CreditCardType creditCardType) {
        selenium.type(DETAILS_CARD_NUMBER, creditCardNumber);
        populateBookingFields(creditCardType);
    }

    private enum CreditCardType {
        VISA("VISA"),
        MASTERCARD("MasterCard"),
        AMEX("AMEX"),
        DISCOVER("Discover");

        private String name;

        private CreditCardType(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public OptionLocator<?> getLocator() {
            return new OptionValueLocator(name);
        }
    }
}
