#include "gene/coding/dna.hpp"
#include "gene/policies.hpp"
#include "gene/algorithm.hpp"

#include <utility>
#include <boost/noncopyable.hpp>

using namespace gene;
using namespace gene::coding::dna;

///////////////////////////////////////////////////////////////////////////////
struct Neuron : boost::noncopyable
{
  uint8_t x;
  uint8_t y;
  uint8_t z;
  uint8_t distanceThreshold;
  uint8_t outputThreshold;
};

///////////////////////////////////////////////////////////////////////////////
struct Network
{
  std::vector<Network> neurons;
};

///////////////////////////////////////////////////////////////////////////////
struct MyFactory : public Codec<Network, Genotype>
{
  Network decode(const Genotype&) const throw(std::invalid_argument) override { }
  Genotype encode(const Network&) const override { };
};

///////////////////////////////////////////////////////////////////////////////
struct MyFitness : FitnessFunction<Network, Genotype>
{
  Fitness calculate(const Population<Network, Genotype>&) override { }
};

///////////////////////////////////////////////////////////////////////////////
struct Survival : public SurvivalPolicy<Network, Genotype>
{
  Population<Network, Genotype>
          selectSurvivors (Population<Network, Genotype>&& ancestors,
                           Population<Network, Genotype>&& offspring) override { }
};

///////////////////////////////////////////////////////////////////////////////
int main(void)
{
  std::vector<gene::coding::dna::Chromosome> chromosomes;
  gene::coding::dna::Genotype g(std::move(chromosomes));

  BaseMutation<Network> mutation(0.10, 423);
  ConstantMutationRate<Network> mutationRate(0.20);
  SimpleCrossover crossover(1245);

  MyFactory factory;
  MyFitness fitness;
  Survival survival;
/*
  GeneticAlgorithm<Individual, Genotype> (factory,
                                          fitness,
                                          mutation,
                                          mutationRate,
                                          attraction,
                                          crossover,
                                          survival);
*/  return 0;
}


#ifndef BOOST_PREPROCESSOR_REPEAT_FROM_TO_HPP
#define BOOST_PREPROCESSOR_REPEAT_FROM_TO_HPP

/* Copyright (C) 2002
 * Housemarque Oy
 * http://www.housemarque.com
 *
 * Permission to copy, use, modify, sell and distribute this software is
 * granted provided this copyright notice appears in all copies. This
 * software is provided "as is" without express or implied warranty, and
 * with no claim as to its suitability for any purpose.
 *
 * See http://www.boost.org for most recent version.
 */

#include <boost/preprocessor/repeat.hpp>
#include <boost/preprocessor/arithmetic/sub.hpp>
#include <boost/preprocessor/arithmetic/add.hpp>

/**
Repeats the macro MACRO(INDEX,DATA) for INDEX = [FIRST,LAST).

In other words, expands to the sequence:

  MACRO(FIRST,DATA) MACRO(BOOST_PP_INC(FIRST),DATA) ... MACRO(BOOST_PP_DEC(LAST),DATA)

For example,

  #define TEST(INDEX,DATA) DATA(INDEX);
  BOOST_PP_REPEAT_FROM_TO(4,7,TEST,X)

expands to:

  X(4); X(5); X(6);

Uses

BOOST_PP_REPEAT()

Test

repeat_test.cpp

*/
#define BOOST_PP_REPEAT_FROM_TO(FIRST,LAST,MACRO,DATA) BOOST_PP_REPEAT(BOOST_PP_SUB(LAST,FIRST),BOOST_PP_REPEAT_FROM_TO_F,(FIRST,MACRO,DATA))
#define BOOST_PP_REPEAT_FROM_TO_F(I,SMP) BOOST_PP_TUPLE_ELEM(3,1,SMP)(BOOST_PP_ADD(I,BOOST_PP_TUPLE_ELEM(3,0,SMP)),BOOST_PP_TUPLE_ELEM(3,2,SMP))
#endif


//----------------------------------------------------------------------------
// Copyright (C) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Server Side Public License, version 1,
// as published by the author.
//
// 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
// Server Side Public License for more details.
//
// You should have received a copy of the Server Side Public License
// along with this program. If not, see
// <https://github.com/NilFoundation/plugin/blob/master/LICENSE_1_0.txt>.
//----------------------------------------------------------------------------

#define BOOST_TEST_MODULE drg_compound_test

#include <boost/test/data/monomorphic.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/unit_test.hpp>

#include <nil/filecoin/storage/proofs/porep/drg/compound.hpp>

using namespace nil::filecoin;

template<typename MerkleTreeType>
void drgporep_test_compound() {

    const auto rng = XorShiftRng::from_seed(crate::TEST_SEED);

    const auto nodes = 8;
    const auto degree = BASE_DEGREE;
    const std::vector<auto> challenges = {1, 3};

    const auto replica_id : Fr = Fr::random(rng);
    std::vector<std::uint8_t> data = (0..nodes).flat_map(| _ | fr_into_bytes(&Fr::random(rng))).collect();

    // MT for original data is always named tree-d, and it will be
    // referenced later in the process as such.
    const auto cache_dir = tempfile::tempdir();
    const auto config = StoreConfig(cache_dir.path(), cache_key::CommDTree.to_string(),
                                   default_rows_to_discard(nodes, BINARY_ARITY), );

    // Generate a replica path.
    const auto replica_path = cache_dir.path().join("replica-path");
    auto mmapped_data = setup_replica(&data, &replica_path);

    const auto setup_params = compound_proof::SetupParams {
        vanilla_params : drg::SetupParams {
            drg : drg::DrgParams {
                nodes,
                degree,
                expansion_degree : 0,
                porep_id : [32; 32],
            },
            private : false,
            challenges_count : 2,
        },
        partitions : None,
        priority : false,
    };

    const auto public_params =
        drg_porep_compound<typename MerkleTreeType::hash_type, BucketGraph<typename MerkleTreeType::hash_type>>::setup(&setup_params).expect("setup failed");

    const auto data_tree : Option<BinaryMerkleTree<typename MerkleTreeType::hash_type>> = None;
    const auto(tau, aux) = drg::DrgPoRep::<typename MerkleTreeType::hash_type, BucketGraph<_>>::replicate(
                        &public_params.vanilla_params, &replica_id.into(), (mmapped_data).into(), data_tree,
                        config, replica_path.clone(), )
                        .expect("failed to replicate");

    const auto public_inputs = drg::PublicInputs:: <typename MerkleTreeType::hash_type::digest_type > {
        replica_id : Some(replica_id.into()),
        challenges,
        tau : Some(tau),
    };
    const auto private_inputs = drg::PrivateInputs {
        tree_d : &aux.tree_d,
        tree_r : &aux.tree_r,
        tree_r_config_rows_to_discard : default_rows_to_discard(nodes, BINARY_ARITY),
    };

    // This duplication is necessary so public_params don't outlive public_inputs and private_inputs.
    const auto setup_params = compound_proof::SetupParams {
        vanilla_params : drg::SetupParams {
            drg : drg::DrgParams {
                nodes,
                degree,
                expansion_degree : 0,
                porep_id : [32; 32],
            },
            private : false,
            challenges_count : 2,
        },
        partitions : None,
        priority : false,
    };

    const auto public_params =
        drg_porep_compound<typename MerkleTreeType::hash_type, BucketGraph<typename MerkleTreeType::hash_type>>::setup(&setup_params).expect("setup failed");

    const auto(circuit, inputs) =
        drg_porep_compound<typename MerkleTreeType::hash_type, _>::circuit_for_test(&public_params, &public_inputs, &private_inputs, )
            ;

    auto cs = TestConstraintSystem();

    circuit.synthesize(cs).expect("failed to synthesize test circuit");
    assert(cs.is_satisfied());
    assert(cs.verify(&inputs));

    const auto blank_circuit =
        <drg_porep_compound<_, _> as CompoundProof<_, _>>::blank_circuit(&public_params.vanilla_params, );

    auto cs_blank = MetricCS();
    blank_circuit.synthesize(cs_blank).expect("failed to synthesize blank circuit");

    const auto a = cs_blank.pretty_print_list();
    const auto b = cs.pretty_print_list();

    for (i, (a, b)) in a.chunks(100).zip(b.chunks(100)).enumerate() {
        BOOST_ASSERT_MSG(a == b, std::format("failed at chunk %d", i));
    }

    const auto gparams = drg_porep_compound<typename MerkleTreeType::hash_type>::groth_params(Some(rng), &public_params.vanilla_params, )
                      .expect("failed to get groth params");

    const auto proof = drg_porep_compound<typename MerkleTreeType::hash_type>::prove(&public_params, &public_inputs, &private_inputs, &gparams, )
                    .expect("failed while proving");

    const auto verified = drg_porep_compound<typename MerkleTreeType::hash_type>::verify(&public_params, &public_inputs, &proof, &NoRequirements, )
                       .expect("failed while verifying");

    assert(verified);

    cache_dir.close().expect("Failed to remove cache dir");
}

BOOST_AUTO_TEST_SUITE(drg_compound_test_suite)

BOOST_AUTO_TEST_CASE(test_drgporep_compound_pedersen) {
    drgporep_test_compound<BinaryMerkleTree<PedersenHasher>>();
}

BOOST_AUTO_TEST_CASE(test_drgporep_compound_poseidon) {
    drgporep_test_compound<BinaryMerkleTree<PoseidonHasher>>();
}

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/**
  Copyright 2017 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================

#ifndef BOOST_SIMD_FUNCTION_SIMD_ASIN_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SIMD_ASIN_HPP_INCLUDED

#include <boost/simd/function/scalar/asin.hpp>
#include <boost/simd/arch/common/generic/function/autodispatcher.hpp>
#include <boost/simd/arch/common/simd/function/asin.hpp>

#endif


// __BEGIN_LICENSE__
//  Copyright (c) 2006-2013, United States Government as represented by the
//  Administrator of the National Aeronautics and Space Administration. All
//  rights reserved.
//
//  The NASA Vision Workbench is licensed under the Apache License,
//  Version 2.0 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// __END_LICENSE__

#ifdef _MSC_VER
#pragma warning(disable:4244)
#pragma warning(disable:4267)
#pragma warning(disable:4996)
#endif

#include <vw/Core/Debugging.h>
#include <vw/Math/Geometry.h>
#include <vw/Math/RANSAC.h>
#include <vw/Math/Vector.h>
#include <vw/Image/Algorithms.h>
#include <vw/Image/EdgeExtension.h>
#include <vw/Image/Manipulation.h>
#include <vw/Image/MaskViews.h>
#include <vw/Image/Transform.h>
#include <vw/InterestPoint/InterestData.h>
#include <vw/Stereo/CorrelationView.h>
#include <vw/Stereo/CostFunctions.h>
#include <vw/Stereo/PreFilter.h>
#include <vw/Stereo/DisparityMap.h>
#include <vw/Cartography/GeoReferenceUtils.h>

#include <boost/program_options.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
namespace po = boost::program_options;
namespace fs = boost::filesystem;

using namespace vw;
using namespace vw::stereo;

int main( int argc, char *argv[] ) {

  std::string left_file_name, right_file_name;
  float log;
  int32 h_corr_min, h_corr_max;
  int32 v_corr_min, v_corr_max;
  int32 xkernel, ykernel;
  int   lrthresh;
  int   min_lr_level;
  int   nThreads;
  int   tile_size;
  int   collar_size;
  int   sgm_filter_size;
  int   max_pyramid_levels;
  int   cost_mode;
  int   stereo_algorithm;
  bool  found_alignment = false;
  int   blob_filter_area;
  Matrix3x3 alignment;
  float mask_value;
  int   filter_radius;
  int   mem_limit_gb;

  po::options_description desc("Options");
  desc.add_options()
    ("help,h", "Display this help message")
    ("left",       po::value(&left_file_name),                 "Explicitly specify the \"left\" input file")
    ("right",      po::value(&right_file_name),                "Explicitly specify the \"right\" input file")
    ("log",        po::value(&log)->default_value(1.4),        "Apply LOG filter with the given sigma, or 0 to disable")
    ("h-corr-min", po::value(&h_corr_min)->default_value(-30), "Minimum horizontal disparity")
    ("h-corr-max", po::value(&h_corr_max)->default_value( 30), "Maximum horizontal disparity")
    ("v-corr-min", po::value(&v_corr_min)->default_value(-5),  "Minimum vertical disparity")
    ("v-corr-max", po::value(&v_corr_max)->default_value(5),   "Maximum vertical disparity")
    ("xkernel",    po::value(&xkernel)->default_value(15),     "Horizontal correlation kernel size")
    ("ykernel",    po::value(&ykernel)->default_value(15),     "Vertical correlation kernel size")
    ("lrthresh",   po::value(&lrthresh)->default_value(2),     "Left/right correspondence threshold")
    ("min-lr-level",    po::value(&min_lr_level)->default_value(1),     "Min level to check L/R correspondence at (SGM only).")
    ("filter-radius",      po::value(&filter_radius)->default_value(5), "Disp filtering radius")
    ("cost-mode",          po::value(&cost_mode)->default_value(0),
                        "0 - Abs difference; 1 - Sq Difference; 2 - NormXCorr; 3 - Census; 4 - Ternary Census")
    ("stereo-algorithm",   po::value(&stereo_algorithm)->default_value(0),
                    "Choose the stereo algorithm: 0=BM, 1=SGM, 2=MGM, 3=FinalMGM")
    //("affine-subpix", "Enable affine adaptive sub-pixel correlation (slower, but more accurate)") // TODO: Unused!
    ("blob-filter-area",   po::value(&blob_filter_area)->default_value(0),     "Filter blobs of this size.")
    ("threads",            po::value(&nThreads)->default_value(0),    "Manually specify the number of threads")
    ("tile-size",          po::value(&tile_size)->default_value(0),   "Manually specify the tile size")
    ("collar-size",        po::value(&collar_size)->default_value(0), "Specify a collar size")
    ("max-mem-GB",         po::value(&mem_limit_gb)->default_value(0), "Specify the maximum memory size")
    ("sgm-filter-size",    po::value(&sgm_filter_size)->default_value(0), "Filter SGM subpixel results with this size")
    ("mask-value",         po::value(&mask_value)->default_value(-32768), "Specify a mask value")
    ("max-pyramid-levels", po::value(&max_pyramid_levels)->default_value(5),
      "Limit the maximum number of pyramid levels")
    ("debug",      "Write out debugging images")
    ;
  po::positional_options_description p;
  p.add("left", 1);
  p.add("right", 1);

  po::variables_map vm;
  try {
    po::store( po::command_line_parser( argc, argv ).options(desc).positional(p).run(), vm );
    po::notify( vm );
  } catch (const po::error& e) {
    std::cout << "An error occurred while parsing command line arguments.\n";
    std::cout << "\t" << e.what() << "\n\n";
    std::cout << desc << std::endl;
    return 1;
  }

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

  if( vm.count("left") != 1 || vm.count("right") != 1 ) {
    vw_out() << "Error: Must specify one (and only one) left and right input file!" << std::endl;
    vw_out() << desc << std::endl;
    return 1;
  }

  if (nThreads > 0)
    vw::vw_settings().set_default_num_threads(nThreads);
  if (tile_size > 0)
    vw::vw_settings().set_default_tile_size(tile_size);

  // If the user provided a match file, use it to align the images.
  std::string match_filename = fs::path( left_file_name ).replace_extension().string() + "__" +
                               fs::path( right_file_name ).stem().string() + ".match";
  if ( fs::exists( match_filename ) ) {
    vw_out() << "Found a match file. Using it to pre-align images.\n";
    std::vector<ip::InterestPoint> matched_ip1, matched_ip2;
    ip::read_binary_match_file( match_filename,
                                matched_ip1, matched_ip2 );
    std::vector<Vector3> ransac_ip1 = ip::iplist_to_vectorlist(matched_ip1);
    std::vector<Vector3> ransac_ip2 = ip::iplist_to_vectorlist(matched_ip2);
    vw::math::RandomSampleConsensus<vw::math::HomographyFittingFunctor, vw::math::InterestPointErrorMetric>
        ransac( vw::math::HomographyFittingFunctor(), vw::math::InterestPointErrorMetric(), 100, 30, ransac_ip1.size()/2, true );
    alignment = ransac( ransac_ip2, ransac_ip1 );

    DiskImageView<PixelGray<float> > right_disk_image( right_file_name );
    right_file_name = "aligned_right.tif";
    write_image( right_file_name, transform(right_disk_image, HomographyTransform(alignment)),
                 TerminalProgressCallback( "tools.correlate", "Aligning: ") );
    found_alignment = true;
  }

  // Load input images
  DiskImageView<PixelGray<float> > left_disk_image (left_file_name );
  DiskImageView<PixelGray<float> > right_disk_image(right_file_name );
  int cols = std::min(left_disk_image.cols(),right_disk_image.cols());
  int rows = std::min(left_disk_image.rows(),right_disk_image.rows());
  ImageViewRef<PixelGray<float> > left  = edge_extend(left_disk_image, 0,0,cols,rows);
  ImageViewRef<PixelGray<float> > right = edge_extend(right_disk_image,0,0,cols,rows);

  // Set up masks
  ImageView<uint8> left_mask  = constant_view( uint8(255), left );
  ImageView<uint8> right_mask = constant_view( uint8(255), right);
  if (vm.count("mask-value")) {
    left_mask  = apply_mask(copy_mask(left_mask,  create_mask(left,  mask_value)), 0);
    right_mask = apply_mask(copy_mask(right_mask, create_mask(right, mask_value)), 0);

    write_image("correlate_left_mask.tif", left_mask);
    write_image("correlate_right_mask.tif", right_mask);
  }

  bool write_debug_images = (vm.count("debug"));

  // TODO: Hook up to options!
  SemiGlobalMatcher::SgmSubpixelMode sgm_subpixel_mode = SemiGlobalMatcher::SUBPIXEL_LC_BLEND;
  Vector2i sgm_search_buffer(4,2);
  size_t memory_limit_mb = 1024*5;
  if (mem_limit_gb > 0)
    memory_limit_mb = mem_limit_gb * 1024;

  ImageViewRef<PixelMask<Vector2f> > disparity_map;
  int corr_timeout = 0;
  double seconds_per_op = 0.0;
  BBox2i search_range(Vector2i(h_corr_min, v_corr_min),
                      Vector2i(h_corr_max, v_corr_max));
  Vector2i kernel_size(xkernel, ykernel);
  std::cout << "Correlate max search range = " << search_range << std::endl;
  disparity_map =
    stereo::pyramid_correlate( left,      right,
                               left_mask, right_mask,
                               stereo::PREFILTER_LOG, log,
                               search_range, kernel_size,
                               static_cast<vw::stereo::CostFunctionType>(cost_mode),
                               corr_timeout, seconds_per_op,
                               lrthresh, min_lr_level,
                               filter_radius, max_pyramid_levels,
                               static_cast<vw::stereo::CorrelationAlgorithm>(stereo_algorithm),
                               collar_size,
                               sgm_subpixel_mode,
                               sgm_search_buffer,
                               memory_limit_mb,
                               blob_filter_area,
                               write_debug_images);

  // TODO: Call the code used in stereo_fltr!

/*

// TODO: Debug code for experimenting with disparity filtering code.
  int texture_smooth_range = 15;
  float texture_max_percentage = 0.85;
  int max_kernel_size = 13;

  std::cout << "Generating texture image...\n";
  ImageView<float> texture_image;
  ImageView<float> leftR = left;
  float max_texture = texture_measure(leftR, texture_image, texture_smooth_range);
  write_image( "texture_image.tif", texture_image );

  std::cout << "Rasterizing disparity image...\n";
  ImageView<PixelMask<Vector2f> > disparity_map_raster = disparity_map;
  //disparity_map.rasterize(disparity_map_raster, bounding_box(disparity_map));

  std::cout << "Filtering disparity image...\n";
  ImageView<PixelMask<Vector2f> > disparity_map_filtered;
  texture_preserving_disparity_filter(disparity_map_raster, disparity_map_filtered, texture_image,
                                      texture_max_percentage*max_texture, max_kernel_size);

  write_image( "texture_filtered_disp.tif", disparity_map_filtered );
  //write_image( "subpixel_disp.tif", disparity );
  //write_image( "subpixel_deltas.tif", deltas );

  std::cout << "Done!\n";

*/

  //ImageViewRef<PixelMask<Vector2f> > result = pixel_cast<PixelMask<Vector2f> >(disparity_map);
  if ( found_alignment )
    disparity_map = transform_disparities(disparity_map, HomographyTransform(alignment) );

  // Actually invoke the raster
  {
    vw::Timer corr_timer("Correlation Time");
    cartography::GdalWriteOptions geo_opt;
    geo_opt.raster_tile_size = Vector2i(1024, 1024);
    if (stereo_algorithm == VW_CORRELATION_BM) {
      block_write_gdal_image("disparity.tif", disparity_map, geo_opt);
    }
    else { // SGM/MGM needs to be rasterized in a single tile.
      ImageView<PixelMask<Vector2f> > result = disparity_map;
      block_write_gdal_image("disparity.tif", result, geo_opt);
    }
  }

  //// Write disparity debug images
  //DiskImageView<PixelMask<Vector2i> > solution("disparity.tif");
  //BBox2 disp_range = get_disparity_range(solution);
  //std::cout << "Found disparity range: " << disp_range << "\n";

  // Are these working properly?
  //write_image( "x_disparity.tif",
  //             channel_cast<uint8>(apply_mask(copy_mask(clamp(normalize(select_channel(solution,0),
  //                                 disp_range.min().x(), disp_range.max().x(),0,255)),solution))) );
  //write_image( "y_disparity.tif",
  //             channel_cast<uint8>(apply_mask(copy_mask(clamp(normalize(select_channel(solution,1),
  //                                 disp_range.min().y(), disp_range.max().y(),0,255)),solution))) );

  return 0;
}


#include <string>

#include <boost/lexical_cast.hpp>

#include <crypto++/aes.h>
#include <crypto++/ccm.h>
#include <crypto++/cryptlib.h>
#include <crypto++/filters.h>
#include <crypto++/hex.h>

#include "ccrypt.hpp"
//#include "error.hpp"
#include "errlogger.hpp"

using namespace std;
using CryptoPP::AES;
using CryptoPP::CBC_Mode;
using CryptoPP::Exception;
using CryptoPP::HexDecoder;
using CryptoPP::HexEncoder;
using CryptoPP::StreamTransformationFilter;
using CryptoPP::StringSink;
using CryptoPP::StringSource;
using namespace CCrypt;

unsigned char EncDec::key[] = { 0x23, 0x56, 0x31, 0x76, 0x61, 0x4d, 0x61, 0x72, 0x61, 0x6c, 0x3a, 0x2d, 0x2a, 0x2f, 0x2e, 0x3f };
unsigned char EncDec::iv[] = { 0x38, 0x34, 0x32, 0x34, 0x33, 0x30, 0x31, 0x30, 0x34, 0x33, 0x35, 0x39, 0x32, 0x34, 0x2e, 0x34 };

const string EncDec::Encrypt(const string& plainText) {
    string cipher, encoded;

    try {
        CBC_Mode<AES>::Encryption enc;
        enc.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv));

        cipher.clear();
        StringSource(plainText, true, new StreamTransformationFilter(enc, new StringSink(cipher)));

        encoded.clear();
        StringSource(cipher, true, new HexEncoder(new StringSink(encoded)));
    }
    catch(CryptoPP::Exception& ex) {
        SAAIIR::ErrLogger::LogError(("Encrypt :  " + boost::lexical_cast<string>(ex.what())).c_str());
        //throw Error(ex.what());
    }

    return encoded;
}

const string EncDec::Decrypt(const string& cipherText) {
    string cipher, recovered;

    try {
        CBC_Mode<AES>::Decryption dec;
        dec.SetKeyWithIV(key, sizeof(key), iv, sizeof(iv));

        cipher.clear();
        StringSource(cipherText, true, new HexDecoder(new StringSink(cipher)));

        recovered.clear();
        StringSource s(cipher, true, new StreamTransformationFilter(dec, new StringSink(recovered)));
    }
    catch(CryptoPP::Exception& ex) {
        SAAIIR::ErrLogger::LogError(("Decrypt :  " + boost::lexical_cast<string>(ex.what())).c_str());
        //throw Error(ex.what());
    }

    return recovered;
}


/*****************************************************************************
 * Licensed to Qualys, Inc. (QUALYS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * QUALYS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ****************************************************************************/

/**
 * @file
 * @brief IronBee --- CLIPP Split Modifier Implementation
 *
 * @author Christopher Alfeld <calfeld@qualys.com>
 */

#include "split_modifier.hpp"

#include <clipp/random_support.hpp>

#ifdef __clang__
#pragma clang diagnostic push
#if __has_warning("-Wunused-local-typedef")
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif
#endif
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#ifdef __clang__
#pragma clang diagnostic pop
#endif

#include <ctime>

using boost::bind;
using namespace std;

namespace IronBee {
namespace CLIPP {

// Split Data

struct SplitDataModifier::State
{
    //! Distribution of targets.
    distribution_t distribution;
};

SplitDataModifier::SplitDataModifier(size_t n) :
    m_state(new State())
{
    m_state->distribution = bind(constant_distribution, n);
}

bool SplitDataModifier::operator()(Input::input_p& input)
{
    if (! input) {
        return true;
    }

    BOOST_FOREACH(Input::Transaction& tx, input->connection.transactions) {
        Input::event_p event;
        for (
            Input::event_list_t::iterator i = tx.events.begin();
            i != tx.events.end();
            ++i
        ) {
            event = *i;
            Input::event_list_t::iterator at = boost::next(i);
            if (
                event->which == Input::REQUEST_BODY ||
                event->which == Input::RESPONSE_BODY ||
                event->which == Input::CONNECTION_DATA_IN ||
                event->which == Input::CONNECTION_DATA_OUT
            ) {
                double post_delay = event->post_delay;
                event->post_delay = 0;

                Input::DataEvent* new_event =
                    dynamic_cast<Input::DataEvent*>(event.get());
                if (! new_event) {
                    throw logic_error("Event had type/which mismatch.");
                }

                Input::Buffer original = new_event->data;

                while (original.length > 0) {
                    size_t length = min(
                        original.length,
                        m_state->distribution()
                    );

                    new_event->data = Input::Buffer(original.data, length);
                    original.data += length;
                    original.length -= length;

                    if (original.length > 0) {
                        new_event = new Input::DataEvent(event->which);
                        tx.events.insert(at, Input::event_p(new_event));
                    }
                }

                i = boost::prior(at);
                (*i)->post_delay = post_delay;
            }
        }
    }

    return true;
}

SplitDataModifier SplitDataModifier::uniform(
    unsigned int min,
    unsigned int max
)
{
    if (min > max) {
        throw runtime_error("Min must be less than or equal to max.");
    }
    if (min == 0 || max == 0) {
        throw runtime_error("Min and max must be positive.");
    }
    SplitDataModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::uniform_int_distribution<>(min, max)
    );
    return mod;
}

SplitDataModifier SplitDataModifier::binomial(unsigned int t, double p)
{
    if (t == 0 || p <= 0) {
        throw runtime_error("t and p must be positive.");
    }
    if (p > 1) {
        throw runtime_error("p must be less than or equal to 1.");
    }
    SplitDataModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::binomial_distribution<>(t, p)
    );
    return mod;
}

SplitDataModifier SplitDataModifier::geometric(double p)
{
    if (p < 0 || p >= 1) {
        throw runtime_error("p must be in [0,1)");
    }
    SplitDataModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::geometric_distribution<>(p)
    );
    return mod;
}

SplitDataModifier SplitDataModifier::poisson(double mean)
{
    SplitDataModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::poisson_distribution<>(mean)
    );
    return mod;
}

// SplitHeader

struct SplitHeaderModifier::State
{
    //! Distribution of targets.
    distribution_t distribution;
};

SplitHeaderModifier::SplitHeaderModifier(size_t n) :
    m_state(new State())
{
    m_state->distribution = bind(constant_distribution, n);
}

bool SplitHeaderModifier::operator()(Input::input_p& input)
{
    if (! input) {
        return true;
    }

    BOOST_FOREACH(Input::Transaction& tx, input->connection.transactions) {
        Input::event_p event;
        for (
            Input::event_list_t::iterator i = tx.events.begin();
            i != tx.events.end();
            ++i
        ) {
            event = *i;
            Input::event_list_t::iterator at = boost::next(i);
            if (
                event->which == Input::REQUEST_HEADER ||
                event->which == Input::RESPONSE_HEADER
            ) {
                double post_delay = event->post_delay;
                event->post_delay = 0;

                Input::HeaderEvent* new_event =
                    dynamic_cast<Input::HeaderEvent*>(event.get());
                if (! new_event) {
                    throw logic_error("Event had type/which mismatch.");
                }

                Input::header_list_t original = new_event->headers;
                Input::header_list_t::const_iterator original_i =
                    original.begin();
                size_t original_remaining = original.size();

                while (original_remaining > 0) {
                    size_t length = min(
                        original_remaining,
                        m_state->distribution()
                    );

                    new_event->headers.clear();
                    Input::header_list_t::const_iterator original_j =
                        boost::next(original_i, length);
                    copy(
                        original_i, original_j,
                        back_inserter(new_event->headers)
                    );
                    original_i = original_j;
                    original_remaining -= length;

                    if (original_remaining > 0) {
                        new_event = new Input::HeaderEvent(event->which);
                        tx.events.insert(at, Input::event_p(new_event));
                    }
                }

                i = boost::prior(at);
                (*i)->post_delay = post_delay;
            }
        }
    }

    return true;
}

SplitHeaderModifier SplitHeaderModifier::uniform(
    unsigned int min,
    unsigned int max
)
{
    if (min > max) {
        throw runtime_error("Min must be less than or equal to max.");
    }
    if (min == 0 || max == 0) {
        throw runtime_error("Min and max must be positive.");
    }
    SplitHeaderModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::uniform_int_distribution<>(min, max)
    );
    return mod;
}

SplitHeaderModifier SplitHeaderModifier::binomial(unsigned int t, double p)
{
    if (t == 0 || p <= 0) {
        throw runtime_error("t and p must be positive.");
    }
    if (p > 1) {
        throw runtime_error("p must be less than or equal to 1.");
    }
    SplitHeaderModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::binomial_distribution<>(t, p)
    );
    return mod;
}

SplitHeaderModifier SplitHeaderModifier::geometric(double p)
{
    if (p < 0 || p >= 1) {
        throw runtime_error("p must be in [0,1)");
    }
    SplitHeaderModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::geometric_distribution<>(p)
    );
    return mod;
}

SplitHeaderModifier SplitHeaderModifier::poisson(double mean)
{
    SplitHeaderModifier mod;
    mod.m_state->distribution = make_random_distribution(
        boost::random::poisson_distribution<>(mean)
    );
    return mod;
}

} // CLIPP
} // IronBee


//  Copyright (c) 2011 John Maddock
//  Use, modification and distribution are 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_MP_BIG_LANCZOS
#define BOOST_MP_BIG_LANCZOS

#include <boost/math/bindings/detail/big_lanczos.hpp>

namespace boost{ namespace math{

namespace lanczos{

template <class T, class Policy>
struct lanczos;

template<class Backend, boost::multiprecision::expression_template_option ExpressionTemplates, class Policy>
struct lanczos<multiprecision::number<Backend, ExpressionTemplates>, Policy>
{
   typedef typename boost::math::policies::precision<multiprecision::number<Backend, ExpressionTemplates>, Policy>::type precision_type;
   typedef typename mpl::if_c<
      precision_type::value <= 73,
      lanczos13UDT,
      typename mpl::if_c<
         precision_type::value <= 122,
         lanczos22UDT,
         undefined_lanczos
      >::type
   >::type type;
};

} // namespace lanczos

}} // namespaces

#endif


/**
 * Copyright (c) 2017 Melown Technologies SE
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include <algorithm>

#include <boost/format.hpp>
#include <boost/filesystem.hpp>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include "math/math.hpp"
#include "math/geometry.hpp"
#include "math/transform.hpp"
#include "imgproc/scanconversion.hpp"
#include "imgproc/binterpolate.hpp"

#include "geometry/faceclip.hpp"
#include "geometry/pointindex.hpp"
#include "imgproc/uvpack.hpp"

#include "geometry/mesh.hpp"
#include "geometry/meshop.hpp"

#include "tileset.hpp"
#include "merge.hpp"
#include "io.hpp"

#include "geometry/binmesh.hpp"

namespace ublas = boost::numeric::ublas;
namespace va = vtslibs;
namespace fs = boost::filesystem;

#ifndef BUILDSYS_CUSTOMER_BUILD
//#define DEBUG 1 // save debug images
#endif

namespace cv {

// comparison operators on cv::Point2 and cv::Point3 for va::PointIndex
template<typename T>
inline bool operator< (const Point_<T> &lhs, const Point_<T> &rhs)
{
    if (lhs.x != rhs.x) return lhs.x < rhs.x;
    return lhs.y < rhs.y;
}

template<typename T>
inline bool operator< (const Point3_<T> &lhs, const Point3_<T> &rhs)
{
    if (lhs.x != rhs.x) return lhs.x < rhs.x;
    if (lhs.y != rhs.y) return lhs.y < rhs.y;
    return lhs.z < rhs.z;
}

} // namespace cv

namespace vtslibs { namespace vts0 {

namespace {

const auto QBufferCvType = CV_16S;
typedef signed short QBufferCType;

double triangleArea(const math::Point3 &a, const math::Point3 &b,
                    const math::Point3 &c)
{
    return norm_2(math::crossProduct(b - a, c - a)) * 0.5;
}

//! Returns a trasformation from tile local coordinates to qbuffer coordinates.
//!
math::Matrix4 tileToBuffer(const math::Size2f &tileSize, int bufferSize)
{
    math::Matrix4 trafo(ublas::identity_matrix<double>(4));
    trafo(0,0) = double(bufferSize) / tileSize.width;
    trafo(1,1) = double(bufferSize) / tileSize.height;
    trafo(0,3) = trafo(1,3) = double(bufferSize) / 2;
    return trafo;
}

//! "Draws" all faces of a tile into the qbuffer, i.e., stores the tile's index
//! everywhere its mesh lies.
//!
void rasterizeTile(const Tile &tile, const math::Matrix4 &trafo,
                   int index, cv::Mat &qbuffer)
{
    std::vector<imgproc::Scanline> scanlines;

    // draw all faces into the qbuffer
    for (const auto face : tile.mesh.facets)
    {
        cv::Point3f tri[3];
        for (int i = 0; i < 3; i++) {
            auto pt(transform(trafo, tile.mesh.vertices[face.v[i]]));
            tri[i] = {float(pt(0)), float(pt(1)), float(pt(2))};
        }

        scanlines.clear();
        imgproc::scanConvertTriangle(tri, 0, qbuffer.rows, scanlines);

        for (const auto& sl : scanlines) {
            imgproc::processScanline(sl, 0, qbuffer.cols,
                [&](int x, int y, float)
                    {
                        qbuffer.at<QBufferCType>(y, x) = index;
                    } );
        }
    }
}

//! Calculates tile Inverse Geometry Coarseness, defined as the ratio of mesh area and area
//! of the covered tile part
//!
double tileInvGeometryCoarseness(const Tile &tile
                                 , const math::Size2f &tileSize)
{
    // calculate the total area of the faces in both the XYZ and UV spaces
    double xyzArea(0);
    const Mesh &mesh(tile.mesh);

    //clip mesh to the tile extents
    geometry::opencv::ClipTriangle::list triangles;
    for(auto &face : mesh.facets){
        triangles.emplace_back( 0, 0,
                                mesh.vertices[face.v[0]],
                                mesh.vertices[face.v[1]],
                                mesh.vertices[face.v[2]],
                                math::Point2(0,0),
                                math::Point2(0,0),
                                math::Point2(0,0));
    }

    //cut small border of the tile to get rid of the skirts
    double eps = 0.0001;
    math::Size2f halfsize(0.5*tileSize.width - tileSize.width*eps
                          , 0.5*tileSize.height - tileSize.height*eps);

    geometry::opencv::ClipPlane planes[4];
    planes[0] = {+1.,  0., 0., halfsize.width};
    planes[1] = {-1.,  0., 0., halfsize.width};
    planes[2] = { 0., +1., 0., halfsize.height};
    planes[3] = { 0., -1., 0., halfsize.height};

    for (int i = 0; i < 4; i++) {
        triangles = geometry::opencv::clipTriangles(triangles, planes[i]);
    }

    //compute the mesh area from clipped triangles
    for (const auto &face : triangles)
    {
        xyzArea += triangleArea(
            math::Point3(face.pos[0].x, face.pos[0].y, face.pos[0].z),
            math::Point3(face.pos[1].x, face.pos[1].y, face.pos[1].z),
            math::Point3(face.pos[2].x, face.pos[2].y, face.pos[2].z));
    }

    const int cBSize(512);
    cv::Mat cbuffer(cBSize, cBSize, CV_32S, cv::Scalar(0));

    math::Matrix4 trafo(tileToBuffer(tileSize, cBSize));
    rasterizeTile(tile, trafo,1, cbuffer);
    double coveredArea = static_cast<double>(cv::countNonZero(cbuffer))
                            /(cBSize*cBSize);

    return coveredArea ? xyzArea/coveredArea : 0;
}

//! Returns true if x and y are valid column and row indices, respectively.
//!
bool validMatPos(int x, int y, const cv::Mat &mat)
{
    return x >= 0 && x < mat.cols && y >= 0 && y < mat.rows;
}

//! Adjusts x and y so that they are valid column and row indices, respectively.
//!
void clampMatPos(int &x, int &y, const cv::Mat &mat)
{
    if (x < 0) x = 0;
    else if (x >= mat.cols) x = mat.cols-1;

    if (y < 0) y = 0;
    else if (y >= mat.rows) y = mat.rows-1;
}

template <int Steps, int Radius>
void close(cv::Mat &mat)
{
    auto element
        (cv::getStructuringElement
         (cv::MORPH_RECT, { 1 + 2 * Radius, 1 + 2 * Radius }));
    cv::Mat tmp(mat.size(), mat.type());
    cv::morphologyEx(mat, tmp, cv::MORPH_CLOSE, element
                     , { -1, -1 }, Steps, cv::BORDER_REPLICATE);
    swap(mat, tmp);
}

//! Returns true if at least one element in the area of the qbuffer
//! corresponding to the given face belongs to tile number 'index'.
//!
bool faceCovered(const Mesh &mesh, const Mesh::Facet &face,
                 const math::Matrix4 &trafo, int index,
                 const cv::Mat/*<QBufferCType>*/ &qbuffer)
{
    cv::Point3f tri[3];
    for (int i = 0; i < 3; i++) {
        auto pt(transform(trafo, mesh.vertices[face.v[i]]));
        tri[i] = {float(pt(0)), float(pt(1)), float(pt(2))};
    }

    std::vector<imgproc::Scanline> scanlines;
    imgproc::scanConvertTriangle(tri, 0, qbuffer.rows, scanlines);

    for (const auto& sl : scanlines) {
        bool covered(false);
        imgproc::processScanline(sl, 0, qbuffer.cols, [&](int x, int y, float)
        {
            if (qbuffer.at<QBufferCType>(y, x) == index) {
                covered = true;
            }
        });
        if (covered) { return true; }
    }

    // do one more check in case the triangle is thinner than one pixel
    for (int i = 0; i < 3; i++) {
        int x(round(tri[i].x)), y(round(tri[i].y));
        clampMatPos(x, y, qbuffer);
        if (qbuffer.at<QBufferCType>(y, x) == index) { return true; }
    }

    return false;
}

//! Draws a face into a matrix. Used to mark useful areas of an atlas.
//!
void markFace(const geometry::opencv::ClipTriangle &face
              , cv::Mat/*<char>*/ &mat)
{
    cv::Point3f tri[3];
    for (int i = 0; i < 3; i++) {
        tri[i] = {face.uv[i].x, face.uv[i].y, 0};

        // make sure the vertices are always marked
        int x(round(tri[i].x)), y(round(tri[i].y));
        if (validMatPos(x, y, mat)) {
            mat.at<char>(y, x) = 1;
        }
    }

    std::vector<imgproc::Scanline> scanlines;
    imgproc::scanConvertTriangle(tri, 0, mat.rows, scanlines);

    for (const auto& sl : scanlines) {
        imgproc::processScanline(sl, 0, mat.cols,
            [&](int x, int y, float){ mat.at<char>(y, x) = 1; } );
    }
}

//! A wrapper for cv::findContours to solve for the 1-pixel border that the cv
//! function ignores.
//!
void findContours(const cv::Mat &mat,
                  std::vector<std::vector<cv::Point> > &contours)
{
    cv::Mat tmp(mat.rows + 2, mat.cols + 2, mat.type(), cv::Scalar(0));
    cv::Rect rect(1, 1, mat.cols, mat.rows);
    mat.copyTo(cv::Mat(tmp, rect));

    cv::findContours(tmp, contours, cv::RETR_EXTERNAL
                     , cv::CHAIN_APPROX_NONE
                     , cv::Point(-1, -1));
}

//! Clips faces to tile boundaries.
//!
void clipFaces(geometry::opencv::ClipTriangle::list &faces
               , const math::Size2f &tileSize)
{
    math::Size2f ts2(tileSize.width / 2.0, tileSize.height / 2.0);

    geometry::opencv::ClipPlane planes[4] = {
        { 1.,  0., 0., ts2.width},
        {-1.,  0., 0., ts2.width},
        { 0.,  1., 0., ts2.height},
        { 0., -1., 0., ts2.height}
    };

    for (int i = 0; i < 4; i++) {
        faces = clipTriangles(faces, planes[i]);
    }
}

//! Convert from normalized to pixel-based texture coordinates.
//!
imgproc::UVCoord denormalizeUV(const math::Point3d &uv, cv::Size texSize)
{
    return {float(uv(0) * texSize.width),
            //float(texSize.height-1 - uv(1)*texSize.height)
            float((1.0 - uv(1)) * texSize.height)};
}

//! Convert from pixel-based to normalized texture coordinates.
//!
math::Point3d normalizeUV(const imgproc::UVCoord &uv, cv::Size texSize)
{
    return {double(uv.x) / texSize.width,
            //(texSize.height-1 - double(uv.y)) / texSize.height,
            1.0 - double(uv.y) / texSize.height,
            0.0};
}

// helpers to convert between math points and cv points
cv::Point3d vertex(const math::Point3d &v)
    { return {v(0), v(1), v(2)}; }

math::Point3d vertex(const cv::Point3d &v)
    { return {v.x, v.y, v.z}; }

cv::Point2d tCoord(const math::Point2 &t)
    { return { t(0), t(1)}; }

math::Point2 tCoord(const cv::Point2d &t)
    { return {t.x, t.y}; }

//! Calculate a bounding rectangle of a list of points in the UV space.
//!
imgproc::UVRect makeRect(const std::vector<cv::Point> &points)
{
    imgproc::UVRect rect;
    for (const auto &pt : points) {
        rect.update(imgproc::UVCoord(pt.x, pt.y));
    }
    return rect;
}

//! Finds a UV rectangle containing the given face.
//!
unsigned findRect(const geometry::opencv::ClipTriangle &face
                  , const std::vector<imgproc::UVRect> &rects)
{
    const auto &pt(face.uv[0]);
    for (unsigned i = 0; i < rects.size(); i++)
    {
        const imgproc::UVRect &r(rects[i]);
        const float safety(0.51);

        if (pt.x > (r.min.x - safety) && pt.x < (r.max.x + safety) &&
            pt.y > (r.min.y - safety) && pt.y < (r.max.y + safety))
        {
            return i;
        }
    }
    LOGONCE(err2) << "Rectangle not found (pt = " << pt << ").";
    return 0;
}

//! Copies a rectangle from one atlas to another (src -> dst).
//!
void copyRect(const imgproc::UVRect &rect, const cv::Mat &src, cv::Mat &dst)
{
    for (int y = 0; y < rect.height(); y++)
    for (int x = 0; x < rect.width(); x++)
    {
        int sx(rect.x() + x), sy(rect.y() + y);
        clampMatPos(sx, sy, src);

        int dx(rect.packX + x), dy(rect.packY + y);
        if (!validMatPos(dx, dy, dst)) continue;

        dst.at<cv::Vec3b>(dy, dx) = src.at<cv::Vec3b>(sy, sx);
    }
}

//! Returns a trasformation of the (2x larger) fallback tile so that its given
//! quadrant aligns with tile of size `tileSize`.
//!
math::Matrix4 quadrantTransform(const math::Size2f &tileSize, int fallbackQuad)
{
    math::Matrix4 trafo(ublas::identity_matrix<double>(4));
    math::Size2f shift(tileSize.width / 2.0, tileSize.height / 2.0);

    switch (fallbackQuad) {
    case 0: trafo(0,3) = +shift.width, trafo(1,3) = +shift.height; break;
    case 1: trafo(0,3) = -shift.width, trafo(1,3) = +shift.height; break;
    case 2: trafo(0,3) = +shift.width, trafo(1,3) = -shift.height; break;
    case 3: trafo(0,3) = -shift.width, trafo(1,3) = -shift.height; break;
    }
    return trafo;
}

math::Matrix4 tileTransform(const math::Size2f &dstTileSize
                            , const math::Point2 dst
                            , const math::Size2f &srcTileSize
                            , const math::Point2 src)
{
    math::Matrix4 trafo(ublas::identity_matrix<double>(4));

    math::Size2f shiftSrc(srcTileSize.width / 2.0, srcTileSize.height / 2.0);
    math::Size2f shiftDst(dstTileSize.width / 2.0, dstTileSize.height / 2.0);

    math::Point2 quadrantShift(src - dst);

    trafo(0,3) = shiftSrc.width+quadrantShift(0)-shiftDst.width;
    trafo(1,3) = shiftSrc.height+quadrantShift(1)-shiftDst.height;

    return trafo;
}

MergeInput::list sortMergeInput(const MergeInput::list &mergeInput
                                , const math::Size2f &tileSize)
{
    /** Sorts via coarseness. Geometry-based coarsness is computed in lazy way.
     */
    struct TileSortInfo {
        std::size_t id;
        double coarseness;

        TileSortInfo(std::size_t id, const Tile &tile
                     , const math::Size2f &tileSize)
            : id(id), coarseness(tile.metanode.coarseness)
            , tile_(&tile), tileSize_(tileSize)
            , invGeometryCoarseness_(-1)
        {}

        double invGeometryCoarseness() const {
            if (invGeometryCoarseness_ < 0) {
                invGeometryCoarseness_
                    = tileInvGeometryCoarseness(*tile_, tileSize_);
            }
            return invGeometryCoarseness_;
        }

    private:
        const Tile *tile_;
        math::Size2f tileSize_;
        mutable double invGeometryCoarseness_;
    };

    std::vector<TileSortInfo> sortingInfos;
    bool sortinvGeometryCoarseness = false;
    for (unsigned i = 0; i < mergeInput.size(); ++i) {
        if (mergeInput[i].tile().metanode.coarseness < 0){
            sortinvGeometryCoarseness = true;
        }
        sortingInfos.emplace_back(i, mergeInput[i].tile(), tileSize);
    }

    auto compareCoarseness
        ([](const TileSortInfo &a, const TileSortInfo &b) -> bool
    {
        // tiles with lower coarseness comes last
        if (a.coarseness > b.coarseness) {
            return true;
        } else if (a.coarseness < b.coarseness) {
            return false;
        }

        // fallback if coarsenesses aren't set or are equal
        return (a.invGeometryCoarseness() < b.invGeometryCoarseness());
    });

    auto compareinvGeometryCoarseness
        ([](const TileSortInfo &a, const TileSortInfo &b) -> bool
    {
        // tiles with higher geometry quality comes last
        return (a.invGeometryCoarseness() < b.invGeometryCoarseness());
    });

    if (sortinvGeometryCoarseness){
        std::sort(sortingInfos.begin(), sortingInfos.end()
                  , compareinvGeometryCoarseness);
    } else{
        std::sort(sortingInfos.begin(), sortingInfos.end()
                  , compareCoarseness);
    }

    MergeInput::list result;
    for (const auto &tq : sortingInfos) {
        result.push_back(mergeInput[tq.id]);
    }

    return result;
}

Mesh removeDuplicateVertexes(geometry::opencv::ClipTriangle::list & faces
                             , cv::Size atlasSize)
{
    Mesh result;
    geometry::PointIndex<geometry::opencv::ClipTriangle::Point> vindex;
    geometry::PointIndex<geometry::opencv::ClipTriangle::TCoord> tindex;

    for (const auto &face1 : faces)
    {
        result.facets.emplace_back();
        Mesh::Facet &face2(result.facets.back());

        for (int i = 0; i < 3; i++) {
            face2.v[i] = vindex.assign(face1.pos[i]);
            if (unsigned(face2.v[i]) >= result.vertices.size()) {
                result.vertices.push_back(vertex(face1.pos[i]));
            }

            face2.t[i] = tindex.assign(face1.uv[i]);
            if (unsigned(face2.t[i]) >= result.texcoords.size()) {
                auto uv(normalizeUV(face1.uv[i], atlasSize));
                result.texcoords.push_back(uv);
            }
        }
    }

    return result;
}

Atlas mergeAtlases( const std::vector<Atlas*> & atlases
                  , geometry::opencv::ClipTriangle::list & faces
                    , float inflate = 0)
{
    std::vector<cv::Mat> marks;
    cv::Size safety(1, 1);

    for(uint i=0; i<atlases.size(); ++i){
        marks.emplace_back(atlases[i]->size() + safety, CV_8U, cv::Scalar(0));
    }
    for (const geometry::opencv::ClipTriangle &face : faces) {
        markFace(face, marks[face.id1]);
    }

    // determine UV rectangles that will get repacked
    std::vector<std::vector<imgproc::UVRect> > rects;
    for (const auto &m : marks)
    {
        std::vector<std::vector<cv::Point> > contours;
        findContours(m, contours);

        // add one UVRect for each connected component in the mark matrix
        rects.emplace_back();
        for (const auto& c : contours) {
            rects.back().push_back(makeRect(c));
        }

#if DEBUG
        cv::Mat dbg(m.size(), CV_8UC3, cv::Scalar(0,0,0));
        for (const auto &c : contours) {
            cv::Vec3b color(rand() % 256, rand() % 256, rand() % 256);
            for (const cv::Point &pt : c) {
                dbg.at<cv::Vec3b>(pt.y, pt.x) = color;
            }
        }
        static int idx = 0;
        auto filename(str(boost::format("merge%03d.png") % (idx++)));
        cv::imwrite(filename, dbg);
#endif
    }

    // recalculate the rectangles directly from face UVs for subpixel precision
    for (auto &face : faces) {
        face.id2 = findRect(face, rects[face.id1]);
    }
    for (auto &rlist : rects) {
        for (auto &r : rlist)
            r.clear();
    }
    for (const auto &face : faces) {
        for (int i = 0; i < 3; i++)
            rects[face.id1][face.id2].update(face.uv[i]);
    }

    //inflate rectangles
    if(inflate>0){
        for (auto &rlist : rects) {
            for (auto &r : rlist)
                r.inflate(inflate);
        }
    }

    // pack the rectangles
    imgproc::RectPacker packer;
    for (auto &rlist : rects) {
        for (auto &r : rlist)
            packer.addRect(&r);
    }
    packer.pack();

    // copy rectangles to final atlas
    cv::Mat atlas(packer.height(), packer.width(), CV_8UC3, cv::Scalar(0,0,0));
    for (unsigned i = 0; i < atlases.size(); i++) {
        for (const auto &rect : rects[i]) {
            // TODO: mask out pixels that are not needed!
            copyRect(rect, *atlases[i], atlas);
        }
    }

    // adjust face UVs to point to the new atlas
    for (auto &face : faces) {
        for (int i = 0; i < 3; i++)
            rects[face.id1][face.id2].adjustUV(face.uv[i]);
    }

    return atlas;
}

int sameIndices(const cv::Mat &qbuffer)
{
    int index = -1;
    for (auto it = qbuffer.begin<QBufferCType>();
         it != qbuffer.end<QBufferCType>(); ++it)
    {
        const auto value(*it);
        if (value < 0) { continue; }
        if (value != index) {
            if (index < 0) {
                index = value;
            } else {
                return -1;
            }
        }
    }
    return index;
}

//! Returns true if 'qbuffer' contains at least one element equal to 'index'.
//!
bool haveIndices(const cv::Mat &qbuffer, int index)
{
    for (auto it = qbuffer.begin<QBufferCType>();
         it != qbuffer.end<QBufferCType>(); ++it)
    {
        if (*it == index) return true;
    }
    return false;
}

//! Bilinearly upsamples the specified quadrant of the fallback tile heightmap.
//!
void getFallbackHeightmap(const Tile &fallback, int fallbackQuad,
                          float heightmap[MetaNode::HMSize][MetaNode::HMSize])
{
    const int hms(MetaNode::HMSize);

    double x(0.), y(0.);
    if (fallbackQuad & 1) x += double(hms - 1)*0.5;
    if (fallbackQuad & 2) y += double(hms - 1)*0.5;

    for (int i = 0; i < hms; i++)
    for (int j = 0; j < hms; j++)
    {
        math::Point2 point(x + 0.5*j, y + 0.5*i);
        heightmap[i][j] = 0;
        imgproc::bilinearInterpolate(
                   (float*) fallback.metanode.heightmap, hms, hms, hms, 1,
                   point, &heightmap[i][j]);
    }
}

MergeInput clipQuad( const MergeInput & mergeInput, int fallbackQuad
                     , const math::Size2f &tileSize)
{

    math::Matrix4 shift = quadrantTransform(tileSize, fallbackQuad);
    const Tile &tile(mergeInput.tile());
    const Mesh &mesh(tile.mesh);
    cv::Size asize(tile.atlas.size());
    asize.width *= 2;
    asize.height *= 2;

    geometry::opencv::ClipTriangle::list faces;
    for (unsigned j = 0; j < mesh.facets.size(); j++) {
        const Mesh::Facet &face(mesh.facets[j]);
        faces.emplace_back(0,0,
                vertex(transform(shift,mesh.vertices[face.v[0]])),
                vertex(transform(shift,mesh.vertices[face.v[1]])),
                vertex(transform(shift,mesh.vertices[face.v[2]])),
                denormalizeUV(mesh.texcoords[face.t[0]], asize),
                denormalizeUV(mesh.texcoords[face.t[1]], asize),
                denormalizeUV(mesh.texcoords[face.t[2]], asize));
    }
    clipFaces(faces, tileSize);

    Atlas atlas(asize.height, asize.width, tile.atlas.type());
    cv::resize( tile.atlas, atlas, atlas.size()
              , 0, 0, cv::INTER_LANCZOS4);

    std::vector<Atlas*> usedAtlases;
    usedAtlases.push_back(&atlas);

    Tile resultTile;
    resultTile.atlas = mergeAtlases(usedAtlases, faces, 2);
    resultTile.mesh = removeDuplicateVertexes(faces, resultTile.atlas.size());
    resultTile.metanode = tile.metanode;
    getFallbackHeightmap(tile, fallbackQuad, resultTile.metanode.heightmap);

    return { resultTile, &mergeInput.tileSet()
           , mergeInput.tileId(), mergeInput.srcFaceCount() };
}

} // namespace

//! Merges several tiles. The process has four phases:
//!
//! 1. AncestorTiles are clipped and transformed according to 'quad'.
//!    The meshes of all tiles are rasterized into a buffer which determines
//!    the source of geometry at each point of the result. The tiles are
//!    rasterized from worst to best (in terms of their geometry quality).
//!    This means that the best tile will be complete in the result and only
//!    the remaining space will be filled with other tiles.
//!
//! 2. Faces that will appear in the output are collected, according to the
//!    contents of the buffer. A face from a tile is used if at least one
//!    underlying element of the qbuffer indicates that the tile should be used
//!    at that place.
//!
//! 3. Pixels in atlases that are needed by the collected faces are marked.
//!    Rectangles that cover the marked areas are identified and then repacked
//!    into a single new atlas.
//!
//! 4. The isolated faces are converted into a standard mesh with vertices and
//!    texture vertices that don't repeat.
//!
MergedTile merge( const TileId &tileId, const math::Size2f &tileSize
                , const MergeInput::list &mergeInput
                , int quad
                , const MergeInput::list &ancestorTiles
                , MergeInput::list &incidentTiles)
{
    LOG(info2)<<"Merging tile "<<tileId<<" from "<<mergeInput.size()<<" sets";

    // sort tiles by quality
    auto sortedMergeInput = sortMergeInput(mergeInput, tileSize);
    // create qbuffer, rasterize meshes in increasing order of quality
    const int QBSize(512);
    cv::Mat qbuffer(QBSize, QBSize, QBufferCvType, cv::Scalar(-1));
    math::Matrix4 trafo(tileToBuffer(tileSize, QBSize));

    std::set<const TileSet*> mergedTileSetMap;
    for (const auto &mi : sortedMergeInput) {
        mergedTileSetMap.insert(&mi.tileSet());
    }

    incidentTiles.clear();
    //clip and repack ancestor tiles
    for (const auto &fb : ancestorTiles){
        if (mergedTileSetMap.find(&fb.tileSet()) == mergedTileSetMap.end()) {
            MergeInput mi = clipQuad(fb, quad, tileSize);
            if (!mi.tile().mesh.facets.empty()) {
                incidentTiles.push_back(mi);
            }
        }
    }
    incidentTiles.insert(incidentTiles.end(), sortedMergeInput.begin()
                         , sortedMergeInput.end());

    for (uint i = 0; i<incidentTiles.size(); ++i) {
        rasterizeTile(incidentTiles[i].tile(), trafo, i, qbuffer);
    }

    // close small areas of bad stuff with good stuff
    close<1, 6>(qbuffer);

    int index(sameIndices(qbuffer));
    if (index > -1) {
        return { incidentTiles[index].tile(), incidentTiles[index].tileSet() };
    }

    std::vector<Atlas*> usedAtlases;
    geometry::opencv::ClipTriangle::list mergedFaces;
    const int hms(MetaNode::HMSize);
    float minGsd = std::numeric_limits<float>::max();
    float heightmap[hms][hms];
    cv::Mat hmask(hms, hms, CV_32S, cv::Scalar(0));

    // result tile
    MergedTile result;

    for(int i=incidentTiles.size() - 1; i>=0; --i) {
        if(haveIndices(qbuffer,i)){

            const Tile &tile(incidentTiles[i].tile());
            cv::Size asize(tile.atlas.size());

            geometry::Obj mesh = tile.mesh;

            //refine mesh if necessary
            if (incidentTiles[i].srcFaceCount() > tile.mesh.facets.size()) {
                // tile has been clipped -> refine
                auto cmesh = *geometry::asMesh(mesh);
                auto rmesh(geometry::refine( cmesh
                                       , incidentTiles[i].srcFaceCount()));
                mesh = geometry::asObj(rmesh);
            }

            usedAtlases.emplace_back(&incidentTiles[i].tile().atlas);
            for (unsigned j = 0; j < mesh.facets.size(); j++) {
                const Mesh::Facet &face(mesh.facets[j]);
                if (faceCovered(mesh, face, trafo, i, qbuffer)) {
                    mergedFaces.emplace_back(usedAtlases.size()-1,0,
                            vertex(mesh.vertices[face.v[0]]),
                            vertex(mesh.vertices[face.v[1]]),
                            vertex(mesh.vertices[face.v[2]]),
                            denormalizeUV(mesh.texcoords[face.t[0]], asize),
                            denormalizeUV(mesh.texcoords[face.t[1]], asize),
                            denormalizeUV(mesh.texcoords[face.t[2]], asize) );
                }
            }

            //update metadata
            minGsd = std::min(tile.metanode.gsd, minGsd);
            for (int c = 0; c < hms; c++)
            for (int r = 0; r < hms; r++)
            {
                int what = qbuffer.at<QBufferCType>( c * (QBSize-1) / hms
                                                     , r * (QBSize-1) / hms);
                if(what==i || hmask.at<int>(c,r)==0){
                    hmask.at<int>(c,r)=1;
                    heightmap[c][r] = tile.metanode.heightmap[c][r];
                }
            }

            // remember tile's set as origin of this tile
            result.sources.push_back(&incidentTiles[i].tileSet());
        }
    }

    // almost done
    result.atlas = mergeAtlases(usedAtlases, mergedFaces);
    result.mesh = removeDuplicateVertexes(mergedFaces, result.atlas.size());

    if(sortedMergeInput.size()>0){
        result.metanode = sortedMergeInput.front().tile().metanode;
    }

    result.metanode.gsd = minGsd;
    //coarseness is not used after merge - set to invalid value
    result.metanode.coarseness = -1;

    // copy heightmap
    std::copy(&heightmap[0][0], &heightmap[hms - 1][hms]
              , &result.metanode.heightmap[0][0]);

    return result;
}

boost::optional<double> MergedTile::pixelSize() const
{
    // pixel size is valid only when tile is from single source and has valid
    // pixelsize
    if (singleSource() && metanode.exists()) {
        return metanode.pixelSize[0][0];
    }
    return boost::none;
}

} } // namespace vtslibs::vts0


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <memory>
#include <utility>
#include <vector>

#include "DataStructures/DataBox/PrefixHelpers.hpp"
#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Tensor/TypeAliases.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Domain/Tags.hpp"
#include "Evolution/DgSubcell/Tags/Inactive.hpp"
#include "Evolution/DgSubcell/Tags/Mesh.hpp"
#include "Evolution/DgSubcell/Tags/NeighborData.hpp"
#include "Evolution/Systems/GrMhd/ValenciaDivClean/FiniteDifference/Reconstructor.hpp"
#include "Evolution/Systems/GrMhd/ValenciaDivClean/Tags.hpp"
#include "Options/Options.hpp"
#include "Parallel/CharmPupable.hpp"
#include "PointwiseFunctions/GeneralRelativity/Tags.hpp"
#include "PointwiseFunctions/Hydro/Tags.hpp"
#include "Utilities/TMPL.hpp"

/// \cond
class DataVector;
template <size_t Dim>
class Direction;
template <size_t Dim>
class Element;
template <size_t Dim>
class ElementId;
namespace EquationsOfState {
template <bool IsRelativistic, size_t ThermodynamicDim>
class EquationOfState;
}  // namespace EquationsOfState
template <size_t Dim>
class Mesh;
namespace gsl {
template <typename T>
class not_null;
}  // namespace gsl
namespace PUP {
class er;
}  // namespace PUP
template <typename TagsList>
class Variables;
/// \endcond

namespace grmhd::ValenciaDivClean::fd {
/*!
 * \brief Monotised central reconstruction. See
 * ::fd::reconstruction::monotised_central() for details.
 */
class MonotisedCentralPrim : public Reconstructor {
 private:
  using prims_to_reconstruct_tags =
      tmpl::list<hydro::Tags::RestMassDensity<DataVector>,
                 hydro::Tags::Pressure<DataVector>,
                 hydro::Tags::LorentzFactorTimesSpatialVelocity<DataVector, 3>,
                 hydro::Tags::MagneticField<DataVector, 3>,
                 hydro::Tags::DivergenceCleaningField<DataVector>>;

 public:
  static constexpr size_t dim = 3;

  using options = tmpl::list<>;
  static constexpr Options::String help{
      "Monotised central reconstruction scheme using primitive variables."};

  MonotisedCentralPrim() = default;
  MonotisedCentralPrim(MonotisedCentralPrim&&) = default;
  MonotisedCentralPrim& operator=(MonotisedCentralPrim&&) = default;
  MonotisedCentralPrim(const MonotisedCentralPrim&) = default;
  MonotisedCentralPrim& operator=(const MonotisedCentralPrim&) = default;
  ~MonotisedCentralPrim() override = default;

  explicit MonotisedCentralPrim(CkMigrateMessage* msg);

  WRAPPED_PUPable_decl_base_template(Reconstructor, MonotisedCentralPrim);

  auto get_clone() const -> std::unique_ptr<Reconstructor> override;

  void pup(PUP::er& p) override;

  size_t ghost_zone_size() const override { return 2; }

  using reconstruction_argument_tags = tmpl::list<
      ::Tags::Variables<hydro::grmhd_tags<DataVector>>,
      hydro::Tags::EquationOfStateBase, domain::Tags::Element<dim>,
      evolution::dg::subcell::Tags::NeighborDataForReconstruction<dim>,
      evolution::dg::subcell::Tags::Mesh<dim>>;

  template <size_t ThermodynamicDim, typename TagsList>
  void reconstruct(
      gsl::not_null<std::array<Variables<TagsList>, dim>*> vars_on_lower_face,
      gsl::not_null<std::array<Variables<TagsList>, dim>*> vars_on_upper_face,
      const Variables<hydro::grmhd_tags<DataVector>>& volume_prims,
      const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
      const Element<dim>& element,
      const FixedHashMap<
          maximum_number_of_neighbors(dim),
          std::pair<Direction<dim>, ElementId<dim>>, std::vector<double>,
          boost::hash<std::pair<Direction<dim>, ElementId<dim>>>>&
          neighbor_data,
      const Mesh<dim>& subcell_mesh) const;

  /// Called by an element doing DG when the neighbor is doing subcell.
  template <size_t ThermodynamicDim, typename TagsList>
  void reconstruct_fd_neighbor(
      gsl::not_null<Variables<TagsList>*> vars_on_face,
      const Variables<hydro::grmhd_tags<DataVector>>& subcell_volume_prims,
      const EquationsOfState::EquationOfState<true, ThermodynamicDim>& eos,
      const Element<dim>& element,
      const FixedHashMap<
          maximum_number_of_neighbors(dim),
          std::pair<Direction<dim>, ElementId<dim>>, std::vector<double>,
          boost::hash<std::pair<Direction<dim>, ElementId<dim>>>>&
          neighbor_data,
      const Mesh<dim>& subcell_mesh,
      const Direction<dim> direction_to_reconstruct) const;
};

bool operator==(const MonotisedCentralPrim& /*lhs*/,
                const MonotisedCentralPrim& /*rhs*/);

bool operator!=(const MonotisedCentralPrim& lhs,
                const MonotisedCentralPrim& rhs);
}  // namespace grmhd::ValenciaDivClean::fd


/*
 * Copyright 2021 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <SPL/Runtime/Common/RuntimeDebugAspect.h>
#include <SPL/Runtime/ProcessingElement/ElasticThreadAdapter.h>
#include <TRC/DistilleryDebug.h>

#include <boost/thread/thread.hpp>

#include <cmath>
#include <fstream>

using namespace SPL;

const int32_t NOISE_LIMIT = 5;
const double THROUGHPUT_TOLERANCE = 0.05;
const double MAX_USAGE = 0.8;
const double LOAD_CHANGE_SENSITIVITY = 0.2;

ElasticThreadAdapter::ElasticThreadAdapter()
  : _belowLevel(0)
  , _currLevel(0)
  , _aboveLevel(0)
  , _minLevel(0)
  , _maxLevel(0)
  , _time(0)
  , _noiseDetector(NOISE_LIMIT)
  , _changeSensitivity(THROUGHPUT_TOLERANCE)
{
    APPTRC(L_INFO, "ElasticThreadAdapter: throughput change tolerance: " << _changeSensitivity,
           SPL_PE_DBG);
}

void ElasticThreadAdapter::initialize(uint32_t minThreads,
                                      uint32_t maxThreads,
                                      uint32_t activeThreads)
{
    _threadInfos.clear();

    // we're assuming that the PE did sanity checks on these values already
    _minLevel = minThreads;
    _currLevel = activeThreads;
    _maxLevel = maxThreads;
    _belowLevel = activeThreads / 2;
    _aboveLevel = activeThreads * 2;

    // 0 counts as a level, starts at 1
    for (uint32_t i = 0; i < _aboveLevel + 1; ++i) {
        _threadInfos.push_back(ThreadInfo());
    }

    // for the "out of bounds" levels, say they are trusted, but have zero throughput
    for (uint32_t i = 0; i < _minLevel; ++i) {
        _threadInfos.at(i).trusted = true;
        _threadInfos.at(i).firstThroughput = 0;
        _threadInfos.at(i).lastThroughput = 0;
    }

    for (uint32_t i = _maxLevel; i < _aboveLevel + 1; ++i) {
        _threadInfos.at(i).trusted = true;
        _threadInfos.at(i).firstThroughput = 0;
        _threadInfos.at(i).lastThroughput = 0;
    }
}

ElasticThreadAdapter::~ElasticThreadAdapter() {}

ElasticThreadAdapter::LoadChange ElasticThreadAdapter::pingNoiseDetector(
  ElasticThreadAdapter::LoadChange direction)
{
    if (_noiseDetector > 0) {
        _noiseDetector--;
        APPTRC(L_DEBUG, "set off noise detector, " << _noiseDetector << " remaining.", SPL_PE_DBG);
        return PotentialNoise;
    } else {
        return direction;
    }
}

ElasticThreadAdapter::LoadChange ElasticThreadAdapter::checkForChangeInLoadBasedOnThroughput(
  double throughput)
{
    // Obviously, if t=0, we can't have a load change. But why t=1? The reason is that the
    // first throughput sample tends to be too high. It counts all of the queues filling up
    // as real throughput, but that's much higher than our steady-state throughput.
    if (_time == 0 || _time == 1) {
        return UnknownLoadChange;
    }

    if (!_threadInfos.at(_currLevel).trusted) {
        return UnknownLoadChange;
    }

    ThreadInfo* ti = &_threadInfos.at(_currLevel);

    // we were were here the last time, but throughput changed
    if (ti->lastTime == _time - 1) {
        double oldThroughput = ti->firstThroughput;
        double throughputDiff = throughput - oldThroughput;
        if (throughputDiff < 0.0) {
            if (-throughputDiff > LOAD_CHANGE_SENSITIVITY * throughput) {
                APPTRC(L_DEBUG, "less load", SPL_PE_DBG);
                return pingNoiseDetector(LessLoad);
            }
        } else {
            if (throughputDiff > LOAD_CHANGE_SENSITIVITY * oldThroughput) {
                APPTRC(L_DEBUG, "more load", SPL_PE_DBG);
                return pingNoiseDetector(MoreLoad);
            }
        }
    }

    _noiseDetector = NOISE_LIMIT;
    return UnknownLoadChange;
}

void ElasticThreadAdapter::untrustOtherData(double throughput)
{
    APPTRC(L_DEBUG, "change in throughput, untrusting all the data", SPL_PE_DBG);
    for (size_t i = _minLevel; i < _threadInfos.size(); ++i) {
        _threadInfos.at(i).trusted = false;
    }
}

void ElasticThreadAdapter::untrustAbove()
{
    for (size_t i = _currLevel + 1; i < _threadInfos.size(); ++i) {
        _threadInfos.at(i).trusted = false;
    }
}

void ElasticThreadAdapter::untrustLevel(uint32_t level, double t)
{
    _threadInfos.at(level).firstThroughput = t;
    _threadInfos.at(level).lastTime = _time;
}

bool ElasticThreadAdapter::trustBelow()
{
    if (_currLevel == _minLevel) {
        return false;
    }
    if (_threadInfos.at(_belowLevel).trusted) {
        APPTRC(L_DEBUG, "trust below", SPL_PE_DBG);
    }
    return _threadInfos.at(_belowLevel).trusted;
}

bool ElasticThreadAdapter::trustAbove()
{
    if (_currLevel == _threadInfos.size() - 1) {
        return false;
    }
    if (_threadInfos.at(_aboveLevel).trusted) {
        APPTRC(L_DEBUG, "trust above; " << _aboveLevel, SPL_PE_DBG);
    }
    return _threadInfos.at(_aboveLevel).trusted;
}

bool ElasticThreadAdapter::improvementTrendBelow(double throughput)
{
    if (_currLevel == 1) {
        return false;
    }

    ThreadInfo& pci = _threadInfos.at(_belowLevel);
    if (!pci.trusted) {
        return false;
    }

    if ((throughput > pci.lastThroughput) &&
        (throughput - pci.lastThroughput) > _changeSensitivity * pci.lastThroughput) {
        APPTRC(L_DEBUG, "improvement trend below: " << pci.lastThroughput << " -> " << throughput,
               SPL_PE_DBG);
        return true;
    }

    return false;
}

bool ElasticThreadAdapter::improvementTrendAbove(double throughput)
{
    ThreadInfo& nci = _threadInfos.at(_aboveLevel);
    if (!nci.trusted) {
        return false;
    }

    if ((nci.lastThroughput > throughput) &&
        (nci.lastThroughput - throughput) > _changeSensitivity * throughput) {
        APPTRC(L_DEBUG, "improvement trend above: " << throughput << " -> " << nci.lastThroughput,
               SPL_PE_DBG);
        return true;
    }
    return false;
}

void ElasticThreadAdapter::decreaseLevel()
{
    if (_currLevel > _minLevel) {
        APPTRC(L_DEBUG, "decreasing level", SPL_PE_DBG);
        if (_currLevel > 3) {
            _aboveLevel = _currLevel;
            uint32_t belowPow2 =
              static_cast<uint32_t>(::exp2(std::ceil(::log2(static_cast<double>(_currLevel)) - 1)));
            uint32_t abovePow2 = belowPow2 * 2;
            uint32_t pow2Counter = belowPow2 / 2;
            uint32_t oldLevel = _currLevel;
            for (uint32_t i = abovePow2 - (belowPow2 / 2); i > belowPow2; i -= pow2Counter) {
                if (i <= _minLevel) {
                    _currLevel = _minLevel;
                    break;
                }
                _currLevel = i;
                pow2Counter = std::max(1u, pow2Counter / 2);
                if (i < oldLevel && !_threadInfos.at(i).trusted) {
                    _belowLevel = belowPow2;
                    break;
                }
            }

            if (_currLevel == belowPow2 + 1) {
                if (!_threadInfos.at(belowPow2 + 1).trusted) {
                    if (belowPow2 >= _minLevel) {
                        _belowLevel = belowPow2;
                        _currLevel = belowPow2 + 1;
                    } else {
                        _belowLevel = _minLevel - 1;
                        _currLevel = _minLevel;
                    }
                } else {
                    if (belowPow2 >= _minLevel) {
                        _belowLevel = belowPow2 / 2;
                        _currLevel = belowPow2;
                    } else {
                        _belowLevel = _minLevel - 1;
                        _currLevel = _minLevel;
                    }
                }
            }
        } else if (_currLevel == 3) {
            if (_minLevel <= 2) {
                _belowLevel = 1;
                _currLevel = 2;
                _aboveLevel = 3;
            }
        } else { // _currLevel must be 2
            if (_minLevel == 1) {
                _belowLevel = 0;
                _currLevel = 1;
                _aboveLevel = 2;
            }
        }
    }
}

void ElasticThreadAdapter::increaseLevel()
{
    if (_currLevel < _maxLevel) {
        APPTRC(L_DEBUG, "increasing level", SPL_PE_DBG);
        _belowLevel = _currLevel;
        uint32_t abovePow2 =
          2 * static_cast<uint32_t>(::exp2(std::floor(::log2(static_cast<double>(_currLevel)))));

        if (_threadInfos.at(_aboveLevel).trusted) {
            // if we trust the level above, then we must have been allowed to visit it before, which
            // means it must be under our maxLevel, so we don't need to check maxLevel
            _currLevel = _aboveLevel;
            if (abovePow2 > _threadInfos.size()) {
                _aboveLevel = abovePow2;
            } else if (abovePow2 == _aboveLevel) {
                _aboveLevel = abovePow2 * 2;
            } else {
                for (uint32_t i = _currLevel + 1; i < abovePow2 + 1; ++i) {
                    _aboveLevel = i;
                    if (_threadInfos.at(i).trusted) {
                        break;
                    }
                }
            }
        } else if (_currLevel > 2) {
            if (abovePow2 <= _maxLevel) {
                _currLevel = abovePow2;
                _aboveLevel = abovePow2 * 2;
            } else {
                _currLevel = _maxLevel;
                _aboveLevel = _maxLevel + 1;
            }
        } else {
            if (_currLevel * 2 <= _maxLevel) {
                _currLevel = _currLevel * 2;
                _aboveLevel = _aboveLevel * 2;
            } else {
                _currLevel = _maxLevel;
                _aboveLevel = _maxLevel + 1;
            }
        }

        if (_aboveLevel >= _threadInfos.size()) {
            while (_threadInfos.size() < _aboveLevel + 1) {
                _threadInfos.push_back(ThreadInfo());
            }
        }
    }
}

ElasticThreadAdapter::CPUStat ElasticThreadAdapter::getCPUStat()
{
    std::ifstream stat("/proc/stat");
    if (!stat) {
        APPTRC(L_ERROR, "error opening /proc/stat", SPL_PE_DBG);
        return CPUStat();
    }

    std::string cpu;
    CPUStat usage;
    stat >> cpu >> usage;
    if (!stat) {
        APPTRC(L_ERROR, "error reading /proc/stat", SPL_PE_DBG);
        return CPUStat();
    }
    return usage;
}

bool ElasticThreadAdapter::isCPUUsageAcceptable()
{
    CPUStat currStat = getCPUStat();
    if (currStat.isDefault()) {
        // error reading /proc/stat, assume the worst
        return false;
    }

    if (_lastStat.isDefault()) {
        _lastStat = currStat;
        // say no until we get some data
        return false;
    }

    CPUStat diff = currStat - _lastStat;
    _lastStat = currStat;
    APPTRC(L_DEBUG, "cpu usage: " << diff.usage(), SPL_PE_DBG);
    return diff.usage() <= MAX_USAGE;
}

uint32_t ElasticThreadAdapter::calculateThreads(double throughput, bool& loadChanged)
{
    switch (checkForChangeInLoadBasedOnThroughput(throughput)) {
        case PotentialNoise:
            return _currLevel;
        case LessLoad:
        case MoreLoad:
            loadChanged = true;
            _noiseDetector = NOISE_LIMIT;
            untrustOtherData(throughput);
            _time = 0;
            return _currLevel;
            break;
        default:;
    }

    // update info for the current channel
    ThreadInfo& ti = _threadInfos.at(_currLevel);
    ti.lastTime = _time++;
    ti.lastThroughput = throughput;
    if (!ti.trusted) {
        ti.firstThroughput = throughput;
    }
    ti.trusted = true;

    if (((improvementTrendBelow(throughput) && !trustAbove()) ||
         improvementTrendAbove(throughput) || (_currLevel == _minLevel && !trustAbove()))) {
        if (isCPUUsageAcceptable()) {
            increaseLevel();
        }
    } else if (!trustBelow() || !improvementTrendBelow(throughput)) {
        decreaseLevel();
    }

    return _currLevel;
}