/*
 * @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.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import net.ranides.assira.collection.maps.MapCollectors;
import net.ranides.assira.collection.maps.MultiMap;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.CQueryBuilder;
import net.ranides.assira.text.StringUtils;
import net.ranides.assira.xml.XMLContext;
import net.ranides.assira.xml.XMLElement;
import net.ranides.assira.xml.XMLElements;
import net.ranides.assira.xml.XMLSelector;
import org.w3c.dom.Node;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class CElements implements XMLElements {
    
    public static final XMLElements EMPTY = new CElements(CQueryBuilder.empty());
    
    private final CQuery<XMLElement> stream;

    public CElements(Collection<Node> nodes) {
        this.stream = CQueryBuilder.fromCollection(nodes).map(e -> W3Element.wrap(e));
    }
    
    public CElements(CQuery<XMLElement> nodes) {
        this.stream = nodes;
    }
    
    @Override
    public Iterator<XMLElement> iterator() {
        return stream().iterator();
    }

    @Override
    public List<Node> nodes() {
        return stream().map(XMLElement::node).list();
    }

    @Override
    public List<String> names() {
        return stream().map(XMLElement::name).list();
    }

    @Override
    public List<String> xpaths() {
        return stream().map(XMLElement::xpath).list();
    }
    
    @Override
    public MultiMap<String, String> map() {
        return stream().collect(MapCollectors.multimap(XMLElement::name, XMLElement::content));
    }
    
    @Override
    public String content() {
        return StringUtils.join(contents(), "");
    }

    @Override
    public List<String> contents() {
        return stream().map(e -> e.content()).list();
    }
    
    @Override
    public String text() {
        return StringUtils.join(texts(), "");
    }

    @Override
    public String text(Function<String, String> function) {
        return StringUtils.join(this, "", n -> function.apply(n.text()));
    }

    @Override
    public List<String> texts() {
        return stream().map(e -> e.text()).list();
    }
    
    @Override
    public List<String> texts(Function<String, String> function) {
        return stream().map(n -> function.apply(n.text())).list();
    }
    

    @Override
    public XMLElements attrs() {
        return new W3FlatAttributes(stream());
    }

    @Override
    public XMLElements attrs(String name) {
        return new CElements(stream()
            .map(n -> n.attrs())
            .filter(n -> n.has(name))
            .map(a -> a.first(name))
        );
    }

    @Override
    public int size() {
        return stream().size();
    }

    @Override
    public CQuery<XMLElement> stream() {
        return stream;
    }

    @Override
    public <T> CQuery<T> stream(Function<? super XMLElement, ? extends T> function) {
        return stream().map(function);
    }

    @Override
    public XMLElements each(Consumer<? super XMLElement> consumer) {
        stream().each(consumer);
        return this;
    }

    @Override
    public XMLElement first() {
        return stream().first().get();
    }

    @Override
    public XMLElement first(String selector) {
        return first(XMLSelector.compile(selector));
    }

    @Override
    public XMLElement first(Predicate<XMLContext> selector) {
        CContext c = new CContext();
        return stream().filter(n -> c.test(n, selector)).first().get();
    }
    
    @Override
    public XMLElement last() {
        return stream().last().get();
    }

    @Override
    public XMLElement last(String selector) {
        return last(XMLSelector.compile(selector));
    }

    @Override
    public XMLElement last(Predicate<XMLContext> selector) {
        CContext c = new CContext();
        return stream().filter(n -> c.test(n, selector)).last().get();
    }

    @Override
    public XMLElements concat(XMLElements elements) {
        return new CElements(new CQueryBuilder<XMLElement>()
            .addQuery(stream())
            .addQuery(elements.stream()).result()
        );
    }

    @Override
    public XMLElements filter(String selector) {
        return filter(XMLSelector.compile(selector)); 
    }

    @Override
    public XMLElements filter(Predicate<XMLContext> selector) {
        CContext c = new CContext();
        return new CElements(stream().filter(n -> c.test(n, selector)));
    }

    @Override
    public XMLElements discard(String selector) {
        return discard(XMLSelector.compile(selector)); 
    }

    @Override
    public XMLElements discard(Predicate<XMLContext> selector) {
        CContext c = new CContext();
        return new CElements(stream().filter(n -> !c.test(n, selector)));
    }

    @Override
    public XMLElements limit(String selector) {
        return limit(XMLSelector.compile(selector)); 
    }

    @Override
    public XMLElements limit(Predicate<XMLContext> selector) {
        CContext c = new CContext();
        return new CElements(stream().limit(n -> c.test(n, selector)));
    }

    @Override
    public boolean matches() {
        return !stream().isEmpty();
    }
    
    XMLElements unfold(Function<XMLElement, XMLElements> function) {
        return new CElements(stream().unfold(n -> function.apply(n).stream()));
    }

    @Override
    public XMLElements map(XMLSelector function) {
        CContext c = new CContext();
        return unfold(n -> c.select(n, function));
    }
    
    @Override
    public XMLElements map(Function<? super XMLElement, ? extends XMLElement> function) {
        return new CElements(stream(function));
    }

    @Override
    public XMLElements find(String selector) {
        return find(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements find(XMLSelector selector) {
        CContext c = new CContext();
        return unfold(n -> c.select(n, selector));
    }

    @Override
    public XMLElements children() {
        return unfold(n -> n.children());
    }

    @Override
    public XMLElements children(String selector) {
        return children(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements children(Predicate<XMLContext> selector) {
        return unfold(n -> n.children().filter(selector));
    }

    @Override
    public boolean has() {
        return stream().matchAny(n -> n.has());
    }

    @Override
    public boolean has(String selector) {
        return has(XMLSelector.compile(selector));
    }

    @Override
    public boolean has(Predicate<XMLContext> selector) {
        return stream().matchAny(n -> n.has(selector));
    }
    

    @Override
    public XMLElements next() {
        return new CElements(stream().map(n -> n.next()));
    }

    @Override
    public XMLElements next(String selector) {
        return next(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements next(Predicate<XMLContext> selector) {
        return unfold(n -> n.next(selector));
    }

    @Override
    public XMLElements prev() {
        return new CElements(stream().map(n -> n.prev()));
    }

    @Override
    public XMLElements prev(String selector) {
        return prev(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements prev(Predicate<XMLContext> selector) {
        return unfold(n -> n.prev(selector));
    }

    @Override
    public XMLElements siblings() {
        return unfold(n -> n.siblings());
    }

    @Override
    public XMLElements siblings(String selector) {
        return siblings(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements siblings(Predicate<XMLContext> selector) {
        return unfold(n -> n.siblings(selector));
    }

    @Override
    public XMLElements parent() {
        return new CElements(stream().map(n -> n.parent()));
    }

    @Override
    public XMLElements parent(String selector) {
        return parent(XMLSelector.compile(selector));
    }

    @Override
    public XMLElements parent(Predicate<XMLContext> selector) {
        return unfold(n -> n.parents(selector));
    }

    @Override
    public XMLElements normalize() {
        return each(n -> n.normalize());
    }
    
    @Override
    public XMLElements clear() {
        return each(n -> n.clear());
    }
    
    @Override
    public XMLElements content(XMLElement value) {
        return each(n -> n.content(value));
    }
    
    @Override
    public XMLElements content(String value) {
        return each(n -> n.content(value));
    }

    @Override
    public XMLElements text(String value) {
        return each(n -> n.text(value));
    }
    
    @Override
    public XMLElements before(XMLElement element) {
        return each(n -> n.before(element));
    }

    @Override
    public XMLElements after(XMLElement element) {
        return each(n -> n.after(element));
    }

    @Override
    public XMLElements prepend(XMLElement element) {
        return each(n -> n.prepend(element));
    }

    @Override
    public XMLElements append(XMLElement element) {
        return each(n -> n.append(element));
    }

    @Override
    public XMLElements replace(XMLElement element) {
        return each(n -> n.replace(element));
    }

    @Override
    public XMLElements rename(String tagname) {
        return each(n -> n.rename(tagname));
    }

    @Override
    public XMLElements remove() {
        return each(n -> n.remove());
    }

    @Override
    public XMLElements wrap(String tagname) {
        return each(n -> n.wrap(tagname));
    }

    @Override
    public XMLElements unwrap() {
        return each(n -> n.unwrap());
    }
    
}
