#include "segmatch/recognizers/partitioned_geometric_consistency_recognizer.hpp"

#include <algorithm>
#include <vector>

#include <boost/graph/adjacency_list.hpp>
#include <glog/logging.h>
#include <laser_slam/benchmarker.hpp>

#include "segmatch/common.hpp"
#include "segmatch/recognizers/graph_utilities.hpp"
#include "segmatch/recognizers/matches_partitioner.hpp"

namespace segmatch {

PartitionedGeometricConsistencyRecognizer::PartitionedGeometricConsistencyRecognizer(
    const GeometricConsistencyParams& params, float max_model_radius) noexcept
  : GraphBasedGeometricConsistencyRecognizer(params), partition_size_(max_model_radius * 2.0f) {
}

inline size_t PartitionedGeometricConsistencyRecognizer::findAndAddInPartitionConsistencies(
    const PairwiseMatches& predicted_matches, const std::vector<size_t>& partition_indices,
    ConsistencyGraph& consistency_graph) const {
  size_t num_consistency_tests = 0u;
  for (size_t i2 = 0u; i2 < partition_indices.size(); ++i2) {
    size_t i = partition_indices[i2];
    const Eigen::Vector3f& scene_point_i = predicted_matches[i].centroids_.first.getVector3fMap();
    const Eigen::Vector3f& model_point_i = predicted_matches[i].centroids_.second.getVector3fMap();
    for (size_t j2 = i2 + 1u; j2 < partition_indices.size(); ++j2) {
      size_t j = partition_indices[j2];
      const Eigen::Vector3f& scene_point_j =
          predicted_matches[j].centroids_.first.getVector3fMap();
      const Eigen::Vector3f& model_point_j =
          predicted_matches[j].centroids_.second.getVector3fMap();

      // Compute the difference between the distances and add edge to the consistency graph if the
      // matches are consistent.
      ++num_consistency_tests;
      float dist_scene = (scene_point_i - scene_point_j).norm();
      float dist_model = (model_point_i - model_point_j).norm();
      float distance = fabs(dist_scene - dist_model);
      if (distance <= static_cast<float>(params_.resolution))
        boost::add_edge(i, j, consistency_graph);
    }
  }
  return num_consistency_tests;
}

inline size_t PartitionedGeometricConsistencyRecognizer::findAndAddCrossPartitionConsistencies(
    const PairwiseMatches& predicted_matches, const std::vector<size_t>& partition_indices_1,
    const std::vector<size_t>& partition_indices_2, ConsistencyGraph& consistency_graph) const {
  size_t num_consistency_tests = 0u;
  for (size_t i2 = 0u; i2 < partition_indices_1.size(); ++i2) {
    size_t i = partition_indices_1[i2];
    const Eigen::Vector3f& scene_point_i = predicted_matches[i].centroids_.first.getVector3fMap();
    const Eigen::Vector3f& model_point_i = predicted_matches[i].centroids_.second.getVector3fMap();
    for (size_t j2 = 0u; j2 < partition_indices_2.size(); ++j2) {
      size_t j = partition_indices_2[j2];
      const Eigen::Vector3f& scene_point_j =
          predicted_matches[j].centroids_.first.getVector3fMap();
      const Eigen::Vector3f& model_point_j =
          predicted_matches[j].centroids_.second.getVector3fMap();

      // Compute the difference between the distances and add edge to the consistency graph if the
      // matches are consistent.
      ++num_consistency_tests;
      float dist_scene = (scene_point_i - scene_point_j).norm();
      float dist_model = (model_point_i - model_point_j).norm();
      float distance = fabs(dist_scene - dist_model);
      if (distance <= static_cast<float>(params_.resolution))
        boost::add_edge(i, j, consistency_graph);
    }
  }
  return num_consistency_tests;
}

PartitionedGeometricConsistencyRecognizer::ConsistencyGraph
PartitionedGeometricConsistencyRecognizer::buildConsistencyGraph(
    const PairwiseMatches& predicted_matches) {
  BENCHMARK_BLOCK("SM.Worker.Recognition.BuildConsistencyGraph");

  // Partition the matches in a grid by the position of the scene points. The size of the
  // partitions is greater or equal the size of the model. This way we can safely assume that, if
  // the model is actually present in the scene, all matches will be contained in a 2x2 group of
  // adjacent partitions.
  BENCHMARK_START("SM.Worker.Recognition.BuildConsistencyGraph.Partitioning");
  MatchesGridPartitioning<PartitionData> partitioning =
      MatchesPartitioner::computeGridPartitioning<PartitionData>(predicted_matches,
                                                                 partition_size_);
  BENCHMARK_RECORD_VALUE("SM.Worker.Recognition.BuildConsistencyGraph.NumPartitions",
                         partitioning.getHeight() * partitioning.getWidth());
  BENCHMARK_STOP("SM.Worker.Recognition.BuildConsistencyGraph.Partitioning");
  ConsistencyGraph consistency_graph(predicted_matches.size());
  size_t num_consistency_tests = 0u;

  // Find all possible consistency within a partition and within neighbor partitions.
  for (size_t i = 0; i < partitioning.getHeight(); ++i) {
    for (size_t j = 0; j < partitioning.getWidth(); ++j) {
      // Find in-partition consistencies and add them to the consistency graph.
      findAndAddInPartitionConsistencies(
          predicted_matches, partitioning(i, j).match_indices, consistency_graph);

      // Determine which neighbor partitions exist.
      bool has_right_neighbors = j < partitioning.getWidth() - 1u;
      bool has_bottom_neighbors = i < partitioning.getHeight() - 1u;
      bool has_left_neighbors = j > 0u;

      // Find possible cross-partition consistencies and add them to the consistency graph.
      if (has_right_neighbors) {
        num_consistency_tests += findAndAddCrossPartitionConsistencies(
            predicted_matches, partitioning(i, j).match_indices,
            partitioning(i, j + 1u).match_indices, consistency_graph);
      }
      if (has_right_neighbors && has_bottom_neighbors) {
        num_consistency_tests += findAndAddCrossPartitionConsistencies(
            predicted_matches, partitioning(i, j).match_indices,
            partitioning(i + 1u, j + 1u).match_indices, consistency_graph);
      }
      if (has_bottom_neighbors) {
        num_consistency_tests += findAndAddCrossPartitionConsistencies(
            predicted_matches, partitioning(i, j).match_indices,
            partitioning(i + 1u, j).match_indices, consistency_graph);
      }
      if (has_bottom_neighbors && has_left_neighbors) {
        num_consistency_tests += findAndAddCrossPartitionConsistencies(
            predicted_matches, partitioning(i, j).match_indices,
            partitioning(i + 1u, j - 1u).match_indices, consistency_graph);
      }
    }
  }
  BENCHMARK_RECORD_VALUE("SM.Worker.Recognition.BuildConsistencyGraph.NumConsistencyTests",
                         num_consistency_tests);

  return consistency_graph;
}

} // namespace segmatch


#ifndef JHMI_LIVER_MACROCELL_TREE_HPP_NRC_20150803
#define JHMI_LIVER_MACROCELL_TREE_HPP_NRC_20150803

#include "liver/cell_list.hpp"
#include "liver/physical_vessel_tree.hpp"
#include "shape/voxelized_shape.hpp"
#include "utility/protobuf_zip_ostream.hpp"
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/rolling_mean.hpp>
#include <boost/filesystem.hpp>
#include <chrono>
#include <vector>

namespace jhmi {

  class macrocell_tree {
    std::mt19937 gen_;
    voxelized_shape const& liver_;
    physical_vessel_tree vessels_;
    cell_list cells_;

    int update_macrocells(float grow_prob, float die_prob) {
      std::uniform_real_distribution<> dist;
      auto rand = std::bind(dist, std::ref(gen_));

      //Determine which cells die
      std::vector<cell_id> dying;
      RANGES_FOR(auto& cell, cells_.list()) {
        if (rand() < die_prob && vessels_.remove(cell.parent_vessel))
          dying.push_back(cell.id);
      }
      //For each mc (in random order) attempt to fill empty spaces;
      std::vector<m3> clone_cells;
      RANGES_FOR(auto& cell, cells_.list()) {
        if (rand() < grow_prob)
          clone_cells.push_back(cell.center);
      }
      int num_died = dying.size();
      RANGES_FOR(auto id, dying) {
        cells_.erase(id);
      }
      ranges::shuffle(clone_cells, gen_);
      int num_created = 0;
      RANGES_FOR(auto center, clone_cells) {
        auto new_id = cells_.add_cell_near(center);
        if (new_id.valid()) {
          if (vessels_.connect_cell(cells_.at(new_id), gen_).valid()) {
            ++num_created;
          }
          else
            cells_.erase(new_id);
        }
      }
#define PER_SUBCYCLE
#ifdef PER_SUBCYCLE
      fmt::print("total: {:8} | attempted: {:6} | created: {:6} | died: {:6} | net: {:6}",
        vessels_.size(), clone_cells.size(), num_created, num_died, num_created - num_died);
#endif
      return num_created - num_died;
    }
    void fill_with_pcts(float grow_prob, float die_prob) {
#if 0
      using namespace boost::accumulators;
      //auto acc = accumulator_set<int, stats<tag::rolling_mean>>{tag::rolling_window::window_size = 20};
      do {
        acc(update_macrocells(grow_prob, die_prob));
# ifdef PER_SUBCYCLE
        fmt::print(" | avg: {}\n", rolling_mean(acc));
# endif
      } while (std::abs(rolling_mean(acc)) > 1);
#else
      while (update_macrocells(grow_prob, die_prob) > 0)
        fmt::print("\n");
      fmt::print("\n");
#endif
    }
    void normalize_all() {
      vessels_.normalize_all();
      //If we're solving for flow, we need to update the cell values afterward
    }

  public:
    static m initial_size() { return 5_mm / .3679; }
    macrocell_tree(build_tree_tag, boost::filesystem::path const& vesselfile,
                   voxelized_shape const& liver,
                   std::random_device::result_type seed,
                   cubic_meters_per_second proper_ha_flow,
                   double gamma, Pa cell_pressure, bool initial_fill = true)
      : gen_{seed}, liver_{liver},
        vessels_{build_tree, vesselfile, extents(liver_), gamma, cell_pressure, proper_ha_flow / 1868346., gen_},
        cells_{build_tree, gen_, liver_, initial_size(), proper_ha_flow, cell_pressure} {
      //We now add macrocells to each terminal vessel in the liver model.
      auto terminal_vessels = vessels_.terminal_vessels() | ranges::to_vector;
      for (auto&& v : terminal_vessels) {
        auto new_id = cells_.add_cell_near(v.start(), boost::optional<m3>{v.end()});
        if (!new_id.valid() || !vessels_.connect_cell_initial(cells_.at(new_id), v.id()).valid())
          throw std::runtime_error("Error adding an initial terminal cell");
      }
      //Finally, fill the liver with macrocells and connect them to this
      // initial map.
      if (initial_fill)
        fill_with_pcts(1.f, 0.f);
      normalize_all();
      if (!validate()) {
        throw std::runtime_error(fmt::format("Invalid tree detected!\n"));
      }
    }
    macrocell_tree(load_tree_tag, boost::filesystem::path const& treefile,
          voxelized_shape const& liver = voxelized_shape{})
      : gen_{0}, liver_{liver}, vessels_{load_tree, treefile, gen_},
        cells_{load_tree, treefile, liver, gen_} {
      //Takes a really long time, not strictly necessary
      //validate();
    }

    auto const& macrocells() const { return cells_; }
    physical_vessel_tree const& vessel_tree() const { return vessels_; }

    void write(boost::filesystem::path const& filename) const {
      namespace io = google::protobuf::io;
      jhmi_message::VesselTree vt;
      vessels_.store(vt);
      cells_.store(vt);
      protobuf_zip_ostream out_stream{filename};
      if (!vt.SerializeToZeroCopyStream(out_stream.get()))
        throw std::runtime_error("Failed to write macrocell tree.");
    }
    auto const& liver_shape() const { return liver_; }

    void reduce_cell_size(double scale, bool final) {
      assert((scale > 0 && scale < 1) || final);
      cells_.reduce_cell_size(scale, final);
    }
    void build(int cycles, m final_radius,
               boost::filesystem::path const& p = boost::filesystem::path{},
               std::string const& filestem = "") {
      float m1 = 1.f, m2 = 9.8f, n1 = .3f, n2 = 9.f;
      auto t = (final_radius - macrocell_tree::initial_size()) / double(cycles);
      RANGES_FOR(int cycle, ranges::view::ints(0, cycles)) {
        auto grow_prob = m1 * expf(-cycle / m2);
        auto die_prob = n1 * expf(-cycle / n2);
        bool fit_to_lobules = cycle == cycles - 1 && final_radius < 2_mm;
        reduce_cell_size(
          1. + t / (macrocell_tree::initial_size() + double(cycle) * t), fit_to_lobules);
        auto start_num_cells = ranges::size(macrocells().list());
        auto start = std::chrono::high_resolution_clock::now();

        fill_with_pcts(grow_prob, die_prob);
        vessels_.normalize_all();
        auto stop = std::chrono::high_resolution_clock::now();
        auto end_num_cells = ranges::size(macrocells().list());
        if (!validate()) {
          fmt::print("Invalid tree detected!\n");
        }
#if 1
        fmt::print("Cycle {:2} | Mitosis: {:10} | Necrosis: {:10} | Change: {:6} | Time: {} s\n",
          cycle + 1, grow_prob, die_prob, int(end_num_cells) - int(start_num_cells),
          std::chrono::duration<float>(stop - start).count());
#endif
        if (!filestem.empty() && cycle < cycles - 1) {
          write(p / fmt::format(filestem, cycle));
        }
      }

      auto start = std::chrono::high_resolution_clock::now();
      //How could this be necessary?
      vessels_.normalize_all();
      auto stop = std::chrono::high_resolution_clock::now();
      fmt::print("Time to normalize: {} s\n", std::chrono::duration<float>(stop - start).count());
    }

    bool validate() const {
      bool no_errors = vessels_.validate(ranges::empty(cells_.list())) && cells_.validate();
      //Verify each cell matches the exit pressure and flow of the parent_vessel
      RANGES_FOR(auto& cell, cells_.list()) {
        auto& v = vessels_.at(cell.parent_vessel);
//        no_errors &= check_close(v.flow(), cell.flow, 1e-8, v.id(),
//          "vessel flow", "cell flow");
        no_errors &= check_close(v.exit_pressure(), cell.pressure, 1e-4, v.id(),
          "vessel exit pressure", "cell pressure");
      }
      RANGES_FOR(auto& vessel, vessels_.vessels()) {
        try {
          if (vessel.cell().valid())
            cells_.at(vessel.cell());
        }
        catch(std::exception& e) {
          fmt::print("Vessel references non-existent cell\n");
          no_errors = false;
        }
      }
      if (!no_errors)
        fmt::print("Errors found in tree\n");
      return no_errors;
    }
  };

  bool operator==(macrocell_tree const& lhs, macrocell_tree const& rhs) {
    return lhs.macrocells() == rhs.macrocells() &&
           lhs.vessel_tree() == rhs.vessel_tree();
  }
}//jhmi
#endif


#ifndef BOOST_GEOMETRY_PROJECTIONS_BIPC_HPP
#define BOOST_GEOMETRY_PROJECTIONS_BIPC_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// 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 <boost/geometry/util/math.hpp>
#include <boost/math/special_functions/hypot.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct bipc {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace bipc
    {

            static const double EPS = 1e-10;
            static const double EPS10 = 1e-10;
            static const double ONEEPS = 1.000000001;
            static const int NITER = 10;
            static const double lamB = -.34894976726250681539;
            static const double n = .63055844881274687180;
            static const double F = 1.89724742567461030582;
            static const double Azab = .81650043674686363166;
            static const double Azba = 1.82261843856185925133;
            static const double T = 1.27246578267089012270;
            static const double rhoc = 1.20709121521568721927;
            static const double cAzc = .69691523038678375519;
            static const double sAzc = .71715351331143607555;
            static const double C45 = .70710678118654752469;
            static const double S45 = .70710678118654752410;
            static const double C20 = .93969262078590838411;
            static const double S20 = -.34202014332566873287;
            static const double R110 = 1.91986217719376253360;
            static const double R104 = 1.81514242207410275904;

            struct par_bipc
            {
                int    noskew;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_bipc_spheroid : public base_t_fi<base_bipc_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_bipc m_proj_parm;

                inline base_bipc_spheroid(const Parameters& par)
                    : base_t_fi<base_bipc_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par) {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    static const CalculationType HALFPI = detail::HALFPI<CalculationType>();
                    static const CalculationType ONEPI = detail::ONEPI<CalculationType>();

                    CalculationType cphi, sphi, tphi, t, al, Az, z, Av, cdlam, sdlam, r;
                    int tag;

                    cphi = cos(lp_lat);
                    sphi = sin(lp_lat);
                    cdlam = cos(sdlam = lamB - lp_lon);
                    sdlam = sin(sdlam);
                    if (fabs(fabs(lp_lat) - HALFPI) < EPS10) {
                        Az = lp_lat < 0. ? ONEPI : 0.;
                        tphi = HUGE_VAL;
                    } else {
                        tphi = sphi / cphi;
                        Az = atan2(sdlam , C45 * (tphi - cdlam));
                    }
                    if( (tag = (Az > Azba)) ) {
                        cdlam = cos(sdlam = lp_lon + R110);
                        sdlam = sin(sdlam);
                        z = S20 * sphi + C20 * cphi * cdlam;
                        if (fabs(z) > 1.) {
                            if (fabs(z) > ONEEPS)
                                BOOST_THROW_EXCEPTION( projection_exception(-20) );
                            else
                                z = z < 0. ? -1. : 1.;
                        } else
                            z = acos(z);
                        if (tphi != HUGE_VAL)
                            Az = atan2(sdlam, (C20 * tphi - S20 * cdlam));
                        Av = Azab;
                        xy_y = rhoc;
                    } else {
                        z = S45 * (sphi + cphi * cdlam);
                        if (fabs(z) > 1.) {
                            if (fabs(z) > ONEEPS)
                                BOOST_THROW_EXCEPTION( projection_exception(-20) );
                            else
                                z = z < 0. ? -1. : 1.;
                        } else
                            z = acos(z);
                        Av = Azba;
                        xy_y = -rhoc;
                    }
                    if (z < 0.) BOOST_THROW_EXCEPTION( projection_exception(-20) );
                    r = F * (t = pow(tan(.5 * z), n));
                    if ((al = .5 * (R104 - z)) < 0.)
                        BOOST_THROW_EXCEPTION( projection_exception(-20) );
                    al = (t + pow(al, n)) / T;
                    if (fabs(al) > 1.) {
                        if (fabs(al) > ONEEPS)
                            BOOST_THROW_EXCEPTION( projection_exception(-20) );
                        else
                            al = al < 0. ? -1. : 1.;
                    } else
                        al = acos(al);
                    if (fabs(t = n * (Av - Az)) < al)
                        r /= cos(al + (tag ? t : -t));
                    xy_x = r * sin(t);
                    xy_y += (tag ? -r : r) * cos(t);
                    if (this->m_proj_parm.noskew) {
                        t = xy_x;
                        xy_x = -xy_x * cAzc - xy_y * sAzc;
                        xy_y = -xy_y * cAzc + t * sAzc;
                    }
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    CalculationType t, r, rp, rl, al, z, fAz, Az, s, c, Av;
                    int neg, i;

                    if (this->m_proj_parm.noskew) {
                        t = xy_x;
                        xy_x = -xy_x * cAzc + xy_y * sAzc;
                        xy_y = -xy_y * cAzc - t * sAzc;
                    }
                    if( (neg = (xy_x < 0.)) ) {
                        xy_y = rhoc - xy_y;
                        s = S20;
                        c = C20;
                        Av = Azab;
                    } else {
                        xy_y += rhoc;
                        s = S45;
                        c = C45;
                        Av = Azba;
                    }
                    rl = rp = r = boost::math::hypot(xy_x, xy_y);
                    fAz = fabs(Az = atan2(xy_x, xy_y));
                    for (i = NITER; i ; --i) {
                        z = 2. * atan(pow(r / F,1 / n));
                        al = acos((pow(tan(.5 * z), n) +
                           pow(tan(.5 * (R104 - z)), n)) / T);
                        if (fAz < al)
                            r = rp * cos(al + (neg ? Az : -Az));
                        if (fabs(rl - r) < EPS)
                            break;
                        rl = r;
                    }
                    if (! i)
                        BOOST_THROW_EXCEPTION( projection_exception(-20) );
                    Az = Av - Az / n;
                    lp_lat = asin(s * cos(z) + c * sin(z) * cos(Az));
                    lp_lon = atan2(sin(Az), c / tan(z) - s * cos(Az));
                    if (neg)
                        lp_lon -= R110;
                    else
                        lp_lon = lamB - lp_lon;
                }

                static inline std::string get_name()
                {
                    return "bipc_spheroid";
                }

            };

            // Bipolar conic of western hemisphere
            template <typename Parameters>
            inline void setup_bipc(Parameters& par, par_bipc& proj_parm)
            {
                proj_parm.noskew = pj_param(par.params, "bns").i;
                par.es = 0.;
            }

    }} // namespace detail::bipc
    #endif // doxygen

    /*!
        \brief Bipolar conic of western hemisphere projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Conic
         - Spheroid
        \par Projection parameters
         - ns (boolean)
        \par Example
        \image html ex_bipc.gif
    */
    template <typename CalculationType, typename Parameters>
    struct bipc_spheroid : public detail::bipc::base_bipc_spheroid<CalculationType, Parameters>
    {
        inline bipc_spheroid(const Parameters& par) : detail::bipc::base_bipc_spheroid<CalculationType, Parameters>(par)
        {
            detail::bipc::setup_bipc(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::bipc, bipc_spheroid, bipc_spheroid)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class bipc_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<bipc_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void bipc_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("bipc", new bipc_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_BIPC_HPP


/*
* TS Elements
* Copyright 2015-2018 M. Newhouse
* Released under the MIT license.
*/

#include "world.hpp"
#include "car.hpp"
#include "world_limits.hpp"
#include "control_point_manager_detail.hpp"
#include "entity_id_conversion.hpp"
#include "world_event_interface.hpp"

#include "resources/terrain_library.hpp"
#include "resources/car_definition.hpp"
#include "resources/terrain_definition.hpp"
#include "resources/collision_mask_detail.hpp"

#include "utility/random.hpp"
#include "utility/line_plotter.hpp"
#include "utility/debug_log.hpp"
#include "utility/math_utilities.hpp"

#include <boost/function_output_iterator.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/optional.hpp>

#include <chipmunk/chipmunk.h>

#include <vector>
#include <iostream>
#include <cmath>

namespace ts
{
  namespace world
  {
    static void update_body_position(cpBody* body, cpFloat dt)
    {
      cpBodyUpdatePosition(body, dt);

      auto pos = cpBodyGetPosition(body);
      auto space = cpBodyGetSpace(body);

      auto user_data = static_cast<const PhysicsSpace::UserData*>(cpSpaceGetUserData(space));

      pos.x = cpfclamp(pos.x, 0.0, user_data->size.x);
      pos.y = cpfclamp(pos.y, 0.0, user_data->size.y);
      cpBodySetPosition(body, pos);
    }

    PhysicsSpace::PhysicsSpace(Vector2d size)
      : physics_space_(static_cast<void*>(cpSpaceNew())),
      user_data_(std::make_unique<UserData>())
    {
      auto space = static_cast<cpSpace*>(physics_space_.get());
      cpSpaceSetUserData(space, user_data_.get());

      user_data_->size = size;
    }

    void PhysicsSpace::update(std::uint32_t frame_duration)
    {
      auto fd = frame_duration * 0.001;

      cpSpaceStep(static_cast<cpSpace*>(physics_space_.get()), fd);
    }

    void PhysicsSpace::add_entity(Entity* entity)
    {
      auto body = static_cast<cpBody*>(entity->physics_body_.get());

      cpSpaceAddBody(static_cast<cpSpace*>(physics_space_.get()), body);

      cpBodySetPositionUpdateFunc(body, update_body_position);
    }

    void PhysicsSpace::Deleter::operator()(void* space) const
    {
      cpSpaceFree(static_cast<cpSpace*>(space));
    }

    World::World(resources::Track track, TerrainMap terrain_map)
      : track_(std::move(track)),
      terrain_map_(std::move(terrain_map)),
      control_point_manager_(track_.control_points().data(), track_.control_points().size()),
      physics_space_(vector2_cast<double>(track_.size()))
    {
      entity_map_.resize(limits::max_car_count);
      cars_.reserve(limits::max_car_count);
    }

    Car* World::create_car(const CarDefinition& car_definition, std::uint8_t car_id, std::uint16_t start_pos)
    {
      auto entity_id = car_id_to_entity_id(car_id);

      const auto& start_points = track_.start_points();
      if (cars_.size() >= limits::max_car_count || entity_map_[entity_id] != nullptr || start_pos >= start_points.size())
        return nullptr;

      auto position = make_vector2(0.0, 0.0);
      auto rotation = degrees(0.0);
      auto z_position = 0.0;

      auto point = start_points[start_pos];
      position = vector2_cast<double>(point.position);
      rotation = degrees(static_cast<double>(point.rotation));
      z_position = static_cast<double>(point.level);

      auto car = std::make_unique<Car>(car_definition, entity_id);
      auto car_ptr = car.get();

      cars_.push_back(car_ptr);
      entity_map_[entity_id] = std::move(car);

      physics_space_.add_entity(car_ptr);

      car_ptr->set_position(position);
      car_ptr->set_rotation(rotation);
      car_ptr->set_z_position(z_position);

      return car_ptr;
    }

    void World::update(std::uint32_t frame_duration, world::EventInterface& event_interface)
    {
      double fd = frame_duration * 0.001;

      auto max_corner = vector2_cast<std::int32_t>(world_size()) - make_vector2(1, 1);

      const auto& terrain_lib = track_.terrain_library();

      entity_states_.clear();
      for (auto* car : cars_)
      {
        entity_states_.push_back({ car, car->position() });

        car->update(*this, fd);
      }

      physics_space_.update(frame_duration);

      for (auto& es : entity_states_)
      {
        auto cp_hit_callback = [&](const ControlPoint& point, double time_point)
        {
          auto frame_offset = static_cast<std::uint32_t>(frame_duration * time_point);

          event_interface.on_control_point_hit(es.entity, point, frame_offset);
        };

        control_point_manager_.test_control_point_intersections(es.old_position, es.entity->position(), cp_hit_callback);
      }
    }

    Car* World::find_car(std::uint8_t car_id)
    {
      auto entity_id = car_id_to_entity_id(car_id);
      return static_cast<Car*>(entity_map_[entity_id].get());
    }

    const Car* World::find_car(std::uint8_t car_id) const
    {
      auto entity_id = car_id_to_entity_id(car_id);
      return static_cast<const Car*>(entity_map_[entity_id].get());
    }

    World::car_range World::cars() const
    {
      return car_range(cars_.data(), cars_.data() + cars_.size());
    }

    Vector2<double> World::world_size() const
    {
      return vector2_cast<double>(track().size());
    }

    const resources::Track& World::track() const noexcept
    {
      return track_;
    }

    resources::TerrainDefinition World::terrain_at(Vector2i position) const
    {
      return terrain_at(position, 0);
    }

    resources::TerrainDefinition World::terrain_at(Vector2i position, std::int32_t level) const
    {
      return terrain_map_.terrain_at(position, level, track_.terrain_library());
    }

    resources::TerrainDefinition World::terrain_at(Vector2d position) const
    {
      return terrain_at(position, 0);
    }

    resources::TerrainDefinition World::terrain_at(Vector2d position, std::int32_t level) const
    {
      return terrain_at(vector2_cast<std::int32_t>(position), level);
    }
  }
}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

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

#ifndef BOOST_GEOMETRY_PROJECTIONS_NSPER_HPP
#define BOOST_GEOMETRY_PROJECTIONS_NSPER_HPP

#include <boost/config.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

#include <boost/geometry/util/math.hpp>

#include <boost/math/special_functions/hypot.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace nsper
    {

            static const double epsilon10 = 1.e-10;
            enum mode_type {
                n_pole = 0,
                s_pole = 1,
                equit  = 2,
                obliq  = 3
            };

            template <typename T>
            struct par_nsper
            {
                T   height;
                T   sinph0;
                T   cosph0;
                T   p;
                T   rp;
                T   pn1;
                T   pfact;
                T   h;
                T   cg;
                T   sg;
                T   sw;
                T   cw;
                mode_type mode;
                int tilt;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_nsper_spheroid
                : public base_t_fi<base_nsper_spheroid<T, Parameters>, T, Parameters>
            {
                par_nsper<T> m_proj_parm;

                inline base_nsper_spheroid(const Parameters& par)
                    : base_t_fi<base_nsper_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T  coslam, cosphi, sinphi;

                    sinphi = sin(lp_lat);
                    cosphi = cos(lp_lat);
                    coslam = cos(lp_lon);
                    switch (this->m_proj_parm.mode) {
                    case obliq:
                        xy_y = this->m_proj_parm.sinph0 * sinphi + this->m_proj_parm.cosph0 * cosphi * coslam;
                        break;
                    case equit:
                        xy_y = cosphi * coslam;
                        break;
                    case s_pole:
                        xy_y = - sinphi;
                        break;
                    case n_pole:
                        xy_y = sinphi;
                        break;
                    }
                    if (xy_y < this->m_proj_parm.rp) {
                        BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                    }
                    xy_y = this->m_proj_parm.pn1 / (this->m_proj_parm.p - xy_y);
                    xy_x = xy_y * cosphi * sin(lp_lon);
                    switch (this->m_proj_parm.mode) {
                    case obliq:
                        xy_y *= (this->m_proj_parm.cosph0 * sinphi -
                           this->m_proj_parm.sinph0 * cosphi * coslam);
                        break;
                    case equit:
                        xy_y *= sinphi;
                        break;
                    case n_pole:
                        coslam = - coslam;
                        BOOST_FALLTHROUGH;
                    case s_pole:
                        xy_y *= cosphi * coslam;
                        break;
                    }
                    if (this->m_proj_parm.tilt) {
                        T yt, ba;

                        yt = xy_y * this->m_proj_parm.cg + xy_x * this->m_proj_parm.sg;
                        ba = 1. / (yt * this->m_proj_parm.sw * this->m_proj_parm.h + this->m_proj_parm.cw);
                        xy_x = (xy_x * this->m_proj_parm.cg - xy_y * this->m_proj_parm.sg) * this->m_proj_parm.cw * ba;
                        xy_y = yt * ba;
                    }
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T xy_x, T xy_y, T& lp_lon, T& lp_lat) const
                {
                    T  rh, cosz, sinz;

                    if (this->m_proj_parm.tilt) {
                        T bm, bq, yt;

                        yt = 1./(this->m_proj_parm.pn1 - xy_y * this->m_proj_parm.sw);
                        bm = this->m_proj_parm.pn1 * xy_x * yt;
                        bq = this->m_proj_parm.pn1 * xy_y * this->m_proj_parm.cw * yt;
                        xy_x = bm * this->m_proj_parm.cg + bq * this->m_proj_parm.sg;
                        xy_y = bq * this->m_proj_parm.cg - bm * this->m_proj_parm.sg;
                    }
                    rh = boost::math::hypot(xy_x, xy_y);
                    if ((sinz = 1. - rh * rh * this->m_proj_parm.pfact) < 0.) {
                        BOOST_THROW_EXCEPTION( projection_exception(error_tolerance_condition) );
                    }
                    sinz = (this->m_proj_parm.p - sqrt(sinz)) / (this->m_proj_parm.pn1 / rh + rh / this->m_proj_parm.pn1);
                    cosz = sqrt(1. - sinz * sinz);
                    if (fabs(rh) <= epsilon10) {
                        lp_lon = 0.;
                        lp_lat = this->m_par.phi0;
                    } else {
                        switch (this->m_proj_parm.mode) {
                        case obliq:
                            lp_lat = asin(cosz * this->m_proj_parm.sinph0 + xy_y * sinz * this->m_proj_parm.cosph0 / rh);
                            xy_y = (cosz - this->m_proj_parm.sinph0 * sin(lp_lat)) * rh;
                            xy_x *= sinz * this->m_proj_parm.cosph0;
                            break;
                        case equit:
                            lp_lat = asin(xy_y * sinz / rh);
                            xy_y = cosz * rh;
                            xy_x *= sinz;
                            break;
                        case n_pole:
                            lp_lat = asin(cosz);
                            xy_y = -xy_y;
                            break;
                        case s_pole:
                            lp_lat = - asin(cosz);
                            break;
                        }
                        lp_lon = atan2(xy_x, xy_y);
                    }
                }

                static inline std::string get_name()
                {
                    return "nsper_spheroid";
                }

            };

            template <typename Params, typename Parameters, typename T>
            inline void setup(Params const& params, Parameters& par, par_nsper<T>& proj_parm)
            {
                proj_parm.height = pj_get_param_f<T, srs::spar::h>(params, "h", srs::dpar::h);
                if (proj_parm.height <= 0.)
                    BOOST_THROW_EXCEPTION( projection_exception(error_h_less_than_zero) );

                if (fabs(fabs(par.phi0) - geometry::math::half_pi<T>()) < epsilon10)
                    proj_parm.mode = par.phi0 < 0. ? s_pole : n_pole;
                else if (fabs(par.phi0) < epsilon10)
                    proj_parm.mode = equit;
                else {
                    proj_parm.mode = obliq;
                    proj_parm.sinph0 = sin(par.phi0);
                    proj_parm.cosph0 = cos(par.phi0);
                }
                proj_parm.pn1 = proj_parm.height / par.a; /* normalize by radius */
                proj_parm.p = 1. + proj_parm.pn1;
                proj_parm.rp = 1. / proj_parm.p;
                proj_parm.h = 1. / proj_parm.pn1;
                proj_parm.pfact = (proj_parm.p + 1.) * proj_parm.h;
                par.es = 0.;
            }

            // Near-sided perspective
            template <typename Params, typename Parameters, typename T>
            inline void setup_nsper(Params const& params, Parameters& par, par_nsper<T>& proj_parm)
            {
                proj_parm.tilt = 0;

                setup(params, par, proj_parm);
            }

            // Tilted perspective
            template <typename Params, typename Parameters, typename T>
            inline void setup_tpers(Params const& params, Parameters& par, par_nsper<T>& proj_parm)
            {
                T const omega = pj_get_param_r<T, srs::spar::tilt>(params, "tilt", srs::dpar::tilt);
                T const gamma = pj_get_param_r<T, srs::spar::azi>(params, "azi", srs::dpar::azi);
                proj_parm.tilt = 1;
                proj_parm.cg = cos(gamma); proj_parm.sg = sin(gamma);
                proj_parm.cw = cos(omega); proj_parm.sw = sin(omega);

                setup(params, par, proj_parm);
            }

    }} // namespace detail::nsper
    #endif // doxygen

    /*!
        \brief Near-sided perspective projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
        \par Projection parameters
         - h: Height
        \par Example
        \image html ex_nsper.gif
    */
    template <typename T, typename Parameters>
    struct nsper_spheroid : public detail::nsper::base_nsper_spheroid<T, Parameters>
    {
        template <typename Params>
        inline nsper_spheroid(Params const& params, Parameters const& par)
            : detail::nsper::base_nsper_spheroid<T, Parameters>(par)
        {
            detail::nsper::setup_nsper(params, this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Tilted perspective projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Azimuthal
         - Spheroid
        \par Projection parameters
         - tilt: Tilt, or Omega (real)
         - azi: Azimuth (or Gamma) (real)
         - h: Height
        \par Example
        \image html ex_tpers.gif
    */
    template <typename T, typename Parameters>
    struct tpers_spheroid : public detail::nsper::base_nsper_spheroid<T, Parameters>
    {
        template <typename Params>
        inline tpers_spheroid(Params const& params, Parameters const& par)
            : detail::nsper::base_nsper_spheroid<T, Parameters>(par)
        {
            detail::nsper::setup_tpers(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_nsper, nsper_spheroid, nsper_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_tpers, tpers_spheroid, tpers_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(nsper_entry, nsper_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(tpers_entry, tpers_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(nsper_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(nsper, nsper_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(tpers, tpers_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_NSPER_HPP


// __BEGIN_LICENSE__
// Copyright (C) 2006-2010 United States Government as represented by
// the Administrator of the National Aeronautics and Space Administration.
// All Rights Reserved.
// __END_LICENSE__

#include <vw/gui/TileGenerator.h>
#include <vw/Image/ViewImageResource.h>
#include <vw/Image/ImageResourceView.h>
#include <vw/Image/Statistics.h>
#include <vw/Image/MaskViews.h>

#include <QUrl>
#include <QBuffer>
#include <QImage>
#include <QHttp>
#include <QByteArray>

using namespace vw;
using namespace vw::gui;
using namespace vw::platefile;

#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;

// --------------------------------------------------------------------------------
//                                Utility Functions
// --------------------------------------------------------------------------------

BBox2i vw::gui::tile_to_bbox(Vector2i tile_size, int col, int row, int level, int max_level) {
  if (col < 0 || row < 0 || col >= pow(2, max_level) || row >= pow(2, max_level) ) {
    return BBox2i();
  } else {
    BBox2i result(tile_size[0]*col, tile_size[1]*row, tile_size[0], tile_size[1]);
    return result * pow(2,max_level - level);
  }
}

std::list<TileLocator> vw::gui::bbox_to_tiles(Vector2i tile_size, BBox2i bbox, int level, int max_level, int transaction_id, bool exact_transaction_id_match) {
  std::list<TileLocator> results;

  // Compute the bounding box at the current level.
  BBox2i level_bbox = bbox / pow(2,max_level - level);

  // Grow that bounding box to align with tile boundaries
  BBox2i aligned_level_bbox = level_bbox;
  aligned_level_bbox.min().x() = ( (level_bbox.min().x() / tile_size[0]) * tile_size[0] );
  aligned_level_bbox.min().y() = ( (level_bbox.min().y() / tile_size[1]) * tile_size[1] );
  aligned_level_bbox.max().x() = ( int(ceilf( float(level_bbox.max().x()) / tile_size[0] ))
                                   * tile_size[0] );
  aligned_level_bbox.max().y() = ( int(ceilf( float(level_bbox.max().y()) / tile_size[1] ))
                                   * tile_size[1] );

  int tile_y = aligned_level_bbox.min().y() / tile_size[1];
  int dest_row = 0;
  while ( tile_y < aligned_level_bbox.max().y() / tile_size[1] ) {

    int tile_x = aligned_level_bbox.min().x() / tile_size[0];
    int dest_col = 0;
    while ( tile_x < aligned_level_bbox.max().x() / tile_size[0] ) {
      BBox2i tile_bbox(dest_col, dest_row, tile_size[0], tile_size[1]);
      TileLocator loc;
      loc.col = tile_x;
      loc.row = tile_y;
      loc.level = level;
      loc.transaction_id = transaction_id;
      loc.exact_transaction_id_match = exact_transaction_id_match;
      results.push_back(loc);

      ++tile_x;
      dest_col += tile_size[0];
    }
    ++tile_y;
    dest_row += tile_size[1];
  }
  return results;
}

// --------------------------------------------------------------------------
//                              TILE GENERATOR
// --------------------------------------------------------------------------

boost::shared_ptr<TileGenerator> TileGenerator::create(std::string filename) {

  // Strip off any trailing slashes to make sure we aren't
  // accidentally misparsing a platefile name.
  if (filename[filename.size()-1] == '/')
    filename.erase(filename.size()-1, 1);

  try {

    // If ends in .plate, then assume platefile.
    if ( fs::extension(filename) == ".plate") {

      return boost::shared_ptr<TileGenerator>( new PlatefileTileGenerator(filename) );

    // If begins with http://, then assume web tiles.
    } else if ( filename.find("http://") == 0) {

      return boost::shared_ptr<TileGenerator>( new WebTileGenerator(filename,17));

    // If testpattern, then we use the testpattern tile generator
    } else if (filename == "testpattern") {

      vw_out() << "\t--> Starting vwv in testpattern mode.\n";
      return boost::shared_ptr<TileGenerator>( new TestPatternTileGenerator(256) );

    // Otherwise, assume an image.
    } else {
      return boost::shared_ptr<TileGenerator>( new ImageTileGenerator(filename) );
    }
  } catch (vw::IOErr &e) {
    std::cout << "An error occurred opening \"" << filename << "\":\n\t" << e.what() << "\n";
    exit(0);
  }
}

// --------------------------------------------------------------------------
//                         TEST PATTERN TILE GENERATOR
// --------------------------------------------------------------------------

boost::shared_ptr<ViewImageResource> TestPatternTileGenerator::generate_tile(TileLocator const& /*tile_info*/) {
  ImageView<PixelRGBA<uint8> > tile(m_tile_size, m_tile_size);
  for (int j = 0; j < m_tile_size; ++j){
    for (int i = 0; i < m_tile_size; ++i){
      if (abs(i - j) < 10 || abs(i - (m_tile_size - j)) < 10)
        tile(i,j) = PixelRGBA<uint8>(255,0,0,255);
      else
        tile(i,j) = PixelRGBA<uint8>(0,0,0,255);
    }
  }
  boost::shared_ptr<ViewImageResource> result( new ViewImageResource(tile) );
  return result;
}

Vector2 TestPatternTileGenerator::minmax() { return Vector2(0.0, 1.0); }

PixelRGBA<float32> TestPatternTileGenerator::sample(int /*x*/, int /*y*/, int /*level*/, int /*transaction_id*/) {
  PixelRGBA<float32> result;
  return result;
}

int TestPatternTileGenerator::cols() const { return 2048; }
int TestPatternTileGenerator::rows() const { return 2048; }
PixelFormatEnum TestPatternTileGenerator::pixel_format() const { return VW_PIXEL_RGBA; }
ChannelTypeEnum TestPatternTileGenerator::channel_type() const { return VW_CHANNEL_UINT8; }
Vector2i TestPatternTileGenerator::tile_size() const {
  return Vector2i(m_tile_size, m_tile_size);
}
int32 TestPatternTileGenerator::num_levels() const {
  return 4;
}

// --------------------------------------------------------------------------
//                            WEB TILE GENERATOR
// --------------------------------------------------------------------------

HttpDownloadThread::HttpDownloadThread() {
  m_http = new QHttp(NULL);
  connect(m_http, SIGNAL(requestStarted(int)),this, SLOT(request_started(int)));
  connect(m_http, SIGNAL(requestFinished(int, bool)),this, SLOT(request_finished(int, bool)));
  connect(m_http, SIGNAL(responseHeaderReceived(QHttpResponseHeader)),this, SLOT(response_header_received(QHttpResponseHeader)));
  connect(m_http, SIGNAL(stateChanged(int)),this, SLOT(state_changed(int)));
}

void HttpDownloadThread::run() {
  QThread::exec();
}

HttpDownloadThread::~HttpDownloadThread() {
  if (m_http)
    delete m_http;
}

int HttpDownloadThread::get(std::string url_string, int transaction_id,
                            bool exact_transaction_id_match) {
  Mutex::Lock lock(m_mutex);
  QUrl url(url_string.c_str());

  /// XXX: Hard coding file_type as PNG for now. FIXME!!!!
  std::string file_type = "png";

  int request_id;
  if (m_http) {
    m_http->setHost(url.host(),url.port());

    // Set up request buffer
    RequestBuffer buf;
    buf.file_type = file_type;
    buf.url = url_string;

    std::ostringstream path_with_opts;
    path_with_opts << url.path().toStdString() << "?nocache=1&transaction_id=" << transaction_id;
    if (exact_transaction_id_match)
      path_with_opts << "&exact=1";
    vw_out() << "\t --> Fetching http://" << url.host().toStdString() << path_with_opts.str() << "\n";
    QString final_path_str(path_with_opts.str().c_str());
    request_id = m_http->get (final_path_str, buf.buffer.get());
    m_requests[request_id] = buf;
    m_current_request = request_id;
  }
  return request_id;
}

bool HttpDownloadThread::result_available(int request_id) {
  Mutex::Lock lock(m_mutex);
  return m_requests[request_id].finished;
}

vw::ImageView<vw::PixelRGBA<float> > HttpDownloadThread::pop_result(int request_id) {
  Mutex::Lock lock(m_mutex);
  vw::ImageView<vw::PixelRGBA<float> > result = m_requests[request_id].result;
  m_requests.erase(request_id);
  return result;
}

void HttpDownloadThread::request_started(int /*request_id*/) {
  //  std::cout << "Request " << request_id << " started\n";
}

void HttpDownloadThread::response_header_received( const QHttpResponseHeader & resp ) {
  Mutex::Lock lock(m_mutex);
  //  std::cout << "Header received...\n";

  std::map<int, RequestBuffer>::iterator request_iter = m_requests.find(m_current_request);
  if (request_iter != m_requests.end()) {
    RequestBuffer &buf = request_iter->second;
    buf.status = resp.statusCode();
    if (resp.contentType() == "image/png")
      buf.file_type = "png";
    else if (resp.contentType() == "image/jpeg" || resp.contentType() == "image/jpg")
      buf.file_type = "jpg";
    else if (resp.contentType() == "image/tiff" || resp.contentType() == "image/tif")
      buf.file_type = "tif";
    else if (resp.contentType() == "text/html") {
      /* do nothing... this is likely a 404 */
    } else
      vw_out() << "WARNING: unrecognized content-type: " << resp.contentType().toStdString() << "\n";
  }
}

void HttpDownloadThread::state_changed(int /*state*/) {
  Mutex::Lock lock(m_mutex);
  //  std::cout << "HTTP State changed: " << state << "\n";
}

void HttpDownloadThread::request_finished(int request_id, bool error) {
  Mutex::Lock lock(m_mutex);
  std::map<int, RequestBuffer>::iterator request_iter = m_requests.find(request_id);
  if (request_iter != m_requests.end()) {
    RequestBuffer &buf = request_iter->second;

    if (error || (buf.status != 404 && buf.status != 200)) {
      std::cout << "WARNING: Request " << request_id << " failed for URL: "
                << buf.url << "\n\t" << m_http->errorString().toStdString() << "\n";
      ImageView<PixelRGBA<float> > vw_image(1,1);
      vw_image(0,0) = PixelRGBA<float>(1.0,0.0,0.0,1.0);
      buf.result = vw_image;
    } else if (buf.status == 404) {
      ImageView<PixelRGBA<float> > vw_image(1,1);
      vw_image(0,0) = PixelRGBA<float>(0.0,0.1,0.0,1.0);
      buf.result = vw_image;
    }

    // Handle the error case.
    if (error || buf.buffer->buffer().length() == 0 || buf.status != 200) {
      buf.finished = true;
      return;
    }

    // Save the data to a temporary file, and then read the image file
    // using the Vision Workbench FileIO subsystem.
    std::string temp_filename = platefile::TemporaryTileFile::unique_tempfile_name(buf.file_type);
    std::ofstream of(temp_filename.c_str());
    if ( !(of.good()) )
      vw_throw(IOErr() << "Could not open temporary tile file for writing: " << temp_filename);
    of.write(buf.buffer->buffer().data(), buf.buffer->buffer().length());
    of.close();
    TemporaryTileFile temp_tile_file(temp_filename);

    // Now read the image out of the tempfile and save the decoded
    // pixels as the result.
    try {
      boost::shared_ptr<DiskImageResource> rsrc(DiskImageResource::open(temp_filename));
      if (rsrc->channel_type() == VW_CHANNEL_UINT8) {
        ImageView<PixelRGBA<uint8> > vw_image = temp_tile_file.read<PixelRGBA<uint8> >();
        buf.result = channel_cast_rescale<float>(vw_image);
      } else if (rsrc->channel_type() == VW_CHANNEL_UINT16) {
        ImageView<PixelRGBA<int16> > vw_image = temp_tile_file.read<PixelRGBA<int16> >();
        buf.result = channel_cast_rescale<float>(vw_image);
      } else {
        vw_out() << "WARNING: Image contains unsupported channel type: "
                 << rsrc->channel_type() << "\n";
      }
      buf.finished = true;
    } catch (IOErr &e) {
      vw_out(WarningMessage) << "Could not read data from temporary file: "
                             << temp_filename << "\n";
      buf.finished = true;
    }
  }
}

WebTileGenerator::WebTileGenerator(std::string url, int levels) :
  m_tile_size(256), m_levels(levels), m_url(url) {
  m_download_thread.start();
}

boost::shared_ptr<ViewImageResource> WebTileGenerator::generate_tile(TileLocator const& tile_info) {

  std::ostringstream full_url;
  full_url << m_url << "/" << tile_info.level
           << "/" << tile_info.col
           << "/" << tile_info.row << ".png";
  int request_id = m_download_thread.get(full_url.str(),
                                         tile_info.transaction_id,
                                         tile_info.exact_transaction_id_match);
  while(!m_download_thread.result_available(request_id));

  vw::ImageView<vw::PixelRGBA<float> > result = m_download_thread.pop_result(request_id);
  return boost::shared_ptr<ViewImageResource>( new ViewImageResource( result ) );
}

Vector2 WebTileGenerator::minmax() { return Vector2(0.0, 1.0); }

PixelRGBA<float32> WebTileGenerator::sample(int /*x*/, int /*y*/, int /*level*/, int /*transaction_id*/) {
  PixelRGBA<float32> result;
  return result;
}

int WebTileGenerator::cols() const { return m_tile_size * pow(2,m_levels-1); }
int WebTileGenerator::rows() const { return m_tile_size * pow(2,m_levels-1); }
PixelFormatEnum WebTileGenerator::pixel_format() const { return VW_PIXEL_RGBA; }
ChannelTypeEnum WebTileGenerator::channel_type() const { return VW_CHANNEL_UINT8; }
Vector2i WebTileGenerator::tile_size() const {
  return Vector2i(m_tile_size, m_tile_size);
}
int32 WebTileGenerator::num_levels() const {
  return m_levels;
}

// --------------------------------------------------------------------------
//                         PLATE FILE TILE GENERATOR
// --------------------------------------------------------------------------

PlatefileTileGenerator::PlatefileTileGenerator(std::string platefile_name) :
  m_platefile(new vw::platefile::PlateFile(platefile_name)) {
  m_num_levels = m_platefile->num_levels();
  std::cout << "\t--> Loading platefile \"" << platefile_name << "\" with "
            << m_num_levels << " levels.\n";
}

#define VW_DELEGATE_BY_PIXEL_TYPE(func, arg1, arg2)                                  \
  switch (this->pixel_format()) {                                                    \
  case VW_PIXEL_GRAY:                                                                \
  case VW_PIXEL_GRAYA:                                                               \
      if (this->channel_type() == VW_CHANNEL_UINT8) {                                \
        return func<PixelGrayA<uint8> >(arg1, arg2);                                 \
      } else if (this->channel_type() == VW_CHANNEL_INT16) {                         \
        return func<PixelGrayA<int16> >(arg1, arg2);                                 \
      } else if (this->channel_type() == VW_CHANNEL_FLOAT32) {                       \
        return func<PixelGrayA<float> >(arg1, arg2);                                 \
      } else {                                                                       \
        std::cout << "This platefile has a channel type that is not yet support by vwv.\n"; \
        std::cout << "Exiting...\n\n";                                               \
        exit(0);                                                                     \
      }                                                                              \
      break;                                                                         \
    case VW_PIXEL_RGB:                                                               \
    case VW_PIXEL_RGBA:                                                              \
      if (this->channel_type() == VW_CHANNEL_UINT8) {                                \
        return func<PixelRGBA<uint8> >(arg1, arg2);                                  \
      } else if (this->channel_type() == VW_CHANNEL_UINT16) {                        \
        return func<PixelRGBA<uint16> >(arg1, arg2);                                 \
      } else {                                                                       \
        std::cout << "This platefile has a channel type that is not yet support by vwv.\n"; \
        std::cout << "Exiting...\n\n";                                               \
        exit(0);                                                                     \
      }                                                                              \
      break;                                                                         \
    default:                                                                         \
      std::cout << "This platefile has a pixel format that is not yet support by vwv.\n"; \
      std::cout << "Exiting...\n\n";                                                 \
      exit(0);                                                                       \
    }                                                                                \

template<class PixelT>
Vector2 minmax_impl(TileLocator const& tile_info,
                    boost::shared_ptr<vw::platefile::PlateFile> platefile) {
  ImageView<PixelT> tile;
  platefile->read(tile, tile_info.col, tile_info.row, tile_info.level, tile_info.transaction_id);
  typename PixelChannelType<PixelT>::type min, max;
  min_max_channel_values(alpha_to_mask(tile), min, max);
  Vector2 result(min, max);
  std::cout << "Here is the original answer: " << result << "\n";
  result /= ChannelRange<typename PixelChannelType<PixelT>::type>::max();
  std::cout << "NEW MIN AND MAX: " << result << "\n";
  return result;
}

Vector2 PlatefileTileGenerator::minmax() {
  try {
    TileLocator loc;
    loc.col = 0;
    loc.row = 0;
    loc.level = 0;
    VW_DELEGATE_BY_PIXEL_TYPE(minmax_impl, loc, m_platefile)
  } catch (platefile::TileNotFoundErr &e) {
    return Vector2(0,1);
  }
}

// template<class PixelT>
// std::string sample_impl(TileLocator const& tile_info,
//                         Vector2 const& px_loc) {
//   ImageView<PixelT> tile;
//   platefile->read(tile, tile_info.col, tile_info.row, tile_info.level, tile_info.transaction_id);
//   VW_ASSERT(px_loc[0] >= 0 && px_loc[0] < tile.cols() &&
//             px_loc[1] >= 0 && px_loc[1] < tile.rows(),
//             ArgumentErr() << "sample_impl() invalid pixel location");
//   return tile(px_loc[0], px_loc[1]);
// }

PixelRGBA<float32> PlatefileTileGenerator::sample(int /*x*/, int /*y*/, int /*level*/, int /*transaction_id*/) {
  // TileLocator tile_loc;
  // tile_loc.col = floor(x/this->tile_size[0]);
  // tile_loc.row = floor(y/this->tile_size[1]);
  // tile_loc.level = this->num_levels();
  // px_loc = Vector2(x % this->tile_size[0],
  //                  y % this->tile_size[1]);

  try {
    return PixelRGBA<float32>(1.0, 0.0, 0.0, 1.0);
    //    VW_DELEGATE_BY_PIXEL_TYPE(sample_tile_impl, tile_loc, px_loc)
  } catch (platefile::TileNotFoundErr &e) {
    ImageView<PixelGrayA<uint8> > blank_tile(1,1);
    return PixelRGBA<float32>();
  }
}

template <class PixelT>
boost::shared_ptr<ViewImageResource> generate_tile_impl(TileLocator const& tile_info,
                                      boost::shared_ptr<vw::platefile::PlateFile> platefile) {
  ImageView<PixelT> tile(1,1);
  try {

    platefile->read(tile, tile_info.col, tile_info.row,
                    tile_info.level, tile_info.transaction_id,
                    tile_info.exact_transaction_id_match);

  } catch (platefile::TileNotFoundErr &e) {

    ImageView<PixelRGBA<uint8> > blank_tile(1,1);
    blank_tile(0,0) = PixelRGBA<uint8>(0, 20, 0, 255);
    return boost::shared_ptr<ViewImageResource>( new ViewImageResource(blank_tile) );

  } catch (vw::IOErr &e) {

    std::cout << "WARNING: AMQP ERROR -- " << e.what() << "\n";

  }
  return boost::shared_ptr<ViewImageResource>( new ViewImageResource(tile) );
}

boost::shared_ptr<ViewImageResource> PlatefileTileGenerator::generate_tile(TileLocator const& tile_info) {

  vw_out(DebugMessage, "gui") << "Request to generate platefile tile "
                              << tile_info.col << " " << tile_info.row
                              << " @ " << tile_info.level << "\n";

  VW_DELEGATE_BY_PIXEL_TYPE(generate_tile_impl, tile_info, m_platefile)

  // If we get to here, then there was no support for the pixel format.
  vw_throw(NoImplErr() << "Unsupported pixel format or channel type in TileGenerator.\n");
}

int PlatefileTileGenerator::cols() const {
  return this->tile_size()[0] * pow(2, m_num_levels-1);
}

int PlatefileTileGenerator::rows() const {
  return this->tile_size()[1] * pow(2, m_num_levels-1);
}

PixelFormatEnum PlatefileTileGenerator::pixel_format() const {
  return m_platefile->pixel_format();
}

ChannelTypeEnum PlatefileTileGenerator::channel_type() const {
  return m_platefile->channel_type();
}

Vector2i PlatefileTileGenerator::tile_size() const {
  return Vector2i(m_platefile->default_tile_size(),
                  m_platefile->default_tile_size());
}

int32 PlatefileTileGenerator::num_levels() const {
  return m_num_levels;
}

// --------------------------------------------------------------------------
//                             IMAGE TILE GENERATOR
// --------------------------------------------------------------------------

ImageTileGenerator::ImageTileGenerator(std::string filename) :
  m_filename(filename), m_rsrc( DiskImageResource::open(filename) ) {
  vw_out() << "\t--> Loading image: " << filename << ".\n";
}

// This little template makes the code below much cleaner.
template <class PixelT>
boost::shared_ptr<ViewImageResource> do_image_tilegen(boost::shared_ptr<SrcImageResource> rsrc,
                                                      BBox2i tile_bbox,
                                                      int level, int num_levels) {
  ImageView<PixelT> tile(tile_bbox.width(), tile_bbox.height());
  rsrc->read(tile.buffer(), tile_bbox);
  ImageView<PixelT> reduced_tile = subsample(tile, pow(2,(num_levels-1) - level));
  return boost::shared_ptr<ViewImageResource>( new ViewImageResource(reduced_tile) );
}

boost::shared_ptr<ViewImageResource> ImageTileGenerator::generate_tile(TileLocator const& tile_info) {

  // Compute the bounding box of the image and the tile that is being
  // requested.  The bounding box of the tile depends on the pyramid
  // level we are looking at.
  BBox2i image_bbox(0,0,m_rsrc->cols(),m_rsrc->rows());
  BBox2i tile_bbox = tile_to_bbox(this->tile_size(), tile_info.col,
                                  tile_info.row, tile_info.level, this->num_levels());

  // Check to make sure the image intersects the bounding box.  Print
  // an error to screen and return an empty tile if it does not.
  if (!image_bbox.intersects(tile_bbox)) {
    vw_out() << "WARNING in ImageTileGenerator: a tile was requested that doesn't exist.";
    ImageView<PixelGray<uint8> > blank_tile(this->tile_size()[0], this->tile_size()[1]);
    return boost::shared_ptr<ViewImageResource>( new ViewImageResource(blank_tile) );
  }

  // Make sure we don't access any pixels outside the image boundary
  // by cropping the tile to the image dimensions.
  tile_bbox.crop(image_bbox);

  switch (this->pixel_format()) {
  case VW_PIXEL_GRAY:
    if (this->channel_type() == VW_CHANNEL_UINT8) {
      return do_image_tilegen<PixelGray<uint8> >(m_rsrc, tile_bbox,
                                                 tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_INT16) {
      return do_image_tilegen<PixelGray<int16> >(m_rsrc, tile_bbox,
                                                 tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_UINT16) {
      return do_image_tilegen<PixelGray<uint16> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_FLOAT32) {
      return do_image_tilegen<PixelGray<float> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else {
      std::cout << "This platefile has a channel type that is not yet support by vwv.\n";
      std::cout << "Exiting...\n\n";
      exit(0);
  }
  break;

  case VW_PIXEL_GRAYA:
    if (this->channel_type() == VW_CHANNEL_UINT8) {
      return do_image_tilegen<PixelGrayA<uint8> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_INT16) {
      return do_image_tilegen<PixelGrayA<int16> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_UINT16) {
      return do_image_tilegen<PixelGrayA<uint16> >(m_rsrc, tile_bbox,
                                                   tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_FLOAT32) {
      return do_image_tilegen<PixelGrayA<float> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else {
      std::cout << "This image has a channel type that is not yet support by vwv.\n";
      std::cout << "Exiting...\n\n";
      exit(0);
      }

    break;

  case VW_PIXEL_RGB:
    if (this->channel_type() == VW_CHANNEL_UINT8) {
      return do_image_tilegen<PixelRGB<uint8> >(m_rsrc, tile_bbox,
                                                tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_UINT16) {
      return do_image_tilegen<PixelRGB<uint16> >(m_rsrc, tile_bbox,
                                                tile_info.level, this->num_levels());
    } else {
      std::cout << "This image has a channel type that is not yet support by vwv.\n";
      std::cout << "Exiting...\n\n";
      exit(0);
    }

    break;

  case VW_PIXEL_RGBA:
    if (this->channel_type() == VW_CHANNEL_UINT8) {
      return do_image_tilegen<PixelRGBA<uint8> >(m_rsrc, tile_bbox,
                                                 tile_info.level, this->num_levels());
    } else if (this->channel_type() == VW_CHANNEL_UINT16) {
      return do_image_tilegen<PixelRGBA<uint16> >(m_rsrc, tile_bbox,
                                                  tile_info.level, this->num_levels());
    } else {
      std::cout << "This image has a channel type that is not yet support by vwv.\n";
      std::cout << "Exiting...\n\n";
      exit(0);
    }

    break;

  default:
    std::cout << "This image has a pixel format that is not yet support by vwv.\n";
    std::cout << "Exiting...\n\n";
    exit(0);
  }

  vw_throw(NoImplErr() << "Unsupported pixel format or channel type in TileGenerator.\n");

}

Vector2 ImageTileGenerator::minmax() {
  return Vector2(0,1.0); // TODO: Implement this properly
}

PixelRGBA<float32> ImageTileGenerator::sample(int /*x*/, int /*y*/, int /*level*/, int /*transaction_id*/) {
  PixelRGBA<float32> result; // TODO: Implement this properly
  return result;
}

int ImageTileGenerator::cols() const {
  return m_rsrc->cols();
}

int ImageTileGenerator::rows() const {
  return m_rsrc->rows();
}

PixelFormatEnum ImageTileGenerator::pixel_format() const {
  return m_rsrc->pixel_format();
}

ChannelTypeEnum ImageTileGenerator::channel_type() const {
  return m_rsrc->channel_type();
}

Vector2i ImageTileGenerator::tile_size() const {
  return m_rsrc->block_read_size();
}

int32 ImageTileGenerator::num_levels() const {
  int32 max_dimension = std::max(this->cols(), this->rows());
  int32 max_tilesize = std::max(this->tile_size()[0], this->tile_size()[1]);
  return ceil(log(float(max_dimension) / max_tilesize) / log(2));
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2011 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <cctype>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <vector>

#include <boost/timer.hpp>

#include "rangecoder.hh"

template <class UInt>
void encode(std::vector<double> const& data,
	    Alphabet<UInt> const& alphabet,
	    std::ostream& out) {
  UInt const N=alphabet.size();
  RangeEncoder<UInt> encoder(out);

  for (size_t i=0; i<data.size(); ++i) {
    UInt s = static_cast<UInt>(floor(N*data[i]));
    s = std::min(s,N-1);
    encodeSymbol(encoder,alphabet,s);
  }
  // std::cerr << "compression rate: "
  //           << (double)data.size()*sizeof(double)/encoder.size()
  //           << '\n';
  // std::cerr.flush();
}

template <class UInt>
void basic(std::vector<double> const& data,
	    std::ostream& out) {
  out.write((char const*)(&data[0]),data.size()*sizeof(double));
}

template <class UInt>
double test(size_t n, char const* fname, bool renc=true) {
  std::vector<double> data(n);
  for (size_t i=0; i<n; ++i) {
    data[i] = (random()%1000000)/1000000.0;
    data[i] = std::pow(data[i],3.0);
  }

  UInt N = 1000;
  std::vector<UInt> count(N);
  for (size_t i=0; i<N; ++i)
    count[i] = (UInt) floor(20*std::pow((i+.5)/N,-0.6667));

  Alphabet<UInt> alphabet(count.begin(),count.end());

  std::ofstream out(fname,std::ios::binary);
  boost::timer timer;
  if (renc)
    encode(data,alphabet,out);
  else
    basic<UInt>(data,out);
  return timer.elapsed();
}

int main(int argc, char const* argv[]) {
  size_t n=100000;
  for (int i=0; i<45; ++i) {
    std::cout << n << ' ' << test<unsigned long>(n,"/dev/null")
	      << ' ' << test<unsigned long>(n,"/tmp/nix2")
	      << ' ' << test<unsigned long>(n,"/tmp/nix2",false)
	      << '\n';
    std::cout.flush();

    n = 6*n/5;
  }

  return 0;
}


#pragma once

#include <boost/accumulators/statistics/sum.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/any.hpp>

#include "AlloShared/StatsUtils.hpp"

namespace AlloReceiver
{
	const int FACE_COUNT = 12;

	Stats::StatValsMaker statValsMaker = [](boost::chrono::microseconds             window,
	                                        boost::chrono::steady_clock::time_point now)
	{
		std::list<Stats::StatVal> statVals;

		statVals.insert(statVals.end(),
		{
			StatsUtils::cubemapsCount("cubemapsCount",
			window,
			now)/*,
			StatsUtils::nalusBitSum("droppedNALUsBitSum",
			-1,
			StatsUtils::NALU::DROPPED,
			window,
			now),
			StatsUtils::nalusBitSum("addedNALUsBitSum",
			-1,
			StatsUtils::NALU::ADDED,
			window,
			now),
			StatsUtils::nalusBitSum("sentNALUsBitSum",
			-1,
			StatsUtils::NALU::SENT,
			window,
			now)*/
		});

		for (int face = -1; face < FACE_COUNT; face++)
		{
            std::string faceStr = std::to_string(face);

			statVals.insert(statVals.end(),
			{
				StatsUtils::facesCount("facesCount" + faceStr,
                    face,
                    window,
                    now),
                Stats::StatVal::makeStatVal(StatsUtils::andFilter(
                    {
                        StatsUtils::timeFilter(window,
                                               now),
                        StatsUtils::typeFilter(typeid(StatsUtils::Frame)),
                        [face](Stats::TimeValueDatum datum)
                        {
                            return (face == -1) ? true : boost::any_cast<StatsUtils::Frame>(datum.value).face == face;
                        },
                        [face](Stats::TimeValueDatum datum)
                        {
                            return boost::any_cast<StatsUtils::Frame>(datum.value).status == StatsUtils::Frame::RECEIVED;
                        }
                    }),
                    [](Stats::TimeValueDatum datum)
                    {
                        return 0.0;
                    },
                    boost::accumulators::tag::count(),
                    "receivedFrames" + faceStr),
                Stats::StatVal::makeStatVal(StatsUtils::andFilter(
                    {
                        StatsUtils::timeFilter(window,
                                               now),
                        StatsUtils::typeFilter(typeid(StatsUtils::Frame)),
                        [face](Stats::TimeValueDatum datum)
                        {
                          return (face == -1) ? true : boost::any_cast<StatsUtils::Frame>(datum.value).face == face;
                        },
                        [face](Stats::TimeValueDatum datum)
                        {
                            return boost::any_cast<StatsUtils::Frame>(datum.value).status == StatsUtils::Frame::DECODED;
                        }
                    }),
                    [](Stats::TimeValueDatum datum)
                    {
                        return 0.0;
                    },
                    boost::accumulators::tag::count(),
                    "decodedFrames" + faceStr),
                Stats::StatVal::makeStatVal(StatsUtils::andFilter(
                                                                  {
                                                                      StatsUtils::timeFilter(window,
                                                                                now),
                                                                      StatsUtils::typeFilter(typeid(StatsUtils::Frame)),
                                                                      [face](Stats::TimeValueDatum datum)
                                                                      {
                                                                          return (face == -1) ? true : boost::any_cast<StatsUtils::Frame>(datum.value).face == face;
                                                                      },
                                                                      [face](Stats::TimeValueDatum datum)
                                                                      {
                                                                          return boost::any_cast<StatsUtils::Frame>(datum.value).status == StatsUtils::Frame::COLOR_CONVERTED;
                                                                      }
                                                                  }),
                                            [](Stats::TimeValueDatum datum)
                                            {
                                                return 0.0;
                                            },
                                            boost::accumulators::tag::count(),
                                            "colorConvertedFrames" + faceStr),
                Stats::StatVal::makeStatVal(StatsUtils::andFilter(
                    {
                        [window, now, face](Stats::TimeValueDatum datum)
                        {
                            return (now - datum.time) < window && // time filter
                                datum.value.type() == typeid(StatsUtils::CubemapFace) && // type filter
                                boost::any_cast<StatsUtils::CubemapFace>(datum.value).status == StatsUtils::CubemapFace::ADDED && // status filter
                                (face == -1 || boost::any_cast<StatsUtils::CubemapFace>(datum.value).face == face); // face filter
                        }
                    }),
                    [](Stats::TimeValueDatum datum)
                    {
                        return 0.0;
                    },
                    boost::accumulators::tag::count(),
                    "addedFacesCount" + faceStr),
                Stats::StatVal::makeStatVal(StatsUtils::andFilter(
                    {
                        [window, now, face](Stats::TimeValueDatum datum)
                        {
                          return (now - datum.time) < window && // time filter
                            datum.value.type() == typeid(StatsUtils::CubemapFace) && // type filter
                            boost::any_cast<StatsUtils::CubemapFace>(datum.value).status == StatsUtils::CubemapFace::SCHEDULED && // status filter
                            (face == -1 || boost::any_cast<StatsUtils::CubemapFace>(datum.value).face == face); // face filter
                        }
                    }),
                    [](Stats::TimeValueDatum datum)
                    {
                        return 0.0;
                    },
                    boost::accumulators::tag::count(),
                    "scheduledFacesCount" + faceStr)
				/*StatsUtils::nalusCount("droppedNALUsCount" + std::to_string(face),
				face,
				StatsUtils::NALU::DROPPED,
				window,
				now),
				StatsUtils::nalusCount("addedNALUsCount" + std::to_string(face),
				face,
				StatsUtils::NALU::ADDED,
				window,
				now),
				StatsUtils::nalusCount("sentNALUsCount" + std::to_string(face),
				face,
				StatsUtils::NALU::SENT,
				window,
				now)*/
			});
		}

		return statVals;
	};

	Stats::PostProcessorMaker postProcessorMaker = [](boost::chrono::microseconds             window,
		                                              boost::chrono::steady_clock::time_point now)
	{
		Stats::PostProcessor postProcessor = [window, now](std::map<std::string, double>& results)
		{
			unsigned long seconds = boost::chrono::duration_cast<boost::chrono::seconds>(window).count();

			results["fps"] = results["cubemapsCount"] / seconds;

			//results.insert(
			//{

				/*{
					"naluDropRate",
					results["droppedNALUsCount-1"] / results["addedNALUsCount-1"]
				},*/
				/*{
					"fps",
					results["cubemapsCount"] / seconds
				},*/
				/*{
					"receivedNALUsBitS",
					(results["droppedNALUsBitSum"] + results["addedNALUsBitSum"]) / seconds
				},
				{
					"processedNALUsBitS",
					results["addedNALUsBitSum"] / seconds
				},
				{
					"sentNALUsBitS",
					results["sentNALUsBitSum"] / seconds
				},*/
			//});

			for (int face = -1; face < FACE_COUNT; face++)
			{
				std::string faceStr = std::to_string(face);

				results.insert(
				{
					{
						"facesPS" + faceStr,
						results["facesCount" + faceStr] / seconds
					},
					/*{
						"receivedNALUsPS" + faceStr,
						(results["droppedNALUsCount" + faceStr] + results["addedNALUsCount" + faceStr]) / seconds
					},
					{
						"processedNALUsPS" + faceStr,
						results["addedNALUsCount" + faceStr] / seconds
					},
					{
						"sentNALUsPS" + faceStr,
						results["sentNALUsCount" + faceStr] / seconds
					},*/
                    {
                        "receivedFrames" + faceStr + "PS",
                        results["receivedFrames" + faceStr] / seconds
                    },
                    {
                        "decodedFrames" + faceStr + "PS",
                        results["decodedFrames" + faceStr] / seconds
                    },
                    {
                        "colorConvertedFrames" + faceStr + "PS",
                        results["colorConvertedFrames" + faceStr] / seconds
                    },
                    {
                        "addedFaces" + faceStr + "PS",
                        results["addedFacesCount" + faceStr] / seconds
                    },
                    {
                        "scheduledFaces" + faceStr + "PS",
                        results["scheduledFacesCount" + faceStr] / seconds
                    }
				});
			}
		};

		return postProcessor;
	};

	auto formatStringMaker = []()
	{
		std::stringstream stream;
		/*stream << "-------------------------------------------------------------------------------" << std::endl;
		stream << "NALU drop rate: {naluDropRate:0.1f}" << std::endl;
		stream << "received NALUs/s: {receivedNALUsPS-1:0.1f}; {receivedNALUsBitS:0.1f} MBit/s;" << std::endl;
		for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
		{
			stream << "recvd NALUs/s per face (" << ((j == 0) ? "left" : "right") << "):";
			for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
			{
				stream << "\t{receivedNALUsPS" << j * 6 + i << ":0.1f}";
			}
			stream << ";" << std::endl;
		}
		stream << "-------------------------------------------------------------------------------" << std::endl;
		stream << "processed NALUs/s: {processedNALUsPS-1:0.1f}; {processedNALUsBitS:0.1f} MBit/s;" << std::endl;
		for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
		{
			stream << "prced NALUs/s per face (" << ((j == 0) ? "left" : "right") << "):";
			for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
			{
				stream << "\t{processedNALUsPS" << j * 6 + i << ":0.1f}";
			}
			stream << ";" << std::endl;
		}

		stream << "-------------------------------------------------------------------------------" << std::endl;
		stream << "sent NALUs/s: {sentNALUsPS-1:0.1f}; {sentNALUsBitS:0.1f} MBit/s;" << std::endl;
		for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
		{
			stream << "sent NALUs/s per face (" << ((j == 0) ? "left" : "right") << "):";
			for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
			{
				stream << "\t{sentNALUsPS" << j * 6 + i << ":0.1f}";
			}
			stream << ";" << std::endl;
		}*/

        stream << "-------------------------------------------------------------------------------" << std::endl;
        stream << "Received frames/s:" << std::endl;
        for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
        {
            stream << ((j == 0) ? "left" : "right") << ":";
            for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
            {
                stream << "\t{receivedFrames" << j * 6 + i << "PS:0.1f}";
            }
            stream << ";" << std::endl;
        }

        stream << "-------------------------------------------------------------------------------" << std::endl;
        stream << "Decoded frames/s:" << std::endl;
        for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
        {
            stream << ((j == 0) ? "left" : "right") << ":";
            for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
            {
                stream << "\t{decodedFrames" << j * 6 + i << "PS:0.1f}";
            }
            stream << ";" << std::endl;
        }

        stream << "-------------------------------------------------------------------------------" << std::endl;
        stream << "Color converted frames/s:" << std::endl;
        for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
        {
            stream << ((j == 0) ? "left" : "right") << ":";
            for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
            {
                stream << "\t{colorConvertedFrames" << j * 6 + i << "PS:0.1f}";
            }
            stream << ";" << std::endl;
        }

        /*stream << "Decoded:\t";
        for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
        {
            stream << "{receivedFrames" << faceStr << "PS:0.1f}\t";
        }
        stream << ";" << std::endl;*/

		stream << "-------------------------------------------------------------------------------" << std::endl;
		stream << "Added faces/s:" << std::endl;
        for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
        {
            stream << ((j == 0) ? "left" : "right") << ":";
            for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
            {
                stream << "\t{addedFaces" << j * 6 + i << "PS:0.1f}";
            }
            stream << ";" << std::endl;
        }

        stream << "-------------------------------------------------------------------------------" << std::endl;
        stream << "Scheduled faces/s:" << std::endl;
        for (int j = 0; j < (std::min) (2, FACE_COUNT); j++)
        {
            stream << ((j == 0) ? "left" : "right") << ":";
            for (int i = 0; i < (std::min) (6, FACE_COUNT - j * 6); i++)
            {
                stream << "\t{scheduledFaces" << j * 6 + i << "PS:0.1f}";
            }
            stream << ";" << std::endl;
        }

        stream << "-------------------------------------------------------------------------------" << std::endl;
        stream << "cubemap face 0-5 (left ) fps:";
        for (int i = 0; i < 6; i++)
        {
            stream << "\t{facesPS" << i << ":0.1f}";
        }
        stream << ";" << std::endl;

        stream << "cubemap face 0-5 (right) fps:";
        for (int i = 6; i < 12; i++)
        {
            stream << "\t{facesPS" << i << ":0.1f}";
        }
        stream << ";" << std::endl;
        stream << "fps: {fps:0.1f}" << std::endl;

		return stream.str();
	};
}