//***************************************************************************
//* Copyright (c) 2015 Saint Petersburg State University
//* Copyright (c) 2011-2014 Saint Petersburg Academic University
//* All Rights Reserved
//* See file LICENSE for details.
//***************************************************************************

#include "io/reads/ireadstream.hpp"
#include "utils/parallel/openmp_wrapper.h"

#include "hammer_tools.hpp"
#include "hamcluster.hpp"
#include "kmer_cluster.hpp"
#include "config_struct_hammer.hpp"

#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/symmetric.hpp>

#include <iostream>
#include <fstream>
#include <algorithm>

using std::max_element;
using std::min_element;

namespace numeric = boost::numeric::ublas;

using namespace hammer;

std::string KMerClustering::GetGoodKMersFname() const {
  // FIXME: This is ugly!
  std::ostringstream tmp;
  tmp.str("");
  tmp << workdir_ << "/" << "kmers.solid";

  return tmp.str();
}

std::string KMerClustering::GetBadKMersFname() const {
  // FIXME: This is ugly!
  std::ostringstream tmp;
  tmp.str("");
  tmp << workdir_ << "/" << "kmers.bad";

  return tmp.str();
}

static hammer::ExpandedSeq ConsensusWithMask(const std::vector<hammer::ExpandedKMer> &kmers,
                                             const std::vector<size_t> &mask, size_t maskVal) {
  size_t block_size = kmers.size();

  // consensus of a single string is trivial
  if (block_size == 1)
    return kmers[0].seq();

  uint64_t scores[4*K] = {0};
  for (size_t j = 0; j < block_size; ++j) {
    if (mask[j] != maskVal)
      continue;

    const ExpandedSeq &kmer = kmers[j].seq();

    for (unsigned i = 0; i < K; ++i)
      scores[4*i + kmer[i]] += kmers[j].count();
  }

  hammer::ExpandedSeq res;
  for (unsigned i = 0; i < K; ++i)
    res[i] = (char)(std::max_element(scores + 4*i, scores + 4*i + 4) - (scores + 4*i));

  return res;
}

static hammer::ExpandedSeq Consensus(const std::vector<hammer::ExpandedKMer> &kmers) {
  size_t block_size = kmers.size();

  // consensus of a single string is trivial
  if (block_size == 1)
    return kmers[0].seq();

  uint64_t scores[4*K] = {0};
  for (size_t j = 0; j < block_size; ++j) {
    const ExpandedSeq &kmer = kmers[j].seq();

    for (unsigned i = 0; i < K; ++i)
      scores[4*i + kmer[i]] += kmers[j].count();
  }

  hammer::ExpandedSeq res;
  for (unsigned i = 0; i < K; ++i)
    res[i] = (char)(std::max_element(scores + 4*i, scores + 4*i + 4) - (scores + 4*i));

  return res;
}

double KMerClustering::ClusterBIC(const std::vector<Center> &centers,
                                  const std::vector<size_t> &indices, const std::vector<hammer::ExpandedKMer> &kmers) const {
  size_t block_size = indices.size();
  size_t clusters = centers.size();
  if (block_size == 0)
    return -std::numeric_limits<double>::infinity();
  assert(centers.size() > 0);

  double loglik = 0;
  unsigned total = 0;
  for (size_t i = 0; i < block_size; ++i) {
    loglik += kmers[i].count()*kmers[i].logL(centers[indices[i]].center_);
    total += kmers[i].count();
  }

  size_t nparams = (clusters - 1) + clusters*K + 2*clusters*K;

  if (cfg::get().bayes_debug_output > 1) {
#   pragma omp critical
    {
        std::cout << "  logL: " << loglik << ", clusters: " << clusters << ", nparams: " << nparams << ", N: " << block_size << std::endl;
    }
  }

  return loglik - (double)nparams * log((double)total) / 2.0;
}

double KMerClustering::lMeansClustering(unsigned l, const std::vector<hammer::ExpandedKMer> &kmers,
                                        std::vector<size_t> &indices, std::vector<Center> &centers) {
  centers.resize(l); // there are l centers

  // if l==1 then clustering is trivial
  if (l == 1) {
    centers[0].center_ = Consensus(kmers);
    centers[0].count_ = kmers.size();
    for (size_t i = 0; i < kmers.size(); ++i)
      indices[i] = 0;
    return ClusterBIC(centers, indices, kmers);
  }

  // Provide the initial approximation.
  double totalLikelihood = 0.0;
  if (cfg::get().bayes_initial_refine) {
    // Refine the current approximation
    centers[l-1].center_ = kmers[l-1].seq();
    for (size_t i = 0; i < kmers.size(); ++i) {
      size_t cidx = indices[i];
      unsigned cdist = kmers[i].hamdist(centers[cidx].center_, K);
      unsigned mdist = kmers[i].hamdist(centers[l-1].center_, cdist);
      if (mdist < cdist) {
        indices[i] = l - 1;
        cidx = l - 1;
      }
      totalLikelihood += kmers[i].logL(centers[cidx].center_);
    }
  } else {
    // We assume that kmers are sorted wrt the count.
    for (size_t j = 0; j < l; ++j)
      centers[j].center_ = kmers[j].seq();

    for (size_t i = 0; i < kmers.size(); ++i) {
      unsigned mdist = K;
      unsigned cidx = 0;
      for (unsigned j = 0; j < l; ++j) {
        unsigned cdist = kmers[i].hamdist(centers[j].center_, mdist);
        if (cdist < mdist) {
          mdist = cdist;
          cidx = j;
        }
      }
      indices[i] = cidx;
      totalLikelihood += kmers[i].logL(centers[cidx].center_);
    }
  }

  if (cfg::get().bayes_debug_output > 1) {
#   pragma omp critical
    {
      std::cout << "    centers:\n";
      for (size_t i=0; i < centers.size(); ++i) {
        std::cout << "    " << centers[i].center_ << "\n";
      }
    }
  }

  // Main loop
  bool changed = true, improved = true;

  // auxiliary variables
  std::vector<size_t> dists(l);
  std::vector<double> loglike(l);
  std::vector<bool> changedCenter(l);

  while (changed && improved) {
    // fill everything with zeros
    changed = false;
    std::fill(changedCenter.begin(), changedCenter.end(), false);
    for (unsigned j = 0; j < l; ++j)
      centers[j].count_ = 0;

    double curlik = 0;

    // E step: find which clusters we belong to
    for (size_t i = 0; i < kmers.size(); ++i) {
      size_t newInd = 0;
      if (cfg::get().bayes_use_hamming_dist) {
        for (unsigned j = 0; j < l; ++j)
          dists[j] = kmers[i].hamdist(centers[j].center_);

        newInd = std::min_element(dists.begin(), dists.end()) - dists.begin();
      } else {
        for (unsigned j = 0; j < l; ++j)
          loglike[j] = kmers[i].logL(centers[j].center_);
        newInd = std::max_element(loglike.begin(), loglike.end()) - loglike.begin();
      }

      curlik += loglike[newInd];
      if (indices[i] != newInd) {
        changed = true;
        changedCenter[indices[i]] = true;
        changedCenter[newInd] = true;
        indices[i] = newInd;
      }
      ++centers[indices[i]].count_;
    }

    if (cfg::get().bayes_debug_output > 1) {
#     pragma omp critical
      {
        std::cout << "      total likelihood=" << curlik << " as compared to previous " << totalLikelihood << std::endl;
      }
    }
    improved = (curlik > totalLikelihood);
    if (improved)
      totalLikelihood = curlik;

    // M step: find new cluster centers
    for (unsigned j=0; j < l; ++j) {
      if (!changedCenter[j])
        continue; // nothing has changed

      centers[j].center_ = ConsensusWithMask(kmers, indices, j);
    }
  }

  // last M step
  for (unsigned j=0; j < l; ++j)
    centers[j].center_ = ConsensusWithMask(kmers, indices, j);

  if (cfg::get().bayes_debug_output > 1) {
#   pragma omp critical
    {
      std::cout << "    final centers:\n";
      for (size_t i=0; i < centers.size(); ++i) {
        std::cout << "    " << centers[i].center_ << "\n";
      }
    }
  }

  return ClusterBIC(centers, indices, kmers);
}

size_t KMerClustering::SubClusterSingle(const std::vector<size_t> & block, std::vector< std::vector<size_t> > & vec) {
  size_t newkmers = 0;

  if (cfg::get().bayes_debug_output > 0) {
#   pragma omp critical
    {
      std::cout << "  kmers:\n";
      for (size_t i = 0; i < block.size(); i++) {
        std::cout << data_.kmer(block[i]) << '\n';
      }
    }
  }

  size_t origBlockSize = block.size();
  if (origBlockSize == 0) return 0;

  // Ad-hoc max cluster limit: we start to consider only those k-mers which
  // multiplicity differs from maximum multiplicity by 10x.
  size_t maxcls = 0;
  size_t cntthr = std::max(10u, data_[block[0]].count() / 10);
  for (size_t i = 0; i < block.size(); ++i)
    maxcls += (data_[block[i]].count() > cntthr);
  // Another limit: we're interested in good centers only
  size_t maxgcnt = 0;
  for (size_t i = 0; i < block.size(); ++i) {
    float center_quality = 1 - data_[block[i]].total_qual;
    if ((center_quality > cfg::get().bayes_singleton_threshold) ||
        (cfg::get().correct_use_threshold && center_quality > cfg::get().correct_threshold))
      maxgcnt += 1;
  }

  maxcls = std::min(maxcls, maxgcnt) + 1;
  if (cfg::get().bayes_debug_output > 0) {
    #pragma omp critical
    {
      std::cout << "\nClustering an interesting block. Maximum # of clusters estimated: " << maxcls << std::endl;
    }
  }

  // Prepare the expanded k-mer structure
  std::vector<hammer::ExpandedKMer> kmers;
  for (size_t idx : block)
    kmers.emplace_back(data_.kmer(idx), data_[idx]);

  double bestLikelihood = -std::numeric_limits<double>::infinity();
  std::vector<Center> bestCenters;
  std::vector<size_t> indices(origBlockSize);
  std::vector<size_t> bestIndices(origBlockSize);

  unsigned max_l = cfg::get().bayes_hammer_mode ? 1 : (unsigned) origBlockSize;
  std::vector<Center> centers;
  for (unsigned l = 1; l <= max_l; ++l) {
    double curLikelihood = lMeansClustering(l, kmers, indices, centers);
    if (cfg::get().bayes_debug_output > 0) {
      #pragma omp critical
      {
        std::cout << "    indices: ";
        for (uint32_t i = 0; i < origBlockSize; i++) std::cout << indices[i] << " ";
        std::cout << "\n";
        std::cout << "  likelihood with " << l << " clusters is " << curLikelihood << std::endl;
      }
    }
    if (curLikelihood > bestLikelihood) {
      bestLikelihood = curLikelihood;
      bestCenters = centers; bestIndices = indices;
    } else if (l >= maxcls)
      break;
  }

  // find if centers are in clusters
  size_t NO_CENTER = size_t(-1);
  std::vector<size_t> centersInCluster(bestCenters.size(), NO_CENTER);
  for (unsigned i = 0; i < origBlockSize; i++) {
    unsigned dist = kmers[i].hamdist(bestCenters[bestIndices[i]].center_);
    if (dist == 0)
      centersInCluster[bestIndices[i]] = i;
  }

  if (cfg::get().bayes_debug_output > 0) {
#   pragma omp critical
    {
      std::cout << "Centers: \n";
      for (size_t k=0; k<bestCenters.size(); ++k) {
        std::cout << "  " << std::setw(4) << bestCenters[k].count_ << ": ";
        if (centersInCluster[k] != NO_CENTER) {
          const KMerStat &kms = data_[block[centersInCluster[k]]];
          std::cout << kms << " " << std::setw(8) << block[centersInCluster[k]] << "  ";
        } else {
          std::cout << bestCenters[k].center_;
        }
        std::cout << '\n';
      }
      std::cout << "The entire block:" << std::endl;
      for (uint32_t i = 0; i < origBlockSize; i++) {
        const KMerStat &kms = data_[block[i]];
        std::cout << "  " << kms << " " << std::setw(8) << block[i] << "  ";
        for (uint32_t j=0; j<K; ++j) std::cout << std::setw(3) << (unsigned)getQual(kms, j) << " "; std::cout << "\n";
      }
      std::cout << std::endl;
    }
  }

  // it may happen that consensus string from one subcluster occurs in other subclusters
  // we need to check for that
  bool foundBadCenter = true;
  while (foundBadCenter) {
    foundBadCenter = false;
    for (size_t k=0; k<bestCenters.size(); ++k) {
      if (foundBadCenter) break; // restart if found one bad center
      if (bestCenters[k].count_ == 0) continue;
      if (centersInCluster[k] != NO_CENTER) continue;
      for (size_t s = 0; s< bestCenters.size(); ++s) {
        if (s == k || centersInCluster[s] == NO_CENTER) continue;
        unsigned dist = hamdist(bestCenters[k].center_, bestCenters[s].center_);
        if (dist == 0) {
          // OK, that's the situation, cluster k should be added to cluster s
          for (uint32_t i = 0; i < origBlockSize; i++) {
            if (indices[i] == k) {
              indices[i] = (unsigned)s;
              bestCenters[s].count_++;
            }
          }
          bestCenters[k].count_ = 0; // it will be skipped now
          foundBadCenter = true;
          break;
        }
      }
    }
  }

  if (cfg::get().bayes_debug_output > 0 && origBlockSize > 2) {
    #pragma omp critical
    {
      std::cout << "\nAfter the check we got centers: \n";
      for (size_t k=0; k<bestCenters.size(); ++k) {
        std::cout << "  " << bestCenters[k].center_ << " (" << bestCenters[k].count_ << ")";
        if (centersInCluster[k] != NO_CENTER) std::cout << block[centersInCluster[k]];
        std::cout << "\n";
      }
      std::cout << std::endl;
    }
  }

  for (size_t k = 0; k < bestCenters.size(); ++k) {
    if (bestCenters[k].count_ == 0)
      continue; // superfluous cluster with zero elements

    std::vector<size_t> v;
    if (bestCenters[k].count_ == 1) {
      for (size_t i = 0; i < origBlockSize; i++) {
        if (indices[i] == k) {
          v.push_back(block[i]);
          break;
        }
      }
    } else { // there are several kmers in this cluster
      for (size_t i = 0; i < origBlockSize; i++) {
        if (bestIndices[i] == k) {
          if (centersInCluster[k] == i) {
            v.insert(v.begin(), block[i]);
          } else {
            v.push_back(block[i]);
          }
        }
      }

      if (centersInCluster[k] == NO_CENTER) {
        KMer newkmer(bestCenters[k].center_);
        size_t new_idx = data_.checking_seq_idx(newkmer);
        if (new_idx == -1ULL) {
          #pragma omp critical
          {
            KMerStat kms(0 /* cnt */, 1.0 /* total quality */, NULL /*quality */);
            kms.mark_good();
            new_idx = data_.push_back(newkmer, kms);
            newkmers += 1;
          }
        }
        v.insert(v.begin(), new_idx);
      }
    }
    vec.push_back(v);
  }

  return newkmers;
}

static void UpdateErrors(numeric::matrix<uint64_t> &m,
                         const KMer k, const KMer kc) {
  for (unsigned i = 0; i < K; ++i) {
    m(kc[i], k[i]) += 1;
  }
}

size_t KMerClustering::ProcessCluster(const std::vector<size_t> &cur_class,
                                      numeric::matrix<uint64_t> &errs,
                                      std::ofstream &ofs, std::ofstream &ofs_bad,
                                      size_t &gsingl, size_t &tsingl, size_t &tcsingl, size_t &gcsingl,
                                      size_t &tcls, size_t &gcls, size_t &tkmers, size_t &tncls) {
    size_t newkmers = 0;

    // No need for clustering for singletons
    if (cur_class.size() == 1) {
        size_t idx = cur_class[0];
        KMerStat &singl = data_[idx];
        if ((1-singl.total_qual) > cfg::get().bayes_singleton_threshold) {
            singl.mark_good();
            gsingl += 1;

            if (ofs.good()) {
#               pragma omp critical
                {
                    ofs << " good singleton: " << idx << "\n  " << singl << '\n';
                }
            }
        } else {
            if (cfg::get().correct_use_threshold && (1-singl.total_qual) > cfg::get().correct_threshold)
                singl.mark_good();
            else
                singl.mark_bad();

            if (ofs_bad.good()) {
#               pragma omp critical
                {
                    ofs_bad << " bad singleton: " << idx << "\n  " << singl << '\n';
                }
            }
        }
        tsingl += 1;
        return 0;
    }

    std::vector<std::vector<size_t> > blocksInPlace;
    if (cfg::get().bayes_debug_output) {
#       pragma omp critical
        {
          std::cout << "process_SIN with size=" << cur_class.size() << std::endl;
        }
      }
    newkmers += SubClusterSingle(cur_class, blocksInPlace);

    tncls += 1;
    for (size_t m = 0; m < blocksInPlace.size(); ++m) {
        const std::vector<size_t> &currentBlock = blocksInPlace[m];
        if (currentBlock.size() == 0)
            continue;

        size_t cidx = currentBlock[0];
        KMerStat &center = data_[cidx];
        KMer ckmer = data_.kmer(cidx);
        double center_quality = 1 - center.total_qual;

        // Computing the overall quality of a cluster.
        double cluster_quality = 1;
        if (currentBlock.size() > 1) {
            for (size_t j = 1; j < currentBlock.size(); ++j)
                cluster_quality *= data_[currentBlock[j]].total_qual;

            cluster_quality = 1-cluster_quality;
        }

        if (currentBlock.size() == 1)
            tcsingl += 1;
        else
            tcls += 1;

        if ((center_quality > cfg::get().bayes_singleton_threshold &&
             cluster_quality > cfg::get().bayes_nonsingleton_threshold) ||
            cfg::get().bayes_hammer_mode) {
          center.mark_good();

          if (currentBlock.size() == 1)
              gcsingl += 1;
          else
              gcls += 1;

          if (ofs.good()) {
#             pragma omp critical
              {
                  ofs << " center of good cluster (" << currentBlock.size() << ", " << cluster_quality << ")" << "\n  "
                  << center << '\n';
              }
          }
        } else {
            if (cfg::get().correct_use_threshold && center_quality > cfg::get().correct_threshold)
                center.mark_good();
            else
                center.mark_bad();
            if (ofs_bad.good()) {
#               pragma omp critical
                {
                    ofs_bad << " center of bad cluster (" << currentBlock.size() << ", " << cluster_quality << ")" << "\n  "
                            << center << '\n';
                }
            }
        }

        tkmers += currentBlock.size();

        for (size_t j = 1; j < currentBlock.size(); ++j) {
            size_t eidx = currentBlock[j];
            KMerStat &kms = data_[eidx];

            UpdateErrors(errs, data_.kmer(eidx), ckmer);

            if (ofs_bad.good()) {
#               pragma omp critical
                {
                    ofs_bad << " part of cluster (" << currentBlock.size() << ", " << cluster_quality << ")" << "\n  "
                            << kms << '\n';
                }
            }
        }
    }

    return newkmers;
}

class KMerStatCountComparator {
  const KMerData &data_;
public:
  KMerStatCountComparator(const KMerData &data)
      : data_(data) {}
  bool operator()(size_t a, size_t b) {
    return data_[a].count() > data_[b].count();
  }
};

void KMerClustering::process(const std::string &Prefix) {
  size_t newkmers = 0;
  size_t gsingl = 0, tsingl = 0, tcsingl = 0, gcsingl = 0, tcls = 0, gcls = 0, tkmers = 0, tncls = 0;

  std::ofstream ofs, ofs_bad;
  if (cfg::get().bayes_write_solid_kmers)
    ofs.open(GetGoodKMersFname());
  if (cfg::get().bayes_write_bad_kmers)
    ofs_bad.open(GetBadKMersFname());

  // Open and read index file
  MMappedRecordReader<size_t> findex(Prefix + ".idx",  /* unlink */ !debug_, -1ULL);

  std::vector<numeric::matrix<uint64_t> > errs(nthreads_, numeric::matrix<double>(4, 4, 0.0));

# pragma omp parallel for shared(ofs, ofs_bad, errs) num_threads(nthreads_) schedule(guided) reduction(+:newkmers, gsingl, tsingl, tcsingl, gcsingl, tcls, gcls, tkmers, tncls)
  for (size_t chunk = 0; chunk < nthreads_ * nthreads_; ++chunk) {
      size_t *current = findex.data() + findex.size() * chunk / nthreads_ / nthreads_;
      size_t *next = findex.data() + findex.size() * (chunk + 1)/ nthreads_ / nthreads_;
      std::ifstream is(Prefix, std::ios::in | std::ios::binary);

      // Calculate how much we need to seek
      size_t soff = 0;
      for (size_t *csz = findex.data(); csz != current; ++csz)
          soff += *csz;

      // Now see the stream and start processing
      is.seekg(soff * sizeof(size_t));

      for (; current != next; ++current) {
          std::vector<size_t> cluster(*current);
          VERIFY(is.good());
          is.read((char*)&cluster[0], *current * sizeof(cluster[0]));

          // Underlying code expected classes to be sorted in count decreasing order.
          std::sort(cluster.begin(), cluster.end(), KMerStatCountComparator(data_));

          newkmers += ProcessCluster(cluster,
                                     errs[omp_get_thread_num()],
                                     ofs, ofs_bad,
                                     gsingl, tsingl, tcsingl, gcsingl,
                                     tcls, gcls, tkmers, tncls);
      }
  }

  if (!debug_) {
      int res = unlink(Prefix.c_str());
      CHECK_FATAL_ERROR(res == 0,
                        "unlink(2) failed. Reason: " << strerror(errno) << ". Error code: " << errno);
  }

  for (unsigned i = 1; i < nthreads_; ++i)
    errs[0] += errs[i];

  numeric::matrix<uint64_t> rowsums = prod(errs[0], numeric::scalar_matrix<double>(4, 1, 1));
  numeric::matrix<double> err(4, 4);
  for (unsigned i = 0; i < 4; ++i)
    for (unsigned j = 0; j < 4; ++j)
      err(i, j) = 1.0 * (double)errs[0](i, j) / (double)rowsums(i, 0);

  INFO("Subclustering done. Total " << newkmers << " non-read kmers were generated.");
  INFO("Subclustering statistics:");
  INFO("  Total singleton hamming clusters: " << tsingl << ". Among them " << gsingl << " (" << 100.0 * (double)gsingl / (double)tsingl << "%) are good");
  INFO("  Total singleton subclusters: " << tcsingl << ". Among them " << gcsingl << " (" << 100.0 * (double)gcsingl / (double)tcsingl << "%) are good");
  INFO("  Total non-singleton subcluster centers: " << tcls << ". Among them " << gcls << " (" << 100.0 * (double)gcls / (double)tcls << "%) are good");
  INFO("  Average size of non-trivial subcluster: " << 1.0 * (double)tkmers / (double)tcls << " kmers");
  INFO("  Average number of sub-clusters per non-singleton cluster: " << 1.0 * (double)(tcsingl + tcls) / (double)tncls);
  INFO("  Total solid k-mers: " << gsingl + gcsingl + gcls);
  INFO("  Substitution probabilities: " << err);
}


#include <boost/hana/fwd/permutations.hpp>


/*
Copyright (c) 2015, Florian Sittel (www.lettis.net)
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "coords_file/coords_file.hpp"

#include <boost/program_options.hpp>

#include <iostream>
#include <string>
#include <queue>

#include "state_filter.hpp"

namespace {
  int io_error(std::string fname) {
    std::cerr << "error: cannot open file " << fname << "." << std::endl;
    return EXIT_FAILURE;
  }
} // end local namespace

namespace Clustering {
namespace Filter {
  void
  main(boost::program_options::variables_map args) {
    // load states
    std::string fname_states = args["states"].as<std::string>();
    std::vector<std::size_t> states;
    {
      std::ifstream ifs(fname_states);
      if (ifs.fail()) {
        exit(io_error(fname_states));
      } else {
        while (ifs.good()) {
          std::size_t buf;
          ifs >> buf;
          if (ifs.good()) {
            states.push_back(buf);
          }
        }
      }
    }
    if (args["list"].as<bool>()) {
      std::priority_queue<std::pair<std::size_t, std::size_t>> pops;
      // list states with pops
      std::set<std::size_t> state_ids(states.begin(), states.end());
      for (std::size_t id: state_ids) {
        std::size_t pop = std::count(states.begin(), states.end(), id);
        pops.push({pop, id});
      }
      while ( ! pops.empty()) {
        auto pop_id = pops.top(); // get top element
        pops.pop(); // remove top element
        std::cout << pop_id.second << " " << pop_id.first << "\n";
      }
    } else {
      // filter data
      std::size_t selected_state = args["state"].as<std::size_t>();
      CoordsFile::FilePointer coords_in = CoordsFile::open(args["coords"].as<std::string>(), "r");
      CoordsFile::FilePointer coords_out = CoordsFile::open(args["output"].as<std::string>(), "w");
      for (std::size_t s: states) {
        if (s == selected_state) {
          coords_out->write(coords_in->next());
        } else {
          coords_in->next();
        }
      }
    }
  }
} // end namespace Filter
} // end namespace Clustering


/* ************************************************************************
 * Copyright 2013 Advanced Micro Devices, Inc.
 *
 * 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 <iostream>
#include <stdio.h>
#include <clBLAS.h>
#include <boost/program_options.hpp>

#if defined( _WIN32 )
#define popen _popen
#define pclose _pclose
#pragma warning (disable:4996)
#endif

namespace po = boost::program_options;

int
main(int argc, char *argv[])
{
    size_t M;
    size_t N;
    size_t K;
    cl_double alpha;
    cl_double beta;
    cl_uint profileCount;
    int order_option;
    int transA_option;
    int transB_option;
    int uplo_option;
    int side_option;
    int diag_option;
    size_t lda;
    size_t ldb;
    size_t ldc;
    size_t offA;
    size_t offBX;
    size_t offCY;
    std::string function;
    std::string perf_options;
    std::string precision;
    std::string command_line;
    FILE *perf_pipe;
    float perfGFL;
    int test_case;

    perf_options = "";
    po::options_description desc( "clBLAS client command line options" );
    desc.add_options()
        ( "help,h", "produces this help message" )
        ( "gpu,g", "Force instantiation of an OpenCL GPU device" )
        ( "cpu,c", "Force instantiation of an OpenCL CPU device" )
        ( "all,a", "Force instantiation of all OpenCL devices" )
        ( "useimages", "Use an image-based kernel" )
        ( "sizem,m", po::value<size_t>( &M )->default_value(128), "number of rows in A and C" )
        ( "sizen,n", po::value<size_t>( &N )->default_value(128), "number of columns in B and C" )
        ( "sizek,k", po::value<size_t>( &K )->default_value(128), "number of columns in A and rows in B" )
        ( "lda", po::value<size_t>( &lda )->default_value(0), "first dimension of A in memory. if set to 0, lda will default to M (when transposeA is \"no transpose\") or K (otherwise)" )
        ( "ldb", po::value<size_t>( &ldb )->default_value(0), "first dimension of B in memory. if set to 0, ldb will default to K (when transposeB is \"no transpose\") or N (otherwise)" )
        ( "ldc", po::value<size_t>( &ldc )->default_value(0), "first dimension of C in memory. if set to 0, ldc will default to M" )
        ( "offA", po::value<size_t>( &offA )->default_value(0), "offset of the matrix A in memory object (ignored, just for compatibility with the python script)" )
        ( "offBX", po::value<size_t>( &offBX )->default_value(0), "offset of the matrix B or vector X in memory object (ignored, just for compatibility with the python script)" )
        ( "offCY", po::value<size_t>( &offCY )->default_value(0), "offset of the matrix C or vector Y in memory object (ignored, just for compatibility with the python script)" )
        ( "alpha", po::value<cl_double>( &alpha )->default_value(1.0f), "specifies the scalar alpha" )
        ( "beta", po::value<cl_double>( &beta )->default_value(1.0f), "specifies the scalar beta" )
        ( "order,o", po::value<int>( &order_option )->default_value(0), "0 = row major, 1 = column major" )
        ( "transposeA", po::value<int>( &transA_option )->default_value(0), "0 = no transpose, 1 = transpose, 2 = conjugate transpose" )
        ( "transposeB", po::value<int>( &transB_option )->default_value(0), "0 = no transpose, 1 = transpose, 2 = conjugate transpose" )
        ( "function,f", po::value<std::string>( &function )->default_value("gemm"), "BLAS function to test. Options: gemm, trsm, trmm, gemv, symv, syrk, syr2k" )
        ( "precision,r", po::value<std::string>( &precision )->default_value("s"), "Options: s,d,c,z" )
        ( "side", po::value<int>( &side_option )->default_value(0), "0 = left, 1 = right. only used with trmm, trsm" )
        ( "uplo", po::value<int>( &uplo_option )->default_value(0), "0 = upper, 1 = lower. only used with trmm, trs, syrk, syr2k, symv" )
        ( "diag", po::value<int>( &diag_option )->default_value(0), "0 = unit diagonal, 1 = non unit diagonal. only used with trmm, trsm" )
        ( "profile,p", po::value<cl_uint>( &profileCount )->default_value(1), "Time and report the kernel speed (default: profiling off)" )
        ;

        po::variables_map vm;
        po::store( po::parse_command_line( argc, argv, desc ), vm );
        po::notify( vm );

        if( vm.count( "help" ) )
        {
            std::cout << desc << std::endl;
            return 0;
        }

        if( vm.count( "cpu" ) )
        {
            perf_options += " --device cpu";
        }
        else
        {
            perf_options += " --device gpu";
        }

        perf_options = " --gtest_filter=Custom/";
        test_case = 0;
        if( function == "gemm" )
        {
            perf_options += "GEMM.";
            test_case += transB_option;
            test_case += 3 * transA_option;
            test_case += 9 * (1 - order_option);
        }
        else if( function == "trmm" )
        {
            perf_options += "TRMM.";
            test_case += diag_option;
            test_case += 2 * transA_option;
            test_case += 6 * uplo_option;
            test_case += 12 * side_option;
            test_case += 24 * (1 - order_option);
        }
        else if( function == "trsm" )
        {
            perf_options += "TRSM.";
            test_case += diag_option;
            test_case += 2 * transA_option;
            test_case += 6 * uplo_option;
            test_case += 12 * side_option;
            test_case += 24 * (1 - order_option);
        }
        else if( function == "syrk" )
        {
            perf_options += "SYRK.";
            test_case += transA_option;
            test_case += 3 * uplo_option;
            test_case += 6 * (1 - order_option);
        }
        else if( function == "syr2k" )
        {
            perf_options += "SYR2K.";
            test_case += transA_option;
            test_case += 3 * uplo_option;
            test_case += 6 * (1 - order_option);
        }
        else if( function == "gemv" )
        {
            perf_options += "GEMV.";
            test_case += transA_option;
            test_case += 3 * (1 - order_option);
        }
        else if( function == "symv" )
        {
            perf_options += "SYMV.";
            test_case += uplo_option;
            test_case += 2 * (1 - order_option);
        }
        else {
            std::cerr << "Invalid value for --function" << std::endl;
            return -1;
        }
        perf_options += precision + function;

        std::stringstream sizes_str;
        sizes_str << "/" <<  test_case << " " << M << " " << N << " " << K;
        perf_options += sizes_str.str();

        command_line = "test-performance" + perf_options;

        std::cerr << "Command line: " << command_line << std::endl;

        perfGFL = 0;
        perf_pipe = popen( command_line.c_str(), "r" );
        if (perf_pipe == NULL) {
            perror(command_line.c_str());
            std::cerr << "Could not run " << command_line << std::endl;
            return -1;
        }
        else {
            char strbuf[512];
            while(!feof(perf_pipe)) {
                strbuf[0] = '\0';
                if (fgets(strbuf, sizeof(strbuf), perf_pipe) == NULL) {
                    std::cout << "[ERROR]: Read from the pipe has failed!" <<
                                 std::endl;
                    pclose(perf_pipe);
                    return 1;
                }

                if (sscanf(strbuf, "average performance = %f", &perfGFL) == 1) {
                    break;
                }
            }

        }
        pclose(perf_pipe);

        std::cout << "BLAS kernel execution Gflops < >: " << perfGFL << std::endl;
        return 0;
}


//
// Copyright (c) 2016-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

#include <boost/beast/core/detail/sha1.hpp>
#include <boost/beast/unit_test/suite.hpp>
#include <array>

namespace boost {
namespace beast {
namespace detail {

class sha1_test : public beast::unit_test::suite
{
public:
    static
    inline
    std::uint8_t
    unhex(char c)
    {
        if(c >= '0' && c <= '9')
            return c - '0';
        if(c >= 'a' && c <= 'f')
            return c - 'a' + 10;
        if(c >= 'A' && c <= 'F')
            return c - 'A' + 10;
        throw std::invalid_argument("not a hex digit");
    }

    static
    std::string
    unhex(std::string const& in)
    {
        std::string out;
        out.reserve(in.size() / 2);
        if(in.size() % 2)
            throw std::domain_error("invalid hex string");
        for(std::size_t i = 0; i < in.size(); i += 2)
            out.push_back(
                (unhex(in[i])<<4) + unhex(in[i+1]));
        return out;
    }

    void
    check(std::string const& message, std::string const& answer)
    {
        std::string digest;
        digest = unhex(answer);
        sha1_context ctx;
        std::string result;
        result.resize(sha1_context::digest_size);
        init(ctx);
        update(ctx, message.data(), message.size());
        finish(ctx, &result[0]);
        BEAST_EXPECT(result == digest);
    }

    void
    run()
    {
        // http://www.di-mgt.com.au/sha_testvectors.html
        //
        check("abc",
            "a9993e36" "4706816a" "ba3e2571" "7850c26c" "9cd0d89d");
        check("",
            "da39a3ee" "5e6b4b0d" "3255bfef" "95601890" "afd80709");
        check("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
            "84983e44" "1c3bd26e" "baae4aa1" "f95129e5" "e54670f1");
        check("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu",
            "a49b2446" "a02c645b" "f419f995" "b6709125" "3a04a259");
    }
};

BEAST_DEFINE_TESTSUITE(beast,core,sha1);

} // test
} // beast
} // boost


#include <iostream>
#include <boost/python.hpp>
#include "prx/planning/planner_functions/planner_functions.hpp"

using namespace boost::python;

prx::heuristic_function_t init_heuristic_function()
{
    prx::heuristic_function_t default_h = [](const prx::space_point_t& s1, const prx::space_point_t& s2)
    {
        return sqrt((s1->at(0)-s2->at(0))*(s1->at(0)-s2->at(0))+
                    (s1->at(1)-s2->at(1))*(s1->at(1)-s2->at(1)));
    };
    // return boost::python::make_function(default_df);
    return default_h;
}

void pyprx_planning_planner_functions_py()
{

   	// class_<prx::heuristic_function_t>("heuristic_function")
    //     .def("__call__", &prx::heuristic_function_t::operator() )
    //     .def("default", make_function(&init_heuristic_function, default_call_policies())).staticmethod("default")
    //     .def("set_f", &create_function<prx::heuristic_function_t, double, const prx::space_point_t&, const prx::space_point_t&>).staticmethod("set_f")
    //     ;
    def("default_valid_state", &prx::default_valid_state);
    class_<prx::valid_state_t>("valid_state")
        .def("__call__", &prx::valid_state_t::operator())
        .def("set_f", &create_function<prx::valid_state_t, bool, prx::space_point_t>).staticmethod("set_f")
        ;

    // typedef std::function<bool (trajectory_t&)> valid_trajectory_t;
    def("default_valid_trajectory", &prx::default_valid_trajectory);
    class_<prx::valid_trajectory_t>("valid_trajectory")
        .def("__call__", &prx::valid_trajectory_t::operator())
        .def("set_f", &create_function<prx::valid_trajectory_t, bool, prx::trajectory_t>).staticmethod("set_f")
        ;
}


#pragma once

#include <boost/optional.hpp>
#include <boost/unordered_map.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>

#include <crab/common/types.hpp>
#include <crab/cfg/cfg.hpp>

/*
 * Datastructures needed during inter-procedural analysis
 */

namespace crab {

  namespace analyzer {

    // XXX: Currently we assume a _contex-insensitive_ approach so we
    // store a single summary per function. For implementing a
    // context-sensitive approach we would need to allow multiple
    // summaries per function.

    /* Store the calling contexts of each function */
    template <typename CFG, typename AbsDomain>
    class call_ctx_table: boost::noncopyable {
     public:

      typedef typename CFG::basic_block_t::callsite_t callsite_t;
      typedef typename CFG::fdecl_t fdecl_t;
      typedef typename CFG::varname_t varname_t;
      typedef typename CFG::variable_t variable_t;
      typedef AbsDomain abs_domain_t;

     private:
      typedef boost::unordered_map <std::size_t, abs_domain_t> call_table_t;

      call_table_t m_call_table;

      // XXX: assume context-insensitive analysis so it will merge all
      // calling contexts using abstract domain's join keeping a
      // single calling context per function.
      void insert_helper (std::size_t func_key, AbsDomain inv) {
        auto it = m_call_table.find (func_key);
        if (it != m_call_table.end ()) {
          it->second = it->second | inv;
        } else {
          m_call_table.insert (std::make_pair (func_key, inv));
	}
      }

     public:

      call_ctx_table() { }

      void insert (callsite_t cs, AbsDomain inv) {
        insert_helper (cfg::cfg_hasher<CFG>::hash (cs), inv);
      }

      void insert (fdecl_t d, AbsDomain inv) {
        insert_helper (cfg::cfg_hasher<CFG>::hash (d), inv);
      }

      AbsDomain get_call_ctx (fdecl_t d) const {
        auto it = m_call_table.find (cfg::cfg_hasher<CFG>::hash (d));
        if (it != m_call_table.end ())
          return it->second;
        else
          return AbsDomain::top ();
      }

    };

    /* Store the summaries for each function*/
    template<typename CFG, typename AbsDomain>
    class summary_table: boost::noncopyable {

     public:

      typedef typename CFG::basic_block_t::callsite_t callsite_t;
      typedef typename CFG::fdecl_t fdecl_t;
      typedef AbsDomain abs_domain_t;
      typedef typename CFG::variable_t variable_t;

      // A summary is an input-output relationship between function
      // parameters. The relationship can be as expressive as
      // AbsDomain.
      class Summary {

        // --- function info
        fdecl_t m_fdecl;
        // --- Summary involving only m_params + m_ret_vals variables
        abs_domain_t m_sum;
        // --- Keep all the input original parameters of the function
        std::vector <variable_t> m_inputs;
        // --- Keep a copy of all input original parameters of the function
        std::vector <variable_t> m_internal_inputs;
        // --- Keep all the output original parameters of the function
        std::vector <variable_t> m_outputs;
        // --- Keep a copy of all output original parameters of the function
        std::vector <variable_t> m_internal_outputs;

	// - m_sum is defined in terms of m_internal_inputs  and m_internal_outputs.
	// - m_fdecl is defined in terms of m_inputs and m_outputs.

	// helper to rename summaries
	void rename (abs_domain_t &abs,
		     const std::vector<variable_t> &from_inputs,
		     const std::vector<variable_t> &from_outputs,
		     const std::vector<variable_t> &to_inputs,
		     const std::vector<variable_t> &to_outputs) const {

	  assert (from_inputs.size() == to_inputs.size());
	  assert (from_outputs.size() == to_outputs.size());

	  // append inputs and outputs
	  std::vector<variable_t> from_vars, to_vars;
	  from_vars.reserve(from_inputs.size() + from_outputs.size());
	  from_vars.insert(from_vars.end(), from_inputs.begin(), from_inputs.end() );
	  from_vars.insert(from_vars.end(), from_outputs.begin(), from_outputs.end() );
	  to_vars.reserve(to_inputs.size() + to_outputs.size());
	  to_vars.insert(to_vars.end(), to_inputs.begin(), to_inputs.end() );
	  to_vars.insert(to_vars.end(), to_outputs.begin(), to_outputs.end() );
	  abs.rename(from_vars, to_vars);
	}

       public:

        Summary (fdecl_t fdecl,
                 abs_domain_t sum,
                 const std::vector<variable_t> &inputs,
                 const std::vector<variable_t> &outputs):
	  m_fdecl(fdecl), m_sum(sum), m_inputs(inputs), m_outputs(outputs) {

	  m_internal_inputs.reserve(m_inputs.size());
	  m_internal_outputs.reserve(m_outputs.size());
	  for (auto v: m_inputs) {
	    variable_t fresh_v(v.name().get_var_factory().get(), v.get_type());
	    m_internal_inputs.push_back(fresh_v);
	  }
	  for (auto v: m_outputs) {
	    variable_t fresh_v(v.name().get_var_factory().get(), v.get_type());
	    m_internal_outputs.push_back(fresh_v);
	  }

          if (m_fdecl.get_num_inputs() != m_inputs.size ())
            CRAB_ERROR ("mismatch between function declaration and summary parameters");
          if (m_fdecl.get_num_outputs() != m_outputs.size ())
            CRAB_ERROR ("mismatch between function declaration and summary return vals");
	  if (m_inputs.size() != m_internal_inputs.size())
	    CRAB_ERROR("internal error in Summary class");
	  if (m_outputs.size() != m_internal_outputs.size())
	    CRAB_ERROR("internal error in Summary class");
        }

	// -- The summary, input, and output variables contain the
	// -- original variable names.
        abs_domain_t get_sum () const { return m_sum;}

	// return the input variables of the summary
        const std::vector<variable_t>& get_inputs () const
	{ return m_inputs;}

	// return the output variables of the summary
        const std::vector<variable_t>& get_outputs () const
	{ return m_outputs;}

	// -- The summary, input, and output variables are renamed so
	//    that they have unique variable names. This avoids naming
	//    clashes when summaries are used in the interprocedural
	//    analysis.
	//
        abs_domain_t get_renamed_sum() const {
	  abs_domain_t res(m_sum);
	  rename(res, m_inputs, m_outputs,
		 m_internal_inputs, m_internal_outputs);
	  return res;
	}

        const std::vector<variable_t>& get_renamed_inputs() const
	{ return m_internal_inputs;}

        const std::vector<variable_t>& get_renamed_outputs() const
	{ return m_internal_outputs;}

        // Check type consistency between function declaration and callsite
        // XXXX: this is needed because we don't type check crab programs.
        void check_type_consistency (callsite_t cs) const {

          if (m_fdecl.get_num_inputs() != cs.get_num_args())
            CRAB_ERROR ("Mismatch between number of callsite and summary parameters");
          if (m_fdecl.get_num_outputs() != cs.get_lhs().size())
            CRAB_ERROR ("Mismatch between number of callsite and summary return values");

          for (unsigned i=0; i < cs.get_num_args (); i++){
            if (m_fdecl.get_input_type(i) != cs.get_arg_type (i))
              CRAB_ERROR ("Mismatch between type of callsite and summary parameter");
          }

          for (unsigned i=0; i < cs.get_lhs().size(); i++){
            if (m_fdecl.get_output_type(i) != cs.get_lhs()[i].get_type())
              CRAB_ERROR ("Mismatch between type of callsite and summary return value");
          }
        }

        void write(crab_os &o) const {
	  o << m_fdecl.get_func_name() << "(IN:{";
	  for (unsigned i=0; i<m_fdecl.get_num_inputs(); i++){
	    o << m_inputs[i] << ":" <<  m_fdecl.get_input_type(i);
	    if (i != m_fdecl.get_num_inputs() - 1)
	      o << ",";
	  }
	  o << "},OUT:{";
	  for (unsigned i=0; i<m_fdecl.get_num_outputs(); i++){
	    o << m_outputs[i] << ":" <<  m_fdecl.get_output_type(i);
	    if (i != m_fdecl.get_num_outputs() - 1)
	      o << ",";
	  }
	  o << "}) ==>\n";
          abs_domain_t tmp (m_sum);
          o << tmp;
        }

      };

     private:

      typedef boost::shared_ptr <Summary> summary_ptr;
      typedef boost::unordered_map <std::size_t, summary_ptr> summary_table_t;

      summary_table_t m_sum_table;

     public:

      summary_table () { }

      // insert summary information
      void insert (fdecl_t d,
                   AbsDomain sum,
                   const std::vector<variable_t>& inputs,
                   const std::vector<variable_t>& outputs) {

        std::vector<variable_t> ins(inputs.begin(), inputs.end ());
        std::vector<variable_t> outs(outputs.begin(), outputs.end ());
        summary_ptr sum_tuple (new Summary (d, sum, ins, outs));
        m_sum_table.insert (std::make_pair (cfg::cfg_hasher<CFG>::hash (d), sum_tuple));
      }

      // return true if there is a summary
      bool hasSummary (callsite_t cs) const {
        auto it = m_sum_table.find (cfg::cfg_hasher<CFG>::hash (cs));
        return (it != m_sum_table.end ());
      }

      bool hasSummary (fdecl_t d) const {
        auto it = m_sum_table.find (cfg::cfg_hasher<CFG>::hash (d));
        return (it != m_sum_table.end ());
      }

      // get the summary
      Summary& get (callsite_t cs) const {
        auto it = m_sum_table.find (cfg::cfg_hasher<CFG>::hash (cs));
        assert (it != m_sum_table.end ());

        return *(it->second);
      }

      Summary& get (fdecl_t d) const {
        auto it = m_sum_table.find (cfg::cfg_hasher<CFG>::hash (d));
        assert (it != m_sum_table.end ());

        return *(it->second);
      }

      void write (crab_os &o) const {
        o << "--- Begin summary table: \n";
        for (auto const &p: m_sum_table) {
          p.second->write (o);
          o << "\n";
        }
        o << "--- End summary table\n";
      }

      friend crab_os& operator<<(crab_os& o, const summary_table<CFG, AbsDomain>&t) {
        t.write (o);
        return o;
      }

      friend crab_os& operator<<(crab_os& o,
				 const typename summary_table<CFG,AbsDomain>::Summary &sum) {
        sum.write (o);
        return o;
      }

    };

  } // end namespace
} // end namespace


/**

\file
\author Datta Ramadasan
//==============================================================================
//         Copyright 2015 INSTITUT PASCAL UMR 6602 CNRS/Univ. Clermont II
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================

*/

#ifndef __OPTIMISATION2_BUNDLE_BUNDLE_UTILS_HPP__
#define __OPTIMISATION2_BUNDLE_BUNDLE_UTILS_HPP__

#include "../function/function.hpp"
#include <libv/lma/color/console.hpp>

#include <boost/type_traits/remove_pointer.hpp>
#include <boost/fusion/mpl.hpp>
#include <boost/fusion/include/find.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/pop_back.hpp>
#include <boost/fusion/include/make_tuple.hpp>
#include <boost/fusion/include/io.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/fusion/include/equal_to.hpp>
#include <boost/fusion/include/as_vector.hpp>
#include "make_sparse_indice.hpp"
#include "../container/map_container.hpp"
#include <libv/lma/ttt/fusion/copy_f.hpp>
#include <libv/lma/ttt/mpl/naming.hpp>

#include <libv/lma/ttt/traits/unroll1.hpp>
#include <tuple>

namespace lma
{

  template<class ID, class Scalar> ttt::Indice<ID> to_indice(const Scalar& value){ return ttt::Indice<ID>(value); }

  namespace detail
  {
    template<class T> struct FonctorPairTic { typedef bf::pair<T,TIC<T,T>> type;};

    template<int K, class OBS, int I, int F> struct AddWithAll
    {

      template<class Key, class Bdl>
      static void update(Bdl& bundle, const ttt::Indice<Key>& i, const ttt::Indice<Key>& j)
      {
        //! bundle.sparse_indice est utilisé uniquement pour remplir la hessienne : hessien() "
        assert(i < j && "solver.add(F(), A a0, A a1,...) -> &a0 < &a1 , essayer solver.add(F(),&a0,&a1,...) au lieu de solver.add(F(),&a1,&a0,...)");
        bundle.template indices<Key,Key>().add(i,j);
      }

      template<class Key1, class Key2, class Bdl>
      static void update(Bdl& bundle, const ttt::Indice<Key1>& i, const ttt::Indice<Key2>& j)
      {
        //! bundle.sparse_indice est utilisé uniquement pour remplir la hessienne : hessien() "
        bundle.template indices<Key2,Key1>().add(j,i);
//         bf::at_c<I>(bf::at_c<K>(bundle.spi2.template at<OBS>().back().second)) =
        bundle.template indices<Key1,Key2>().add(i,j);
      }

      template<class Bdl, class Indice, class Tuple>
      static void add(Bdl& bundle, const Indice& indice, const Tuple& tuple)
      {
        update(bundle,indice,boost::fusion::at_c<I>(tuple));
        AddWithAll<K,OBS,I+1,F>::add(bundle,indice,tuple);
      }

      template<class Key, class Bdl>
      static void update2(Bdl& bundle, const ttt::Indice<Key>& i, const ttt::Indice<Key>& j)
      {
        //! bundle.sparse_indice est utilisé uniquement pour remplir la hessienne : hessien() "
          bf::at_c<I>(bf::at_c<K>(bundle.spi2.template at<OBS>().back().second)) =  bundle.template indices<Key,Key>().get(i,j);
      }

      template<class Key1, class Key2, class Bdl>
      static void update2(Bdl& bundle, const ttt::Indice<Key1>& i, const ttt::Indice<Key2>& j)
      {
        //! bundle.sparse_indice est utilisé uniquement pour remplir la hessienne : hessien() "
        bf::at_c<I>(bf::at_c<K>(bundle.spi2.template at<OBS>().back().second)) = bundle.template indices<Key1,Key2>().get(i,j);
      }

      template<class Bdl, class Indice, class Tuple>
      static void add2(Bdl& bundle, const Indice& indice, const Tuple& tuple)
      {
        update2(bundle,indice,boost::fusion::at_c<I>(tuple));
        AddWithAll<K,OBS,I+1,F>::add2(bundle,indice,tuple);
      }
    };

    template<int K, class Obs, int F> struct AddWithAll<K,Obs,F,F>
      {
        template<class Bdl, class Indice, class Tuple> static void add(Bdl&, const Indice&, const Tuple&){}
        template<class Bdl, class Indice, class Tuple> static void add2(Bdl&, const Indice&, const Tuple&){}
      };

    template<class Obs, int I, int F> struct AddTupleIterate
    {
      template<class Bdl, class Tuple1, class Tuple2>
      static void add(Bdl& bundle, const Tuple1& original, const Tuple2& tuple)
      {
        static const std::size_t S = boost::fusion::result_of::size<Tuple2>::value;
        AddWithAll<I,Obs,0,S>::add(bundle,boost::fusion::at_c<I>(original),tuple);
        AddTupleIterate<Obs,I+1,F>::add(bundle,original,boost::fusion::pop_front(tuple));
      }

      template<class Bdl, class Tuple1, class Tuple2>
      static void add2(Bdl& bundle, const Tuple1& original, const Tuple2& tuple)
      {
        static const std::size_t S = boost::fusion::result_of::size<Tuple2>::value;
        AddWithAll<I,Obs,0,S>::add2(bundle,boost::fusion::at_c<I>(original),tuple);
        AddTupleIterate<Obs,I+1,F>::add2(bundle,original,boost::fusion::pop_front(tuple));
      }
    };

    template<class Obs, int F> struct AddTupleIterate<Obs,F,F>
    {
      template<class Bdl, class Tuple1, class Tuple2> static void add(Bdl&, const Tuple1&, const Tuple2&) { }
      template<class Bdl, class Tuple1, class Tuple2> static void add2(Bdl&, const Tuple1&, const Tuple2&) { }
    };

    template<class Bdl, class Tuple> struct AddSelf
    {
      Bdl& bundle;
      const Tuple& tuple;
      AddSelf(Bdl& bundle_, const Tuple& tuple_):bundle(bundle_),tuple(tuple_){}
      template<size_t I> void operator()(Int<I> const &)
      {
        auto indice = bf::at_c<I>(tuple);
        typedef typename decltype(indice)::IdType Key;
        bundle.template indices<Key,Key>().add(indice,indice);
      }
    };

    struct AddTuple
    {
      template<class Obs, class Bdl, class Tuple> static void add(Bdl& bundle, const Tuple& tuple)
      {
        static const std::size_t S= boost::fusion::result_of::size<Tuple>::value;// nombre d'indices

        // association de chaque élément i du tuple avec les autres j tel que i <= j
        ttt::unroll<0,S>(AddSelf<Bdl,Tuple>(bundle,tuple));
        AddTupleIterate<Obs,0,S>::add(bundle,tuple,boost::fusion::pop_front(tuple));
      }

      template<class Obs, class Bdl, class Tuple> static void add2(Bdl& bundle, const Tuple& tuple)
      {
        static const std::size_t S= boost::fusion::result_of::size<Tuple>::value;// nombre d'indices

        // association de chaque élément i du tuple avec les autres j tel que i <= j
        AddTupleIterate<Obs,0,S>::add2(bundle,tuple,boost::fusion::pop_front(tuple));
      }
    };

    template<bool obs> struct DispName
    {
      std::ostream& stream;

      DispName(std::ostream& stream_):stream(stream_){}

      template<template<class,class> class Pair, class Key,class Value> void operator()(const Pair<Key,Value>& obj) const
      {
        if (obs)
        stream << " " << color.cyan() << ttt::name<Key>() << "(" << color.bold() << obj.second.size() << color.reset() << color.cyan() << ")" << color.reset() << std::endl;
        else
        stream << " " << color.cyan() << ttt::name<Key>() << "(" << obj.second.size() << ")" << color.reset() << std::endl;
      }
    };

    struct Print6
    {
      std::ostream& o;
      Print6( std::ostream& o_):o(o_){}

      template<class A, class B, class C> void operator()(const boost::fusion::pair<A,SIC<B,C>>& pair) const
      {
        pair.second.disp(o);
      }

      template<class Key> void operator()(const boost::fusion::pair<Key,TIC<Key>>& obj) const
      {
        obj.second.disp(o);
      }

      template<class A, class B> void operator()(const TIC<A,B>& obj) const
      {
        obj.disp(o);
      }

      template<template<class,class> class Pair, class K, class V> void operator()( const Pair<K,V>& pair_map) const
      {
        this->operator()(pair_map.second);
      }

      template<class T> void operator()( const T& map) const
      {
        boost::fusion::for_each(map,*this);
      }
    };

  }// eon detail

  template<class T> struct ToPair { typedef boost::fusion::pair<T,T> type;};
  template<class Liste> struct TransformToPair : mpl::transform< Liste, ToPair<mpl::_1> >::type {};

  typedef boost::fusion::pair<mpl::_1,br::as_map<mpl::_2>> FunctorPairKeyMap;

  template<class L1, class L2, class F> struct MapMapMaker : br::as_map<typename mpl::transform<L1,L2,F>::type> {};

  template<class F, class Apply> struct CreateListArgApply :
    mpl::transform<
                    typename Function<F>::ParametersType,
                    mpl::apply<Apply,mpl::_1>
                  > {};

  struct MapFromTuple
  {
    template<template<class,class> class Pair, class Key, class Value, class B> void operator()(Pair<Key,Value>& a, const B& b) const
    {
      a.second = b;
    }
  };
}// eon lma

#endif


#pragma once
#include <Eigen/Dense>
#include <fastad_bits/reverse/core/constant.hpp>
#include <autoppl/util/traits/var_traits.hpp>
#include <autoppl/util/traits/shape_traits.hpp>
#include <autoppl/util/traits/var_expr_traits.hpp>

#define PPL_DATA_SHAPE_UNSUPPORTED \
    "Unsupported shape for Data. "
#define PPL_DATAVIEW_SHAPE_UNSUPPORTED \
    "Unsupported shape for DataView. "

namespace ppl {

/**
 * DataView is a class that only views data values.
 * It cannot modify the underlying value.
 * If there are multiple values, i.e. shape is vec or mat,
 * it views all of the elements.
 * Specializations for ppl::scl, vec, and mat are provided
 * and all else are disabled.
 *
 * @tparam  ValueType   underlying value type (usually double or int).
 * @tparam  ShapeType   one of the three shape tags.
 */
template <class ValueType
        , class ShapeType = ppl::scl>
struct DataView
{
    static_assert(util::is_shape_v<ShapeType>,
                  PPL_DATAVIEW_SHAPE_UNSUPPORTED);
};

template <class ValueType>
struct DataView<ValueType, ppl::scl>:
    util::VarExprBase<DataView<ValueType, ppl::scl>>,
    util::DataBase<DataView<ValueType, ppl::scl>>
{
    using value_t = ValueType;
    using var_t = value_t;
    using id_t = const void*;
    using shape_t = ppl::scl;
    static constexpr bool has_param = false;

    DataView(const value_t* begin) noexcept
        : var_{begin}
        , id_{this}
    {}

    template <class Func>
    void traverse(Func&&) const {}

    const var_t& eval() const { return get(); }
    const var_t& get() const { return *var_; }

    constexpr size_t size() const { return 1; }
    constexpr size_t rows() const { return 1; }
    constexpr size_t cols() const { return 1; }
    id_t id() const { return id_; }

    template <class PtrPackType>
    auto ad(const PtrPackType&) const
    { return ad::constant(*var_); }

    template <class PtrType>
    void bind(PtrType begin)
    {
        static_cast<void>(begin);
        if constexpr (std::is_convertible_v<PtrType, value_t*>) {
            var_ = begin;
        }
    }

    void activate_refcnt() const {}

private:
    const var_t* var_;
    id_t id_;
};

template <class ValueType>
struct DataView<ValueType, ppl::vec> :
    util::VarExprBase<DataView<ValueType, ppl::vec>>,
    util::DataBase<DataView<ValueType, ppl::vec>>
{
    using value_t = ValueType;
    using var_t = Eigen::Map<const Eigen::Matrix<value_t, Eigen::Dynamic, 1>>;
    using id_t = const void*;
    using shape_t = ppl::vec;
    static constexpr bool has_param = false;

    DataView(const value_t* begin,
             size_t rows) noexcept
        : var_(begin, rows)
        , id_{this}
    {}

    const var_t& eval() const { return get(); }
    const var_t& get() const { return var_; }
    size_t size() const { return var_.size(); }
    size_t rows() const { return var_.rows(); }
    constexpr size_t cols() const { return 1; }
    id_t id() const { return id_; }

    template <class PtrPackType>
    auto ad(const PtrPackType&) const
    { return ad::constant_view(var_.data(), size()); }

    template <class PtrType>
    void bind(PtrType begin)
    {
        static_cast<void>(begin);
        if constexpr (std::is_convertible_v<PtrType, value_t*>) {
            new (&var_) var_t(begin, size());
        }
    }

    void activate_refcnt() const {}

private:
    var_t var_;
    id_t id_;
};

template <class ValueType>
struct DataView<ValueType, ppl::mat> :
    util::VarExprBase<DataView<ValueType, ppl::mat>>,
    util::DataBase<DataView<ValueType, ppl::mat>>
{
    using value_t = ValueType;
    using var_t = Eigen::Map<const Eigen::Matrix<value_t, Eigen::Dynamic, Eigen::Dynamic>>;
    using id_t = const void*;
    using shape_t = ppl::mat;
    static constexpr bool has_param = false;

    DataView(const value_t* begin,
             size_t rows,
             size_t cols) noexcept
        : var_(begin, rows, cols)
        , id_{this}
    {}

    const var_t& eval() const { return get(); }
    const var_t& get() const { return var_; }
    size_t size() const { return var_.size(); }
    size_t rows() const { return var_.rows(); }
    size_t cols() const { return var_.cols(); }
    id_t id() const { return id_; }

    template <class PtrPackType>
    auto ad(const PtrPackType&) const
    { return ad::constant_view(var_.data(), rows(), cols()); }

    template <class PtrType>
    void bind(PtrType begin)
    {
        static_cast<void>(begin);
        if constexpr (std::is_convertible_v<PtrType, value_t*>) {
            new (&var_) var_t(begin, rows(), cols());
        }
    }

    void activate_refcnt() const {}

private:
    var_t var_;
    id_t id_;
};

/**
 * Data a user-friendly wrapper of DataView.
 * It is a DataView (it views itself).
 * The difference is that it owns the container of values.
 * This will usually be used as a quick means to add
 * values directly into a data object.
 *
 * A Data is not a variable expression, but a DataView is.
 * This means any model expression that references Data objects will
 * not create copies of underlying container.
 *
 * @tparam  ValueType   underlying value type (usually double or int)
 * @tparam  ShapeType   one of the three shape tags.
 */

template <class ValueType
        , class ShapeType = ppl::scl>
struct Data
{
    static_assert(util::is_shape_v<ShapeType>,
                  PPL_DATA_SHAPE_UNSUPPORTED);
};

// Specialization: scalar
template <class ValueType>
struct Data<ValueType, ppl::scl>:
    DataView<ValueType, ppl::scl>,
    util::DataBase<Data<ValueType, ppl::scl>>
{
    using base_t = DataView<ValueType, ppl::scl>;
    using typename base_t::value_t;
    using base_t::get;

    Data(value_t v) noexcept
        : base_t(&value_)
        , value_(v)
    {}
    Data() noexcept : Data(0) {}

    auto& get() { return value_; }

private:
    value_t value_;  // store value associated with data
};

// Specialization: vector
template <class ValueType>
struct Data<ValueType, ppl::vec>:
    DataView<ValueType, ppl::vec>,
    util::DataBase<Data<ValueType, ppl::vec>>
{
    using base_t = DataView<ValueType, ppl::vec>;
    using typename base_t::value_t;
    using base_t::bind;
    using base_t::get;

    Data(size_t n)
        : base_t(nullptr, n)
        , vec_(n)
    { this->bind(vec_.data()); }

    auto& get() { return vec_; }

private:
    using vec_t = Eigen::Matrix<value_t, Eigen::Dynamic, 1>;
    vec_t vec_;
};

// Specialization: matrix
template <class ValueType>
struct Data<ValueType, ppl::mat>:
    DataView<ValueType, ppl::mat>,
    util::DataBase<Data<ValueType, ppl::mat>>
{
    using base_t = DataView<ValueType, ppl::mat>;
    using typename base_t::value_t;
    using base_t::bind;
    using base_t::get;

    Data(size_t rows, size_t cols)
        : base_t(nullptr, rows, cols)
        , mat_(rows, cols)
    { this->bind(mat_.data()); }

    auto& get() { return mat_; }

private:
    using mat_t = Eigen::Matrix<value_t, Eigen::Dynamic, Eigen::Dynamic>;
    mat_t mat_;
};

} // namespace ppl

#undef PPL_DATA_SHAPE_UNSUPPORTED
#undef PPL_DATAVIEW_SHAPE_UNSUPPORTED


#include <boost/hof.hpp>
#include <functional>

template<typename T>
T sum(T a, T b) {
    return a + b;
}

int main() {
    std::function<int(int)> addFiveInt = boost::hof::partial(
            BOOST_HOF_LIFT(sum)
        )(5);

    addFiveInt(10);
    // returns 15
}


/*
  Copyright (c) 2020, Robin Zimmeck <robin@robin-zimmeck.de>

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this
     list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice,
     this list of conditions and the following disclaimer in the documentation
     and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

  This software is provided by the copyright holders and contributors "as is"
  and any express or implied warranties, including, but not limited to, the
  implied warranties of merchantability and fitness for a particular purpose are
  disclaimed. In no event shall the copyright holder or contributors be liable
  for any direct, indirect, incidental, special, exemplary, or consequential
  damages (including, but not limited to, procurement of substitute goods or
  services; loss of use, data, or profits; or business interruption) however
  caused and on any theory of liability, whether in contract, strict liability,
  or tort (including negligence or otherwise) arising in any way out of the use
  of this software, even if advised of the possibility of such damage.
*/

#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>

#include "minisi/literals.h"
#include "minisi/io.h"

using namespace minisi;

BOOST_AUTO_TEST_CASE(literals_test)
{
  using namespace minisi::literals;
  BOOST_CHECK_EQUAL( 10.0_m, 10.0 * metre );
  BOOST_CHECK_EQUAL( 10.0_m², 10.0 * metre * metre );
  BOOST_CHECK_EQUAL( 10.0_s, 10.0 * second );
  BOOST_CHECK_EQUAL( 100.0_kg, 100.0 * kilogram );
  BOOST_CHECK_EQUAL( 1.0_N, 1.0 * newton );
  BOOST_CHECK_EQUAL( 543.0_J, 543.0 * joule );
  BOOST_CHECK_EQUAL( 100.0_W, 100.0 * watt );
  BOOST_CHECK_EQUAL( 3.6_mps, 3.6 * metre / second );
  BOOST_CHECK_EQUAL( 9.81_mps², 9.81 * metre / (second * second) );
  BOOST_CHECK_EQUAL( 6.53_mps³, 6.53 * metre / second / second / second );
  BOOST_CHECK_EQUAL( 70.0_kmph, 70.0 * kilometre / hour );
}