// Copyright (c) 2013-2020, SIB - Swiss Institute of Bioinformatics and
//                          Biozentrum - University of Basel
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include <promod3/core/graph.hh>
#include <promod3/core/message.hh>
#include <algorithm>
#include <stack>
#include <string.h>

namespace promod3{ namespace core{

Graph::Graph(int n){
  if(n <= 1){
  	throw promod3::Error("Num nodes must be larger one!");
  }
  n_ = n;
  adjacency_matrix_ = new int[n * n];
  memset(adjacency_matrix_, 0, n_ * n_ * sizeof(int));
}

Graph::Graph(const Graph& other){
  n_ = other.n_;
  adjacency_matrix_ = new int[n_ * n_];
  memcpy(adjacency_matrix_, other.adjacency_matrix_, n_ * n_ * sizeof(int));
}

Graph::~Graph(){
  delete [] adjacency_matrix_;
}

Graph& Graph::operator = (const Graph& other){
  if(this != &other){
    delete [] adjacency_matrix_;
    n_ = other.n_;
    adjacency_matrix_ = new int[n_ * n_];
    memcpy(adjacency_matrix_, other.adjacency_matrix_, n_ * n_ * sizeof(int));
  }
  return *this;
}

Graph Graph::SubGraph(const std::vector<int>& indices) const{

  int new_num_nodes = indices.size();

  Graph graph(new_num_nodes);

  for(int i = 0; i < new_num_nodes; ++i){
  	for(int j = i + 1; j < new_num_nodes; ++j){
  	  if(this->IsConnected(indices[i],indices[j])) graph.Connect(i,j);
  	}
  }

  return graph;
}

void Graph::ClearNode(int idx){
  //clear row
  memset(&adjacency_matrix_[idx * n_], 0, n_ * sizeof(int));
  //clear col
  int* col_ptr = & adjacency_matrix_[idx];
  for(int i = 0; i < n_; ++i){
    *col_ptr = 0;
    col_ptr += n_;
  }
}

void Graph::BuildClique(const std::vector<int>& indices){
  //let's connect them all
  int num_indices = indices.size();
  for(int i = 0; i < num_indices; ++i){
    for(int j = i+1; j < num_indices; ++j){
      this->Connect(indices[i],indices[j]);    
    }
  }	
}

void Graph::ConnectedComponents(std::vector<std::vector<int> >& components) const{

  components.clear();

  bool visited[n_];
  memset(visited, 0, n_ * sizeof(bool));

  for(int i = 0; i < n_; ++i){
    if(visited[i]) continue;
    //it's not visited, so let's start with a new connected component...
    components.push_back(std::vector<int>());
    std::stack<int> queue;
    queue.push(i);
    int actual_element;
    while(!queue.empty()){
      actual_element = queue.top();
      queue.pop();
      if(!visited[actual_element]){
        //the actual element is not yet visited
        // =>let's add it to the actual component and
        //put all nodes it is connected to at the back of the
        //queue
        visited[actual_element] = true;
        components.back().push_back(actual_element);
        int* adj_ptr = &adjacency_matrix_[actual_element * n_];
        for(int j = 0; j < n_; ++j){
          if(*adj_ptr == 1) queue.push(j);
          ++adj_ptr;
        }
      }
    }
    std::sort(components.back().begin(), components.back().end());
  }
}

void Graph::GetNeighbours(int idx, std::vector<int>& neighbours) const{
  neighbours.clear();
  int* row = &adjacency_matrix_[idx * n_];
  for(int i = 0; i < n_; ++i){
  	if(row[i]) neighbours.push_back(i);
  }
}

void Graph::NumConnections(std::vector<int>& connections) const{
  connections.resize(n_);
  int* row_ptr;
  int row_sum;
  for(int i = 0; i < n_; ++i){
    row_sum = 0;
    row_ptr = &adjacency_matrix_[i * n_];
    for(int j = 0; j < n_; ++j){
      if(row_ptr[j]) ++row_sum;
    }
    connections[i] = row_sum;
  }
}

bool Graph::IsConnected() const{

  int visited[n_];
  memset(visited, 0, n_ * sizeof(int));
  std::stack<int> queue; 
  std::vector<int> neighbours; 

  //add first element to the queue
  queue.push(0);
  
  while(!queue.empty()){
    int actual_node = queue.top();
    queue.pop();
    //is it already visited?
    if(visited[actual_node] == 0){
      visited[actual_node] = 1;
      //get all the neighbours
      this->GetNeighbours(actual_node, neighbours);
      //add all neighbours not being visited yet
      for(uint i = 0; i < neighbours.size(); ++i){
        if(!visited[neighbours[i]]) queue.push(neighbours[i]);
      }
    }
  }

  //check whether all nodes have been visited
  int sum = 0;
  for(int i = 0; i < n_; ++i){
    sum += visited[i];
  }

  return sum == n_;
}

}} //ns
