#ifndef dplyr_OrderVectorVisitor_Impl_H
#define dplyr_OrderVectorVisitor_Impl_H

#include <dplyr/checks.h>

#include <tools/comparisons.h>

#include <dplyr/visitors/CharacterVectorOrderer.h>
#include <dplyr/visitors/order/OrderVisitor.h>
#include <dplyr/visitors/vector/DataFrameVisitors.h>
#include <dplyr/visitors/vector/MatrixColumnVisitor.h>
#include <tools/bad.h>

namespace dplyr {

// version used for ascending = true
template <int RTYPE, bool ascending, typename VECTOR>
class OrderVectorVisitorImpl : public OrderVisitor {
  typedef comparisons<RTYPE> compare;

public:
  /**
   * The type of data : int, double, SEXP, Rcomplex
   */
  typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE;

  OrderVectorVisitorImpl(const VECTOR& vec_) : vec(vec_) {}

  inline bool equal(int i, int j) const {
    return compare::equal_or_both_na(vec[i], vec[j]);
  }

  inline bool before(int i, int j) const {
    return compare::is_less(vec[i], vec[j]);
  }

private:
  VECTOR vec;
};

// version used for ascending = false
template <int RTYPE, typename VECTOR>
class OrderVectorVisitorImpl<RTYPE, false, VECTOR> : public OrderVisitor {
  typedef comparisons<RTYPE> compare;

public:

  /**
   * The type of data : int, double, SEXP, Rcomplex
   */
  typedef typename Rcpp::traits::storage_type<RTYPE>::type STORAGE;

  OrderVectorVisitorImpl(const VECTOR& vec_) : vec(vec_) {}

  inline bool equal(int i, int j) const {
    return compare::equal_or_both_na(vec[i], vec[j]);
  }

  inline bool before(int i, int j) const {
    return compare::is_greater(vec[i], vec[j]);
  }

private:
  VECTOR vec;
};

template <bool ascending>
class OrderCharacterVectorVisitorImpl : public OrderVisitor {
public:
  OrderCharacterVectorVisitorImpl(const CharacterVector& vec_) :
    vec(vec_),
    orders(CharacterVectorOrderer(vec).get())
  {}

  inline bool equal(int i, int j) const {
    return orders.equal(i, j);
  }

  inline bool before(int i, int j) const {
    return orders.before(i, j);
  }

private:
  CharacterVector vec;
  OrderVectorVisitorImpl<INTSXP, ascending, IntegerVector> orders;
};

// ---------- data frame columns

// ascending = true
template <bool ascending>
class OrderVisitorDataFrame : public OrderVisitor {
public:
  OrderVisitorDataFrame(const DataFrame& data_) : data(data_), visitors(data) {}

  inline bool equal(int i, int j) const {
    return visitors.equal(i, j);
  }

  inline bool before(int i, int j) const {
    return visitors.less(i, j);
  }

private:
  DataFrame data;
  DataFrameVisitors visitors;
};

template <>
class OrderVisitorDataFrame<false> : public OrderVisitor {
public:
  OrderVisitorDataFrame(const DataFrame& data_) : data(data_), visitors(data) {}

  inline bool equal(int i, int j) const {
    return visitors.equal(i, j);
  }

  inline bool before(int i, int j) const {
    return visitors.greater(i, j);
  }

private:
  DataFrame data;
  DataFrameVisitors visitors;
};

// ---------- matrix columns

// ascending = true
template <int RTYPE, bool ascending>
class OrderVisitorMatrix : public OrderVisitor {
public:
  OrderVisitorMatrix(const Matrix<RTYPE>& data_) : data(data_), visitors(data) {}

  inline bool equal(int i, int j) const {
    return visitors.equal(i, j);
  }

  inline bool before(int i, int j) const {
    return visitors.less(i, j);
  }

private:
  Matrix<RTYPE> data;
  MatrixColumnVisitor<RTYPE> visitors;
};

// ascending = false
template <int RTYPE>
class OrderVisitorMatrix<RTYPE, false> : public OrderVisitor {
public:
  OrderVisitorMatrix(const Matrix<RTYPE>& data_) : data(data_), visitors(data) {}

  inline bool equal(int i, int j) const {
    return visitors.equal(i, j);
  }

  inline bool before(int i, int j) const {
    return visitors.greater(i, j);
  }

private:
  Matrix<RTYPE> data;
  MatrixColumnVisitor<RTYPE> visitors;
};


inline OrderVisitor* order_visitor(SEXP vec, const bool ascending, const int i);

template <bool ascending>
OrderVisitor* order_visitor_asc(SEXP vec);

template <bool ascending>
OrderVisitor* order_visitor_asc_matrix(SEXP vec);

template <bool ascending>
OrderVisitor* order_visitor_asc_vector(SEXP vec);

inline OrderVisitor* order_visitor(SEXP vec, const bool ascending, const int i) {
  try {
    if (ascending) {
      return order_visitor_asc<true>(vec);
    }
    else {
      return order_visitor_asc<false>(vec);
    }
  }
  catch (const Rcpp::exception& e) {
    bad_pos_arg(i + 1, e.what());
  }
}

template <bool ascending>
inline OrderVisitor* order_visitor_asc(SEXP vec) {
  if (Rf_isMatrix(vec)) {
    return order_visitor_asc_matrix<ascending>(vec);
  }
  else {
    return order_visitor_asc_vector<ascending>(vec);
  }
}

template <bool ascending>
inline OrderVisitor* order_visitor_asc_matrix(SEXP vec) {
  switch (check_supported_type(vec)) {
  case DPLYR_INTSXP:
    return new OrderVisitorMatrix<INTSXP, ascending>(vec);
  case DPLYR_REALSXP:
    return new OrderVisitorMatrix<REALSXP, ascending>(vec);
  case DPLYR_LGLSXP:
    return new OrderVisitorMatrix<LGLSXP, ascending>(vec);
  case DPLYR_STRSXP:
    return new OrderVisitorMatrix<STRSXP, ascending>(vec);
  case DPLYR_CPLXSXP:
    return new OrderVisitorMatrix<CPLXSXP, ascending>(vec);
  case DPLYR_RAWSXP:
    return new OrderVisitorMatrix<RAWSXP, ascending>(vec);
  case DPLYR_VECSXP:
    stop("Matrix can't be a list");
  }

  stop("Unreachable");
  return 0;
}

template <bool ascending>
inline OrderVisitor* order_visitor_asc_vector(SEXP vec) {
  switch (TYPEOF(vec)) {
  case INTSXP:
    return new OrderVectorVisitorImpl<INTSXP, ascending, Vector<INTSXP > >(vec);
  case REALSXP:
    return new OrderVectorVisitorImpl<REALSXP, ascending, Vector<REALSXP> >(vec);
  case LGLSXP:
    return new OrderVectorVisitorImpl<LGLSXP, ascending, Vector<LGLSXP > >(vec);
  case STRSXP:
    return new OrderCharacterVectorVisitorImpl<ascending>(vec);
  case CPLXSXP:
    return new OrderVectorVisitorImpl<CPLXSXP, ascending, Vector<CPLXSXP > >(vec);
  case RAWSXP:
    return new OrderVectorVisitorImpl<RAWSXP, ascending, Vector<RAWSXP > >(vec);
  case VECSXP:
  {
    if (Rf_inherits(vec, "data.frame")) {
      return new OrderVisitorDataFrame<ascending>(vec);
    }
    break;
  }
  default:
    break;
  }

  stop("is of unsupported type %s", Rf_type2char(TYPEOF(vec)));
}
}

#endif
