/**
 * @file contig_graph_path.h
 * @brief ContigGraph Class.
 * @author Yu Peng (ypeng@cs.hku.hk)
 * @version 1.0.0
 * @date 2011-08-16
 */

#ifndef __GRAPH_CONTIG_GRAPH_PATH_H_

#define __GRAPH_CONTIG_GRAPH_PATH_H_

#include <stdint.h>

#include <deque>

#include "idba/contig_builder.h"
#include "idba/contig_graph_vertex.h"
#include "idba/contig_info.h"

/**
 * @brief It is a path of contigs in ContigGraph.
 */
class ContigGraphPath {
 public:
  ContigGraphPath() {}
  ContigGraphPath(const ContigGraphPath &path)
      : vertices_(path.vertices_), distances_(path.distances_) {}

  const ContigGraphPath &operator=(const ContigGraphPath &path) {
    vertices_ = path.vertices_;
    distances_ = path.distances_;
    return *this;
  }

  bool operator<(const ContigGraphPath &path) const {
    for (unsigned i = 0; i < num_nodes() && i < path.num_nodes(); ++i) {
      if ((*this)[i] != path[i]) return (*this)[i] < path[i];
    }

    return num_nodes() < path.num_nodes();
  }

  ContigGraphVertexAdaptor &operator[](uint32_t index) {
    return vertices_[index];
  }
  const ContigGraphVertexAdaptor &operator[](uint32_t index) const {
    return vertices_[index];
  }

  void Append(const ContigGraphVertexAdaptor &vertex, int d) {
    vertices_.push_back(vertex);
    if (vertices_.size() > 1) distances_.push_back(d);
  }

  void Append(const ContigGraphPath &path, int d) {
    for (unsigned i = 0; i < path.num_nodes(); ++i) {
      if (i == 0)
        Append(path[i], d);
      else
        Append(path[i], path.distances()[i - 1]);
    }
  }

  void Pop() {
    vertices_.pop_back();
    if (!distances_.empty()) distances_.pop_back();
  }

  const ContigGraphPath &ReverseComplement() {
    std::reverse(vertices_.begin(), vertices_.end());
    for (unsigned i = 0; i < vertices_.size(); ++i)
      vertices_[i].ReverseComplement();
    std::reverse(distances_.begin(), distances_.end());
    return *this;
  }

  void Assemble(Sequence &contig, ContigInfo &contig_info) {
    ContigBuilder contig_builder;

    if (vertices_.size() > 0) {
      contig_builder.Append(vertices_[0], 0);
      for (unsigned i = 1; i < vertices_.size(); ++i)
        contig_builder.Append(vertices_[i], distances_[i - 1]);
    }

    contig = contig_builder.contig();
    contig_info = contig_builder.contig_info();
  }

  void swap(ContigGraphPath &path) {
    if (this != &path) {
      vertices_.swap(path.vertices_);
      distances_.swap(path.distances_);
    }
  }

  ContigGraphVertexAdaptor &front() { return vertices_.front(); }
  const ContigGraphVertexAdaptor &front() const { return vertices_.front(); }

  ContigGraphVertexAdaptor &back() { return vertices_.back(); }
  const ContigGraphVertexAdaptor &back() const { return vertices_.back(); }

  uint64_t kmer_count() const {
    uint64_t sum = 0;
    for (unsigned i = 0; i < vertices_.size(); ++i)
      sum += vertices_[i].kmer_count();
    return sum;
  }

  uint32_t size() const {
    uint32_t size = 0;
    for (unsigned i = 0; i < vertices_.size(); ++i)
      size += vertices_[i].contig_size();
    for (unsigned i = 0; i < distances_.size(); ++i) size += distances_[i];
    return size;
  }

  uint32_t internal_size(int kmer_size) const {
    if (vertices_.size() <= 1) return vertices_.size();

    uint32_t size = kmer_size + 1;
    for (unsigned i = 1; i + 1 < vertices_.size(); ++i)
      size += vertices_[i].contig_size();
    for (unsigned i = 0; i < distances_.size(); ++i) size += distances_[i];
    return size;
  }

  uint32_t num_nodes() const { return vertices_.size(); }
  void clear() {
    vertices_.clear();
    distances_.clear();
  }

  std::deque<int> &distances() { return distances_; }
  const std::deque<int> &distances() const { return distances_; }

 private:
  std::deque<ContigGraphVertexAdaptor> vertices_;
  std::deque<int> distances_;
};

namespace std {
template <>
inline void swap(ContigGraphPath &x, ContigGraphPath &y) {
  x.swap(y);
}
}  // namespace std

#endif
