/*
 * Grakn - A Distributed Semantic Database
 * Copyright (C) 2016  Grakn Labs Limited
 *
 * Grakn is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Grakn is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

package ai.grakn.graql.internal.gremlin.spanningtree;

import ai.grakn.graql.internal.gremlin.spanningtree.graph.DirectedEdge;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * An arborescence is a directed graph in which, for the root and any other vertex,
 * there is exactly one directed path between them.
 * (https://en.wikipedia.org/wiki/Arborescence_(graph_theory))
 *
 * @param <V> the type of the nodes
 * @author Jason Liu
 */

public class Arborescence<V> {
    /**
     * In an arborescence, each node (other than the root) has exactly one parent. This is the map
     * from each node to its parent.
     */
    private final ImmutableMap<V, V> parents;

    private final V root;

    private Arborescence(ImmutableMap<V, V> parents, V root) {
        this.parents = parents;
        this.root = root;
    }

    public static <T> Arborescence<T> of(ImmutableMap<T, T> parents) {
        if (parents != null && !parents.isEmpty()) {
            HashSet<T> allParents = Sets.newHashSet(parents.values());
            allParents.removeAll(parents.keySet());
            if (allParents.size() == 1) {
                return new Arborescence<>(parents, allParents.iterator().next());
            }
        }
        return new Arborescence<>(parents, null);
    }

    public static <T> Arborescence<T> empty() {
        return Arborescence.of(ImmutableMap.<T, T>of());
    }

    public boolean contains(DirectedEdge<V> e) {
        final V dest = e.destination;
        return parents.containsKey(dest) && parents.get(dest).equals(e.source);
    }

    public V getRoot() {
        return root;
    }

    public int size() {
        return parents.size();
    }

    public ImmutableMap<V, V> getParents() {
        return parents;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        parents.forEach((key, value) -> stringBuilder.append(value).append(" -> ").append(key).append("; "));
        return stringBuilder.toString();
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        final Arborescence that = (Arborescence) other;
        Set<Map.Entry<V, V>> myEntries = parents.entrySet();
        Set thatEntries = that.parents.entrySet();
        return myEntries.size() == thatEntries.size() && myEntries.containsAll(thatEntries);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(parents);
    }
}
