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

package net.ranides.assira.xml;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.ranides.assira.collection.list.VirtualList;
import net.ranides.assira.collection.map.ListableMap;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author ranides
 */
@SuppressWarnings("PMD.TooManyDifferentMethods")
public abstract class NodeCollection implements List<Node> {

    public static final NodeList EMPTY_LIST = new EmptyNodeList();


    public static List<Node> make(final NodeList list) {
        return new VirtualList<Node>() {
            @Override
            public int size() {
                return list.getLength();
            }
            @Override
            public Node get(int index) {
                return list.item(index);
            }
        };
    }

    public static ListableMap<String, Node> make(final NamedNodeMap map) {
        return new LMap(map);
    }
    
    public static List<Node> nodes(Node node) {
        return make(node.getChildNodes());
    }
    
    public static ListableMap<String, Node> attributes(Node node) {
        return make(node.getAttributes());
    }

    private static class EmptyNodeList implements NodeList {

        @Override
        public Node item(int index) {
            return null;
        }

        @Override
        public int getLength() {
            return 0;
        }
    }
    
    private static class LMap extends AbstractMap<String, Node> implements ListableMap<String, Node> {
        
        private final NamedNodeMap map;
        private final List<Node> nodeList;
        private final List<String> keyList;
        private final List<Entry<String,Node>> entryList;
        private final Set<String> keys;
        private final Set<Entry<String,Node>> entries;

        @SuppressWarnings("PMD")
        LMap(NamedNodeMap source) {
            this.map = source;
            this.nodeList = new VirtualList<Node>() {
                @Override
                public int size() {
                    return map.getLength();
                }
                @Override
                public Node get(int index) {
                    return map.item(index);
                }
            };
            this.keyList = new VirtualList<String>() {
                @Override
                public int size() {
                    return map.getLength();
                }
                @Override
                public String get(int index) {
                    return map.item(index).getNodeName();
                }
            };
            this.entryList = new VirtualList<Entry<String,Node>>() {
                @Override
                public int size() {
                    return map.getLength();
                }
                @Override
                public Entry<String,Node> get(int index) {
                    Node item = map.item(index);
                    return new AbstractMap.SimpleImmutableEntry<>(item.getNodeName(), item);
                }
            };
            this.keys = new AbstractSet<String>() {
                @Override
                public Iterator<String> iterator() {
                    return keyList.iterator();
                }
                @Override
                public int size() {
                    return keyList.size();
                }
                @Override
                public boolean contains(Object key) {
                    return containsKey(key);
                }
            };
            this.entries = new AbstractSet<Entry<String,Node>>() {
                @Override
                public Iterator<Entry<String,Node>> iterator() {
                    return entryList.iterator();
                }
                @Override
                public int size() {
                    return entryList.size();
                }
                @Override
                public boolean contains(Object object) {
                    if (!(object instanceof Map.Entry)) { 
                        return false; 
                    }
                    Map.Entry entry = (Map.Entry)object;
                    if( null == entry.getValue()) {
                        return false;
                    }
                    Object okey = entry.getKey();
                    if(!(okey instanceof String)) {
                        return false;
                    }
                    return entry.getValue() == map.getNamedItem((String)okey);
                }
            };
        }

        @Override
        public List<Node> asListAdapter() {
            return nodeList;
        }

        @Override
        public List<String> keysList() {
            return keyList;
        }

        @Override
        public List<Entry<String, Node>> entryList() {
            return entryList;
        }

        @Override
        public Node at(int index) {
            return map.item(index);
        }

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

        @Override
        public boolean isEmpty() {
            return map.getLength() == 0;
        }

        @Override
        public boolean containsKey(Object key) {
            if(key instanceof String) {
                return null != map.getNamedItem((String)key); 
            } else {
                return false;
            }
        }

        @Override
        public boolean containsValue(Object value) {
            return nodeList.contains(value);
        }

        @Override
        public Node get(Object key) {
            if(key instanceof String) {
                return map.getNamedItem((String)key);
            } else {
                return null;
            }
        }

        @Override
        public Set<String> keySet() {
            return keys;
        }

        @Override
        public Collection<Node> values() {
            return nodeList;
        }

        @Override
        public Set<Entry<String, Node>> entrySet() {
            return entries;
        }

        @Override
        public Iterator<Node> iterator() {
            return nodeList.iterator();
        }

    };
}
