// 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/tree.hh>
#include <promod3/core/message.hh>
#include <stack>
#include <set>
#include <limits>


namespace promod3{ namespace core{

TreeBag<int>* GenerateMinimalWidthTree(const Graph& input_graph){

  if(!input_graph.IsConnected()){
    String err = "Graph must be connected to generate minimal width tree!";
    throw promod3::Error(err);
  }

  //generate copy of graph since it will be modified
  Graph graph(input_graph);

  //bags, that will be produced in a first stage of the algorithm
  std::stack<TreeBag<int>*> bags;


  //the first part of the algorithm builds the bags of the tree as 
  //described in the SCWRL4 paper
  //
  // 1. Select any vertex with the minimal number of adjacent edges.
  //
  // 2. Form a bag of the tree from this vertex and all it's neighbours.
  //    The selected vertex we will denote as primary vertex of the 
  //    corresponding bag.
  //
  // 3. Add edges into the graph being processed so that the neighbours of the
  //    selected vertex become a clique(a subgraph where all nodes have edges 
  //    to each other).
  //
  // 4. Remove the selected vertex and all adjacent edges from the graph.
  //
  // 5. Repeat from the first step until there are no more vertices left in 
  //    the graph
   
  while(true){
    //1.   
    //lets get the number of adjacent edges for every node
    std::vector<int> num_connections;
    graph.NumConnections(num_connections);
    //find the minimal value and the corresponding node index
    int idx = 0;
    int min_connections = std::numeric_limits<int>::max();
    for(uint i = 0; i < num_connections.size(); ++i){
      //check whether the current element has the lowest number of connections
      //a value of zero would mean, that it's already inactivated...
      if(num_connections[i] < min_connections && num_connections[i] != 0){
      	min_connections = num_connections[i];
      	idx = i;
      }
    }

    //5.
    //check, whether there are any active edges remaining
    if(min_connections == std::numeric_limits<int>::max()) break;

    //2.
    //let's add all nodes connected to the selected node to the same bag
    std::vector<int> bag_members;
    graph.GetNeighbours(idx, bag_members);

    // 3.
    //let's connect all neighbouring vertices to a clique
    graph.BuildClique(bag_members);

    //4.
    //let's remove everything regarding node idx from matrix
    graph.ClearNode(idx);

    //Not forget to add the current element itself!
    bag_members.push_back(idx);

    //and finally add everything to the other bags
    bags.push(new TreeBag<int>);
    bags.top()->SetMembers(bag_members);
  }


  //the second part of the algorithm connectes the built bags to a tree.
  //This is done by sequentially fastening these bags to the tree in the 
  //reverse order in which they were constructed. Thus the bag that was 
  //created last becomes the root of the tree.
  //The next bag becomes connected to it and thus becomes its immediate child. 
  //For the next bag, there are two choices for where to attach it. However, 
  //the appropriate node of the tree-decomposition must meet the following 
  //condition:
  //
  //A set of vertices in the appropriate node must contain all vertices 
  //from the bag to be added that are already present in the tree.

  //we need to know which nodes are already added...
  //we directly add the members of the root
  std::set<int> nodes_in_tree;
   for(TreeBag<int>::const_member_iterator i = bags.top()->members_begin();
  	  i != bags.top()->members_end(); ++i){
  	nodes_in_tree.insert(*i);
  }

  //In here we store all bags, to which children can be connected
  //To ensure a low depth, they are sorted by the distance to
  //the top bag => the root
  std::vector<std::pair<int, TreeBag<int>*> > bag_list;
  bag_list.push_back(std::make_pair(0, bags.top()));
  bags.pop();

  //repeat until all bags are connected somewhere
  while(!bags.empty()){

    TreeBag<int>* actual_bag = bags.top();
    bags.pop();

    //we first need to know which of the bags nodes are already in the tree
    std::vector<int> common_nodes;
    for(TreeBag<int>::const_member_iterator i = actual_bag->members_begin();
        i != actual_bag->members_end(); ++i){
      if(nodes_in_tree.find(*i) != nodes_in_tree.end()) common_nodes.push_back(*i);
    }

    //find first possible bag to attach actual bag
    uint parent_bag_index = 0;
    for(std::vector<std::pair<int,TreeBag<int>*> >::iterator i = 
    	bag_list.begin(); i != bag_list.end(); ++i){
      //all nodes in common_nodes must be present in parent bag
      bool add = true;
      for(std::vector<int>::iterator j = common_nodes.begin();
          j != common_nodes.end(); ++j){
        if(!i->second->HasMember(*j)){
          add = false;
          break;
        }
      }
      if(add) break; //thats the bag
      ++parent_bag_index;
    }

    if(parent_bag_index == bag_list.size()){
      String err = "Could not find appropriate parent bag in tree construction";
      throw promod3::Error(err);
    }

    //attach the actual_bag
    int depth = bag_list[parent_bag_index].first + 1;
    bag_list[parent_bag_index].second->AttachBag(actual_bag);

    //insert the actual bag into the bags to attach vector
    //insertion depends on the depth...
    bool inserted_bag = false;
    for(std::vector<std::pair<int,TreeBag<int>*> >::iterator i = 
    	bag_list.begin(); i != bag_list.end(); ++i){
      if(i->first > depth){
        bag_list.insert(i,std::make_pair(depth,actual_bag));
        inserted_bag = true;
        break;
      }
    }
    if(!inserted_bag) bag_list.push_back(std::make_pair(depth,actual_bag));

    //we have to update the nodes present in the tree
    for(TreeBag<int>::const_member_iterator i = actual_bag->members_begin();
        i != actual_bag->members_end(); ++i){
      nodes_in_tree.insert(*i);
    }
  }


  //The last part of the algorithm are postprocessing steps. There are two steps
  //that get performed until they cannot be applied any further
  //
  // 1. If all vertices associated with some bag belong to the vertex set of its 
  //    immediate parent then this node is removed and all its immediate children 
  //    are reconnected to the parent node
  //
  // 2. If some bag contains all vertices associated with its parent node then the 
  //    parent bag is substituded by this bag which thus moves up one edge towards 
  //    the root.

  bool something_happened = true;
  TreeBag<int>* actual_bag;
  TreeBag<int>* actual_parent;

  while(something_happened){

    something_happened = false;

    for(std::vector<std::pair<int, TreeBag<int>*> >::iterator i = bag_list.begin();
        i != bag_list.end(); ++i){

      actual_bag = i->second;
      actual_parent = actual_bag->GetParent();

      if(actual_parent != NULL){
        //1.
        //if all members in actual bag are contained in the parent, we can
        //delete the actual bag and attach all children to the parent
        bool everything_there = true;
        for(TreeBag<int>::const_member_iterator j = actual_bag->members_begin();
            j != actual_bag->members_end(); ++j){
          if(!actual_parent->HasMember(*j)){
            everything_there = false;
            break;
          }
        }
        if(everything_there){
          for(TreeBag<int>::const_child_iterator j = 
          	  actual_bag->children_begin(); 
          	  j != actual_bag->children_end(); ++j){
            actual_parent->AttachBag(*j);
          }
          actual_parent->RemoveBag(actual_bag);
          actual_bag->ClearChildren();
          delete actual_bag;
          i = bag_list.erase(i);
          something_happened = true;
          if(i == bag_list.end()) break;
          //we start with the second check already in the same iteration!
          actual_bag = i->second;
          actual_parent = actual_bag->GetParent();
        }
      }

      if(actual_parent != NULL){
      	//2.
        //if all members in actual parent are contained in actual bag, the 
        //actual parent can be replaced by actual_bag
        bool everything_there = true;
        for(TreeBag<int>::const_member_iterator j = 
        	actual_parent->members_begin(); 
        	j != actual_parent->members_end(); ++j){
          if(!actual_bag->HasMember(*j)){
            everything_there = false;
            break;
          }
        }
        if(everything_there){
          //let's attach all children of actual parent to actual_bag 
          //(except itself)
          for(TreeBag<int>::const_child_iterator j = 
          	  actual_parent->children_begin();
              j != actual_parent->children_end(); ++j){
            if(*j != actual_bag) actual_bag->AttachBag(*j); 
          }
          //lets find the index of the parent
          bool found_parent = false;
          for(uint j = 0; j < bag_list.size(); ++j){
            if(bag_list[j].second == actual_parent){
              //place the actual_bag there
              TreeBag<int>* parents_parent = actual_parent->GetParent();
              if(parents_parent != NULL){
                parents_parent->RemoveBag(actual_parent);
                parents_parent->AttachBag(actual_bag);
              }
              else{
                actual_bag->SetParent(NULL);
              }
              actual_parent->ClearChildren();
              delete actual_parent;
              i = bag_list.erase(bag_list.begin()+j);
              something_happened = true;
              found_parent = true;
              break;
            }
          }
          --i;
          if(!found_parent){
          	String err = "Postprocessing failed in minimal width tree ";
          	err += "construction!";
            throw promod3::Error(err);
          }
        }
      }
    }
  }

  //If everything worked out we should have exactly one bag without parent...
  //That's the root!
  for(std::vector<std::pair<int, TreeBag<int>*> >::iterator i = bag_list.begin();
      i != bag_list.end(); ++i){
    if(i->second->GetParent() == NULL) return i->second;
  }

  String err = "Could not find final root in minimial width tree construction!";
  throw promod3::Error(err);
}

}} //ns
