// Copyright 2004, 2005 The Trustees of Indiana University.

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Authors: Nick Edmonds
//           Andrew Lumsdaine
#ifndef BOOST_GRAPH_SSCA_GENERATOR_HPP
#define BOOST_GRAPH_SSCA_GENERATOR_HPP

#include <iterator>
#include <utility>
#include <vector>
#include <queue>
#include <boost/config.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_01.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/type_traits/is_base_and_derived.hpp>
#include <boost/type_traits/is_same.hpp>

enum Direction
{
    FORWARD = 1,
    BACKWARD = 2,
    BOTH = FORWARD | BACKWARD
};

namespace boost
{

// This generator generates graphs according to the method specified
// in SSCA 1.1.  Current versions of SSCA use R-MAT graphs

template < typename RandomGenerator, typename Graph > class ssca_iterator
{
    typedef typename graph_traits< Graph >::directed_category directed_category;
    typedef
        typename graph_traits< Graph >::vertices_size_type vertices_size_type;

public:
    typedef std::input_iterator_tag iterator_category;
    typedef std::pair< vertices_size_type, vertices_size_type > value_type;
    typedef const value_type& reference;
    typedef const value_type* pointer;
    typedef void difference_type;

    // No argument constructor, set to terminating condition
    ssca_iterator() : gen(), verticesRemaining(0) {}

    // Initialize for edge generation
    ssca_iterator(RandomGenerator& gen, vertices_size_type totVertices,
        vertices_size_type maxCliqueSize, double probUnidirectional,
        int maxParallelEdges, double probIntercliqueEdges)
    : gen(&gen)
    , totVertices(totVertices)
    , maxCliqueSize(maxCliqueSize)
    , probUnidirectional(probUnidirectional)
    , maxParallelEdges(maxParallelEdges)
    , probIntercliqueEdges(probIntercliqueEdges)
    , currentClique(0)
    , verticesRemaining(totVertices)
    {
        cliqueNum = std::vector< int >(totVertices, -1);
        current = std::make_pair(0, 0);
    }

    reference operator*() const { return current; }
    pointer operator->() const { return &current; }

    ssca_iterator& operator++()
    {
        BOOST_USING_STD_MIN();
        while (values.empty() && verticesRemaining > 0)
        { // If there are no values left, generate a new clique
            uniform_int< vertices_size_type > clique_size(1, maxCliqueSize);
            uniform_int< vertices_size_type > rand_vertex(0, totVertices - 1);
            uniform_int< int > num_parallel_edges(1, maxParallelEdges);
            uniform_int< short > direction(0, 1);
            uniform_01< RandomGenerator > prob(*gen);
            std::vector< vertices_size_type > cliqueVertices;

            cliqueVertices.clear();
            vertices_size_type size = min BOOST_PREVENT_MACRO_SUBSTITUTION(
                clique_size(*gen), verticesRemaining);
            while (cliqueVertices.size() < size)
            {
                vertices_size_type v = rand_vertex(*gen);
                if (cliqueNum[v] == -1)
                {
                    cliqueNum[v] = currentClique;
                    cliqueVertices.push_back(v);
                    verticesRemaining--;
                }
            } // Nick: This is inefficient when only a few vertices remain...
              //       I should probably just select the remaining vertices
              //       in order when only a certain fraction remain.

            typename std::vector< vertices_size_type >::iterator first, second;
            for (first = cliqueVertices.begin(); first != cliqueVertices.end();
                 ++first)
                for (second = first + 1; second != cliqueVertices.end();
                     ++second)
                {
                    Direction d;
                    int edges;

                    d = prob() < probUnidirectional
                        ? (direction(*gen) == 0 ? FORWARD : BACKWARD)
                        : BOTH;

                    if (d & FORWARD)
                    {
                        edges = num_parallel_edges(*gen);
                        for (int i = 0; i < edges; ++i)
                            values.push(std::make_pair(*first, *second));
                    }

                    if (d & BACKWARD)
                    {
                        edges = num_parallel_edges(*gen);
                        for (int i = 0; i < edges; ++i)
                            values.push(std::make_pair(*second, *first));
                    }
                }

            if (verticesRemaining == 0)
            {
                // Generate interclique edges
                for (vertices_size_type i = 0; i < totVertices; ++i)
                {
                    double p = probIntercliqueEdges;
                    for (vertices_size_type d = 2; d < totVertices / 2;
                         d *= 2, p /= 2)
                    {
                        vertices_size_type j = (i + d) % totVertices;
                        if (cliqueNum[j] != cliqueNum[i] && prob() < p)
                        {
                            int edges = num_parallel_edges(*gen);
                            for (int i = 0; i < edges; ++i)
                                values.push(std::make_pair(i, j));
                        }
                    }
                }
            }

            currentClique++;
        }

        if (!values.empty())
        { // If we're not done return a value
            current = values.front();
            values.pop();
        }

        return *this;
    }

    ssca_iterator operator++(int)
    {
        ssca_iterator temp(*this);
        ++(*this);
        return temp;
    }

    bool operator==(const ssca_iterator& other) const
    {
        return verticesRemaining == other.verticesRemaining && values.empty()
            && other.values.empty();
    }

    bool operator!=(const ssca_iterator& other) const
    {
        return !(*this == other);
    }

private:
    // Parameters
    RandomGenerator* gen;
    vertices_size_type totVertices;
    vertices_size_type maxCliqueSize;
    double probUnidirectional;
    int maxParallelEdges;
    double probIntercliqueEdges;

    // Internal data structures
    std::vector< int > cliqueNum;
    std::queue< value_type > values;
    int currentClique;
    vertices_size_type verticesRemaining;
    value_type current;
};

} // end namespace boost

#endif // BOOST_GRAPH_SSCA_GENERATOR_HPP
