/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira.drafts
 */
package net.ranides.assira.index;

import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import net.ranides.assira.collection.CollectionUtils;
import net.ranides.assira.collection.maps.ASortedMap;
import net.ranides.assira.collection.maps.MultiMap;
import net.ranides.assira.collection.maps.RBTreeMultiMap;
import net.ranides.assira.collection.sets.ASortedSet;
import net.ranides.assira.collection.sets.IdentSet;
import net.ranides.assira.collection.sets.MultiSet;
import net.ranides.assira.collection.sets.RBTreeMultiSet;

import net.ranides.assira.generic.CompareUtils;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.generic.ValueUtils;

/**
 * Implementation of IQueryMap
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @param <V> type of result
 */
public class IQueryMapTree<V> extends IQueryMapAbstract<V> {
    
    private final Map<String, Comparator> sortIndexer = new HashMap<>();
    
    private final Map<String, Function> mapIndexer = new HashMap<>();
    
    private final Map<Comparator, RBTreeMultiSet<V>> sortValues = new HashMap<>();
    
    private final Map<Function, RBTreeMultiMap<Object, V>> mapValues = new HashMap<>();

    @Override
    public IQueryMap<V> index(String name, Comparator<V> index) {
        RBTreeMultiSet<V> set = new RBTreeMultiSet<>(values().iterator(), index);
        sortValues.put(index, set);
        sortIndexer.put(name, index);
        return this;
    }

    @Override
    public <T> IQueryMap<V> index(String name, Function<V, T> index) {
        RBTreeMultiMap<Object,V> map = new RBTreeMultiMap<>();
        for(V item : values()) {
            map.put(index.apply(item),item);
        }
        mapValues.put(index, map);
        mapIndexer.put(name, index);
        return this;
    }

    @Override
    public void remove(V value) {
        for(SortedSet<V> index : sortValues.values()) {
            remove0(index, value);
        }
        for(SortedMap<Object,V> index : mapValues.values()) {
            remove0(index, value);
        }
    }
    
    private void remove0(SortedSet<V> set, V value) {
        Comparator<? super V> cmp = ValueUtils.or(set.comparator(), CompareUtils.comparator());
        Iterator<V> i = set.headSet(value).iterator();
        while(i.hasNext()) {
            V item = i.next();
            if(cmp.compare(item, value) !=0) {
                return;
            }
            if(CompareUtils.equals(item,value)) {
                i.remove();
                return;
            }
        }
    }
    
    private void remove0(SortedMap<Object,V> map, V value) {
        Comparator<Object> cmp = CompareUtils.comparator();
        Iterator<Entry<Object,V>> i = map.headMap(value).entrySet().iterator();
        while(i.hasNext()) {
            Entry<Object,V> item = i.next();
            if(cmp.compare(item.getKey(), value) !=0) {
                return;
            }
            if(CompareUtils.equals(item.getValue(),value)) {
                i.remove();
                return;
            }
        }
    }
    
    @SuppressWarnings("unchecked")
    @Override
    public void updateAll() {
        List<V> items = new ArrayList<>(values());
        for(SortedSet<V> index : sortValues.values()) {
            index.clear();
            index.addAll(items);
        }
        for(Entry<Function, RBTreeMultiMap<Object,V>> entry : mapValues.entrySet()) {
            Function fun = entry.getKey();
            RBTreeMultiMap<Object, V> index = entry.getValue();
            index.clear();
            for(V item : items) {
                index.put(fun.apply(item), item);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public void updateAll(String index) {
        List<V> items = new ArrayList<>(values());

        Comparator sortKey = sortIndexer.get(index);
        if(sortKey != null) {
            RBTreeMultiSet<V> sortIndex = sortValues.get(sortKey);
            sortIndex.clear();
            sortIndex.addAll(items);
        }

        Function mapKey = mapIndexer.get(index);
        if(mapKey != null) {
            RBTreeMultiMap<Object, V> mapIndex = mapValues.get(mapKey);
            mapIndex.clear();
            for(V item : items) {
                mapIndex.put(mapKey.apply(item), item);
            }
        }
    }
    
    @Override
    public IQuery<V> find() {
        return new IndexedQuery(null);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void put(V value) {
        for(SortedSet<V> index : sortValues.values()) {
            index.add(value);
        }
        for(Entry<Function, RBTreeMultiMap<Object,V>> entry : mapValues.entrySet()) {
            Function fun = entry.getKey();
            RBTreeMultiMap<Object, V> index = entry.getValue();
            index.put(fun.apply(value), value);
        }
    }

    @Override
    public void clear() {
        for(SortedSet<V> index : sortValues.values()) {
            index.clear();
        }
        for(SortedMap<Object,V> index : mapValues.values()) {
            index.clear();
        }
    }
    
    private Collection<V> values() {
        Ref<Collection<V>> values = CollectionUtils.first(sortValues.values());
        return values.orElseGet(Collections::emptySet);
    }
    
    private static <T> Collection<T> tail(ASortedMap<Object, T> mi, Object value) {
        Object last = after(mi,value);
        return last==null ? Collections.emptyList() : mi.tailMap(last).values();
    }
    
    private static <T> Collection<T> tail(ASortedSet<T> ci, T value) {
        T last = after(ci,value);
        return last==null ? Collections.emptyList() : ci.tailSet(last);
    }

    private static Object after(ASortedMap<Object, ?> map, Object value) {
        Comparator<Object> cmp = CompareUtils.comparator();
        Iterator<Object> i = map.keySet().iterator(value);
        while(i.hasNext()) {
            Object next = i.next();
            if(cmp.compare(value, next)!=0) {
                return next;
            }
        }
        return null;
    }
    
    private static <T> T after(ASortedSet<T> set, T value) {
        Comparator<? super T> cmp = ValueUtils.or(set.comparator(), CompareUtils.comparator());
        Iterator<T> i = set.iterator(value);
        while(i.hasNext()) {
            T next = i.next();
            if(cmp.compare(value, next)!=0) {
                return next;
            }
        }
        return null;
    }
    
    private static <T> Collection<T> merge(Collection<T> a, Collection<T> b) {
        Collection<T> out = new IdentSet<>(a.size() + b.size());
        out.addAll(a);
        out.addAll(b);
        return out;
    }

    
    private final class IndexedQuery extends IQueryAbstract<V> {
        
        private final List<Supplier<Collection<V>>> selectors;

        private final Supplier<? extends RuntimeException> message;

        public IndexedQuery(Supplier<? extends RuntimeException> message) {
            this.selectors = Collections.emptyList();
            this.message = message;
        }
        
        public IndexedQuery(IndexedQuery query, Supplier<Collection<V>> selector, Supplier<? extends RuntimeException> message) {
            this.selectors = new ArrayList<>(query.selectors.size()+1);
            this.selectors.addAll(query.selectors);
            this.selectors.add(selector);
            this.message = message;
        }

        public IndexedQuery(List<Supplier<Collection<V>>> selectors, Supplier<? extends RuntimeException> message) {
            this.selectors = new ArrayList<>(selectors);
            this.message = message;
        }

        @SuppressWarnings("unchecked")
        @Override
        protected <T> Function<V, T> mapIndexer(String name) {
            return mapIndexer.get(name);
        }

        @SuppressWarnings("unchecked")
        @Override
        protected Comparator<V> sortIndexer(String name) {
            return sortIndexer.get(name);
        }

        @Override
        public IQuery<V> eq(Comparator<V> index, boolean nullable, V value) {
            RBTreeMultiSet<V> ci = sortValues.get(index);
            return addFilter(ci, nullable, () -> ci.getAll(value));
        }

        @Override
        public <T> IQuery<V> eq(Function<V, T> index, boolean nullable, T value) {
            RBTreeMultiMap<Object, V> mi = mapValues.get(index);
            return addFilter(mi, nullable, () -> mi.getAll(value));
        }

        @Override
        public IQuery<V> gt(Comparator<V> index, boolean nullable, V value) {
            RBTreeMultiSet<V> ci = sortValues.get(index);
            return addFilter(ci, nullable, () -> tail(ci, value));
        }
        
        @Override
        public <T> IQuery<V> gt(Function<V, T> index, boolean nullable, T value) {
            RBTreeMultiMap<Object, V> mi = mapValues.get(index);
            return addFilter(mi, nullable, () -> tail(mi, value));
        }

        @Override
        public IQuery<V> lt(Comparator<V> index, boolean nullable, V value) {
            RBTreeMultiSet<V> ci = sortValues.get(index);
            return addFilter(ci, nullable, () -> ci.headSet(value));
        }

        @Override
        public <T> IQuery<V> lt(Function<V, T> index, boolean nullable, T value) {
            RBTreeMultiMap<Object, V> mi = mapValues.get(index);
            return addFilter(mi, nullable, () -> mi.headMap(value).values());
        }

        @Override
        public IQuery<V> in(Comparator<V> index, boolean nullable, V begin, V end) {
            RBTreeMultiSet<V> ci = sortValues.get(index);
            return addFilter(ci, nullable, () -> ci.subSet(begin, end));
        }

        @Override
        public <T> IQuery<V> in(Function<V, T> index, boolean nullable, T begin, T end) {
            RBTreeMultiMap<Object, V> mi = mapValues.get(index);
            return addFilter(mi, nullable, () -> mi.subMap(begin, end).values());
        }

        @Override
        public void update(Consumer<? super V> consumer) {
            select().forEach(v -> IQueryMapTree.this.update(v, consumer));
        }

        @Override
        public void remove() {
            IQueryMapTree.this.removeAll(select());
        }

        private <T> IQuery<V> addFilter(MultiSet<V> ci, boolean nullable, Supplier<Collection<V>> f) {
            if(nullable) {
                return new IndexedQuery(this, () -> merge(f.get(), ci.getAll(null)), null);
            } else {
                return new IndexedQuery(this, f, null);
            }
        }

        private <T> IQuery<V> addFilter(MultiMap<Object, V> mi, boolean nullable, Supplier<Collection<V>> f) {
            if(nullable) {
                return new IndexedQuery(this, () -> merge(f.get(), mi.getAll(null)), null);
            } else {
                return new IndexedQuery(this, f, null);
            }
        }

        @Override
        public Supplier<? extends RuntimeException> message() {
            return message;
        }

        @Override
        public IQuery<V> message(Supplier<? extends RuntimeException> message) {
            return new IndexedQuery(selectors, message);
        }

        @Override
        public Stream<V> stream() {
            return select().stream();
        }

        @Override
        public Iterator<V> iterator() {
            return select().iterator();
        }

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

        private Collection<V> select() {
            Collection<V> primary = null;
            List<Collection<V>> results = new ArrayList<>(selectors.size()-1);

            for(Supplier<Collection<V>> s : selectors) {
                Collection<V> current = s.get();

                if(primary!=null && primary.size() > current.size()) {
                    results.add(primary);
                    primary = null;
                }
                if(primary==null) {
                    primary = current;
                } else {
                    results.add(current);
                }
            }
            
            if(primary==null) {
                return Collections.emptyList();
            }
            
            List<V> out = new ArrayList<>(primary.size());
            for(V item : primary) {
                if(isInsideAll(results,item)) {
                    out.add(item);
                }
            }
            return out;
        }
        
        private boolean isInsideAll(List<Collection<V>> sec, V item) {
            for(Collection<V> s : sec) {
                if(!s.contains(item)) {
                    return false;
                }
            }
            return true;
        }
        
    }
    
}
