/*******************************************************************\

Module:

Author: Daniel Kroening, kroening@kroening.com

\*******************************************************************/

#include <assert.h>

#include <iostream>
#include <stack>

#include "graph.h"

/*******************************************************************\

Function: intersection

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void intersection(
  const graph::node_sett &a,
  const graph::node_sett &b,
  graph::node_sett &dest)
{
  graph::node_sett::const_iterator it_a=a.begin(), it_b=b.begin();

  while(it_a!=a.end() && it_b!=b.end())
  {
    if(*it_a==*it_b)
    {
      dest.insert(*it_a);
      it_a++;
      it_b++;
    }
    else if(*it_a<*it_b)
      it_a++;
    else // *it_a>*it_b
      it_b++;
  }
}

/*******************************************************************\

Function: graph::add_undirected_edge

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::add_undirected_edge(unsigned a, unsigned b)
{
  assert(a<nodes.size());
  assert(b<nodes.size());
  nodet &na=nodes[a];
  nodet &nb=nodes[b];
  na.out.insert(b);
  nb.out.insert(a);
  na.in.insert(b);
  nb.in.insert(a);
}

/*******************************************************************\

Function: graph::remove_undirected_edge

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/
  
void graph::remove_undirected_edge(unsigned a, unsigned b)
{
  nodet &na=nodes[a];
  nodet &nb=nodes[b];
  na.out.erase(b);
  nb.out.erase(a);
  na.in.erase(b);
  nb.in.erase(a);
}

/*******************************************************************\

Function: graph::remove_in_edges

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::remove_in_edges(unsigned n)
{
  nodet &node=nodes[n];
  
  // delete all incoming edges
  for(node_sett::const_iterator it=node.in.begin();
      it!=node.in.end();
      it++)
    nodes[*it].out.erase(n);
    
  node.in.clear();
}

/*******************************************************************\

Function: graph::remove_out_edges

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::remove_out_edges(unsigned n)
{
  nodet &node=nodes[n];
  
  // delete all outgoing edges
  for(node_sett::const_iterator it=node.out.begin();
      it!=node.out.end();
      it++)
    nodes[*it].in.erase(n);
  
  node.out.clear();
}

/*******************************************************************\

Function: nodet::clear_visited

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::clear_visited()
{
  for(nodest::iterator it=nodes.begin();
      it!=nodes.end();
      it++)
    it->visited=false;
}

/*******************************************************************\

Function: graph::shortest_path

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::shortest_path(
  unsigned src,
  unsigned dest,
  patht &path)
{
  // initialization
  for(nodest::iterator it=nodes.begin();
      it!=nodes.end();
      it++)
  {
    it->visited=false;
    it->distance=(unsigned)(-1);
  }

  nodes[src].distance=0;
  nodes[src].visited=true;

  // does BFS, not Dijkstra
  // we hope the graph is sparse
  std::vector<unsigned> frontier_set, new_frontier_set;
  
  frontier_set.reserve(nodes.size());

  frontier_set.push_back(src);
  
  unsigned distance=0;
  bool found=false;
  
  while(!frontier_set.empty() && !found)
  {
    distance++;
  
    new_frontier_set.clear();
    new_frontier_set.reserve(nodes.size());
  
    for(std::vector<unsigned>::const_iterator
        f_it=frontier_set.begin();
        f_it!=frontier_set.end() && !found;
        f_it++)
    {
      unsigned i=*f_it;
      nodet &n=nodes[i];
      
      // do all neighbors
      for(node_sett::iterator o_it=n.out.begin();
          o_it!=n.out.end() && !found;
          o_it++)
      {
        nodet &o=nodes[*o_it];
      
        if(!o.visited)
        {
          o.distance=distance;
          o.previous=i;
          o.visited=true;

          if(*o_it==dest)
            found=true;
          else
            new_frontier_set.push_back(*o_it);
        }
      }
    }
    
    frontier_set.swap(new_frontier_set);
  }
  
  // compute path
  compute_path(dest, path);
}

/*******************************************************************\

Function: graph::visit_reachable

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::visit_reachable(unsigned src)
{
  // DFS

  std::stack<unsigned> s;
  s.push(src);

  while(!s.empty())
  {
    unsigned n=s.top();
    s.pop();

    nodet &node=nodes[n];
    node.visited=true;

    for(node_sett::const_iterator
        it=node.out.begin();
        it!=node.out.end();
        it++)
      if(!nodes[*it].visited)
        s.push(*it);
  }
}

/*******************************************************************\

Function: graph::connected_subgraphs

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

unsigned graph::connected_subgraphs()
{
  unsigned subgraph_nr=0;

  for(unsigned src=0; src<size(); src++)
  {
    if(nodes[src].visited) continue;

    // DFS

    std::stack<unsigned> s;
    s.push(src);

    while(!s.empty())
    {
      unsigned n=s.top();
      s.pop();

      nodet &node=nodes[n];
      node.visited=true;
      node.subgraph_nr=subgraph_nr;

      for(node_sett::const_iterator
          it=node.out.begin();
          it!=node.out.end();
          it++)
        if(!nodes[*it].visited)
          s.push(*it);
    }

    subgraph_nr++;
  }

  return subgraph_nr;
}

/*******************************************************************\

Function: graph::compute_path

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::compute_path(
  unsigned dest,
  patht &path)
{
  // walk towards 0-distance node
  path.clear();
  
  // reachable at all?
  if(nodes[dest].distance==(unsigned)(-1))
    return; // nah

  while(true)
  {
    path.push_front(dest);
    
    nodet &n=nodes[dest];
    if(n.distance==0) break; // we are there

    dest=n.previous;
  }
}

/*******************************************************************\

Function: graph::make_chordal

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void graph::make_chordal()
{
  graph tmp(*this);

  // This assumes an undirected graph.
  // 1. remove all nodes in tmp, reconnecting the remaining ones
  // 2. the chordal graph is the old one plus the new edges

  for(unsigned i=0; i<tmp.size(); i++)
  {
    const nodet &n=tmp[i];

    // connect all the nodes in n.out with each other

    for(node_sett::const_iterator
        it1=n.out.begin();
        it1!=n.out.end();
        it1++)
      for(node_sett::const_iterator
          it2=n.out.begin();
          it2!=n.out.end();
          it2++)
      {
        if(*it1!=*it2)
        {
          tmp.add_undirected_edge(*it1, *it2);
          this->add_undirected_edge(*it1, *it2);
        }
      }

    // remove node from tmp graph
    tmp.remove_edges(i);
  }
}
