// GraphTea Project: http://github.com/graphtheorysoftware/GraphTea
// Copyright (C) 2012 Graph Theory Software Foundation: http://GraphTheorySoftware.com
// Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
// Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/

package graphtea.extensions.reports.connectivity;

import java.util.List;
import graphtea.graph.graph.GraphModel;
import graphtea.graph.graph.Vertex;
import graphtea.platform.lang.CommandAttitude;
import graphtea.plugins.reports.extension.GraphReportExtension;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;

/**
 * @author M. Ali Rostami
 */

@CommandAttitude(name = "k_vertex_connectivity", abbreviation = "_kvc")
public class KConnected implements GraphReportExtension<Integer> {

    public Integer calculate(GraphModel g) {
        return kconn(g);
    }

    public static int kconn(GraphModel g) {
        int pre;
        int now = Integer.MIN_VALUE;
        boolean preconn, nowconn;

        for (Vertex v : g.vertices())
            now = Math.max(now, g.getDegree(v));

        nowconn = kdisconn(g, now - 1) == null;

        do {
            preconn = nowconn;
            pre = now;
            if (preconn) now = pre + 1;
            else now = pre - 1;
            nowconn = kdisconn(g, now - 1) == null;
        } while (!((preconn && !nowconn && pre == now - 1) || (!preconn
                && nowconn && pre == now + 1)));

        if (!preconn)
            return now;

        return pre;
    }

    public static Vertex[] kdisconn(GraphModel g, int k) {
        LinkedList<Integer> remain = new LinkedList<>();
        HashSet<Integer> popped = new HashSet<>();
        boolean[] done = new boolean[g.numOfVertices()];
        specArr karray = new specArr(k, g.numOfVertices());

        if (k >= g.numOfVertices())
            return g.getVertexArray();

        do {
            remain.clear();
            popped.clear();
            Arrays.fill(done, false);

            for (int j = 0; j < k; j++)
                popped.add(karray.get(j));

            for (int j = 0; remain.size() == 0; j++)
                if (!popped.contains(j))
                    remain.add(j);

            while (remain.size() > 0) {
                Vertex n = g.getVertex(remain.poll());
                Integer index;

                done[n.getId()] = true;

                for (Vertex o : g.getNeighbors(n)) {
                    index = o.getId();

                    if (!done[index] && !remain.contains(index)
                            && !popped.contains(index))
                        remain.add(index);
                }
            }

            for (int i = 0; i < done.length; i++)
                if (!done[i] && !popped.contains(i)) {
                    Vertex[] tuple = new Vertex[k];

                    for (int j = 0; j < k; j++)
                        tuple[j] = g.getVertex(karray.get(j));

                    return tuple;
                }
        } while (karray.next());

        return null;
    }

    private static class specArr extends ArrayList<Integer>{
        final int k, n;

        public specArr(int k, int n) {
            this.k = k;
            this.n = n;

            for (int i = 0; i < k; i++)
                this.add(i);
        }

        public boolean next() {
            int i = k - 1;
            while (i >= 0 && this.get(i) >= n - (k - 1 - i))
                i--;
            if (i >= 0) {
                this.set(i,this.get(i)+1);
                for (int j = i + 1; j < k; j++)
                    this.set(j,this.get(j-1)+1);
                return true;
            }
            return false;
        }
    }

    public String getName() {
        return "Vertex Connectivity";
    }

    public String getDescription() {
        return "Vertex Connectivity";
    }


    @Override
    public String getCategory() {
        return "Connectivity";
    }
}
