package test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertArrayEquals;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cz.vutbr.web.css.CSSFactory;
import cz.vutbr.web.css.CombinedSelector;
import cz.vutbr.web.css.RuleFactory;
import cz.vutbr.web.css.RuleSet;
import cz.vutbr.web.css.Selector;
import cz.vutbr.web.css.StyleSheet;
import cz.vutbr.web.css.CSSException;
import cz.vutbr.web.css.Term;
import cz.vutbr.web.css.TermFactory;
import cz.vutbr.web.css.TermNumeric;

public class SelectorTest {
	private static final Logger log = LoggerFactory.getLogger(SelectorTest.class);

	private static final TermFactory tf = CSSFactory.getTermFactory();
	private static final RuleFactory rf = CSSFactory.getRuleFactory();

	public static final String TEST_MULTIPLE = "H1, DIV { display: block;}";

	public static final String TEST_DESCENDANT = "H1 P { display: inline;}";

	public static final String TEST_ADJACENT = "DIV+P { color: blue;}";
	
	public static final String TEST_PRECEDING = "DIV ~ P { color: blue;}";

	public static final String TEST_CHILD = "DIV >P, SPAN, A, FORM+DIV { color: white;}";

	public static final String TEST_CLASS = ".fit { width: 80%;}";

	public static final String TEST_ID = "#krysa { font-size: 100px;}";
    public static final String TEST_INVALID_ID = "#1krysa { font-size: 100px;}";
    public static final String TEST_ESCAPED_ID = "#\\31krysa { font-size: 100px;}";

	public static final String TEST_ATTRIB = "A[href='fit.vutbr.cz'][id|=fit] { text-align: left;}";

	public static final String TEST_PSEUDO = ":hover { text-decoration: underline;}";

	public static final String TEST_PSEUDO_FUNC = ":lang(fr) > Q { quotes: '« ' ' »' }";

	public static final String TEST_PSPECIAL = "P.special:before {content: \"Special! \"}\n"
			+ "P.special:first-letter {color: #ffd800}";

	public static final String TEST_ASTERISK = "*.home { font-family: Verdana, monospace} ";

	public static final String TEST_IDATTRIB = "#krysa[id='krysa'] { text-align: right}";

	public static final String TEST_ATTRIB_PRESENCE = "*[href] { text-decoration: underline}";

	@BeforeClass
	public static void init() {
		log.info("\n\n\n == SelectorTest test at {} == \n\n\n", new Date());
	}

	@Test
	public void testMultiple() throws CSSException, IOException {
		StyleSheet ss = CSSFactory.parseString(TEST_MULTIPLE, null);

		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		assertArrayEquals("Rule contains two selectors H1, DIV  ", SelectorsUtil
				.createSelectors("H1", "DIV"), rule.getSelectors());

		assertEquals("Rule contains one declaration {display:block;}",
				DeclarationsUtil.appendDeclaration(null, "display", tf
						.createIdent("block")), rule.asList());
	}

	@Test
	public void testDescendant() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_DESCENDANT, null);

		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "H1", null);
		SelectorsUtil.appendDescendant(cslist, "P");

		assertArrayEquals("Rule contains one combined selectors H1 P  ", cslist.toArray(),
				rule.getSelectors());

		assertEquals("Rule contains one declaration {display:inline;}",
				DeclarationsUtil.appendDeclaration(null, "display", tf
						.createIdent("inline")), rule.asList());
	}

	@Test
	public void testAdjacent() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_ADJACENT, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "DIV", null);
		SelectorsUtil.appendAdjacent(cslist, "P");

		assertArrayEquals("Rule contains one combined selectors DIV+P ", cslist.toArray(),
				rule.getSelectors());

		assertEquals("Rule contains one declaration {color:blue;}",
				DeclarationsUtil.appendDeclaration(null, "color", tf
						.createColor(0, 0, 255)), rule.asList());

	}

    @Test
    public void testPreceding() throws CSSException, IOException {

        StyleSheet ss = CSSFactory.parseString(TEST_PRECEDING, null);
        assertEquals("One rule is set", 1, ss.size());

        RuleSet rule = (RuleSet) ss.get(0);

        List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
        SelectorsUtil.appendSimpleSelector(cslist, "DIV", null);
        SelectorsUtil.appendPreceding(cslist, "P");

        assertArrayEquals("Rule contains one combined selectors DIV~P ", cslist.toArray(),
                rule.getSelectors());

        assertEquals("Rule contains one declaration {color:blue;}",
                DeclarationsUtil.appendDeclaration(null, "color", tf
                        .createColor(0, 0, 255)), rule.asList());

    }

	@Test
	public void testChild() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_CHILD, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "DIV", null);
		SelectorsUtil.appendChild(cslist, "P");
		SelectorsUtil.appendCS(cslist);
		SelectorsUtil.appendSimpleSelector(cslist, "SPAN", null);
		SelectorsUtil.appendCS(cslist);
		SelectorsUtil.appendSimpleSelector(cslist, "A", null);
		SelectorsUtil.appendCS(cslist);
		SelectorsUtil.appendSimpleSelector(cslist, "FORM", null);
		SelectorsUtil.appendAdjacent(cslist, "DIV");

		assertArrayEquals(
				"Rule contains four combined selectors DIV>P, SPAN, A, FORM+DIV ",
				cslist.toArray(), rule.getSelectors());

		assertEquals("Rule contains one declaration {color:white;}",
				DeclarationsUtil.appendDeclaration(null, "color", tf
						.createColor(255, 255, 255)), rule.asList());
	}

	@Test
	public void testClass() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_CLASS, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, null, null, rf
				.createClass("fit"));

		assertArrayEquals("Rule contains one class selector .fit", cslist.toArray(), rule
				.getSelectors());

		assertEquals("Rule contains one declaration { width: 80%;}",
				DeclarationsUtil.appendDeclaration(null, "width", tf
						.createPercent(80.0f)), rule.asList());
	}

	@Test
	public void testID() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_ID, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, null, null, rf
				.createID("krysa"));

		assertArrayEquals("Rule contains one ID selector #krysa", cslist.toArray(), rule
				.getSelectors());

		assertEquals("Rule contains one declaration { font-size: 100px;}",
				DeclarationsUtil.appendDeclaration(null, "font-size", tf
						.createLength(100.0f).setUnit(TermNumeric.Unit.px)),
				rule.asList());
	}

    @Test
    public void testInvalidID() throws CSSException, IOException {

        StyleSheet ss = CSSFactory.parseString(TEST_INVALID_ID, null);
        assertEquals("No rule is set", 0, ss.size());

    }

    @Test
    public void testEscapedID() throws CSSException, IOException {

        StyleSheet ss = CSSFactory.parseString(TEST_ESCAPED_ID, null);
        assertEquals("One rule is set", 1, ss.size());

        RuleSet rule = (RuleSet) ss.get(0);

        List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
        SelectorsUtil.appendSimpleSelector(cslist, null, null, rf
                .createID("1krysa"));

        assertArrayEquals("Rule contains one ID selector #1krysa", cslist.toArray(), rule
                .getSelectors());

        assertEquals("Rule contains one declaration { font-size: 100px;}",
                DeclarationsUtil.appendDeclaration(null, "font-size", tf
                        .createLength(100.0f).setUnit(TermNumeric.Unit.px)),
                rule.asList());
    }

	@Test
	public void testIDAttrib() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_IDATTRIB, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, null, null, rf
				.createID("krysa"), rf.createAttribute("krysa", true,
				Selector.Operator.EQUALS, "id"));

		assertArrayEquals("Rule contains one ID selector #krysa[id='krysa']",
				cslist.toArray(), rule.getSelectors());

		assertEquals("Rule contains one declaration { text-align: right }",
				DeclarationsUtil.appendDeclaration(null, "text-align", tf
						.createIdent("right")), rule.asList());
	}

	@Test
	public void testAttribute() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_ATTRIB, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "A", null, rf
				.createAttribute("fit.vutbr.cz", true,
						Selector.Operator.EQUALS, "href"), rf.createAttribute(
				"fit", false, Selector.Operator.DASHMATCH, "id"));

		assertArrayEquals(
				"Rule contains one ID attributed selector A[href='fit.vutbr.cz'][id|=fit]",
				cslist.toArray(), rule.getSelectors());

		assertEquals("Rule contains one declaration { text-align: left; }",
				DeclarationsUtil.appendDeclaration(null, "text-align", tf
						.createIdent("left")), rule.asList());
	}

	@Test
	public void testPseudo() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_PSEUDO, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, null, null, rf.createPseudoClass("hover"));

		assertArrayEquals("Rule contains one pseudoselector :hover", cslist.toArray(), rule
				.getSelectors());

		assertEquals(
				"Rule contains one declaration { text-decoration: underline; }",
				DeclarationsUtil.appendDeclaration(null, "text-decoration", tf
						.createIdent("underline")), rule.asList());
	}

	@Test
	public void testPseudoFunc() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_PSEUDO_FUNC, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, null, null, rf.createPseudoClass("lang", "fr"));
		SelectorsUtil.appendChild(cslist, "Q");

		assertArrayEquals("Rule contains one combined pseudoselector :lang(fr)>Q",
				cslist.toArray(), rule.getSelectors());

		List<Term<?>> terms = DeclarationsUtil.appendTerm(null, null, tf
				.createString("« "));
		DeclarationsUtil.appendSpaceTerm(terms, tf.createString(" »"));

		assertEquals("Rule contains one declaration { quotes: '« ' ' »' }",
				DeclarationsUtil.appendDeclaration(null, "quotes", terms), rule
						.asList());
	}

	@Test
	public void testPSpecial() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_PSPECIAL, null);
		assertEquals("Two rules are set", 2, ss.size());

		// test first rule
		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "P", null, 
                rf.createClass("special"), 
                rf.createPseudoElement("before"));

		assertArrayEquals("Rule 1 contains one combined selector P.special:before",
				cslist.toArray(), ((RuleSet) ss.get(0)).getSelectors());

		assertEquals(
				"Rule 2 contains one declaration { content: \"Special! \"}",
				DeclarationsUtil.appendDeclaration(null, "content", tf
						.createString("Special! ")), ((RuleSet) ss.get(0))
						.asList());

		// test second rule
		cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "P", null, 
                rf.createClass("special"), 
                rf.createPseudoElement("first-letter"));

		assertArrayEquals(
				"Rule 2 contains one combined selector P.special:first-letter",
				cslist.toArray(), ((RuleSet) ss.get(1)).getSelectors());

		assertEquals("Rule 2 contains one declaration { color: #ffd800}",
				DeclarationsUtil.appendDeclaration(null, "color", tf
						.createColor(255, 216, 0)), ((RuleSet) ss.get(1))
						.asList());
	}

	@Test
	public void testAsterisk() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_ASTERISK, null);
		assertEquals("One rule is set", 1, ss.size());

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "*", null, rf
				.createClass("home"));

		assertArrayEquals("Rule 1 contains one combined selector *.home", cslist.toArray(),
				((RuleSet) ss.get(0)).getSelectors());

		List<Term<?>> terms = DeclarationsUtil.appendTerm(null, null, tf
				.createIdent("Verdana"));
		DeclarationsUtil.appendCommaTerm(terms, tf.createIdent("monospace"));

		assertEquals(
				"Rule contains one declaration { font-family: Verdana, monospace }",
				DeclarationsUtil.appendDeclaration(null, "font-family", terms),
				((RuleSet) ss.get(0)).asList());
	}

	@Test
	public void testElementName() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_MULTIPLE, null);
		assertEquals("One rule is set", 1, ss.size());

		RuleSet rule = (RuleSet) ss.get(0);

		CombinedSelector[] cslist = rule.getSelectors();

		assertEquals("Rule rule contains two selectors", 2, cslist.length);

		// H1
		CombinedSelector s = cslist[0];

		assertEquals("CombinedSelector 1 contains one simple selector", 1, s
				.size());

		assertEquals("CombinedSelector contains element name", "H1", s.get(0)
				.getElementName());

		// DIV
		s = cslist[1];

		assertEquals("CombinedSelector 2 contains one simple selector", 1, s
				.size());

		assertEquals("CombinedSelector contains element name", "DIV", s.get(0)
				.getElementName());
	}

	@Test
	public void testAttributePresence() throws CSSException, IOException {

		StyleSheet ss = CSSFactory.parseString(TEST_ATTRIB_PRESENCE, null);
		assertEquals("One rule is set", 1, ss.size());

		List<CombinedSelector> cslist = SelectorsUtil.appendCS(null);
		SelectorsUtil.appendSimpleSelector(cslist, "*", null, rf
				.createAttribute(null, false, Selector.Operator.NO_OPERATOR,
						"href"));

		assertArrayEquals("Rule 1 contains one combined selector *[href]", cslist.toArray(),
				((RuleSet) ss.get(0)).getSelectors());

		List<Term<?>> terms = DeclarationsUtil.appendTerm(null, null, tf
				.createIdent("Verdana"));
		DeclarationsUtil.appendCommaTerm(terms, tf.createIdent("monospace"));

		assertEquals(
				"Rule contains one declaration { text-decoration: underline }",
				DeclarationsUtil.appendDeclaration(null, "text-decoration", tf
						.createIdent("underline")), ((RuleSet) ss.get(0))
						.asList());

	}

}