package test;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;

import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import cz.vutbr.web.css.CSSException;
import cz.vutbr.web.css.CSSFactory;
import cz.vutbr.web.css.CombinedSelector;
import cz.vutbr.web.css.NodeData;
import cz.vutbr.web.css.RuleFactory;
import cz.vutbr.web.css.RuleSet;
import cz.vutbr.web.css.Selector;
import cz.vutbr.web.css.Selector.PseudoClassType;
import cz.vutbr.web.css.StyleSheet;
import cz.vutbr.web.css.TermColor;
import cz.vutbr.web.css.TermFactory;
import cz.vutbr.web.csskit.MatchConditionOnElements;
import cz.vutbr.web.domassign.DirectAnalyzer;
import cz.vutbr.web.domassign.StyleMap;
import java.util.List;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;

public class PseudoClassTest {
	private static final Logger log = LoggerFactory.getLogger(PseudoClassTest.class);
	
	private static TermFactory tf = CSSFactory.getTermFactory();
    private static final RuleFactory rf = CSSFactory.getRuleFactory();
	
	@BeforeClass
	public static void init() throws SAXException, IOException {
		log.info("\n\n\n == DOMAssign test at {} == \n\n\n", new Date());
	}
	
    @Test
    public void pseudoClassMap() throws SAXException, IOException {  
        
        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);
        
        MatchConditionOnElements cond = new MatchConditionOnElements("a", PseudoClassType.LINK);
        cond.addMatch(elements.getElementById("l2"), PseudoClassType.HOVER);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.VISITED);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.HOVER);
        cond.removeMatch(elements.getElementById("l3"), PseudoClassType.HOVER);
        CSSFactory.registerDefaultMatchCondition(cond);
        
        StyleMap decl = CSSFactory.assignDOM(doc, null, createBaseFromFilename("data/simple/pseudo.html"),"screen", true);
        
        NodeData l1 = getStyleById(elements, decl, "l1");
        NodeData l2 = getStyleById(elements, decl, "l2");
        NodeData l3 = getStyleById(elements, decl, "l3");
        
        assertThat(l1.getValue(TermColor.class, "color"), is(tf.createColor(0,255,0)));
        assertThat(l2.getValue(TermColor.class, "color"), is(tf.createColor(0,255,255)));
        assertThat(l3.getValue(TermColor.class, "color"), is(tf.createColor(0,0,170)));
    }
    
    @Test
    public void pseudoClassDirect() throws SAXException, IOException {  
        
        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);
        
        MatchConditionOnElements cond = new MatchConditionOnElements("a", PseudoClassType.LINK);
        cond.addMatch(elements.getElementById("l2"), PseudoClassType.HOVER);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.VISITED);
        CSSFactory.registerDefaultMatchCondition(cond);
        
        StyleSheet style = CSSFactory.getUsedStyles(doc, null, createBaseFromFilename("data/simple/selectors.html"),"screen");
        DirectAnalyzer da = new DirectAnalyzer(style);

        NodeData l1 = getStyleById(elements, da, "l1");
        NodeData l2 = getStyleById(elements, da, "l2");
        NodeData l3 = getStyleById(elements, da, "l3");
        
        assertThat(l1.getValue(TermColor.class, "color"), is(tf.createColor(0,255,0)));
        assertThat(l2.getValue(TermColor.class, "color"), is(tf.createColor(0,255,255)));
        assertThat(l3.getValue(TermColor.class, "color"), is(tf.createColor(0,0,170)));
    }
    
    @Test
    public void pseudoClassMapNonStatic() throws SAXException, IOException {

        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);

        MatchConditionOnElements cond = new MatchConditionOnElements("a", PseudoClassType.LINK);
        cond.addMatch(elements.getElementById("l2"), PseudoClassType.HOVER);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.VISITED);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.HOVER);
        cond.removeMatch(elements.getElementById("l3"), PseudoClassType.HOVER);

        StyleMap decl = CSSFactory.assignDOM(doc, null, createBaseFromFilename("data/simple/pseudo.html"),"screen", true, cond);

        NodeData l1 = getStyleById(elements, decl, "l1");
        NodeData l2 = getStyleById(elements, decl, "l2");
        NodeData l3 = getStyleById(elements, decl, "l3");

        assertThat(l1.getValue(TermColor.class, "color"), is(tf.createColor(0,255,0)));
        assertThat(l2.getValue(TermColor.class, "color"), is(tf.createColor(0,255,255)));
        assertThat(l3.getValue(TermColor.class, "color"), is(tf.createColor(0,0,170)));
    }

    @Test
    public void pseudoClassDirectNonStatic() throws SAXException, IOException {

        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);

        MatchConditionOnElements cond = new MatchConditionOnElements("a", PseudoClassType.LINK);
        cond.addMatch(elements.getElementById("l2"), PseudoClassType.HOVER);
        cond.addMatch(elements.getElementById("l3"), PseudoClassType.VISITED);

        StyleSheet style = CSSFactory.getUsedStyles(doc, null, createBaseFromFilename("data/simple/selectors.html"),"screen");
        DirectAnalyzer da = new DirectAnalyzer(style);
        da.registerMatchCondition(cond);

        NodeData l1 = getStyleById(elements, da, "l1");
        NodeData l2 = getStyleById(elements, da, "l2");
        NodeData l3 = getStyleById(elements, da, "l3");

        assertThat(l1.getValue(TermColor.class, "color"), is(tf.createColor(0,255,0)));
        assertThat(l2.getValue(TermColor.class, "color"), is(tf.createColor(0,255,255)));
        assertThat(l3.getValue(TermColor.class, "color"), is(tf.createColor(0,0,170)));
    }

    // Test for issue #52
    @Test
    public void pseudoClassVsPseudoElementSelector() throws SAXException, IOException {

        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);

        StyleSheet style = CSSFactory.getUsedStyles(doc, null, createBaseFromFilename("data/simple/selectors.html"),"screen");
        DirectAnalyzer da = new DirectAnalyzer(style);

        NodeData nodeData = getStyleById(elements, da, "p1");
        assertThat(nodeData.getValue(TermColor.class, "color"), is(tf.createColor(0,128,0)));
    }

    private static final String TEST_PSEUDO_1 = // Legacy support
            "p:first-line { color: red; }" +
            "p:first-letter { color: blue; }" + 
            "p:before { color: yellow; }" + 
            "p:after { color: green; }";
    private static final String TEST_PSEUDO_2 =
            "p::first-line { color: red; }" +
            "p::first-letter { color: blue; }" + 
            "p::before { color: yellow; }" + 
            "p::after { color: green; }";
    
    @Test
    public void legacyPseudoElementSupport() throws CSSException, IOException {
        StyleSheet style = CSSFactory.parseString(TEST_PSEUDO_1, null);
        assertEquals("There are 4 rules", 4, style.size());
        
        List<CombinedSelector> sel1 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("first-line"));
        List<CombinedSelector> sel2 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("first-letter"));
        List<CombinedSelector> sel3 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("before"));
        List<CombinedSelector> sel4 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("after"));

        RuleSet rule1 = (RuleSet) style.get(0);
        assertArrayEquals("Rule contains one selector p::first-line", sel1.toArray(), rule1.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule1.size());
        
        RuleSet rule2 = (RuleSet) style.get(1);
        assertArrayEquals("Rule contains one selector p::first-letter", sel2.toArray(), rule2.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule2.size());
        
        RuleSet rule3 = (RuleSet) style.get(2);
        assertArrayEquals("Rule contains one selector p::before", sel3.toArray(), rule3.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule3.size());
        
        RuleSet rule4 = (RuleSet) style.get(3);
        assertArrayEquals("Rule contains one selector p::after", sel4.toArray(), rule4.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule4.size());
    }
    
    @Test
    public void nonlegacyPseudoElementSupport() throws CSSException, IOException {
        StyleSheet style = CSSFactory.parseString(TEST_PSEUDO_2, null);
        assertEquals("There are 4 rules", 4, style.size());
        
        List<CombinedSelector> sel1 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("first-line"));
        List<CombinedSelector> sel2 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("first-letter"));
        List<CombinedSelector> sel3 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("before"));
        List<CombinedSelector> sel4 = SelectorsUtil.appendSimpleSelector(null, "p", null, rf.createPseudoElement("after"));

        RuleSet rule1 = (RuleSet) style.get(0);
        assertArrayEquals("Rule contains one selector p::first-line", sel1.toArray(), rule1.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule1.size());
        
        RuleSet rule2 = (RuleSet) style.get(1);
        assertArrayEquals("Rule contains one selector p::first-letter", sel2.toArray(), rule2.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule2.size());
        
        RuleSet rule3 = (RuleSet) style.get(2);
        assertArrayEquals("Rule contains one selector p::before", sel3.toArray(), rule3.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule3.size());
        
        RuleSet rule4 = (RuleSet) style.get(3);
        assertArrayEquals("Rule contains one selector p::after", sel4.toArray(), rule4.getSelectors());
        assertEquals("Rule contains one declaration", 1, rule4.size());
    }
    
    // Test for issue #83
    private static final String TEST_RANGE = 
            "input[type=range]::-webkit-slider-runnable-track { width: 1em; }\n" +
            "input[type=range]::-webkit-slider-thumb { width: 2em; }\n" +
            "input[type=range]::-moz-range-track { width: 3em; }\n" +
            "input[type=range]::-moz-range-thumb { width: 4em; }\n" +
            "input[type=range]::-ms-track { width: 5em; }\n" +
            "input[type=range]::-ms-thumb { width: 6em; }";
    private static final String[] TEST_RANGE_ELEMENTS = new String[] {
            "-webkit-slider-runnable-track",
            "-webkit-slider-thumb",
            "-moz-range-track",
            "-moz-range-thumb",
            "-ms-track",
            "-ms-thumb"
            };
    
    @Test
    public void rangeInputPseudoElements() throws CSSException, IOException {
        StyleSheet style = CSSFactory.parseString(TEST_RANGE, null);
        assertEquals("There are 6 rules", 6, style.size());
        
        for (int i = 0; i < TEST_RANGE_ELEMENTS.length; i++) {
            List<CombinedSelector> cslist = SelectorsUtil.appendSimpleSelector(null, 
                "input", 
                null, 
                rf.createAttribute("range", false, Selector.Operator.EQUALS, "type"), 
                rf.createPseudoElement(TEST_RANGE_ELEMENTS[i]));

            RuleSet rule = (RuleSet) style.get(i);
            assertArrayEquals("Rule contains one selector input[type=range]::" + TEST_RANGE_ELEMENTS[i], cslist.toArray(), rule.getSelectors());
            assertEquals("Rule contains one declaration", 1, rule.size());
        }
    }
    
    @Test
    public void childPseudoClassesTest() throws SAXException, IOException {
        DOMSource ds = new DOMSource(getClass().getResourceAsStream("/simple/child-pseudo.html"));
        Document doc = ds.parse();
        ElementMap elements = new ElementMap(doc);

        StyleSheet style = CSSFactory.getUsedStyles(doc, null, null, "screen");
        DirectAnalyzer da = new DirectAnalyzer(style);

        TermColor[] expected = new TermColor[] {
            tf.createColor("#F0F"),
            tf.createColor("#00F"),
            tf.createColor("#00F"),
            tf.createColor("#0F0"),
            tf.createColor("#F00"),
            tf.createColor("#FFF"),
            tf.createColor("#FF0"),
            tf.createColor("#FFF"),
            tf.createColor("#000"),
            tf.createColor("#0F0"),
            tf.createColor("#000"),
            tf.createColor("#FFF"),
            tf.createColor("#0F0"),
            tf.createColor("#FFF"),
            tf.createColor("#F00"),
            tf.createColor("#0F0"),
            tf.createColor("#000"),
            tf.createColor("#0FF")
        };
        
        for (int id = 1; id <= 18; id++) {
            NodeData nodeData = getStyleById(elements, da, "list-" + id);
            assertThat(nodeData.getValue(TermColor.class, "color"), is(expected[id - 1]));
        }
    }
    
    private NodeData getStyleById(ElementMap elements, StyleMap decl, String id)
    {
        NodeData data = decl.get(elements.getElementById(id));
        assertNotNull("Data for #" + id + " exists", data);
        return data;
    }
    
    private NodeData getStyleById(ElementMap elements, DirectAnalyzer da, String id)
    {
        NodeData data = da.getElementStyle(elements.getElementById(id), null, "screen");
        assertNotNull("Data for #" + id + " exists", data);
        return data;
    }
    
	private static URL createBaseFromFilename(String filename) {
		try {
			File f = new File(filename);
			return f.toURI().toURL();
		} catch (MalformedURLException e) {
			return null;
		}
	}
}