/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.xml.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Function;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.suite.IteratorTester;
import net.ranides.assira.xml.XMLElement;
import static net.ranides.assira.junit.NewAssert.*;
import net.ranides.assira.text.StringUtils;
import net.ranides.assira.TestFiles;
import net.ranides.assira.text.Charsets;
import net.ranides.assira.text.IOStrings;
import net.ranides.assira.xml.XMLDocument;
import net.ranides.assira.xml.XMLElements;
import org.junit.Test;
import net.ranides.assira.xml.XMLSelector;
import static net.ranides.assira.xml.XMLElement.$;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class CElementsTest {
 
    private static final String[] LOGINS = new String[]{
        "ranides", "admin", "guest", "owner", "saren"
    };
    
    private static final String[] IDS = new String[]{
        "101", "102", "103", "104", "105"
    };

    @Test
    public void testIterator() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        Iterator<String> i1 = IteratorUtils.map(doc.find("login").iterator(), n -> n.text());
        IteratorTester.basicIterator(i1, 0, LOGINS);
        
        Iterator<String> i2 = IteratorUtils.map(doc.find("user").iterator(), n -> n.attr("id").text());
        IteratorTester.basicIterator(i2, 0, IDS);
    }
    
    @Test
    public void testNodes() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> list = ListUtils.map(doc.find("login").nodes(), n -> n.getTextContent());
        assertEquals(Arrays.asList(LOGINS), list);
    }
    
    @Test
    public void testTextContent() {
        XMLElement doc = $(TestFiles.XML_INPUT);

        List<String> exp = Arrays.asList(
            "#101", "#102", "#103", "#104", "#105"
        );
        
        assertEquals(Arrays.asList(IDS), doc.find("? //user/@id").contents());
        assertEquals(Arrays.asList(IDS), doc.find("? //user/@id").texts());
        assertEquals(StringUtils.join(IDS,""), doc.find("? //user/@id").content());
        assertEquals(StringUtils.join(IDS,""), doc.find("? //user/@id").text());
        
        assertEquals(Arrays.asList(null,null,null,null,null), doc.find("? //user/login").contents());
        assertEquals(Arrays.asList(LOGINS), doc.find("? //user/login").texts());
        assertEquals("", doc.find("? //user/login").content());
        assertEquals(StringUtils.join(LOGINS,""), doc.find("? //user/login").text());
        
        assertEquals(exp, doc.find("? //user/@id").texts(n -> "#"+n));        
        assertEquals(StringUtils.join(exp,""), doc.find("? //user/@id").text(n -> "#"+n));        
    }
    
    @Test
    public void testConcat() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        XMLElements a = doc.find("? //user/login");
        XMLElements b = doc.find("? //user/@id");

        List<String> exp1 = new ArrayList<>();
        exp1.addAll(Arrays.asList(LOGINS));
        exp1.addAll(Arrays.asList(IDS));
        assertEquals(exp1, a.concat(b).texts());
    }
    
    @Test
    public void testFilter() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
        List<String> exp2 = Arrays.asList("ranides", "admin", "saren");
        List<String> exp3 = Arrays.asList("guest", "owner");
        List<String> exp4 = Arrays.asList();
        
        assertEquals(exp1, doc.find("user login").texts());
        assertEquals(exp2, doc.find("user[active='true'] login").texts());
        assertEquals(exp3, doc.find("user[active='false'] login").texts());
        
        assertEquals(exp1, doc.find("user").find("login").texts());
        assertEquals(exp2, doc.find("user").filter("[active='true']").find("login").texts());
        assertEquals(exp3, doc.find("user").filter("[active='false']").find("login").texts());
        
        assertEquals(exp1, doc.find("user").filter(n -> true).find("login").texts());
        assertEquals(exp4, doc.find("user").filter(n -> false).find("login").texts());
        assertEquals(exp2, doc.find("user").filter(n -> n.node().attr("active").content().equals("true")).find("login").texts());
        assertEquals(exp3, doc.find("user").filter(n -> !n.node().attr("active").content().equals("true")).find("login").texts());
    }
    
    @Test
    public void testDiscard() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
        List<String> exp2 = Arrays.asList("ranides", "admin", "saren");
        List<String> exp3 = Arrays.asList("guest", "owner");
        List<String> exp4 = Arrays.asList();

        assertEquals(exp1, doc.find("user").find("login").texts());
        assertEquals(exp3, doc.find("user").discard("[active='true']").find("login").texts());
        assertEquals(exp2, doc.find("user").discard("[active='false']").find("login").texts());
        
        assertEquals(exp4, doc.find("user").discard(n -> true).find("login").texts());
        assertEquals(exp1, doc.find("user").discard(n -> false).find("login").texts());
        assertEquals(exp3, doc.find("user").discard(n -> n.node().attr("active").content().equals("true")).find("login").texts());
        assertEquals(exp2, doc.find("user").discard(n -> !n.node().attr("active").content().equals("true")).find("login").texts());
    }
    
    @Test
    public void testLimit() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("ranides", "admin");
        List<String> exp2 = Arrays.asList();
        
        assertEquals(exp1, doc.find("user").limit("[active='true']").find("login").texts());
        assertEquals(exp2, doc.find("user").limit("[active='false']").find("login").texts());
        
        assertEquals(exp1, doc.find("user").limit(n -> n.node().attr("active").content().equals("true")).find("login").texts());
        assertEquals(exp2, doc.find("user").limit(n -> n.node().attr("active").content().equals("false")).find("login").texts());
    }
    
    @Test
    public void testMap() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        List<String> exp1 = Arrays.asList(LOGINS);
        List<String> exp2 = Arrays.asList(IDS);
        
        XMLSelector s1 = XMLSelector.compile("login");
        XMLSelector s2 = XMLSelector.compile("? @id");
        assertEquals(exp1, doc.find("user").map(s1).texts());
        assertEquals(exp2, doc.find("user").map(s2).texts());
        
        XMLElement none =  doc.document().comment("none");
        
        Function<XMLElement, XMLElement> nname = n -> {
            try {
                return n.children("mail").last();
            } catch(NoSuchElementException ex) {
                return none;
            }
        };
        
        List<String> exp3 = Arrays.asList("contact@ranides.net", "admin@ccms.net", "none", "office@ccms.net", "saren@ccms.net");
        assertEquals(exp3, doc.find("user").map(nname).texts());
    }
    
    @Test
    public void testFirstLast() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        assertEquals("tom@ccms.net", doc.find("? //user[4]/mail").first().text());
        assertEquals("tom@domain.com", doc.find("? //user[4]/mail").first("[type]").text());
        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").first("[type='office']").text());
        
        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").last().text());
        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").last("[type]").text());
        assertEquals("tom@domain.com", doc.find("? //user[4]/mail").last("[type='private']").text());
        
    }
    
    @Test
    public void testChildren() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "mail", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text");
        List<String> exp2 = Arrays.asList("Ranides Atterwim", "undefined", "undefined", "Tom", "Sarenka");
        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "tom@ccms.net", "tom@domain.com", "office@ccms.net", "saren@ccms.net");
        List<String> exp4 = Arrays.asList("contact@ranides.net", "tom@domain.com");
        List<String> exp5 = Arrays.asList("office@ccms.net");
        List<String> exp6 = Arrays.asList("", "ranides", "", "C2A7B5", "", "Ranides Atterwim", "", "ranides@ccms.net", "", "contact@ranides.net", "", "", "admin", "", "F76A33", "", "undefined", "", "admin@ccms.net", "", "", "guest", "", "E4C156", "", "undefined", "", "", "owner", "", "AC3750", "", "Tom", "", "tom@ccms.net", "", "tom@domain.com", "", "office@ccms.net", "", "", "saren", "", "D046A3", "", "Sarenka", "", "saren@ccms.net", "");
        
        assertEquals(exp1, doc.find("user").children().names());
        assertEquals(exp6, doc.find("user").children().texts(StringUtils::trim));
        
        assertEquals(exp2, doc.find("user").children("name").texts());
        assertEquals(exp3, doc.find("user").children("mail").texts());
        assertEquals(exp4, doc.find("user").children("mail[type='private']").texts());
        assertEquals(exp5, doc.find("user").children("mail[type='office']").texts());
    }
    
    @Test
    public void testHas() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        assertTrue( doc.find("user login").has() );
        assertFalse( doc.find("user logins").has() );
        
        assertTrue( doc.find("user").has() );
        assertTrue( doc.find("user").has("mail[type]") );
        assertTrue( doc.find("user").has("mail[type]") );
        assertTrue( doc.find("user").has("mail[type='private']") );
        assertFalse( doc.find("user").has("mails") );
        assertFalse( doc.find("user").has("mail[type='public']") );
    }
    
    @Test
    public void testNext() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        List<String> exp1 = Arrays.asList("", "", "", "", "");
        List<String> exp2 = Arrays.asList("C2A7B5", "F76A33", "E4C156", "AC3750", "D046A3");
        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "tom@ccms.net", "tom@domain.com", "office@ccms.net", "saren@ccms.net");
        
        assertEquals(exp1, doc.find("user login").next().texts(StringUtils::trim));
        assertEquals(exp2, doc.find("user login").normalize().next().texts(StringUtils::trim));
        
        assertEquals(exp2, doc.find("user login").next("password").texts());
        assertEquals(exp3, doc.find("user login").next("mail").texts());
    }
    
    @Test
    public void testPrev() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        List<String> exp1 = Arrays.asList("", "", "", "", "");
        List<String> exp2 = Arrays.asList("C2A7B5", "F76A33", "E4C156", "AC3750", "D046A3");
        List<String> exp3 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
        
        
        assertEquals(exp1, doc.find("user name").prev().texts(StringUtils::trim));
        assertEquals(exp2, doc.find("user name").normalize().prev().texts(StringUtils::trim));
        
        assertEquals(exp2, doc.find("user name").prev("password").texts(StringUtils::trim));
        assertEquals(exp3, doc.find("user name").prev("login").texts(StringUtils::trim));
    }
    
    @Test
    public void testSiblings() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("", "C2A7B5", "", "ranides", "", "", "ranides@ccms.net", "", "contact@ranides.net", "", "", "F76A33", "", "admin", "", "", "admin@ccms.net", "", "", "D046A3", "", "saren", "", "", "saren@ccms.net", "");
        List<String> exp2 = Arrays.asList("C2A7B5", "ranides", "ranides@ccms.net", "contact@ranides.net", "F76A33", "admin", "admin@ccms.net", "D046A3", "saren", "saren@ccms.net");
        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "saren@ccms.net");
        List<String> exp4 = Arrays.asList("ranides", "admin", "saren");
        
        assertEquals(exp1, doc.find("user[active='true'] name").siblings().texts(StringUtils::trim));
        doc.normalize();
        assertEquals(exp2, doc.find("user[active='true'] name").siblings().texts(StringUtils::trim));
        assertEquals(exp3, doc.find("user[active='true'] name").siblings("mail").texts(StringUtils::trim));
        assertEquals(exp4, doc.find("user[active='true'] name").siblings("login").texts(StringUtils::trim));
    }
    
    @Test
    public void testParent() {
        XMLElement doc = $(TestFiles.XML_INPUT);
        
        List<String> exp1 = Arrays.asList("101", "102", "103", "104", "105");
        List<String> exp2 = Arrays.asList("users", "users", "users", "users", "users");
        List<String> exp3 = Arrays.asList("101", "101", "102", "104", "104", "104", "105");
        List<String> exp4 = Arrays.asList("users", "users", "users", "users", "users", "users", "users");
        List<String> exp5 = Arrays.asList("user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document");
        List<String> exp6 = Arrays.asList("user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document");
        
        assertEquals(exp1, doc.find("user login").parent().map(n -> n.attr("id")).contents() );
        assertEquals(exp2, doc.find("user login").parent("users").names());
        assertEquals(exp6, doc.find("user login").parent(n -> true).names());
        
        assertEquals(exp3, doc.find("user mail").parent().map(n -> n.attr("id")).contents() );
        assertEquals(exp3, doc.find("user mail").parent("user").map(n -> n.attr("id")).contents() );
        assertEquals(exp4, doc.find("user mail").parent("users").names());
        assertEquals(exp5, doc.find("user mail").parent(n -> true).names());
    }

    @Test
    public void testModification() throws IOException {
        XMLElement doc = $(TestFiles.XML_INPUT).normalize();
        XMLDocument builder = doc.document();
        XMLElements users = doc.find("user[active='true']");
        
        String exp1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><meta>user:active</meta>";
        String exp2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><meta-v2>7</meta-v2>";
        
        XMLElement t1 = $("<meta>user:active</meta>");
        XMLElement t2 = $("<meta-v2>7</meta-v2>");
        
        users.before(builder.comment("active user"));        
        users.before(t1.root());
        
        users.after(builder.tag("EOF"));        
        users.after(t2.root());
        
        doc.find("user[active='true'] login").prepend(builder.text("!"));
        doc.find("user[active='false'] login").append(builder.text("?"));
        doc.find("user mail").replace(builder.tag("nomail"));
        doc.find("user nomail").after(builder.comment("no email information"));
        doc.find("user password").remove();
        doc.find("user name").rename("username");
        doc.find("user nomail").wrap("info");
        doc.find("param[name='2']").unwrap();
        doc.find("? //user/@id").content(".");
        doc.find("nomail").text("HIDDEN");
        
        assertEquals(exp1, t1.writer().toString());
        assertEquals(exp2, t2.writer().toString());
        
        String out = doc.writer().indent(4).toString();
//        System.out.printf("%s%n", out);
        assertEquals(IOStrings.read(TestFiles.XML_ELEMENTS, Charsets.UTF8), out);
        
        XMLElement e = builder.fragment()
            .append(builder.tag("item1").text("value1"))
            .append(builder.tag("item2").text("value2"))
            .append(builder.tag("item2").text("<h1>value3</h1>"));
        doc.find("configuration").append(e);
        doc.find("configuration").append(e);
        
        XMLElement ddoc2 = builder.fragment("<var1>tt</var1><var2>tt</var2>");
        
        doc.find("configuration").append(ddoc2);
        
        doc.find("configuration var2").content("<ul class='list'><li>a</li><li>b</li></ul>");
        String out2 = doc.writer().indent(4).toString();
        
//        System.out.printf("%s%n", out2);
        assertEquals(IOStrings.read(TestFiles.XML_CONTENT, Charsets.UTF8), out2);
        
    }
    
}
