package net.yacy.search.schema;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import net.yacy.cora.document.id.MultiProtocolURL;

public class HyperlinkEdges implements Iterable<HyperlinkEdge> {

    public static class Targets {
        public Set<HyperlinkEdge.Target> targets;
        public int depth;

        public Targets(int depth) {
            this.targets = new LinkedHashSet<HyperlinkEdge.Target>();
            this.depth = depth;
        }
    }

    private final Map<MultiProtocolURL, Targets> edges;
    private final Map<MultiProtocolURL, Integer> singletonDepth;
    
    public HyperlinkEdges() {
        this.edges = new LinkedHashMap<MultiProtocolURL, Targets>();
        this.singletonDepth = new HashMap<MultiProtocolURL, Integer>();
    }

    public void add(final HyperlinkEdge edge) {
        addEdge(edge.source, edge.target);
    }
    
    public void addEdge(final MultiProtocolURL source, final HyperlinkEdge.Target target) {
        Targets targets = this.edges.get(source);
        Integer d = this.singletonDepth.get(source);
        if (d == null) d = -1; else this.singletonDepth.remove(source);
        if (target.type == HyperlinkType.Inbound) {
            Integer e = this.singletonDepth.remove(target);
            if (e != null && d.intValue() == -1) d = e.intValue() - 1;
        }
        if (targets == null) {
            targets = new Targets(d.intValue());
            this.edges.put(source, targets);
        }
        targets.targets.add(target);
    }
    
    public int size() {
        int s = 0;
        for (Targets t: edges.values()) s += t.targets.size();
        return s;
    }
    
    public void addAll(final HyperlinkEdges oe) {
        for (Map.Entry<MultiProtocolURL, Targets> edges: oe.edges.entrySet()) {
            for (HyperlinkEdge.Target t: edges.getValue().targets) {
                this.addEdge(edges.getKey(), t);
            }
        }
    }
    
    public void updateDepth(final MultiProtocolURL url, final int newdepth) {
        Targets targets = this.edges.get(url);
        if (targets == null) {
            singletonDepth.put(url, newdepth);
            return;
        }
        if (targets.depth == -1) {
            targets.depth = newdepth;
        } else {
            targets.depth = Math.min(targets.depth,  newdepth);
        }
    }
    
    public int getDepth(final MultiProtocolURL url) {
        Targets targets = this.edges.get(url);
        if (targets != null) return targets.depth;
        Integer d = this.singletonDepth.get(url);
        if (d != null) return d.intValue();
        // now search in targets
        String targetHost = url.getHost();
        for (Map.Entry<MultiProtocolURL, Targets> e: this.edges.entrySet()) {
            if (e.getValue().targets.contains(url)) {
                String sourceHost = e.getKey().getHost();
                // check if this is an inbound match
                if ((sourceHost == null && targetHost == null) || (sourceHost != null && targetHost != null && sourceHost.equals(targetHost))) return e.getValue().depth + 1;
            }
        }
        return -1;
    }

    @Override
    public Iterator<HyperlinkEdge> iterator() {
        final Iterator<Map.Entry<MultiProtocolURL, Targets>> i = this.edges.entrySet().iterator();
        @SuppressWarnings("unchecked")
        final Iterator<HyperlinkEdge.Target>[] tc = (Iterator<HyperlinkEdge.Target>[]) Array.newInstance(Iterator.class, 1);
        tc[0] = null;
        final MultiProtocolURL[] su = new MultiProtocolURL[1];
        su[0] = null;
        return new Iterator<HyperlinkEdge>() {

            @Override
            public boolean hasNext() {
                return i.hasNext() || (tc[0] != null && tc[0].hasNext());
            }

            @Override
            public HyperlinkEdge next() {
                while (tc[0] == null || !tc[0].hasNext()) {
                    Map.Entry<MultiProtocolURL, Targets> entry = i.next();
                    tc[0] = entry.getValue().targets.iterator();
                    su[0] = entry.getKey();
                }
                if (!tc[0].hasNext()) return null;
                return new HyperlinkEdge(su[0], tc[0].next());
            }

            @Override
            public void remove() {
                tc[0].remove();
            }
            
        };
    }
}
