// 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.


#ifndef PROMOD3_TREE_HH
#define PROMOD3_TREE_HH

#include <promod3/core/graph.hh>

#include <vector>
#include <cstddef>
#include <algorithm>


namespace promod3 { namespace core {

//If you're brave enough to use the TreeBag class, you can build up any tree
//structure you like. This is done by starting from a root node and attaching 
//children to it. IMPORTANT: if you want to delete such a tree you only have
//to call delete on the root, the destructor will then recuresively be called
//for all children. This might be dangerous if you want to delete a single bag.
//Make sure that you call ClearChildren before you delete it to avoid deleting 
//other connected bags.

template<class ITEM>
class TreeBag{

public:
  TreeBag(): parent_(NULL) { }

  ~TreeBag(){
    for(child_iterator i = this->children_begin(); i != 
        this->children_end(); ++i){
      delete (*i);
    }
  }

  void SetParent(TreeBag<ITEM>* parent) { parent_ = parent; }

  TreeBag<ITEM>* GetParent() const { return parent_; }

  void AttachBag(TreeBag<ITEM>* bag){
    bag->SetParent(this);
    children_.push_back(bag);
  }

  void RemoveBag(TreeBag<ITEM>* bag){
    for(child_iterator i = this->children_begin();
        i != this->children_end(); ++i){
      if((*i) == bag){
        children_.erase(i);
        break;
      }
    }
  }

  void ClearChildren() { children_.clear(); }

  void ClearMembers() { members_.clear(); }

  bool IsLeaf() const { return children_.empty(); }

  void SetIdx(int idx) { bag_idx_ = idx; }

  int GetIdx() { return bag_idx_; }

  void SetMembers(const std::vector<ITEM>& members) { members_ = members; }

  bool HasMember(ITEM member) const { 
    return std::find(members_.begin(), 
                     members_.end(), 
                     member) != members_.end(); 
  }

  ITEM& operator [] (int idx) { return members_[idx]; } 
  const ITEM& operator [] (int idx) const { return members_[idx]; } 

  typedef typename std::vector<TreeBag<ITEM>*>::iterator child_iterator;
  typedef typename std::vector<TreeBag<ITEM>*>::const_iterator const_child_iterator;

  typedef typename std::vector<ITEM>::iterator member_iterator;
  typedef typename std::vector<ITEM>::const_iterator const_member_iterator;

  child_iterator children_begin() { return children_.begin(); }

  child_iterator children_end() { return children_.end(); }

  const_child_iterator children_begin() const { return children_.begin(); }

  const_child_iterator children_end() const { return children_.end(); }

  member_iterator members_begin() { return members_.begin(); }

  member_iterator members_end() { return members_.end(); }

  const_member_iterator members_begin() const { return members_.begin(); }

  const_member_iterator members_end() const { return members_.end(); }

private:

  TreeBag<ITEM>* parent_;
  std::vector<TreeBag<ITEM>*> children_;
  std::vector<ITEM> members_;
  int bag_idx_;
};


namespace{
  template <typename T>
  void RecursivePreOrderTraversal(TreeBag<T>* bag, 
                                  std::vector<TreeBag<T>* >& bags){
    bags.push_back(bag);
    for(typename TreeBag<T>::child_iterator i = bag->children_begin();
        i != bag->children_end(); ++i){
      RecursivePreOrderTraversal(*i, bags);
    }
  }
  
  template <typename T>
  void RecursivePostOrderTraversal(TreeBag<T>* bag, 
                                 std::vector<TreeBag<T>* >& bags){
    for(typename TreeBag<T>::child_iterator i = bag->children_begin();
        i != bag->children_end(); ++i){
      RecursivePostOrderTraversal(*i, bags);
    }
    bags.push_back(bag);
  }
}


template <typename T>
std::vector<TreeBag<T>* > PreOrderTraversal(TreeBag<T>* root){
  std::vector<TreeBag<T>* > bags;
  RecursivePreOrderTraversal(root, bags);
  for(int i = 0; i < static_cast<int>(bags.size()); ++i){
    bags[i]->SetIdx(i);
  }
  return bags;
}

template <typename T>
std::vector<TreeBag<T>* > PostOrderTraversal(TreeBag<T>* root){
  std::vector<TreeBag<T>* > bags;
  RecursivePostOrderTraversal(root, bags);
  for(int i = 0; i < static_cast<int>(bags.size()); ++i){
    bags[i]->SetIdx(i);
  }
  return bags;
}


TreeBag<int>* GenerateMinimalWidthTree(const Graph& graph);

}} //ns

#endif
