/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2006 Ferdinando Ametrano
 Copyright (C) 2006 Katiuscia Manzoni

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#ifndef quantlib_test_swaption_volatility_cube_hpp
#define quantlib_test_swaption_volatility_cube_hpp

#include <boost/test/unit_test.hpp>

/* remember to document new and/or updated tests in the Doxygen
   comment block of the corresponding class */

class SwaptionVolatilityCubeTest {
  public:
    static void testAtmVols();
    static void testSmile();
    static void testSabrVols();
    static void testSpreadedCube();
    static void testObservability();

    static boost::unit_test_framework::test_suite* suite();
};

#endif


/*
 * Copyright (c) 2020-2021, Marco Sánchez Beeckman
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include <algorithm>
#include <cmath>
#include <chrono>
#include <iostream>

#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <opencv2/core.hpp>

#include "RandomWalker.h"

namespace tfg {

/**
 * Construct an instance of a RandomWalker object. The random walk is performed over a graph
 * where the nodes are tracks, so a trackTable object is required to initialize the RandomWalker.
 * @param trackTable A trackTable containing information of the tracks where the random walk is going to be performed.
 */
RandomWalker::RandomWalker(std::shared_ptr<tfg::TrackTable> &trackTable) {
    this->trackTable = trackTable;
}

RandomWalker::~RandomWalker() {}

/**
 * Label nodes (tracks) of the graph as foreground (there can be various foreground labels) or background from a series of seeded images.
 * An initial labeling is needed so that the labels can be propagated to unlabeled nodes.
 * @param seedImages An unordered map of OpenCV Mat objects, whose key is an integer that indicates the frame the seed is for.
 */
void RandomWalker::seed(const std::unordered_map<int, cv::Mat> &seedImages) {
    std::cout << "Seeding tracks" << '\n';

    // For each matrix of seeds, label each track passing through a seed with its corresponding tag.
    // If a track passes through two or more different seeds, tag it as unreliable and don't use it for propagation.
    for(auto& [ frame, image ] : seedImages) {
        for(unsigned int t = 0; t < trackTable->numberOfTracks(); t++) {
            const int trackLabel = trackTable->labelOfTrack(t);
            if(trackLabel == -2) continue; // If unreliable, do nothing to the track
            if(frame < trackTable->firstFrameOfTrack(t) || frame > trackTable->firstFrameOfTrack(t) + trackTable->durationOfTrack(t) - 1) continue;

            const cv::Vec2f position = trackTable->pointsInTrack(t)[frame - trackTable->firstFrameOfTrack(t)];
            const int posX = std::round(position(0));
            const int posY = std::round(position(1));
            const cv::Vec3b& color = image.at<cv::Vec3b>(posY, posX);

            // The labeling in the images must be done using colors the following way:
            // a BGR value of 0-0-255 (full red) corresponds to the background
            // a BGR value of 0-0-0 (no color) means there is no label
            // another BGR value using 255 for any combination of colors (e. g. 0-255-255) is a different foreground label
            // non-255 values are considered 0.
            int label = color[2]/255 + 2*(color[1]/255) + 4*(color[0]/255) - 1;
            int labelWithValidityCheck = trackLabel < 0 || trackLabel == label ? label : -2; // If a track is already labeled and it passes through a seed with a different tag, mark it as unreliable
            trackTable->setLabelToTrack(labelWithValidityCheck, t);
        }
    }

    // Sort tracks by label. The only purpose of this is to separate labeled and unlabeled tracks, so it is easier to mount the
    // system of equations for the propagation
    trackTable->sortTracksByLabel();
    this->numberOfLabels = trackTable->labelOfTrack(trackTable->numberOfTracks() - 1) + 1;
    this->unlabeledTracks = trackTable->indexOfFirstLabel();
    this->labeledTracks = trackTable->numberOfTracks() - unlabeledTracks;

    std::cout << "Sorted tracks according to labels" << '\n';
    std::cout << "There are " << unlabeledTracks << " unlabeled tracks and " << labeledTracks << " labeled tracks" << '\n';
}

void RandomWalker::seedDavis(const std::unordered_map<int, cv::Mat> &seedImages) {
    std::cout << "Seeding tracks" << '\n';

    // For each matrix of seeds, label each track passing through a seed with its corresponding tag.
    // If a track passes through two or more different seeds, tag it as unreliable and don't use it for propagation.
    for(auto& [ frame, image ] : seedImages) {
        for(unsigned int t = 0; t < trackTable->numberOfTracks(); t++) {
            const int trackLabel = trackTable->labelOfTrack(t);
            if(trackLabel == -2) continue; // If unreliable, do nothing to the track
            if(frame < trackTable->firstFrameOfTrack(t) || frame > trackTable->firstFrameOfTrack(t) + trackTable->durationOfTrack(t) - 1) continue;

            const cv::Vec2f position = trackTable->pointsInTrack(t)[frame - trackTable->firstFrameOfTrack(t)];
            const int posX = std::round(position(0));
            const int posY = std::round(position(1));
            const cv::Vec3b& color = image.at<cv::Vec3b>(posY, posX);

            // The labeling in the images must be done using colors the following way:
            // a BGR value of 0-0-0 (black/no color) corresponds to the background
            // a BGR value of 0-0-128 (red) corresponds to the first object
            // any other combination of values using 128 or 255
            int label = color[2]/128 + 2*(color[1]/128) + 4*(color[0]/128);
            int labelWithValidityCheck = trackLabel < 0 || trackLabel == label ? label : -2; // If a track is already labeled and it passes through a seed with a different tag, mark it as unreliable
            trackTable->setLabelToTrack(labelWithValidityCheck, t);
        }
    }

    trackTable->sortTracksByLabel();
    this->numberOfLabels = trackTable->labelOfTrack(trackTable->numberOfTracks() - 1) + 1;
    this->unlabeledTracks = trackTable->indexOfFirstLabel();
    this->labeledTracks = trackTable->numberOfTracks() - unlabeledTracks;

    std::cout << "Sorted tracks according to labels" << '\n';
    std::cout << "There are " << unlabeledTracks << " unlabeled tracks and " << labeledTracks << " labeled tracks" << '\n';
}

/**
 * Propagate the labels of the graph. This is done by solving a system of equations, specifically
 * the one in eq. 10 in the following paper:
 *
 * L. Grady.
 * Random Walks for Image Segmentation
 * IEEE Transactions on Pattern Analysis and Machine Intelligence, 28(11): 1768-1783, 2006
 * DOI: 10.1109/TPAMI.2006.233
 */
void RandomWalker::propagateSeeds(float lambda) {
    std::cout << "Beginning propagation of seeds" << '\n';

    Eigen::SparseMatrix<float> laplacianUnlabeled(unlabeledTracks, unlabeledTracks);
    Eigen::SparseMatrix<float> minusBt(unlabeledTracks, labeledTracks);

    std::cout << "Filling matrices for the system of equations" << '\n';

    std::chrono::steady_clock::time_point beginFill = std::chrono::steady_clock::now();
    std::vector<Eigen::Triplet<float>> laplacianEntries;
    std::vector<Eigen::Triplet<float>> minusBtEntries;
    std::vector<float> unlabeledTracksDegrees(unlabeledTracks, 0.0f);
    laplacianEntries.reserve(unlabeledTracks * unlabeledTracks / 2);
    minusBtEntries.reserve(unlabeledTracks * labeledTracks / 2);

    // Fill the sparse matrices corresponding to L_U and -Bt (minus B transposed) in eq. 7 of Grady's paper
    // L_U is the graph laplacian of the subgraph formed by only the unlabeled tracks
    // B is the matrix of size |V_M| x |V_U| whose entry (i, j) corresponds to minus the weight of the edge from the ith labeled track to the jth unlabeled track
    for(unsigned int tA = 0; tA < unlabeledTracks; tA++) {
        for(unsigned int tB = tA + 1; tB < unlabeledTracks + labeledTracks; tB++) {

            // The weights between affinities are defined from a distance, defined in eq. 2
            // of the following paper:
            // P. Ochs, J. Malik, and T. Brox.
            // Segmentation of moving objects by long term video analysis.
            // IEEE Transactions on Pattern Analysis and Machine Intelligence, 36(6): 1187-1200, Jun 2014.
            // DOI: 10.1109/TPAMI.2013.242
            const float trackDistance2 = trackTable->distance2BetweenTracks(tA, tB);
            const float weightAB = std::exp(-lambda * trackDistance2);

            // If the weight between tracks is almost 0, consider it 0 and do not store it explicitly in the sparse matrix
            // if(weightAB < 1e-8) continue;
            if(weightAB == 0) continue;
            unlabeledTracksDegrees[tA] += weightAB;

            if(tB >= unlabeledTracks) {
                minusBtEntries.push_back(Eigen::Triplet<float>(tA, tB - unlabeledTracks, weightAB));
            } else {
                unlabeledTracksDegrees[tB] += weightAB;
                laplacianEntries.push_back(Eigen::Triplet<float>(tA, tB, -weightAB));
            }
        }
        laplacianEntries.push_back(Eigen::Triplet<float>(tA, tA, unlabeledTracksDegrees[tA]));
    }
    laplacianUnlabeled.setFromTriplets(laplacianEntries.begin(), laplacianEntries.end());
    minusBt.setFromTriplets(minusBtEntries.begin(), minusBtEntries.end());
    std::chrono::steady_clock::time_point endFill = std::chrono::steady_clock::now();
    std::cout << "Filled in " << (std::chrono::duration_cast<std::chrono::microseconds>(endFill-beginFill).count())/1000000.0 << " seconds" << '\n';

    std::cout << "Starting walking process" << '\n';
    std::vector<Eigen::VectorXf> labelProbabilities;
    labelProbabilities.reserve(numberOfLabels - 1);

    // Solve the system of equations L_U*x = -Bt*m_s given in eq. 10 in Grady's paper for each label except the last one
    // m_s (s for each label) is a vector of length |V_M| that serves as an indicator for each label: a 1 in the ith entry means that
    // the ith labeled node is marked with label s, and a 0 means it is not
    for(int k = 0; k < numberOfLabels - 1; k++) {

        Eigen::SparseMatrix<float> M(labeledTracks, 1);
        std::vector<Eigen::Triplet<float>> Mentries;
        Mentries.reserve(labeledTracks);
        // Create indicator for the label
        for(int i = 0; i < labeledTracks; i++) {
            float labelIsPresent = trackTable->labelOfTrack(i + unlabeledTracks) == k ? 1 : 0;
            if(labelIsPresent != 0) Mentries.push_back(Eigen::Triplet<float>(i, 0, labelIsPresent));
        }
        M.setFromTriplets(Mentries.begin(), Mentries.end());

        // Get product -Bt*m_s, right hand of eq. 10 in Grady's paper
        Eigen::SparseMatrix<float> minusBtByM = minusBt * M;
        std::cout << "Solving system of equations for label " << k << '\n';
        std::chrono::steady_clock::time_point beginSolve = std::chrono::steady_clock::now();

        // Solve the system of equations using an iterative conjugate gradient algorithm for self-adjoint sparse matrices
        Eigen::ConjugateGradient<Eigen::SparseMatrix<float>, Eigen::Upper> conjGrad;
        conjGrad.compute(laplacianUnlabeled);
        Eigen::VectorXf probabilityLabel = conjGrad.solve(minusBtByM);
        labelProbabilities.push_back(probabilityLabel);
        std::chrono::steady_clock::time_point endSolve = std::chrono::steady_clock::now();
        std::cout << "Solved in " << (std::chrono::duration_cast<std::chrono::microseconds>(endSolve-beginSolve).count())/1000000.0 << " seconds" << '\n';
    }

    probabilities.clear();
    // probabilities.reserve(unlabeledTracks + labeledTracks);
    probabilities.resize(unlabeledTracks + labeledTracks);

    // For every unlabeled track, save the probabilities for each label
    // and order them the original way
    for(unsigned int t = 0; t < unlabeledTracks; t++) {
        std::vector<float> trackLabelProbabilities;
        trackLabelProbabilities.reserve(numberOfLabels);
        float maxProbability = 0;
        float sumOfProbabilities = 0;
        unsigned int mostLikelyLabel = 0;
        for(unsigned int k = 0; k < numberOfLabels - 1; k++) {
            trackLabelProbabilities.push_back(labelProbabilities[k](t));
            sumOfProbabilities += labelProbabilities[k](t);
            if(labelProbabilities[k](t) > maxProbability) {
                maxProbability = labelProbabilities[k](t);
                mostLikelyLabel = k;
            }
        }
        float probabilityOfLastLabel = 1 - sumOfProbabilities;
        trackLabelProbabilities.push_back(probabilityOfLastLabel);
        if(probabilityOfLastLabel > maxProbability) {
            maxProbability = probabilityOfLastLabel;
            mostLikelyLabel = numberOfLabels - 1;
        }

        probabilities[trackTable->numberOfTrack(t)] = trackLabelProbabilities;
        trackTable->setLabelToTrack(mostLikelyLabel, t);
    }
    // Do the same for the labeled tracks
    for(unsigned int t = unlabeledTracks; t < unlabeledTracks + labeledTracks; t++) {
        std::vector<float> trackLabelProbabilities(numberOfLabels, 0.0f);
        trackLabelProbabilities[trackTable->labelOfTrack(t)] = 1.0f;
        probabilities[trackTable->numberOfTrack(t)] = trackLabelProbabilities;
    }
}

/**
 * Write in a file the probabilities that each track has for every label.
 * @param file The file where the probabilities are going to be stored.
 */
void RandomWalker::writeProbabilities(std::ofstream &file) {
    file << unlabeledTracks + labeledTracks << " " << numberOfLabels << '\n';
    for(unsigned int t = 0; t < unlabeledTracks + labeledTracks; t++) {
        // std::vector<float>& trackLabelProbabilities = probabilities.at(t);
        for(unsigned int k = 0; k < numberOfLabels - 1; k++) {
            // file << trackLabelProbabilities[k] << " ";
            file << probabilities[t][k] << " ";
        }
        // file << trackLabelProbabilities[numberOfLabels - 1] << '\n';
        file << probabilities[t][numberOfLabels - 1] << '\n';
    }
}

} // namespace tfg


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          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 BOOST_SIMD_MEMORY_FUNCTIONS_SIMD_SSE_AVX_SPLAT_HPP_INCLUDED
#define BOOST_SIMD_MEMORY_FUNCTIONS_SIMD_SSE_AVX_SPLAT_HPP_INCLUDED
#ifdef BOOST_SIMD_HAS_AVX_SUPPORT

#include <boost/simd/memory/functions/splat.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < double_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
      return _mm256_set1_pd(double(a0));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < single_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
      return _mm256_set1_ps(float(a0));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < ints8_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
      return _mm256_set1_epi8(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < ints16_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
      return _mm256_set1_epi16(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < ints32_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
      return _mm256_set1_epi32(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( splat_
                                    , boost::simd::tag::avx_
                                    , (A0)(A1)
                                    , (scalar_< fundamental_<A0> >)
                                      ((target_ < simd_ < ints64_<A1>
                                                        , boost::simd::tag::avx_
                                                        >
                                                >
                                      ))
                                    )
  {
    typedef typename A1::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const& a0, A1 const&) const
    {
#ifndef BOOST_MSVC
      return _mm256_set1_epi64x(a0);
#else
      return make<result_type>(a0, a0, a0, a0);
#endif
    }
  };
} } }

#endif
#endif


#include <iostream>
#include <vector>

#include <boost/program_options.hpp>

#include "prob.h"
#include "tdict.h"
#include "scorer.h"
#include "filelib.h"
#include "stringlib.h"

using namespace std;

namespace po = boost::program_options;

void InitCommandLine(int argc, char** argv, po::variables_map* conf) {
  po::options_description opts("Configuration options");
  opts.add_options()
        ("scale,a",po::value<double>()->default_value(1.0), "Posterior scaling factor (alpha)")
        ("loss_function,l",po::value<string>()->default_value("bleu"), "Loss function")
        ("input,i",po::value<string>()->default_value("-"), "File to read k-best lists from")
        ("output_list,L", "Show reranked list as output")
        ("help,h", "Help");
  po::options_description dcmdline_options;
  dcmdline_options.add(opts);
  po::store(parse_command_line(argc, argv, dcmdline_options), *conf);
  bool flag = false;
  if (flag || conf->count("help")) {
    cerr << dcmdline_options << endl;
    exit(1);
  }
}

struct LossComparer {
  bool operator()(const pair<vector<WordID>, double>& a, const pair<vector<WordID>, double>& b) const {
    return a.second < b.second;
  }
};

bool ReadKBestList(istream* in, string* sent_id, vector<pair<vector<WordID>, prob_t> >* list) {
  static string cache_id;
  static pair<vector<WordID>, prob_t> cache_pair;
  list->clear();
  string cur_id;
  if (cache_pair.first.size() > 0) {
    list->push_back(cache_pair);
    cur_id = cache_id;
    cache_pair.first.clear();
  }
  string line;
  string tstr;
  while(*in) {
    getline(*in, line);
    if (line.empty()) continue;
    size_t p1 = line.find(" ||| ");
    if (p1 == string::npos) { cerr << "Bad format: " << line << endl; abort(); }
    size_t p2 = line.find(" ||| ", p1 + 4);
    if (p2 == string::npos) { cerr << "Bad format: " << line << endl; abort(); }
    size_t p3 = line.rfind(" ||| ");
    cache_id = line.substr(0, p1);
    tstr = line.substr(p1 + 5, p2 - p1 - 5);
    double val = strtod(line.substr(p3 + 5).c_str(), NULL);
    TD::ConvertSentence(tstr, &cache_pair.first);
    cache_pair.second.logeq(val);
    if (cur_id.empty()) cur_id = cache_id;
    if (cur_id == cache_id) {
      list->push_back(cache_pair);
      *sent_id = cur_id;
      cache_pair.first.clear();
    } else { break; }
  }
  return !list->empty();
}

int main(int argc, char** argv) {
  po::variables_map conf;
  InitCommandLine(argc, argv, &conf);
  const string metric = conf["loss_function"].as<string>();
  const bool output_list = conf.count("output_list") > 0;
  const string file = conf["input"].as<string>();
  const double mbr_scale = conf["scale"].as<double>();
  cerr << "Posterior scaling factor (alpha) = " << mbr_scale << endl;

  ScoreType type = ScoreTypeFromString(metric);
  vector<pair<vector<WordID>, prob_t> > list;
  ReadFile rf(file);
  string sent_id;
  while(ReadKBestList(rf.stream(), &sent_id, &list)) {
    vector<prob_t> joints(list.size());
    const prob_t max_score = pow(list.front().second, mbr_scale);
    prob_t marginal = prob_t::Zero();
    for (int i = 0 ; i < list.size(); ++i) {
      const prob_t joint = pow(list[i].second, mbr_scale) / max_score;
      joints[i] = joint;
      // cerr << "list[" << i << "] joint=" << log(joint) << endl;
      marginal += joint;
    }
    int mbr_idx = -1;
    vector<double> mbr_scores(output_list ? list.size() : 0);
    double mbr_loss = numeric_limits<double>::max();
    for (int i = 0 ; i < list.size(); ++i) {
      vector<vector<WordID> > refs(1, list[i].first);
      //cerr << i << ": " << list[i].second <<"\t" << TD::GetString(list[i].first) << endl;
      ScorerP scorer = SentenceScorer::CreateSentenceScorer(type, refs);
      double wl_acc = 0;
      for (int j = 0; j < list.size(); ++j) {
        if (i != j) {
          ScoreP s = scorer->ScoreCandidate(list[j].first);
          double loss = 1.0 - s->ComputeScore();
          if (type == TER || type == AER) loss = 1.0 - loss;
          double weighted_loss = loss * (joints[j] / marginal);
          wl_acc += weighted_loss;
          if ((!output_list) && wl_acc > mbr_loss) break;
        }
      }
      if (output_list) mbr_scores[i] = wl_acc;
      if (wl_acc < mbr_loss) {
        mbr_loss = wl_acc;
        mbr_idx = i;
      }
    }
    // cerr << "ML translation: " << TD::GetString(list[0].first) << endl;
    cerr << "MBR Best idx: " << mbr_idx << endl;
    if (output_list) {
      for (int i = 0; i < list.size(); ++i)
        list[i].second.logeq(mbr_scores[i]);
      sort(list.begin(), list.end(), LossComparer());
      for (int i = 0; i < list.size(); ++i)
        cout << sent_id << " ||| "
             << TD::GetString(list[i].first) << " ||| "
             << log(list[i].second) << endl;
    } else {
      cout << TD::GetString(list[mbr_idx].first) << endl;
    }
  }
  return 0;
}


/*
Copyright 2009-2019 Nicolas Colombe

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <math/aabb2dpolygon.hpp>
#include <math/polygon_def.hpp>
#include <boost/polygon/detail/iterator_points_to_compact.hpp>
#include <boost/polygon/detail/iterator_compact_to_points.hpp>
#include <core/containers.hpp>
namespace boost
{
  namespace polygon
  {

    template <typename Real>
    struct geometry_concept<eXl::AABB2DPolygon<Real> >{ typedef polygon_90_with_holes_concept /*polygon_90_concept*/ type; };

    template <typename Real>
    struct geometry_concept<eXl::Vector<eXl::Vector2<Real> > >{ typedef polygon_90_concept type; };

    template <typename Real>
    struct geometry_concept<eXl::Vector<eXl::AABB2DPolygon<Real> > >{ typedef polygon_90_set_concept type; };

    template <typename Real>
    struct polygon_90_traits<eXl::AABB2DPolygon<Real> > {
      typedef Real coordinate_type;
      typedef iterator_points_to_compact<typename eXl::Vector<eXl::Vector2<Real> >::const_iterator,eXl::Vector2<Real> > compact_iterator_type;
      typedef eXl::Vector2<Real> point_type;

      static inline compact_iterator_type begin_compact(eXl::AABB2DPolygon<Real> const& t) {
          return compact_iterator_type (t.Border().begin(),t.Border().end());
      }
      static inline compact_iterator_type end_compact(eXl::AABB2DPolygon<Real> const& t) {
          return compact_iterator_type (t.Border().end(),t.Border().end());
      }

      // Get the number of sides of the polygon
      static inline std::size_t size(eXl::AABB2DPolygon<Real> const& t) {
        return t.Border().size();
      }

      // Get the winding direction of the polygon
      static inline winding_direction winding(eXl::AABB2DPolygon<Real> const& t) {
        return unknown_winding;
      }
    };

    template <typename Real>
    struct polygon_90_mutable_traits<eXl::AABB2DPolygon<Real> > {

      template <typename iT>
      static inline eXl::AABB2DPolygon<Real>& set_compact(eXl::AABB2DPolygon<Real> & t,
                                         iT input_begin, iT input_end) {

        eXl::Vector<eXl::Vector2<Real>> tempStor;
        iterator_compact_to_points<iT,eXl::Vector2<Real>> iterBegin(input_begin,input_end);
        iterator_compact_to_points<iT,eXl::Vector2<Real>> iterEnd(input_end,input_end);

        tempStor.assign(iterBegin,iterEnd);

        t = eXl::AABB2DPolygon<Real>(tempStor);
        t.ForceClockwise();
        return t;
      }

    };

    template <typename Real>
    struct polygon_90_traits<eXl::Vector<eXl::Vector2<Real> > > {
      typedef Real coordinate_type;
      typedef iterator_points_to_compact<typename eXl::Vector<eXl::Vector2<Real> >::const_iterator,eXl::Vector2<Real> > compact_iterator_type;
      typedef eXl::Vector2<Real> point_type;

      static inline compact_iterator_type begin_compact(typename eXl::AABB2DPolygon<Real>::PtList const& t) {
          return compact_iterator_type (t.begin(),t.end());
      }
      static inline compact_iterator_type end_compact(typename eXl::AABB2DPolygon<Real>::PtList const& t) {
          return compact_iterator_type (t.end(),t.end());
      }

      // Get the number of sides of the polygon
      static inline std::size_t size(typename eXl::AABB2DPolygon<Real>::PtList const& t) {
        return t.size();
      }

      // Get the winding direction of the polygon
      static inline winding_direction winding(typename eXl::AABB2DPolygon<Real>::PtList const& t) {
        return unknown_winding;
      }
    };

    template <typename Real>
    struct polygon_90_mutable_traits<eXl::Vector<eXl::Vector2<Real> > > {

      template <typename iT>
      static inline typename eXl::AABB2DPolygon<Real>::PtList& set_compact(typename eXl::AABB2DPolygon<Real>::PtList & t,
                                         iT input_begin, iT input_end) {

        iterator_compact_to_points<iT,eXl::Vector2<Real> > iterBegin(input_begin,input_end);
        iterator_compact_to_points<iT,eXl::Vector2<Real> > iterEnd(input_end,input_end);
        t.assign(iterBegin,iterEnd);

        return t;
      }

    };

    template <typename Real,typename enable>
    struct polygon_with_holes_traits<eXl::AABB2DPolygon<Real>,enable> {
         typedef typename eXl::AABB2DPolygon<Real>::PtLists::const_iterator iterator_holes_type;
         typedef typename eXl::AABB2DPolygon<Real>::PtList hole_type;
         static inline iterator_holes_type begin_holes(const eXl::AABB2DPolygon<Real>& t) {
              return t.Holes().begin();
         }
         static inline iterator_holes_type end_holes(const eXl::AABB2DPolygon<Real>& t) {
              return t.Holes().end();
         }
         static inline Real size_holes(const eXl::AABB2DPolygon<Real>& t) {
              return t.Holes().size();
         }
    };

    template <typename Real, typename enable>
    struct polygon_with_holes_mutable_traits<eXl::AABB2DPolygon<Real>,enable> {
         template <typename iT>
         static inline eXl::AABB2DPolygon<Real>& set_holes(eXl::AABB2DPolygon<Real>& t, iT inputBegin, iT inputEnd) {
              //t.Holes().assign(inputBegin, inputEnd);
              for(;inputBegin != inputEnd;++inputBegin)
              {
                typename eXl::AABB2DPolygon<Real>::PtList hole;

                polygon_90_mutable_traits<typename eXl::AABB2DPolygon<Real>::PtList>::set_compact(hole, polygon_90_traits<decltype(*inputBegin)>::begin_compact(*inputBegin),
                  polygon_90_traits<decltype(*inputBegin)>::end_compact(*inputBegin));

                t.HolesRW().push_back(typename eXl::AABB2DPolygon<Real>::PtList());
                t.HolesRW().back().swap(hole);
              }
              t.ForceCClockwiseHoles();
              return t;
         }
    };

    //template <typename T>
    //struct is_polygon_90_set_type<eXl::Vector<T> > {
    //  typedef gtl_yes type;
    //};
    //
    //template <typename T>
    //struct is_mutable_polygon_90_set_type<eXl::Vector<T> > {
    //  typedef gtl_yes type;
    //};

    template <typename Real>
    struct polygon_90_set_traits<eXl::Vector<eXl::AABB2DPolygon<Real>> >
    {
      typedef Real coordinate_type;
      typedef typename eXl::Vector<eXl::AABB2DPolygon<Real>>::const_iterator iterator_type;
      typedef eXl::Vector<eXl::AABB2DPolygon<Real>> operator_arg_type;

      static inline iterator_type begin(const eXl::Vector<eXl::AABB2DPolygon<Real>>& polygon_set)
      {
        return polygon_set.begin();
      }

      static inline iterator_type end(const eXl::Vector<eXl::AABB2DPolygon<Real>>& polygon_set)
      {
        return polygon_set.end();
      }

      static inline orientation_2d orient(const eXl::Vector<eXl::AABB2DPolygon<Real>>&) { return HORIZONTAL; }

      static inline bool clean(const eXl::Vector<eXl::AABB2DPolygon<Real>>&) { return false; }

      static inline bool sorted(const eXl::Vector<eXl::AABB2DPolygon<Real>>&) { return false; }
    };

    template <typename Real>
    struct polygon_90_set_mutable_traits<eXl::Vector<eXl::AABB2DPolygon<Real>> >
    {
      typedef polygon_90_with_holes_concept concept_type;
      template <typename input_iterator_type>
      static inline void set(eXl::Vector<eXl::AABB2DPolygon<Real>>& polygon_set, input_iterator_type input_begin, input_iterator_type input_end, orientation_2d orient)
      {
        polygon_set.clear();
        size_t num_ele = std::distance(input_begin, input_end);
        polygon_set.reserve(num_ele);
        polygon_90_set_data<Real> ps(orient);
        ps.reserve(num_ele);
        ps.insert(input_begin, input_end, orient);
        ps.clean();
        get_90_dispatch(polygon_set, ps, orient, concept_type());
      }
    };

} }

namespace eXl
{
  class Serializer;
  template <typename PolygonType>
  Err Stream_T(PolygonType& iPoly, Serializer iStreamer);

  template <typename Real>
  struct PreciseVector
  {
    typedef eXl::Vector2<typename eXl::PreciseType<Real>::type > type;
  };

  template <typename Real>
  inline typename PreciseVector<Real>::type ToPrecise(Vector2<Real> const& iVec)
  {
    return typename PreciseVector<Real>::type(Math<Real>::ToPrecise(iVec.X()), Math<Real>::ToPrecise(iVec.Y()));
  }

  template <typename Real>
  inline bool VectorNotnullptr(Vector2<Real> const& iVec)
  {
    return iVec.Length() > Math<Real>::ZERO_TOLERANCE;
  }

  template <>
  inline bool VectorNotnullptr<int>(Vector2i const& iVec)
  {
    return iVec != Vector2i::ZERO;
  }

  template <typename Real>
  AABB2DPolygon<Real>::AABB2DPolygon(){}

  template <typename Real>
  void AABB2DPolygon<Real>::Translate(Vector2<Real> const& iTrans)
  {
    for (unsigned int i = 0; i < m_Ext.size(); ++i)
    {
      m_Ext[i] += iTrans;
    }

    for (unsigned int i = 0; i < m_Holes.size(); ++i)
    {
      for (unsigned int j = 0; j < m_Holes[i].size(); ++j)
      {
        m_Holes[i][j] += iTrans;
      }
    }
    m_AABB.m_Data[0] += iTrans;
    m_AABB.m_Data[1] += iTrans;
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Scale(Real iNum, Real iDenom)
  {
    //Polygone valide si edge confondues ??
    for (unsigned int i = 0; i < m_Ext.size(); ++i)
    {
      m_Ext[i] = (m_Ext[i] * iNum) / iDenom;
    }

    for (unsigned int i = 0; i < m_Holes.size(); ++i)
    {
      for (unsigned int j = 0; j < m_Holes[i].size(); ++j)
      {
        m_Holes[i][j] = (m_Holes[i][j] * iNum) / iDenom;
      }
    }

    boost::polygon::extents(m_AABB, *this);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::ScaleComponents(Real iNumX, Real iNumY, Real iDenomX, Real iDenomY)
  {
    for (unsigned int i = 0; i < m_Ext.size(); ++i)
    {
      m_Ext[i].X() = (m_Ext[i].X() * iNumX) / iDenomX;
      m_Ext[i].Y() = (m_Ext[i].Y() * iNumY) / iDenomY;
    }

    for (unsigned int i = 0; i < m_Holes.size(); ++i)
    {
      for (unsigned int j = 0; j < m_Holes[i].size(); ++j)
      {
        m_Holes[i][j].X() = (m_Holes[i][j].X() * iNumX) / iDenomX;
        m_Holes[i][j].Y() = (m_Holes[i][j].Y() * iNumY) / iDenomY;
      }
    }

    boost::polygon::extents(m_AABB, *this);
  }

  template <typename Real>
  AABB2DPolygon<Real>::AABB2DPolygon(Vector<Vector2<Real>> const& iPoints)
  {
    m_Ext.clear();

    if(iPoints.size()>=4)
    {
      m_Ext = iPoints;
      if((m_Ext[0]-m_Ext[1]).GetY() != 0)
      {
        Vector2<Real> tempPt = m_Ext[0];
        //memmove(&m_Points[0],&m_Points[1],m_Points.size() * sizeof(Vector2<Real>));
        m_Ext.erase(m_Ext.begin());
        m_Ext.push_back(tempPt);
      }
    }
    if(m_Ext.front() != m_Ext.back())
      m_Ext.push_back(m_Ext.front());

    boost::polygon::extents(m_AABB,*this);
  }

  template <typename Real>
  AABB2DPolygon<Real>::AABB2DPolygon(AABB2D<Real> const& iBox)
  {
    AddBox(iBox);
    if(m_Ext.front() != m_Ext.back())
      m_Ext.push_back(m_Ext.front());
  }

  template <typename Real>
  void AABB2DPolygon<Real>::RemoveUselessPoints()
  {
    _RemoveUselessPoints(m_Ext);
    for(unsigned int i = 0; i<m_Holes.size(); ++i)
    {
      _RemoveUselessPoints(m_Holes[i]);
    }
  }

  template <class Real>
  void AABB2DPolygon<Real>::_RemoveUselessPoints(Vector<Vector2<Real> >& ioPoints)
  {
    if(ioPoints.size() > 2)
    {
      if(ioPoints.back() == ioPoints.front())
      {
        ioPoints.pop_back();
      }

      for (uint32_t i = 0; i < ioPoints.size() - 1; ++i)
      {
        if (ioPoints[i] == ioPoints[i + 1])
        {
          ioPoints.erase(ioPoints.begin() + i + 1);
        }
      }

      Vector2<Real> prevPt1 = ioPoints[ioPoints.size() - 1];
      Vector2<Real> prevPt2 = ioPoints[ioPoints.size() - 2];
      for (int i = 0; i<(int)ioPoints.size(); ++i)
      {
        unsigned int prevIdx = i > 0 ? i - 1 : (i == -1 ? ioPoints.size() - 2 : ioPoints.size() - 1);
        unsigned int curIdx = i >= 0 ? i : ioPoints.size() - 1;
        Vector2<Real> curPt = ioPoints[curIdx];
        if (curPt != prevPt1 && prevPt1 != prevPt2)
        {
          typename PreciseVector<Real>::type dir1 = ToPrecise<int>(prevPt1 - prevPt2);
          typename PreciseVector<Real>::type dir2 = ToPrecise<int>(curPt - prevPt2);
          typename PreciseType<Real>::type len1 = dir1.Normalize();
          typename PreciseType<Real>::type len2 = dir2.Normalize();
          if (dir1.Dot(dir2) > (1 - eXl::Math<typename PreciseType<Real>::type>::EPSILON) && len1 < len2)
          {
            ioPoints[prevIdx] = curPt;
            ioPoints.erase(ioPoints.begin() + curIdx);
            prevPt1 = curPt;
            --i;
            continue;
          }
        }
        prevPt2 = prevPt1;
        prevPt1 = curPt;
      }

      if(ioPoints.front() != ioPoints.back())
        ioPoints.push_back(ioPoints.front());
    }
    else
    {
      ioPoints.clear();
    }
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Swap(AABB2DPolygon& iOther)
  {
    AABB2D<Real> temp = iOther.m_AABB;
    iOther.m_Ext.swap(m_Ext);
    iOther.m_Holes.swap(m_Holes);
    iOther.m_AABB = m_AABB;
    m_AABB = temp;
  }

  template <typename Real>
  Real AABB2DPolygon<Real>::Perimeter()const
  {
    return boost::polygon::perimeter(*this);
  }

  template <typename Real>
  Real AABB2DPolygon<Real>::Area() const
  {
    return boost::polygon::area(*this);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::ForceClockwise()
  {
    if(boost::polygon::winding(*this) != boost::polygon::CLOCKWISE)
    {
      std::reverse(m_Ext.begin(),m_Ext.end());
    }
  }

  template <typename Real>
  void AABB2DPolygon<Real>::ForceCClockwiseHoles()
  {
    for(unsigned int i = 0;i<m_Holes.size();++i)
    {
      if(boost::polygon::winding(m_Holes[i]) != boost::polygon::COUNTERCLOCKWISE)
      {
        std::reverse(m_Holes[i].begin(),m_Holes[i].end());
      }
    }
  }

  template <typename Real>
  void AABB2DPolygon<Real>::InternalSwap(AABB2DPolygon &iOther)const
  {
    m_Ext.swap(iOther.m_Ext);
    m_Holes.swap(iOther.m_Holes);
    std::swap(iOther.m_AABB, m_AABB);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Clear()
  {
    m_Ext.clear();
    m_Holes.clear();
    m_AABB = AABB2D<Real>();
  }

  template <typename Real>
  bool AABB2DPolygon<Real>::Empty() const
  {
    return m_Ext.empty();
  }

  template <typename Real>
  void AABB2DPolygon<Real>::GetBoxes(Vector<AABB2D<Real> >& oBoxes)const
  {
    Vector<AABB2DPolygon> meSet;
    meSet.push_back(AABB2DPolygon());
    InternalSwap(meSet[0]);

    Vector<AABB2D<Real>> vBoxes;
    boost::polygon::get_rectangles(vBoxes,meSet);

    Vector<AABB2DPolygon> swappedPoly(1, meSet[0]);
    for(auto& pt : swappedPoly[0].Border())
    {
      std::swap(pt.X(), pt.Y());
    }
    for(auto& hole : swappedPoly[0].Holes())
    {
      for(auto& pt : hole)
      {
        std::swap(pt.X(), pt.Y());
      }
    }

    Vector<AABB2D<Real>> hBoxes;
    boost::polygon::get_rectangles(hBoxes,swappedPoly);

    for(auto const& hBoxSwapped : hBoxes)
    {
      AABB2D<Real> hBox = hBoxSwapped;
      std::swap(hBox.m_Data[0].X(), hBox.m_Data[0].Y());
      std::swap(hBox.m_Data[1].X(), hBox.m_Data[1].Y());
      for(auto const& vBox : vBoxes)
      {
        AABB2D<Real> commonBox;
        commonBox.SetCommonBox(vBox, hBox);
        if(!commonBox.Empty())
        {
          oBoxes.push_back(commonBox);
        }
      }
    }

    InternalSwap(meSet[0]);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Merge(Vector<AABB2DPolygon>& ioPoly)
  {
    Vector<AABB2DPolygon> res;
    if(ioPoly.size() > 0)
    {
      res.push_back(ioPoly.front());
      for(unsigned int i = 1 ; i<ioPoly.size();++i)
      {
        boost::polygon::operators::operator|=(res,ioPoly[i]);
      }
    }
    ioPoly.swap(res);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Union(AABB2DPolygon const& iOther, AABB2DPolygon& oPoly)const
  {
    oPoly.Clear();

    Vector<AABB2DPolygon> meSet;
    Vector<AABB2DPolygon> otherSet;

    meSet.push_back(AABB2DPolygon());
    InternalSwap(meSet[0]);

    otherSet.push_back(AABB2DPolygon());
    iOther.InternalSwap(otherSet[0]);

    Vector<AABB2DPolygon> result;
    boost::polygon::assign(result,boost::polygon::operators::operator+(meSet,otherSet));

    InternalSwap(meSet[0]);
    iOther.InternalSwap(otherSet[0]);
    if(result.size() == 1)
      oPoly.Swap(result[0]);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Difference(AABB2DPolygon const& iOther, Vector<AABB2DPolygon>& oPoly)const
  {
    oPoly.clear();

    Vector<AABB2DPolygon> meSet;
    Vector<AABB2DPolygon> otherSet;

    meSet.push_back(AABB2DPolygon());
    InternalSwap(meSet[0]);

    otherSet.push_back(AABB2DPolygon());
    iOther.InternalSwap(otherSet[0]);

    boost::polygon::assign(oPoly,boost::polygon::operators::operator-(meSet,otherSet));

    InternalSwap(meSet[0]);
    iOther.InternalSwap(otherSet[0]);

  }

  template <typename Real>
  void AABB2DPolygon<Real>::Intersection(AABB2DPolygon const& iOther, Vector<AABB2DPolygon>& oPoly)const
  {
    Vector<AABB2DPolygon> meSet;
    Vector<AABB2DPolygon> otherSet;

    meSet.push_back(AABB2DPolygon());
    InternalSwap(meSet[0]);

    otherSet.push_back(AABB2DPolygon());
    iOther.InternalSwap(otherSet[0]);

    boost::polygon::assign(oPoly,boost::polygon::operators::operator&(meSet,otherSet));

    InternalSwap(meSet[0]);
    iOther.InternalSwap(otherSet[0]);
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Shrink(Real iFactor,Vector<AABB2DPolygon>& oOut)const
  {
    Vector<AABB2DPolygon> meSet;
    meSet.push_back(*this);

    boost::polygon::shrink(meSet,iFactor/2,iFactor/2,iFactor/2,iFactor/2);

    if(meSet.size()>0)
      meSet.swap(oOut);
    else
      oOut.clear();
  }

  template <typename Real>
  void AABB2DPolygon<Real>::Bloat(Real iFactor,AABB2DPolygon& oOut)const
  {
    Vector<AABB2DPolygon> meSet;
    meSet.push_back(*this);

    boost::polygon::bloat(meSet,iFactor/2,iFactor/2,iFactor/2,iFactor/2);

    if(meSet.size()>0)
      meSet[0].Swap(oOut);
    else
      oOut.Clear();
  }

  template <typename Real>
  void AABB2DPolygon<Real>::RemoveTiny(Real iFactor,Vector<AABB2DPolygon>& oOut,Vector<AABB2DPolygon>& oRemoved)const
  {
    oOut.clear();
    oRemoved.clear();
    Vector<AABB2DPolygon> meSet;
    meSet.push_back(*this);

    /*meSet = */boost::polygon::shrink(meSet,iFactor/2,iFactor/2,iFactor/2,iFactor/2);

    /*meSet = */boost::polygon::bloat(meSet,iFactor/2,iFactor/2,iFactor/2,iFactor/2);

    //meSet = boost::polygon::keep(meSet,0,ULLONG_MAX,iFactor,ULLONG_MAX,iFactor,ULLONG_MAX);

    if(meSet.empty())
    {
      oRemoved.push_back(*this);
    }
    else
    {

      Vector<AABB2DPolygon> good;
      good.push_back(AABB2DPolygon());
      InternalSwap(good.back());

      boost::polygon::assign(oRemoved,boost::polygon::operators::operator-(good,meSet));
      InternalSwap(good.back());

      //meSet.back().Swap(*this);
      oOut.swap(meSet);
    }
  }

  template <typename Real>
  int AABB2DPolygon<Real>::AddBox(AABB2D<Real> const& iBox)
  {
    if (Empty())
    {
      m_Ext.push_back(Vector2<Real>(iBox.m_Data[1].X(), iBox.m_Data[0].Y()));
      m_Ext.push_back(Vector2<Real>(iBox.m_Data[0].X(), iBox.m_Data[0].Y()));
      m_Ext.push_back(Vector2<Real>(iBox.m_Data[0].X(), iBox.m_Data[1].Y()));
      m_Ext.push_back(Vector2<Real>(iBox.m_Data[1].X(), iBox.m_Data[1].Y()));

      boost::polygon::extents(m_AABB, *this);

      return 1;
    }
    else
    {

      Vector<Vector2<Real>> temp;
      temp.push_back(Vector2<Real>(iBox.m_Data[1].X(), iBox.m_Data[0].Y()));
      temp.push_back(Vector2<Real>(iBox.m_Data[0].X(), iBox.m_Data[0].Y()));
      temp.push_back(Vector2<Real>(iBox.m_Data[0].X(), iBox.m_Data[1].Y()));
      temp.push_back(Vector2<Real>(iBox.m_Data[1].X(), iBox.m_Data[1].Y()));

      Vector<AABB2DPolygon> meSet;
      Vector<AABB2DPolygon> boxSet;

      meSet.push_back(AABB2DPolygon());
      InternalSwap(meSet[0]);

      boxSet.push_back(AABB2DPolygon());
      boxSet[0].m_Ext.swap(temp);

      boost::polygon::operators::operator|=(boxSet, meSet);
      //meSet.clear();
      //temp.get_polygons(meSet);
      if (boxSet.size() == 1)
      {
        InternalSwap(boxSet[0]);
        boost::polygon::extents(m_AABB, *this);
        return 1;
      }
      else
      {
        InternalSwap(meSet[0]);
        return 0;
      }

      return 1;
    }
  }
}

#include <core/stream/serializer.hpp>

namespace eXl
{

  template <typename PolygonType>
  Err Stream_T(PolygonType& iPoly, Serializer iStreamer)
  {
    iStreamer.BeginStruct();
    iStreamer.PushKey("Border");
    iStreamer &= iPoly.Border();
    iStreamer.PopKey();
    iStreamer.PushKey("Holes");
    iStreamer &= iPoly.Holes();
    iStreamer.PopKey();
    iStreamer.EndStruct();

    RETURN_SUCCESS;
  }

  template <typename Real>
  Err AABB2DPolygon<Real>::Stream(Streamer& iStreamer) const
  {
    Serializer serializer(iStreamer);
    return Stream_T(*this, serializer);
  }

  template <typename Real>
  Err AABB2DPolygon<Real>::Unstream(Unstreamer& iStreamer)
  {
    Serializer serializer(iStreamer);
    Err res = Stream_T(*this, serializer);
    if (res)
    {
      boost::polygon::extents(m_AABB, *this);
    }
    return res;
  }
}


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.
#include <poplibs_support/LogArithmetic.hpp>
#include <poplibs_support/TestDevice.hpp>
#include <poplibs_test/CTCInference.hpp>
#include <poplibs_test/Embedding.hpp>
#include <poplibs_test/MatrixTransforms.hpp>
#include <poplibs_test/Util.hpp>
#include <popnn/CTCInference.hpp>
#include <popnn/codelets.hpp>
#include <popops/codelets.hpp>
#include <poputil/VertexTemplates.hpp>
#include <poputil/exceptions.hpp>

#include <poplar/Engine.hpp>
#include <poplar/Graph.hpp>
#include <poplar/IPUModel.hpp>

#include <boost/multi_array.hpp>
#include <boost/optional/optional_io.hpp>
#include <boost/program_options.hpp>
#include <boost/test/tools/floating_point_comparison.hpp>

#include <fstream>
#include <iomanip>
#include <random>

namespace po = boost::program_options;

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_test;
using namespace poplibs_test::ctc;
using namespace poplibs_test::util;
using namespace poplibs_support;
using namespace poputil;

// Default tolerances used in tests
#define FLOAT_REL_TOL 0.01
#define HALF_REL_TOL 0.1
#define FLOAT_ABS_TOL 1e-6
#define HALF_ABS_TOL 1e-5

template <typename FPType> struct InputSequence {
  boost::multi_array<FPType, 2> input;
  unsigned inputLength;
  bool isLogits;
};

std::vector<std::vector<std::pair<std::vector<unsigned>, double>>>
beamSearchIPU(const std::vector<InputSequence<double>> &inputs,
              std::size_t maxTime, std::size_t batchSize, unsigned blankClass,
              std::size_t numClasses, unsigned beamwidth, unsigned topPaths,
              Type inType, Type outType, OptionFlags planOpts,
              const DeviceType &deviceType, boost::optional<unsigned> tiles,
              bool profile, boost::optional<std::string> &profileDir) {

  auto device = createTestDevice(deviceType, 1, tiles);
  const auto &target = device.getTarget();
  Graph graph(target);
  popnn::addCodelets(graph);
  popops::addCodelets(graph);

  // Create the inputs to the beam search function
  const popnn::ctc::Plan plan = popnn::ctc_infer::plan(
      graph, inType, batchSize, maxTime, numClasses, beamwidth, planOpts);

  auto data = popnn::ctc_infer::createDataInput(
      graph, inType, batchSize, maxTime, numClasses, plan, "DataInput");

  auto dataLengths = graph.addVariable(UNSIGNED_INT, {batchSize});
  graph.setTileMapping(dataLengths, 0);

  // Write the inputs
  Sequence uploadProg, downloadProg;
  std::vector<std::pair<std::string, char *>> tmap;
  std::unique_ptr<char[]> rawDataLengths;
  std::vector<std::unique_ptr<char[]>> rawData(batchSize);
  rawDataLengths = allocateHostMemoryForTensor(
      dataLengths, "dataLengths", graph, uploadProg, downloadProg, tmap);

  for (unsigned i = 0; i < batchSize; i++) {
    rawData[i] = allocateHostMemoryForTensor(data.slice(i, i + 1, 1),
                                             "data_" + std::to_string(i), graph,
                                             uploadProg, downloadProg, tmap);
    copy(target, inputs[i].input, inType, rawData[i].get());
  }
  std::vector<unsigned> initDataLengths(batchSize);
  for (unsigned i = 0; i < batchSize; i++) {
    initDataLengths[i] = inputs[i].inputLength;
  }
  copy(target, initDataLengths, dataLengths.elementType(),
       rawDataLengths.get());

  // Call beam search function
  Sequence prog;
  const auto [probsResult, lengthsResult, labelResult] = [&]() {
    if (inputs[0].isLogits) {
      return popnn::ctc_infer::beamSearchDecoderLogits(
          graph, data, dataLengths, prog, blankClass, beamwidth, topPaths, plan,
          "BeamSearchLogits");
    } else {
      return popnn::ctc_infer::beamSearchDecoderLogProbabilities(
          graph, data, dataLengths, prog, blankClass, beamwidth, topPaths, plan,
          "BeamSearchLogProbabilities");
    }
  }();
  // Check output dimensions
  if (probsResult.rank() != 2 || lengthsResult.rank() != 2 ||
      labelResult.rank() != 3) {
    throw poputil::poplibs_error("Result tensor has the wrong rank");
  }
  if (probsResult.dim(0) != batchSize || probsResult.dim(1) != topPaths) {
    throw poputil::poplibs_error(
        "Result tensor probsResult has the wrong shape");
  }
  if (lengthsResult.dim(0) != batchSize || lengthsResult.dim(1) != topPaths) {
    throw poputil::poplibs_error(
        "Result tensor lengthsResult has the wrong shape");
  }
  if (labelResult.dim(0) != batchSize || labelResult.dim(1) != topPaths ||
      labelResult.dim(2) != maxTime) {
    throw poputil::poplibs_error(
        "Result tensor labelResult has the wrong shape");
  }

  // Create handles for reading the result
  std::vector<std::unique_ptr<char[]>> rawLabelResult(batchSize);
  std::vector<std::unique_ptr<char[]>> rawLabelsLength(batchSize);
  std::vector<std::unique_ptr<char[]>> rawLabelsProbs(batchSize);
  for (unsigned i = 0; i < batchSize; i++) {
    rawLabelResult[i] = allocateHostMemoryForTensor(
        labelResult.slice(i, i + 1, 0), "result_label_" + std::to_string(i),
        graph, uploadProg, downloadProg, tmap);

    rawLabelsLength[i] =
        allocateHostMemoryForTensor(lengthsResult.slice(i, i + 1, 0),
                                    "result_label_length_" + std::to_string(i),
                                    graph, uploadProg, downloadProg, tmap);

    rawLabelsProbs[i] =
        allocateHostMemoryForTensor(probsResult.slice(i, i + 1, 0),
                                    "result_label_probs_" + std::to_string(i),
                                    graph, uploadProg, downloadProg, tmap);
  }

  // Run it
  OptionFlags engineOptions;
  if (profile || profileDir) {
    engineOptions.set("debug.instrumentCompute", "true");
    engineOptions.set("debug.instrumentControlFlow", "true");
    if (profileDir) {
      engineOptions.set("autoReport.all", "true");
      engineOptions.set("autoReport.directory", *profileDir);
    }
  }
  auto s = Sequence({uploadProg, prog, downloadProg});
  Engine engine(graph, s, engineOptions);
  attachStreams(engine, tmap);
  device.bind([&](const Device &d) {
    engine.load(d);
    engine.run();
  });

  // Fetch the result
  std::vector<std::vector<std::pair<std::vector<unsigned>, double>>> results;

  for (unsigned i = 0; i < batchSize; i++) {
    // Initially copy from the IPU into constant sized containers
    std::vector<double> labelProbs(topPaths);
    std::vector<unsigned> labelLengths(topPaths);
    boost::multi_array<unsigned, 2> decodedLabels;
    decodedLabels.resize(boost::extents[topPaths][maxTime]);

    copy(target, outType, rawLabelsProbs[i].get(), labelProbs);
    copy(target, UNSIGNED_INT, rawLabelsLength[i].get(), labelLengths);
    copy(target, UNSIGNED_INT, rawLabelResult[i].get(), decodedLabels);

    // Then put into the output result format
    std::vector<std::pair<std::vector<unsigned>, double>> result(topPaths);
    for (unsigned j = 0; j < topPaths; j++) {
      auto &label = result[j].first;
      auto &probability = result[j].second;
      probability = labelProbs[j];
      label.resize(labelLengths[j]);
      std::copy(decodedLabels[j].begin(),
                decodedLabels[j].begin() + label.size(), label.begin());
    }
    results.push_back(result);
  }

  if (profile && deviceType != DeviceType::Cpu) {
    engine.printProfileSummary(std::cout,
                               OptionFlags{{"showExecutionSteps", "true"}});
  }
  return results;
}

int main(int argc, char **argv) {
  // Default input parameters.
  DeviceType deviceType = DeviceType::IpuModel2;
  boost::optional<unsigned> tiles = boost::none;

  boost::optional<unsigned> minRandomTime = boost::none;
  boost::optional<unsigned> fixedTime = boost::none;
  unsigned maxTime = 15;
  boost::optional<unsigned> minRandomLabelLength = boost::none;
  boost::optional<unsigned> fixedLabelLength = boost::none;
  unsigned maxLabelLength = 5;

  unsigned numClasses = 4;
  unsigned blankClass = 0;
  unsigned batchSize = 1;
  unsigned beamwidth = 3;
  unsigned topPaths = 2;

  Type inType = FLOAT;
  Type partialsType = FLOAT;
  Type outType = FLOAT;

  bool isLogits = false;
  unsigned seed = 42;
  unsigned verbosityLevel = 0;

  boost::optional<std::string> planConstraints;
  boost::optional<std::string> options;

  boost::optional<std::string> profileDir;

  po::options_description desc("Options");
  // clang-format off
  desc.add_options()
    ("help", "Produce help message")
    ("device-type",
     po::value<DeviceType>(&deviceType)->default_value(deviceType),
     deviceTypeHelp)
    ("tiles-per-ipu",po::value<boost::optional<unsigned>>(&tiles),
      "Number of tiles per IPU")

    ("label-length", po::value(&fixedLabelLength),
     "If set, forces every superimposed label to be of length `label-length`")
    ("min-label-length",
      po::value(&minRandomLabelLength),
     "If set, minimum randomly generated superimposed label length")
    ("max-label-length",
      po::value(&maxLabelLength)->default_value(maxLabelLength),
     "Max superimposed label length")
    ("time", po::value(&fixedTime),
     "If set, forces every sequence to be of length `time`")
    ("min-time", po::value(&minRandomTime),
     "If set, minimum randomly generated time length")
    ("max-time", po::value(&maxTime)->default_value(maxTime),
     "Max test length (time)")

    ("num-classes", po::value(&numClasses)->default_value(numClasses),
     "Classes in the alphabet including blank")
    ("blank-class", po::value(&blankClass)->default_value(blankClass),
     "Index of the blank symbol. Range 0 to (num-classes-1)")
    ("batch", po::value(&batchSize)->default_value(batchSize),
     "Batch size")
    ("beamwidth", po::value(&beamwidth)->default_value(beamwidth),
     "Number of the beams to persist at each timestep")
    ("top-paths", po::value(&topPaths)->default_value(topPaths),
     "Final number of beams to return from the operation")

    ("in-type", po::value(&inType)->default_value(inType),
     "Input data type")
    ("partials-type", po::value(&partialsType)->default_value(partialsType),
     "Partials data type")
    ("out-type", po::value(&outType)->default_value(outType),
     "Output data type")

    ("logit-inputs", po::value(&isLogits)->default_value(isLogits),
     "pass logit inputs to ctc infer api, otherwise convert to logProbs prior")
    ("seed", po::value(&seed)->default_value(seed),
     "Seed used for random input generation")

    ("plan-constraints", po::value(&planConstraints),
     "JSON constraints for planner, e.g. {\"parallel\": {\"batch\": 1}}")

    ("options", po::value(&options),
     "JSON options, e.g. {\"sortMethod\":\"rank\"}}\n"
     "                   {\"sortStageGroups\":\"16,4,1\"")

    ("profile", "Show profile report")
    ("profile-dir",
     po::value<decltype(profileDir)>(&profileDir)
      ->default_value(boost::none),
     "Write profile files to the specified directory.")

    ("disable-always-satisfiable-error", "Disable the check when validating time"
    " and the imposed labelLength before generating random labels to superimpose"
    " onto the input. This check ensures that the imposed label is always"
    " representable for given t and labelLength."
    "\nThe length of t required to represent a given label depends on the number"
    " of duplicate classes in the label, this check assumes the worst case where"
    " every class is a duplicate."
    "\nSpecifically:"
    "\n  2 * t - 1 >= labelLength")
    ("ignore-data", "Ignore data, to check execution time")
    ("plan-only", "Only plan the requested passes, don't build or run a graph")
    ("verbosity-level", po::value(&verbosityLevel)->default_value(verbosityLevel),
     "Level of verbosity for debug printouts");
  // clang-format on

  po::variables_map vm;
  try {
    po::store(po::parse_command_line(argc, argv, desc), vm);
    if (vm.count("help")) {
      std::cout << desc << "\n\n";
      return 1;
    }

  } catch (std::exception &e) {
    std::cerr << "error parsing command line: " << e.what() << "\n";
    return 1;
  }

  const bool profile = vm.count("profile");
  const bool ignoreData = vm.count("ignore-data");
  const bool planOnly = vm.count("plan-only");
  const bool disableAlwaysSatisfiableError =
      vm.count("disable-always-satisfiable-error");

  // Needed to set default arguments.
  po::notify(vm);

  if (blankClass >= numClasses) {
    throw poputil::poplibs_error("The blank class must be in the range 0 to "
                                 "(number of classes - 1)");
  }
  if (topPaths > beamwidth) {
    throw poputil::poplibs_error("top-paths must be <= beamwidth");
  }

  if (!minRandomTime && !fixedTime) {
    fixedTime = maxTime;
  }
  if (!minRandomLabelLength && !fixedLabelLength) {
    fixedLabelLength = maxLabelLength;
  }
  ctc::validateTimeAndLabelBounds(minRandomTime, fixedTime, maxTime,
                                  minRandomLabelLength, fixedLabelLength,
                                  maxLabelLength);

  poplar::OptionFlags planOpts;
  if (options) {
    poplar::readJSON(*options, planOpts);
  }
  if (planConstraints) {
    planOpts.set("planConstraints", *planConstraints);
  }
  planOpts.set("partialsType", partialsType.toString());

  if (planOnly) {
    auto device = createTestDevice(deviceType, 1, tiles);
    const auto &target = device.getTarget();
    Graph graph(target);

    const auto plan = popnn::ctc_infer::plan(graph, inType, batchSize, maxTime,
                                             numClasses, beamwidth, planOpts);

    std::cout << plan << std::endl;
    std::cout << "No test run - plan only" << std::endl;
    return 0;
  }

  RandomUtil rand{seed};

  std::vector<InputSequence<double>> tests;
  std::vector<std::vector<std::pair<std::vector<unsigned>, double>>> references;
  for (unsigned i = 0; i < batchSize; i++) {
    const auto [t, labelLength] = getRandomSize(
        minRandomTime, fixedTime, maxTime, minRandomLabelLength,
        fixedLabelLength, maxLabelLength, disableAlwaysSatisfiableError, rand);
    auto [input, label] = getRandomTestInput<double>(
        t, maxTime, labelLength, numClasses, blankClass, isLogits, rand);

    tests.push_back({input, t, isLogits});

    if (verbosityLevel == 1) {
      std::cout << "\nBatch:" << i << " Time:" << t
                << " Imposed label length:" << label.size() << "\n\n";
      if (tests[i].isLogits) {
        std::cout << "Logits in\n";
        printInput(tests[i].input, blankClass);
        std::cout << "Log Softmax Logits in\n";
        printInput(log::log(matrix::transpose(
                       log::softMax(matrix::transpose(tests[i].input)))),
                   blankClass);
      } else {
        std::cout << "Log Softmax in\n";
        printInput(tests[i].input, blankClass);
      }
      std::cout << "\n";
    }

    if (!ignoreData) {
      const auto input = tests[i].input.resize(
          boost::extents[tests[i].inputLength][numClasses]);
      if (isLogits) {
        references.push_back(ctc::infer<double>(
            log::log(log::softMax(matrix::transpose(input))), blankClass,
            beamwidth, topPaths, true, verbosityLevel == 2));
      } else {
        references.push_back(ctc::infer<double>(matrix::transpose(input),
                                                blankClass, beamwidth, topPaths,
                                                true, verbosityLevel == 2));
      }
      if (verbosityLevel == 1) {
        std::cout << "Reference output (batch " << i << "):\n";
        printBeams(references[i], blankClass);
      }
    }
  }

  const auto outputs = beamSearchIPU(
      tests, maxTime, batchSize, blankClass, numClasses, beamwidth, topPaths,
      inType, outType, planOpts, deviceType, tiles, profile, profileDir);

  for (unsigned i = 0; i < batchSize; i++) {
    if (verbosityLevel == 1) {
      std::cout << "Result output (batch " << i << "):\n";
      printBeams(outputs[i], blankClass);
    }
  }

  const double relativeTolerance =
      outType == FLOAT ? FLOAT_REL_TOL : HALF_REL_TOL;
  bool success = true;
  if (!ignoreData) {
    for (unsigned i = 0; i < batchSize; i++) {
      for (unsigned j = 0; j < topPaths; j++) {
        bool lengthMatch =
            outputs[i][j].first.size() == references[i][j].first.size();
        bool pathSuccess = checkIsClose(
            outputs[i][j].second, references[i][j].second, relativeTolerance);
        if (lengthMatch) {
          pathSuccess =
              pathSuccess && checkEqual("Paths", outputs[i][j].first.data(),
                                        {outputs[i][j].first.size()},
                                        references[i][j].first.data(),
                                        references[i][j].first.size());
        }
        if (!pathSuccess || !lengthMatch) {
          if (verbosityLevel == 0) {
            std::cout << "Mismatch in data:";
            std::cout << "Reference output (batch " << i << "):\n";
            printBeams(references[i], blankClass);
            std::cout << "Result output (batch " << i << "):\n";
            printBeams(outputs[i], blankClass);
          }
          success = false;
        }
      }
    }
  }
  if (!success) {
    std::cerr << "Data mismatch\n";
  }
  return !success;
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          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 BOOST_SIMD_ARITHMETIC_FUNCTIONS_REMQUO_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_REMQUO_HPP_INCLUDED
#include <boost/simd/include/functor.hpp>
#include <boost/dispatch/include/functor.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
   /*!
      @brief  remquo generic tag

      Represents the remquo function in generic contexts.

      @par Models:
      Hierarchy
    **/
    struct remquo_ : ext::elementwise_<remquo_>
    {
      /// @brief Parent hierarchy
      typedef ext::elementwise_<remquo_> parent;
      template<class... Args>
      static BOOST_FORCEINLINE BOOST_AUTO_DECLTYPE dispatch(Args&&... args)
      BOOST_AUTO_DECLTYPE_BODY( dispatching_remquo_( ext::adl_helper(), static_cast<Args&&>(args)... ) )
    };
  }
  namespace ext
  {
   template<class Site, class... Ts>
   BOOST_FORCEINLINE generic_dispatcher<tag::remquo_, Site> dispatching_remquo_(adl_helper, boost::dispatch::meta::unknown_<Site>, boost::dispatch::meta::unknown_<Ts>...)
   {
     return generic_dispatcher<tag::remquo_, Site>();
   }
   template<class... Args>
   struct impl_remquo_;
  }

  /*!
    @brief Remainder and part of quotient

    remquo computes the remainder and part of the quotient upon division of
    @c a0 by @c a1. By design, the value of the remainder is the same as that
    computed by the remainder function. The value of the computed quotient has
    the sign of @c a0/a1 and agrees with the actual quotient in at least the low
    order 3 bits.

    @param a0 Dividend value
    @param a1 Divisor value

    @return A Fusion Sequence containing the two remainder and partial quotient
    of @c a0/a1
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::remquo_, remquo, 2)

  /*!
    @brief Remainder and part of quotient

    remquo computes the remainder and part of the quotient upon division of
    @c a0 by @c a1. By design, the value of the remainder is the same as that
    computed by the remainder function. The value of the computed quotient has
    the sign of @c a0/a1 and agrees with the actual quotient in at least the low
    order 3 bits.

    @param a0 Dividend value
    @param a1 Divisor value
    @param a2 L-Value that will receive the partial quotient of @c a0

    @return The remainder of @c a0/a1
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION_TPL( tag::remquo_, remquo
                                            , (A0 const&)(A1 const&)(A2&)
                                            , 3
                                            )

  /*!
    @brief Remainder and part of quotient

    remquo computes the remainder and part of the quotient upon division of
    @c a0 by @c a1. By design, the value of the remainder is the same as that
    computed by the remainder function. The value of the computed quotient has
    the sign of @c a0/a1 and agrees with the actual quotient in at least the low
    order 3 bits.

    @param a0 Dividend value
    @param a1 Divisor value
    @param a2 L-Value that will receive the remainder of @c a0
    @param a3 L-Value that will receive the partial quotient of @c a0
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION_TPL( tag::remquo_, remquo
                                            , (A0 const&)(A1 const&)(A2&)(A3&)
                                            , 4
                                            )
} }

#endif


// Boost.Geometry

// Copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to 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)

#ifndef BOOST_GEOMETRY_SRS_PROJECTIONS_PAR4_HPP
#define BOOST_GEOMETRY_SRS_PROJECTIONS_PAR4_HPP

#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/srs/sphere.hpp>
#include <boost/geometry/srs/spheroid.hpp>

#include <boost/mpl/assert.hpp>
#include <boost/mpl/if.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/is_void.hpp>

namespace boost { namespace geometry { namespace srs { namespace par4
{

// proj
// defined in projections' implementation files

// ellps
struct MERIT {};
struct SGS85 {};
struct GRS80 {};
struct IAU76 {};
struct airy {};
struct APL4_9 {};
struct NWL9D {};
struct mod_airy {};
struct andrae {};
struct aust_SA {};
struct GRS67 {};
struct bessel {};
struct bess_nam {};
struct clrk66 {};
struct clrk80 {};
struct clrk80ign {};
struct CPM {};
struct delmbr {};
struct engelis {};
struct evrst30 {};
struct evrst48 {};
struct evrst56 {};
struct evrst69 {};
struct evrstSS {};
struct fschr60 {};
struct fschr60m {};
struct fschr68 {};
struct helmert {};
struct hough {};
struct intl {};
struct krass {};
struct kaula {};
struct lerch {};
struct mprts {};
struct new_intl {};
struct plessis {};
struct SEasia {};
struct walbeck {};
struct WGS60 {};
struct WGS66 {};
struct WGS72 {};
struct WGS84 {};
struct sphere {};

// datum
//struct WGS84 {}; // already defined above
struct GGRS87 {};
struct NAD83 {};
struct NAD27 {};
struct potsdam {};
struct carthage {};
struct hermannskogel {};
struct ire65 {};
struct nzgd49 {};
struct OSGB36 {};

template <typename P>
struct proj
{
    typedef P type;
};

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template
<
    typename E,
    typename Tag = typename geometry::tag<E>::type
>
struct ellps_impl
    : private E // empty base optimization
{
    typedef E type;

    ellps_impl() : E() {}
    explicit ellps_impl(E const& e) : E(e) {}

    E const& model() const { return *this; }
};

template <typename E>
struct ellps_impl<E, void>
{
    typedef E type;
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

template<typename E>
struct ellps
    : par4::detail::ellps_impl<E>
{
    ellps() {}
    explicit ellps(E const& e)
        : par4::detail::ellps_impl<E>(e)
    {}
};

template <typename D>
struct datum
{
    typedef D type;
};

template <typename P>
struct o_proj
{
    typedef P type;
};

struct guam {};

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

inline double b_from_a_rf(double a, double rf)
{
    return a * (1.0 - 1.0 / rf);
}

template
<
    typename Ellps,
    typename Tag = typename geometry::tag<typename Ellps::type>::type
>
struct ellps_traits
{
    typedef typename Ellps::type model_type;
    static model_type model(Ellps const& e) { return e.model(); }
};

#define BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B(NAME, A, B) \
template <> \
struct ellps_traits<ellps<par4::NAME>, void> \
{ \
    typedef srs::spheroid<double> model_type; \
    static model_type model(ellps<par4::NAME> const&) { return model_type(A, B); } \
};

#define BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(NAME, A, RF) \
template <> \
struct ellps_traits<ellps<par4::NAME>, void> \
{ \
    typedef srs::spheroid<double> model_type; \
    static model_type model(ellps<par4::NAME> const&) { return model_type(A, b_from_a_rf(A, RF)); } \
};

#define BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_SPHERE(NAME, R) \
template <> \
struct ellps_traits<ellps<par4::NAME>, void> \
{ \
    typedef srs::sphere<double> model_type; \
    static model_type model(ellps<par4::NAME> const&) { return model_type(R); } \
};

BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(MERIT, 6378137.0, 298.257)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(SGS85, 6378136.0, 298.257)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(GRS80, 6378137.0, 298.257222101)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(IAU76, 6378140.0, 298.257)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (airy, 6377563.396, 6356256.910)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(APL4_9, 6378137.0, 298.25)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(NWL9D, 6378145.0, 298.25)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (mod_airy, 6377340.189, 6356034.446)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(andrae, 6377104.43, 300.0)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(aust_SA, 6378160.0, 298.25)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(GRS67, 6378160.0, 298.2471674270)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(bessel, 6377397.155, 299.1528128)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(bess_nam, 6377483.865, 299.1528128)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (clrk66, 6378206.4, 6356583.8)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(clrk80, 6378249.145, 293.4663)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(clrk80ign, 6378249.2, 293.4660212936269)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(CPM, 6375738.7, 334.29)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(delmbr, 6376428.0, 311.5)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(engelis, 6378136.05, 298.2566)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(evrst30, 6377276.345, 300.8017)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(evrst48, 6377304.063, 300.8017)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(evrst56, 6377301.243, 300.8017)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(evrst69, 6377295.664, 300.8017)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(evrstSS, 6377298.556, 300.8017)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(fschr60, 6378166.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(fschr60m, 6378155.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(fschr68, 6378150.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(helmert, 6378200.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(hough, 6378270.0, 297.0)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(intl, 6378388.0, 297.0)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(krass, 6378245.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(kaula, 6378163.0, 298.24)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(lerch, 6378139.0, 298.257)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(mprts, 6397300.0, 191.0)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (new_intl, 6378157.5, 6356772.2)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (plessis, 6376523.0, 6355863.0)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (SEasia, 6378155.0, 6356773.3205)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_B (walbeck, 6376896.0, 6355834.8467)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(WGS60, 6378165.0, 298.3)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(WGS66, 6378145.0, 298.25)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(WGS72, 6378135.0, 298.26)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_ELLPS_A_RF(WGS84, 6378137.0, 298.257223563)
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_SPHERE    (sphere, 6370997.0)

template <typename D>
struct datum_traits
{
    typedef void ellps_type;
    static std::string id() { return ""; }
    static std::string def_n() { return ""; }
    static std::string def_v() { return ""; }
};

#define BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(NAME, ID, ELLPS, DEF_N, DEF_V) \
template <> \
struct datum_traits< datum<par4::NAME> > \
{ \
    typedef par4::ellps<par4::ELLPS> ellps_type; \
    static std::string id() { return ID; } \
    static std::string def_n() { return DEF_N; } \
    static std::string def_v() { return DEF_V; } \
};

BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(WGS84, "WGS84", WGS84, "towgs84", "0,0,0")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(GGRS87, "GGRS87", GRS80, "towgs84", "-199.87,74.79,246.62")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(NAD83, "NAD83", GRS80, "towgs84", "0,0,0")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(NAD27, "NAD27", clrk66, "nadgrids", "@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(potsdam, "potsdam", bessel, "towgs84", "598.1,73.7,418.2,0.202,0.045,-2.455,6.7")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(carthage, "carthage", clrk80ign, "towgs84", "-263.0,6.0,431.0")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(hermannskogel, "hermannskogel", bessel, "towgs84", "577.326,90.129,463.919,5.137,1.474,5.297,2.4232")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(ire65, "ire65", mod_airy, "towgs84", "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(nzgd49, "nzgd49", intl, "towgs84", "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993")
BOOST_GEOMETRY_PROJECTIONS_DETAIL_REGISTER_DATUM(OSGB36, "OSGB36", airy, "towgs84", "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894")

template
<
    typename Tuple,
    template <typename> class IsSamePred,
    int I = 0,
    int N = boost::tuples::length<Tuple>::value
>
struct tuples_find_index_if
    : boost::mpl::if_c
        <
            IsSamePred<typename boost::tuples::element<I, Tuple>::type>::value,
            boost::integral_constant<int, I>,
            typename tuples_find_index_if<Tuple, IsSamePred, I+1, N>::type
        >::type
{};

template
<
    typename Tuple,
    template <typename> class IsSamePred,
    int N
>
struct tuples_find_index_if<Tuple, IsSamePred, N, N>
    : boost::integral_constant<int, N>
{};

template
<
    typename Tuple,
    template <typename> class IsSamePred,
    int I = tuples_find_index_if<Tuple, IsSamePred>::value,
    int N = boost::tuples::length<Tuple>::value
>
struct tuples_find_if
    : boost::tuples::element<I, Tuple>
{};

template
<
    typename Tuple,
    template <typename> class IsSamePred,
    int N
>
struct tuples_find_if<Tuple, IsSamePred, N, N>
{
    typedef void type;
};

/*template <typename Param>
struct is_param
{
    template <typename T, int D = 0>
    struct is_same_impl : boost::false_type {};
    template <int D>
    struct is_same_impl<Param, D> : boost::true_type {};

    template <typename T>
    struct is_same : is_same_impl<T> {};
};

template <template <typename> class Param>
struct is_param_t
{
    template <typename T>
    struct is_same : boost::false_type {};
    template <typename T>
    struct is_same<Param<T> > : boost::true_type {};
};*/

// NOTE: The following metafunctions are implemented one for each parameter
// because mingw-gcc-4.1.2 is unable to compile a solution based on template
// template parameter and member struct template partial specialization
// (see above).

/*template <typename T>
struct is_proj : boost::false_type {};
template <typename T>
struct is_proj<proj<T> > : boost::true_type {};

template <typename T>
struct is_ellps : boost::false_type {};
template <typename T>
struct is_ellps<ellps<T> > : boost::true_type {};

template <typename T>
struct is_datum : boost::false_type {};
template <typename T>
struct is_datum<datum<T> > : boost::true_type {};

template <typename T>
struct is_o_proj : boost::false_type {};
template <typename T>
struct is_o_proj<o_proj<T> > : boost::true_type {};

template <typename T>
struct is_guam : boost::false_type {};
template <>
struct is_guam<guam> : boost::true_type {};*/

// NOTE: The following implementation seems to work as well.

// TEST

template <typename T, template <typename> class Param>
struct is_same_t : boost::false_type {};
template <typename T, template <typename> class Param>
struct is_same_t<Param<T>, Param> : boost::true_type {};

template <typename Param>
struct is_param
{
    template <typename T>
    struct pred : boost::is_same<T, Param> {};
};

template <template <typename> class Param>
struct is_param_t
{
    template <typename T>
    struct pred : is_same_t<T, Param> {};
};

// pick proj static name

template <typename Tuple>
struct pick_proj_tag
{
    typedef typename tuples_find_if
        <
            Tuple,
            // is_proj
            is_param_t<proj>::pred
        >::type proj_type;

    static const bool is_non_void = ! boost::is_void<proj_type>::value;

    BOOST_MPL_ASSERT_MSG((is_non_void), PROJECTION_NOT_NAMED, (Tuple));

    typedef typename proj_type::type type;
};

template <typename Ellps, typename Datum, int EllpsIndex>
struct pick_ellps_impl
{
    typedef Ellps type;
    typedef typename ellps_traits<Ellps>::model_type model_type;
    template <typename Tuple>
    static model_type model(Tuple const& tup)
    {
        return ellps_traits<Ellps>::model(boost::get<EllpsIndex>(tup));
    }
};

template <typename Ellps, int EllpsIndex>
struct pick_ellps_impl<Ellps, void, EllpsIndex>
{
    typedef Ellps type;
    typedef typename ellps_traits<Ellps>::model_type model_type;
    template <typename Tuple>
    static model_type model(Tuple const& tup)
    {
        return ellps_traits<Ellps>::model(boost::get<EllpsIndex>(tup));
    }
};

template <typename Datum, int EllpsIndex>
struct pick_ellps_impl<void, Datum, EllpsIndex>
{
    typedef typename datum_traits<Datum>::ellps_type type;

    static const bool is_datum_known = ! boost::is_void<type>::value;
    BOOST_MPL_ASSERT_MSG((is_datum_known), UNKNOWN_DATUM, (types<Datum>));

    typedef typename ellps_traits<type>::model_type model_type;
    template <typename Tuple>
    static model_type model(Tuple const& )
    {
        return ellps_traits<type>::model(type());
    }
};

template <int EllpsIndex>
struct pick_ellps_impl<void, void, EllpsIndex>
{
    // default ellipsoid
    typedef ellps<WGS84> type;
    typedef typename ellps_traits<type>::model_type model_type;
    template <typename Tuple>
    static model_type model(Tuple const& )
    {
        return ellps_traits<type>::model(type());
    }
};

// Pick spheroid/sphere model from ellps or datum
// mimic pj_init() calling pj_datum_set() and pj_ell_set()
template <typename Tuple>
struct pick_ellps
    : pick_ellps_impl
        <
            typename tuples_find_if<Tuple, /*is_ellps*/is_param_t<ellps>::pred>::type,
            typename tuples_find_if<Tuple, /*is_datum*/is_param_t<datum>::pred>::type,
            tuples_find_index_if<Tuple, /*is_ellps*/is_param_t<ellps>::pred>::value
        >
{};

template <typename Tuple>
struct pick_o_proj_tag
{
    typedef typename tuples_find_if
        <
            Tuple,
            //is_o_proj
            is_param_t<o_proj>::pred
        >::type proj_type;

    static const bool is_non_void = ! boost::is_void<proj_type>::value;

    BOOST_MPL_ASSERT_MSG((is_non_void), NO_O_PROJ_PARAMETER, (Tuple));

    typedef typename proj_type::type type;
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

}}}} // namespace boost::geometry::srs::par4

#endif // BOOST_GEOMETRY_SRS_PROJECTIONS_PAR4_HPP