/* Copyright (C) 2010-2019, The Regents of The University of Michigan.
 All rights reserved.

 This software was developed as part of the The Vulcan project in the Intelligent Robotics Lab
 under the direction of Benjamin Kuipers, kuipers@umich.edu. Use of this code is governed by an
 MIT-style License that can be found at "https://github.com/h2ssh/Vulcan".
*/

/**
 * \file     discretized_angle_grid.cpp
 * \author   Paul Foster and Collin Johnson
 *
 * Definition of DiscretizedAngleGrid.
 */

#include "utils/discretized_angle_grid.h"
#include "utils/algorithm_ext.h"
#include "utils/compression.h"
#include "utils/stub.h"
#include "utils/timestamp.h"
#include <algorithm>
#include <boost/algorithm/clamp.hpp>
#include <boost/filesystem.hpp>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <sstream>

// #define DEBUG_BOUNDARY
// #define DEBUG_TILES

namespace vulcan
{
namespace utils
{

const int kTileSize = 16;
const std::string kTileBaseDirectory = "tiles/";

std::string tile_name(const std::string& gridName, Point<double> tilePosition);

const DiscretizedAngleGrid::BinIndex DiscretizedAngleGrid::kUnassignedBin = -1;

DiscretizedAngleGrid::DiscretizedAngleGrid(std::string name,
                                           double metersPerCell,
                                           double activeRadius,
                                           std::size_t numAngleBins)
: DiscretizedAngleGrid(100, 100, metersPerCell, Point<float>(0, 0), activeRadius, numAngleBins, name)
{
}

DiscretizedAngleGrid::DiscretizedAngleGrid(std::size_t widthInCells,
                                           std::size_t heightInCells,
                                           double metersPerCell,
                                           Point<double> globalCenter,
                                           double activeRadius,
                                           std::size_t numAngleBins,
                                           std::string name)
: name_(name)
, kTileWidth_(kTileSize)
, kAngleBinsPerCell_(numAngleBins)
, kAngleBinsPerTile_(kTileWidth_ * kTileWidth_ * kAngleBinsPerCell_)
, cellsPerMeter_(1.0 / metersPerCell)
, metersPerCell_(metersPerCell)
, cells_(widthInCells, heightInCells, metersPerCell, globalCenter, kUnassignedBin)
{
    // Increase the active radius by 3 tile widths because the robot just stays within 1 tile of the center, so need
    // the extra amount to ensure the desired active radius is always valid and the laser pokes up a litle from where
    // the robot center is
    const int kActiveRadiusBuffer = 3;

    assert(metersPerCell_ > 0.0);
    assert(kTileSize == cells_.getTileSize());

    cellsPerMeter_ = 1.0 / metersPerCell_;

    // Determine the number of bins that need to be allocated.
    // The activeRadius is the width in tiles. The width of the region is 2*radius + 1 to give a little slop, plus
    // account for the robot's position. Each tile contains tilewidth * tilewidth cells, so tilewidth^2 * anglebins
    // are needed for each tile.
    activeRegionRadius_ =
      (round_up_to_nearest_n(activeRadius * cellsPerMeter_, kTileWidth_) / kTileWidth_) + kActiveRadiusBuffer;
    int maxActiveWidth = (2 * activeRegionRadius_) + 1;

    int numActiveTiles = (maxActiveWidth + 1) * (maxActiveWidth + 1);
    angleBins_.resize(numActiveTiles * kAngleBinsPerTile_, 0);

    // Create available bins for each tile
    for (int n = 0; n < numActiveTiles; ++n) {
        availableBins_.push_back(n * kAngleBinsPerTile_);
    }

    activeCenter_ = globalCenter;
    activeRegion_ = calculateActiveRegion(globalCenter);
    setActiveBins();
}

void DiscretizedAngleGrid::changeBoundary(const math::Rectangle<double>& newBoundary)
{
    // When changing the boundary, need to worry if the active region changes. All other cells are just marked as
    // unavailable, so their shifting doesn't affect the map. If an active cells shifts of the map, then its bin needs
    // to be added back to the available bins

    if (newBoundary == cells_.getBoundary()) {
        return;
    }

    // In the easy case, the active region is wholely contained in the new boundary, so the shift doesn't change which
    // bins are available, so everything just works
    // Otherwise, need to save the current active bins and after changing the boundary, determine which ones have
    // fallen off the map and make them available again

    using ActiveTileBin = std::pair<Point<int>, int64_t>;
    std::vector<ActiveTileBin> activeBins;
    for (auto activeIt = beginActiveTiles(), activeEnd = endActiveTiles(); activeIt != activeEnd; ++activeIt) {
        activeBins.emplace_back(Tile(activeIt.x(), activeIt.y()), *activeIt);
    }

    auto oldBottomLeft = cells_.getBottomLeft();

    cells_.changeBoundary(newBoundary, kUnassignedBin);

    auto gridShift = oldBottomLeft - cells_.getBottomLeft();
    int xShift = std::round(gridShift.x * cells_.tilesPerMeter());
    int yShift = std::round(gridShift.y * cells_.tilesPerMeter());

    for (auto& a : activeBins) {
        if ((a.first.x + xShift < 0) || (a.first.x + xShift >= static_cast<int>(cells_.getWidthInTiles()))
            || (a.first.y + yShift < 0) || (a.first.y + yShift >= static_cast<int>(cells_.getHeightInTiles()))) {
            assert(a.second != kUnassignedBin);
            availableBins_.push_back(a.second);
            clearBin(a.second);
        }

        int numSame = std::count_if(activeBins.begin(), activeBins.end(), [a](ActiveTileBin b) {
            return b.second == a.second;
        });

        if (numSame != 1) {
            std::cout << "ERROR: DiscretizedAngleGrid: Duplicate bin assigned:" << a.first << "->" << a.second << '\n';
            assert(numSame == 1);
        }
    }

    // After the boundary change, the active region needs to be changed to reflect the new positioning within the map
    // It doesn't need to be recentered through because the same information is stored, just in slightly different
    // position, cell-wise
    activeRegion_ = calculateActiveRegion(activeCenter_);

    // Sort bins so close tiles are as close in memory as possible
    std::sort(availableBins_.begin(), availableBins_.end());

    // If the active region has grown, then some tiles will need to be assigned a bin. Can't use recenter because
    // it will also try save the non-existent (now) cells to file.
    for (auto activeIt = beginActiveTiles(), activeEnd = endActiveTiles(); activeIt != activeEnd; ++activeIt) {
        if (*activeIt == kUnassignedBin) {
            assignTileBins(Tile(activeIt.x(), activeIt.y()));
        }
    }

#ifdef DEBUG_BOUNDARY
    std::cout << "DEBUG: DiscretizedAngleGrid: Assigned boundary:" << newBoundary
              << " Final boundary:" << cells_.getBoundary() << '\n';
#endif

    eraseOffMapTiles();

    verifyActiveRegion();
}

bool DiscretizedAngleGrid::recenterActiveRegion(Point<float> robotPosition)
{
    // If the robot tile is within 1 of the center tile, then don't recenter. Otherwise, need to shift to make the
    // robot tile the center tile.
    auto robotTileOffset = position_to_tile(robotPosition, cells_) - activeRegion_.center();

    if ((std::abs(robotTileOffset.x) <= 1) && (std::abs(robotTileOffset.y) <= 1)) {
#ifdef DEBUG_BOUNDARY
        std::cout << "DEBUG: DiscretizedAngleGrid: Center offset not large enough. Not recentering.\n";
#endif
        return false;
    }

    auto newActiveRegion = calculateActiveRegion(robotPosition);

    // If pushed up against the boundary of the map, even if a shift is needed, there might not be anywhere for the
    // region to grow, so it will still be unchanged for the off-center robot position.
    if (newActiveRegion == activeRegion_) {
#ifdef DEBUG_BOUNDARY
        std::cout << "DEBUG: DiscretizedAngleGrid: Active region unchanged. Not recentering.\n";
#endif
        return false;
    }

    saveNewInactiveBins(newActiveRegion);
    loadNewActiveBins(newActiveRegion);

    activeCenter_ = robotPosition;
    activeRegion_ = newActiveRegion;

    verifyActiveRegion();

#ifdef DEBUG_BOUNDARY
    std::cout << "DEBUG: DiscretizedAngleGrid: Assigned new active region: Tiles:" << activeRegion_ << " Cells: "
              << " Cells:" << getActiveRegionInCells() << '\n';
#endif

    return true;
}

math::Rectangle<float> DiscretizedAngleGrid::getActiveRegion(void) const
{
    auto activeOrigin = grid_point_to_global_point(tile_origin_cell(activeRegion_.bottomLeft, cells_), cells_);
    return math::Rectangle<float>(activeOrigin,
                                  Point<float>(activeOrigin.x + activeWidth() * cells_.metersPerTile(),
                                               activeOrigin.y + activeHeight() * cells_.metersPerTile()));
}

math::Rectangle<int> DiscretizedAngleGrid::getActiveRegionInCells(void) const
{
    auto activeOrigin = tile_origin_cell(activeRegion_.bottomLeft, cells_);
    return math::Rectangle<int>(activeOrigin,
                                Point<int>(activeOrigin.x + (activeWidth() * cells_.getTileSize()),
                                           activeOrigin.y + (activeHeight() * cells_.getTileSize())));
}

bool DiscretizedAngleGrid::isCellInActiveRegion(Point<int> cell) const
{
    return isCellInActiveRegion(cell.x, cell.y);
}

bool DiscretizedAngleGrid::isCellInActiveRegion(int x, int y) const
{
    return (cells_.isCellInGrid(x, y)) ? (cells_(x, y) != kUnassignedBin) : false;
}

DiscretizedAngleGrid::TileIter DiscretizedAngleGrid::beginActiveTiles(void)
{
    return TileIter(activeRegion_.bottomLeft.x, activeRegion_.bottomLeft.y, activeWidth(), cells_);
}

DiscretizedAngleGrid::TileIter DiscretizedAngleGrid::endActiveTiles(void)
{
    return TileIter(activeRegion_.bottomLeft.x, activeRegion_.bottomLeft.y + activeHeight(), activeWidth(), cells_);
}

void DiscretizedAngleGrid::reset(void)
{
    std::fill(angleBins_.begin(), angleBins_.end(), 0);
    resetCachedTiles();
}

void DiscretizedAngleGrid::flush(void)
{
    for (int tileY = activeRegion_.bottomLeft.y; tileY <= activeRegion_.topRight.y; ++tileY) {
        for (int tileX = activeRegion_.bottomLeft.x; tileX <= activeRegion_.topRight.x; ++tileX) {
            tileWrite(Tile(tileX, tileY));
        }
    }
}

DiscretizedAngleGrid::TileRegion DiscretizedAngleGrid::calculateActiveRegion(Point<float> center)
{
    using namespace boost::algorithm;

    auto centerTile = position_to_tile(center, cells_);
    return TileRegion(
      Tile(clamp(centerTile.x - activeRegionRadius_, 0, static_cast<int>(cells_.getWidthInTiles() - 1)),
           clamp(centerTile.y - activeRegionRadius_, 0, static_cast<int>(cells_.getHeightInTiles() - 1))),
      Tile(clamp(centerTile.x + activeRegionRadius_, 0, static_cast<int>(cells_.getWidthInTiles() - 1)),
           clamp(centerTile.y + activeRegionRadius_, 0, static_cast<int>(cells_.getHeightInTiles() - 1))));
}

void DiscretizedAngleGrid::setActiveBins(void)
{
    for (auto activeIt = beginActiveTiles(), activeEnd = endActiveTiles(); activeIt != activeEnd; ++activeIt) {
        assignTileBins(Tile(activeIt.x(), activeIt.y()));
    }
}

void DiscretizedAngleGrid::saveNewInactiveBins(const TileRegion& newActiveRegion)
{
    // Write out the newly inactive tiles and return their bins to the available bins
    int writecount = 0;
    for (int tileY = activeRegion_.bottomLeft.y; tileY <= activeRegion_.topRight.y; ++tileY) {
        for (int tileX = activeRegion_.bottomLeft.x; tileX <= activeRegion_.topRight.x; ++tileX) {
            // If this tile that was loaded with the old origin is no longer in the grid with the changed origin
            // then it should be cached to disk
            if ((tileX < newActiveRegion.bottomLeft.x) || (tileX > newActiveRegion.topRight.x)
                || (tileY < newActiveRegion.bottomLeft.y) || (tileY > newActiveRegion.topRight.y)) {
                tileWrite(Tile(tileX, tileY));
                freeTileBins(Tile(tileX, tileY));
                // If successfully wrote, then cache the position that was written
                cachedTilePositions_.push_back(tile_origin_position(Tile(tileX, tileY), cells_));
                writecount++;
            }
        }
    }
}

void DiscretizedAngleGrid::loadNewActiveBins(const TileRegion& newActiveRegion)
{
    // Sort bins so close tiles are as close in memory as possible
    std::sort(availableBins_.begin(), availableBins_.end());

    int readcount = 0;
    for (int tileY = newActiveRegion.bottomLeft.y; tileY <= newActiveRegion.topRight.y; ++tileY) {
        for (int tileX = newActiveRegion.bottomLeft.x; tileX <= newActiveRegion.topRight.x; ++tileX) {
            // If the tile in the new region wasn't in the old region, its prior state should be loaded from disk
            if ((tileX < activeRegion_.bottomLeft.x) || (tileX > activeRegion_.topRight.x)
                || (tileY < activeRegion_.bottomLeft.y) || (tileY > activeRegion_.topRight.y)) {
                assignTileBins(Tile(tileX, tileY));
                tileRead(Tile(tileX, tileY));
                readcount++;
            }
        }
    }
}

void DiscretizedAngleGrid::eraseOffMapTiles(void)
{
    std::sort(cachedTilePositions_.begin(), cachedTilePositions_.end());
    erase_unique(cachedTilePositions_);

    auto currentBoundary = cells_.getBoundary();

#ifdef DEBUG_TILES
    std::cout << "DEBUG: DiscretizedAngleGrid: Checking for cached tiles in " << currentBoundary << '\n';
#endif

    // Find all cached tiles that are no longer on the map and remove the file for each of them
    for (auto tile : cachedTilePositions_) {
        if (currentBoundary.contains(tile)) {
            continue;
        }

        std::string tileName = tile_name(name_, tile);
        bool success = boost::filesystem::remove(tileName);

        if (!success) {
            std::cerr << "WARNING: DiscretizedAngleGrid: Failed to remove tile: " << tileName << " It didn't exist.\n";
        }
    }

    // Erase them from the list of cached tiles
    erase_remove_if(cachedTilePositions_, [currentBoundary](Point<double> p) {
        return !currentBoundary.contains(p);
    });
}

void DiscretizedAngleGrid::freeTileBins(Tile tile)
{
    tileClear(tile);

    auto originCell = tile_origin_cell(tile, cells_);
    availableBins_.push_back(cells_(originCell.x, originCell.y));

    // When iterating through a single tile, can just use the unsafe iterator for TiledCellGrid
    std::fill(cells_.begin(tile.x, tile.y), cells_.end(tile.x, tile.y), kUnassignedBin);
}

void DiscretizedAngleGrid::assignTileBins(Tile tile)
{
    if (availableBins_.empty()) {
        std::cerr << "ERROR: DiscretizedAngleGrid: Out of available bins!\n";
        assert(!availableBins_.empty());
    }

    BinIndex nextBin = availableBins_.front();
    availableBins_.pop_front();

    // When iterating through a single tile, can just use the unsafe iterator for TiledCellGrid
    auto tileStart = cells_.begin(tile.x, tile.y);
    auto tileEnd = cells_.end(tile.x, tile.y);
    while (tileStart != tileEnd) {
        *tileStart = nextBin;
        nextBin += kAngleBinsPerCell_;
        ++tileStart;
    }
}

void DiscretizedAngleGrid::clearBin(BinIndex binStart)
{
    std::fill(angleBins_.begin() + binStart, angleBins_.begin() + binStart + kAngleBinsPerTile_, 0);
}

///////////////////////////////////// I/O //////////////////////////////////////////////////////

void DiscretizedAngleGrid::resetCachedTiles(void)
{
    // Create the directory for storing the saved tiles
    std::ostringstream directories;
    directories << kTileBaseDirectory << name_ << '/';
    assert(directories.str() != "/");
    boost::filesystem::remove_all(directories.str());
    boost::filesystem::create_directories(directories.str());
}

void DiscretizedAngleGrid::tileWrite(Tile tile)
{
    auto tilePosition = tile_origin_position(tile, cells_);
    std::string tileName = tile_name(name_, tilePosition);
    FILE* fp = fopen(tileName.c_str(), "wb");

    if (fp) {
        const std::size_t kTileBytes = kAngleBinsPerTile_ * sizeof(Value);
        auto tileOrigin = tile_origin_cell(tile, cells_);
        auto tileStartBin = cells_(tileOrigin.x, tileOrigin.y);

        std::size_t compression = 0;
        if (compression) {
            PRINT_STUB("DiscretizedAngleGrid::tileWrite compressed data");
            //         diskBuffer_.reserve(bsz);
            //         diskBuffer_.clear();
            //         compress(tileBuffer_.begin(), tileBuffer_.end(), std::back_inserter(diskBuffer_));
            //         writesize = diskBuffer_.size();
            //
            //         fwrite(&compression,sizeof(std::size_t),1,fp);
            //         fwrite(&writesize,sizeof(std::size_t),1,fp);
            //         fwrite(diskBuffer_.data(),1,writesize,fp);
        } else {
            fwrite(&compression, sizeof(std::size_t), 1, fp);
            fwrite(&kTileBytes, sizeof(std::size_t), 1, fp);
            fwrite(angleBins_.data() + tileStartBin, 1, kTileBytes, fp);
        }

        fclose(fp);
    } else {
        std::cerr << "ERROR:DiscretizedAngleGrid: Failed to open tile cache file:" << tileName << '\n';
        perror("errno:");
    }
}

void DiscretizedAngleGrid::tileRead(Tile tile)
{
    std::string tileFilename = tile_name(name_, tile_origin_position(tile, cells_));
    FILE* fp = fopen(tileFilename.c_str(), "rb");

    if (fp) {
        std::size_t compression;
        if (fread(&compression, sizeof(std::size_t), 1, fp)) {
            // do nothing on error...
        }
        std::size_t readsize;
        if (fread(&readsize, sizeof(std::size_t), 1, fp)) {
            // do nothing on error
        }

        // Data is uncompressed
        if (!compression) {
            const std::size_t kTileBytes = kAngleBinsPerTile_ * sizeof(Value);
            auto tileOrigin = tile_origin_cell(tile, cells_);
            auto tileStartBin = cells_(tileOrigin.x, tileOrigin.y);

            assert(readsize == kTileBytes);
            std::size_t count = fread(angleBins_.data() + tileStartBin, 1, readsize, fp);
            assert(count == readsize);
        } else {   // Data is RL compressed
            PRINT_STUB("DiscretizedAngleGrid::tileRead compressed data");
            //             diskBuffer_.resize(readsize);
            //             if(fread(diskBuffer_.data(),1,readsize,fp));
            //
            //             tileBuffer_.reserve(bsz);
            //             tileBuffer_.clear();
            //             decompress(diskBuffer_.begin(), diskBuffer_.end(), std::back_inserter(tileBuffer_));
            //             assert(tileBuffer_.size() == bsz);
        }
    } else {
        //         std::cerr << "WARNING: DiscretizedAngleGrid: Failed to read tile from " << tileFilename << " Tile:"
        //         << tile <<
        //             " Origin:" << cells_.getBottomLeft() << " Dim:" << cells_.getWidthInTiles() << 'x' <<
        //             cells_.getHeightInTiles() << '\n';
        //         perror("errno:");

        tileClear(tile);
    }

    if (fp) {
        fclose(fp);
    }
}

void DiscretizedAngleGrid::tileClear(Tile tile)
{
    // Zero out the values stored in all angle bins associated with the tile
    auto tileOrigin = tile_origin_cell(tile, cells_);
    auto tileBinStart = cells_(tileOrigin.x, tileOrigin.y);
    clearBin(tileBinStart);
}

void DiscretizedAngleGrid::verifyActiveRegion(void)
{
    for (auto activeIt = beginActiveTiles(), activeEnd = endActiveTiles(); activeIt != activeEnd; ++activeIt) {
        if (*activeIt == kUnassignedBin) {
            std::cout << "ERROR: DiscretizedAngleGrid: Active region:" << activeRegion_ << '\n'
                      << "Unassigned active:" << Tile(activeIt.x(), activeIt.y()) << '\n';
            assert(*activeIt != kUnassignedBin);
        }

        auto unassignedIt =
          std::find(cells_.begin(activeIt.x(), activeIt.y()), cells_.end(activeIt.x(), activeIt.y()), kUnassignedBin);
        if (unassignedIt != cells_.end(activeIt.x(), activeIt.y())) {
            auto badCellIt = activeIt.begin();
            std::advance(badCellIt, std::distance(cells_.begin(activeIt.x(), activeIt.y()), unassignedIt));
            std::cout << "ERROR: DiscretizedAngleGrid: Active region:" << activeRegion_ << '\n'
                      << "Unassigned active:" << Tile(activeIt.x(), activeIt.y()) << " Cell:" << badCellIt.x() << ','
                      << badCellIt.y() << '\n';
            assert(unassignedIt == cells_.end(activeIt.x(), activeIt.y()));
        }
    }
}

std::string tile_name(const std::string& gridName, Point<double> tilePosition)
{
    // The tile is stored based on its global position in millimeters. Storing based on position allows for the boundary
    // to change and come back. Because the grid always shifts in units of tiles, indexing based on the tile origin will
    // always yield a repeatable location for loading the tile data
    int xName = std::round(tilePosition.x * 100.0);
    int yName = std::round(tilePosition.y * 100.0);

    std::ostringstream filename;
    filename << kTileBaseDirectory << gridName << "/Tile_X-" << xName << "_Y-" << yName << "_Sz-" << kTileSize
             << ".tile";
    return filename.str();
}

}   // namespace utils
}   // namespace vulcan


#include <boost/test/unit_test.hpp>

#include <btcm/chain/database.hpp>
#include <btcm/chain/exceptions.hpp>
#include <btcm/chain/hardfork.hpp>
#include <btcm/chain/history_object.hpp>
#include <btcm/chain/base_objects.hpp>

#include <fc/crypto/digest.hpp>

#include "../common/database_fixture.hpp"

#include <cmath>

using namespace btcm::chain;
using namespace btcm::chain::test;

BOOST_FIXTURE_TEST_SUITE( operation_time_tests, clean_database_fixture )

BOOST_AUTO_TEST_CASE( vesting_withdrawals )
{
   try
   {
      ACTORS( (alice) )
      fund( "alice", 100000 );
      vest( "alice", 100000 );

      const auto& new_alice = db.get_account( "alice" );

      BOOST_TEST_MESSAGE( "Setting up withdrawal" );

      signed_transaction tx;
      withdraw_vesting_operation op;
      op.account = "alice";
      op.vesting_shares = asset( new_alice.vesting_shares.amount / 2, VESTS_SYMBOL );
      tx.set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( op );
      tx.sign( alice_private_key, db.get_chain_id() );
      db.push_transaction( tx, 0 );

      auto next_withdrawal = db.head_block_time() + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS;
      asset vesting_shares = new_alice.vesting_shares;
      asset original_vesting = vesting_shares;
      asset withdraw_rate = new_alice.vesting_withdraw_rate;

      BOOST_TEST_MESSAGE( "Generating block up to first withdrawal" );
      generate_blocks( next_withdrawal - ( BTCM_BLOCK_INTERVAL / 2 ), true);

      BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).vesting_shares.amount.value, vesting_shares.amount.value );

      BOOST_TEST_MESSAGE( "Generating block to cause withdrawal" );
      generate_block();

      auto fill_op = get_last_operations( 1 )[0].get< fill_vesting_withdraw_operation >();
      auto gpo = db.get_dynamic_global_properties();

      BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).vesting_shares.amount.value, ( vesting_shares - withdraw_rate ).amount.value );
      BOOST_REQUIRE( ( withdraw_rate * gpo.get_vesting_share_price() ).amount.value - db.get_account( "alice" ).balance.amount.value <= 1 ); // Check a range due to differences in the share price
      BOOST_REQUIRE_EQUAL( fill_op.from_account, "alice" );
      BOOST_REQUIRE_EQUAL( fill_op.to_account, "alice" );
      BOOST_REQUIRE_EQUAL( fill_op.withdrawn.amount.value, withdraw_rate.amount.value );
      BOOST_REQUIRE( std::abs( ( fill_op.deposited - fill_op.withdrawn * gpo.get_vesting_share_price() ).amount.value ) <= 1 );
      validate_database();

      BOOST_TEST_MESSAGE( "Generating the rest of the blocks in the withdrawal" );

      vesting_shares = db.get_account( "alice" ).vesting_shares;
      auto balance = db.get_account( "alice" ).balance;
      auto old_next_vesting = db.get_account( "alice" ).next_vesting_withdrawal;

      for( int i = 1; i < BTCM_VESTING_WITHDRAW_INTERVALS - 1; i++ )
      {
         generate_blocks( db.head_block_time() + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS );

         const auto& alice = db.get_account( "alice" );

         gpo = db.get_dynamic_global_properties();
         fill_op = get_last_operations( 1 )[0].get< fill_vesting_withdraw_operation >();

         BOOST_REQUIRE_EQUAL( alice.vesting_shares.amount.value, ( vesting_shares - withdraw_rate ).amount.value );
         BOOST_REQUIRE( balance.amount.value + ( withdraw_rate * gpo.get_vesting_share_price() ).amount.value - alice.balance.amount.value <= 1 );
         BOOST_REQUIRE_EQUAL( fill_op.from_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.to_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.withdrawn.amount.value, withdraw_rate.amount.value );
         BOOST_REQUIRE( std::abs( ( fill_op.deposited - fill_op.withdrawn * gpo.get_vesting_share_price() ).amount.value ) <= 1 );

         if ( i == BTCM_VESTING_WITHDRAW_INTERVALS - 1 )
            BOOST_REQUIRE( alice.next_vesting_withdrawal == fc::time_point_sec::maximum() );
         else
            BOOST_REQUIRE_EQUAL( alice.next_vesting_withdrawal.sec_since_epoch(), ( old_next_vesting + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS ).sec_since_epoch() );

         validate_database();

         vesting_shares = alice.vesting_shares;
         balance = alice.balance;
         old_next_vesting = alice.next_vesting_withdrawal;
      }

      if (  original_vesting.amount.value % withdraw_rate.amount.value != 0 )
      {
         BOOST_TEST_MESSAGE( "Generating one more block to take care of remainder" );
         generate_blocks( db.head_block_time() + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS, true );
         fill_op = get_last_operations( 1 )[0].get< fill_vesting_withdraw_operation >();

         BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).next_vesting_withdrawal.sec_since_epoch(), ( old_next_vesting + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS ).sec_since_epoch() );
         BOOST_REQUIRE_EQUAL( fill_op.from_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.to_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.withdrawn.amount.value, withdraw_rate.amount.value );
         BOOST_REQUIRE( std::abs( ( fill_op.deposited - fill_op.withdrawn * gpo.get_vesting_share_price() ).amount.value ) <= 1 );

         generate_blocks( db.head_block_time() + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS, true );
         fill_op = get_last_operations( 1 )[0].get< fill_vesting_withdraw_operation >();

         BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).next_vesting_withdrawal.sec_since_epoch(), ( old_next_vesting + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS ).sec_since_epoch() );
         BOOST_REQUIRE_EQUAL( fill_op.to_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.from_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.withdrawn.amount.value, original_vesting.amount.value % withdraw_rate.amount.value );
         BOOST_REQUIRE( std::abs( ( fill_op.deposited - fill_op.withdrawn * gpo.get_vesting_share_price() ).amount.value ) <= 1 );

         validate_database();
      }
      else
      {
         generate_blocks( db.head_block_time() + BTCM_VESTING_WITHDRAW_INTERVAL_SECONDS, true );

         BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).next_vesting_withdrawal.sec_since_epoch(), fc::time_point_sec::maximum().sec_since_epoch() );

         fill_op = get_last_operations( 1 )[0].get< fill_vesting_withdraw_operation >();
         BOOST_REQUIRE_EQUAL( fill_op.from_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.to_account, "alice" );
         BOOST_REQUIRE_EQUAL( fill_op.withdrawn.amount.value, withdraw_rate.amount.value );
         BOOST_REQUIRE( std::abs( ( fill_op.deposited - fill_op.withdrawn * gpo.get_vesting_share_price() ).amount.value ) <= 1 );
      }

      BOOST_REQUIRE_EQUAL( db.get_account( "alice" ).vesting_shares.amount.value, ( original_vesting - op.vesting_shares ).amount.value );
   }
   FC_LOG_AND_RETHROW()
}

BOOST_AUTO_TEST_CASE( vesting_withdraw_route )
{
   try
   {
      ACTORS( (alice)(bob)(sam) )

      auto original_vesting = alice.vesting_shares;

      fund( "alice", 1040000 );
      vest( "alice", 1040000 );

      auto withdraw_amount = alice.vesting_shares - original_vesting;

      BOOST_TEST_MESSAGE( "Setup vesting withdraw" );
      withdraw_vesting_operation wv;
      wv.account = "alice";
      wv.vesting_shares = withdraw_amount;

      signed_transaction tx;
      tx.set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );
      tx.operations.push_back( wv );
      tx.sign( alice_private_key, db.get_chain_id() );
      db.push_transaction( tx, 0 );

      tx.operations.clear();
      tx.signatures.clear();

      BOOST_TEST_MESSAGE( "Setting up bob destination" );
      set_withdraw_vesting_route_operation op;
      op.from_account = "alice";
      op.to_account = "bob";
      op.percent = BTCM_1_PERCENT * 50;
      op.auto_vest = true;
      tx.operations.push_back( op );

      BOOST_TEST_MESSAGE( "Setting up sam destination" );
      op.to_account = "sam";
      op.percent = BTCM_1_PERCENT * 30;
      op.auto_vest = false;
      tx.operations.push_back( op );
      tx.sign( alice_private_key, db.get_chain_id() );
      db.push_transaction( tx, 0 );

      BOOST_TEST_MESSAGE( "Setting up first withdraw" );

      auto vesting_withdraw_rate = alice.vesting_withdraw_rate;
      auto old_alice_balance = alice.balance;
      auto old_alice_vesting = alice.vesting_shares;
      auto old_bob_balance = bob.balance;
      auto old_bob_vesting = bob.vesting_shares;
      auto old_sam_balance = sam.balance;
      auto old_sam_vesting = sam.vesting_shares;
      generate_blocks( alice.next_vesting_withdrawal, true );

      {
         const auto& alice = db.get_account( "alice" );
         const auto& bob = db.get_account( "bob" );
         const auto& sam = db.get_account( "sam" );

         BOOST_REQUIRE( alice.vesting_shares == old_alice_vesting - vesting_withdraw_rate );
         BOOST_REQUIRE( alice.balance == old_alice_balance + asset( ( vesting_withdraw_rate.amount * BTCM_1_PERCENT * 20 ) / BTCM_100_PERCENT, VESTS_SYMBOL ) * db.get_dynamic_global_properties().get_vesting_share_price() );
         BOOST_REQUIRE( bob.vesting_shares == old_bob_vesting + asset( ( vesting_withdraw_rate.amount * BTCM_1_PERCENT * 50 ) / BTCM_100_PERCENT, VESTS_SYMBOL ) );
         BOOST_REQUIRE( bob.balance == old_bob_balance );
         BOOST_REQUIRE( sam.vesting_shares == old_sam_vesting );
         BOOST_REQUIRE( sam.balance ==  old_sam_balance + asset( ( vesting_withdraw_rate.amount * BTCM_1_PERCENT * 30 ) / BTCM_100_PERCENT, VESTS_SYMBOL ) * db.get_dynamic_global_properties().get_vesting_share_price() );

         old_alice_balance = alice.balance;
         old_alice_vesting = alice.vesting_shares;
         old_bob_balance = bob.balance;
         old_bob_vesting = bob.vesting_shares;
         old_sam_balance = sam.balance;
         old_sam_vesting = sam.vesting_shares;
      }

      BOOST_TEST_MESSAGE( "Test failure with greater than 100% destination assignment" );

      tx.operations.clear();
      tx.signatures.clear();

      op.to_account = "sam";
      op.percent = BTCM_1_PERCENT * 50 + 1;
      tx.operations.push_back( op );
      tx.set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );
      tx.sign( alice_private_key, db.get_chain_id() );
      BTCM_REQUIRE_THROW( db.push_transaction( tx, 0 ), fc::assert_exception );

      BOOST_TEST_MESSAGE( "Test from_account receiving no withdraw" );

      tx.operations.clear();
      tx.signatures.clear();

      op.to_account = "sam";
      op.percent = BTCM_1_PERCENT * 50;
      tx.operations.push_back( op );
      tx.sign( alice_private_key, db.get_chain_id() );
      db.push_transaction( tx, 0 );

      generate_blocks( db.get_account( "alice" ).next_vesting_withdrawal, true );
      {
         const auto& alice = db.get_account( "alice" );
         const auto& bob = db.get_account( "bob" );
         const auto& sam = db.get_account( "sam" );

         BOOST_REQUIRE( alice.vesting_shares == old_alice_vesting - vesting_withdraw_rate );
         BOOST_REQUIRE( alice.balance == old_alice_balance );
         BOOST_REQUIRE( bob.vesting_shares == old_bob_vesting + asset( ( vesting_withdraw_rate.amount * BTCM_1_PERCENT * 50 ) / BTCM_100_PERCENT, VESTS_SYMBOL ) );
         BOOST_REQUIRE( bob.balance == old_bob_balance );
         BOOST_REQUIRE( sam.vesting_shares == old_sam_vesting );
         BOOST_REQUIRE( sam.balance ==  old_sam_balance + asset( ( vesting_withdraw_rate.amount * BTCM_1_PERCENT * 50 ) / BTCM_100_PERCENT, VESTS_SYMBOL ) * db.get_dynamic_global_properties().get_vesting_share_price() );
      }
   }
   FC_LOG_AND_RETHROW()
}

BOOST_AUTO_TEST_CASE( feed_publish_mean )
{
   try
   {
      ACTORS( (alice0)(alice1)(alice2)(alice3)(alice4)(alice5)(alice6) )

      BOOST_TEST_MESSAGE( "Setup" );

      generate_blocks( 30 / BTCM_BLOCK_INTERVAL );

      vector< string > accounts;
      accounts.push_back( "alice0" );
      accounts.push_back( "alice1" );
      accounts.push_back( "alice2" );
      accounts.push_back( "alice3" );
      accounts.push_back( "alice4" );
      accounts.push_back( "alice5" );
      accounts.push_back( "alice6" );

      vector< private_key_type > keys;
      keys.push_back( alice0_private_key );
      keys.push_back( alice1_private_key );
      keys.push_back( alice2_private_key );
      keys.push_back( alice3_private_key );
      keys.push_back( alice4_private_key );
      keys.push_back( alice5_private_key );
      keys.push_back( alice6_private_key );

      vector< feed_publish_operation > ops;
      vector< signed_transaction > txs;

      // Upgrade accounts to witnesses
      for( int i = 0; i < 7; i++ )
      {
         transfer( BTCM_INIT_MINER_NAME, accounts[i], 10000 );
         witness_create( accounts[i], keys[i], "foo.bar", keys[i].get_public_key(), 1000 );

         ops.push_back( feed_publish_operation() );
         ops[i].publisher = accounts[i];

         txs.push_back( signed_transaction() );
      }

      ops[0].exchange_rate = price( asset( 100000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[1].exchange_rate = price( asset( 105000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[2].exchange_rate = price( asset(  98000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[3].exchange_rate = price( asset(  97000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[4].exchange_rate = price( asset(  99000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[5].exchange_rate = price( asset(  97500, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );
      ops[6].exchange_rate = price( asset( 102000, BTCM_SYMBOL ), asset( 1000, XUSD_SYMBOL ) );

      for( int i = 0; i < 7; i++ )
      {
         txs[i].set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );
         txs[i].operations.push_back( ops[i] );
         txs[i].sign( keys[i], db.get_chain_id() );
         db.push_transaction( txs[i], 0 );
      }

      BOOST_TEST_MESSAGE( "Jump forward an hour" );

      generate_blocks( BTCM_BLOCKS_PER_HOUR ); // Jump forward 1 hour
      BOOST_TEST_MESSAGE( "Get feed history object" );
      feed_history_object feed_history = db.get_feed_history();
      BOOST_TEST_MESSAGE( "Check state" );
      BOOST_REQUIRE( feed_history.actual_median_history == price( asset( 99000, BTCM_SYMBOL), asset( 1000, XUSD_SYMBOL ) ) );
      BOOST_REQUIRE( feed_history.effective_median_history == price( asset( 99000, BTCM_SYMBOL), asset( 1000, XUSD_SYMBOL ) ) );
      BOOST_REQUIRE( feed_history.price_history[ 0 ] == price( asset( 99000, BTCM_SYMBOL), asset( 1000, XUSD_SYMBOL ) ) );
      validate_database();

      for ( int i = 0; i < 23; i++ )
      {
         BOOST_TEST_MESSAGE( "Updating ops" );

         for( int j = 0; j < 7; j++ )
         {
            txs[j].operations.clear();
            txs[j].signatures.clear();
            ops[j].exchange_rate = price( ops[j].exchange_rate.base, asset( ops[j].exchange_rate.quote.amount + 10, XUSD_SYMBOL ) );
            txs[j].set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );
            txs[j].operations.push_back( ops[j] );
            txs[j].sign( keys[j], db.get_chain_id() );
            db.push_transaction( txs[j], 0 );
         }

         BOOST_TEST_MESSAGE( "Generate Blocks" );

         generate_blocks( BTCM_BLOCKS_PER_HOUR  ); // Jump forward 1 hour

         BOOST_TEST_MESSAGE( "Check feed_history" );

         feed_history = feed_history_id_type()( db );
         BOOST_REQUIRE( feed_history.actual_median_history == feed_history.price_history[ ( i + 1 ) / 2 ] );
         BOOST_REQUIRE( feed_history.effective_median_history == feed_history.price_history[ ( i + 1 ) / 2 ] );
         BOOST_REQUIRE( feed_history.price_history[ i + 1 ] == ops[4].exchange_rate );
         validate_database();
      }
   }
   FC_LOG_AND_RETHROW();
}

BOOST_AUTO_TEST_CASE( tx_rate_limit )
{ try {
   ACTORS( (alice)(bobby)(charlie) )

   fund( "alice", 10000000 );
   vest( "alice", 1000000 );
   fund( "bobby", 10000000 );
   fund( "charlie", 100000000 );
   vest( "charlie", 100000000 );

   generate_block();

   const dynamic_global_property_object& _dgp = dynamic_global_property_id_type(0)(db);
   db.modify( _dgp, []( dynamic_global_property_object& dgp ) {
      dgp.max_virtual_bandwidth = ( 5000 * BTCM_BANDWIDTH_PRECISION * BTCM_BANDWIDTH_AVERAGE_WINDOW_SECONDS )
                                  / BTCM_BLOCK_INTERVAL;
   });

   signed_transaction tx;
   tx.set_expiration( db.head_block_time() + BTCM_MAX_TIME_UNTIL_EXPIRATION );

   delegate_vesting_shares_operation dvs;
   dvs.delegator = "charlie";
   dvs.delegatee = "bobby";
   dvs.vesting_shares = asset( alice_id(db).vesting_shares.amount.value / 2 - bobby_id(db).vesting_shares.amount.value, VESTS_SYMBOL );
   tx.operations.push_back( dvs );
   tx.sign( charlie_private_key, db.get_chain_id() );
   db.push_transaction( tx, 0 );
   tx.clear();

   transfer_operation op;
   op.from = "alice";
   op.to = "charlie";
   op.amount = asset( 1, BTCM_SYMBOL );
   op.memo = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz";
   for( int i = 0; i < 100; i++ )
      tx.operations.push_back( op );
   int alice_count = 0;
   try {
      while( alice_count++ < 100000 ) {
         tx.set_expiration( db.head_block_time() + 60 + (alice_count & 0x7ff) );
         db.push_transaction( tx, database::skip_transaction_signatures );
         if( !(alice_count & 0x7ff) )
         {
            op.amount.amount.value++;
            tx.operations[0] = op;
         }
      }
   } catch( fc::assert_exception& e ) {
       BOOST_REQUIRE( e.to_detail_string().find( "bandwidth" ) != string::npos );
       alice_count--;
   }
   tx.clear();
   BOOST_CHECK_LT( 10, alice_count );

   op.from = "bobby";
   op.amount = asset( 1, BTCM_SYMBOL );
   for( int i = 0; i < 100; i++ )
      tx.operations.push_back( op );
   int bobby_count = 0;
   try {
      while( bobby_count++ < 100000 ) {
         tx.set_expiration( db.head_block_time() + 60 + (bobby_count & 0x7ff) );
         db.push_transaction( tx, database::skip_transaction_signatures );
         if( !(bobby_count & 0x7ff) )
         {
            op.amount.amount.value++;
            tx.operations[0] = op;
         }
      }
   } catch( fc::assert_exception& e ) {
       BOOST_REQUIRE( e.to_detail_string().find( "bandwidth" ) != string::npos );
       bobby_count--;
   }
   tx.clear();

   // bobby has half as many VESTS as alice, so should have about half the bandwidth
   BOOST_CHECK_EQUAL( alice_count / 2, bobby_count );
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()


//==================================================================================================
/**
  Copyright 2016 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_ARCH_X86_AVX_SIMD_FUNCTION_INTERLEAVE_HPP_INCLUDED
#define BOOST_SIMD_ARCH_X86_AVX_SIMD_FUNCTION_INTERLEAVE_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/bitwise_cast.hpp>
#include <boost/simd/function/slice.hpp>
#include <boost/simd/function/combine.hpp>
#include <boost/simd/function/interleave_first.hpp>
#include <boost/simd/function/interleave_second.hpp>
#include <boost/simd/detail/dispatch/meta/as_floating.hpp>
#include <array>

namespace boost { namespace simd { namespace ext
{
  namespace bd =  boost::dispatch;
  namespace bs =  boost::simd;

  BOOST_DISPATCH_OVERLOAD ( interleave_
                          , (typename A0)
                          , bs::avx_
                          , bs::pack_<bd::integer_<A0>, bs::avx_>
                          , bs::pack_<bd::integer_<A0>, bs::avx_>
                         )
  {
    BOOST_FORCEINLINE std::array<A0,2> operator()(A0 const& a0, A0 const& a1 ) const BOOST_NOEXCEPT
    {
      auto x0 = slice_low(a0);
      auto y0 = slice_low(a1);
      auto f  = combine ( interleave_first(x0,y0)
                      , interleave_first( slide<A0::static_size/4>(x0)
                                        , slide<A0::static_size/4>(y0)
                                        )
                      );

      x0 = slice_high(a0);
      y0 = slice_high(a1);

      auto s =  combine ( interleave_first(x0,y0)
                        , interleave_first( slide<A0::static_size/4>(x0)
                                          , slide<A0::static_size/4>(y0)
                                          )
                        );

      std::array<A0,2> that{{f,s}};
      return that;
    }
  };
} } }

#endif


/*
This file has been originally based on source code of SciDB (examples/example_udos/PhysicalHelloInstances.cpp)
which is copyright (C) 2008-2014 SciDB, Inc.

SciDB is free software: you can redistribute it and/or modify
it under the terms of the AFFERO GNU General Public License as published by
the Free Software Foundation.

SciDB is distributed "AS-IS" AND WITHOUT ANY WARRANTY OF ANY KIND,
INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
NON-INFRINGEMENT, OR FITNESS FOR A PARTICULAR PURPOSE. See
the AFFERO GNU General Public License for the complete license terms.

You should have received a copy of the AFFERO GNU General Public License
along with SciDB.  If not, see <http://www.gnu.org/licenses/agpl-3.0.html>

-----------------------------------------------------------------------------
Modification date: (2015-08-01)

Modifications are copyright (C) 2016 Marius Appel <marius.appel@uni-muenster.de>

scidb4geo - A SciDB plugin for managing spacetime earth-observation arrays

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

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
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
-----------------------------------------------------------------------------*/

#include "../plugin.h" // Must be first to define PROJECT_ROOT

#include <boost/make_shared.hpp>
#include "query/Operator.h"
#include "array/TransientCache.h"
#include "array/DBArray.h"
#include "array/Metadata.h"
#include "system/SystemCatalog.h"

#include <log4cxx/logger.h>

#include "../PostgresWrapper.h"
#include "../ErrorCodes.h"

namespace scidb4geo
{
    using namespace std;
    using namespace scidb;

    static log4cxx::LoggerPtr logger ( log4cxx::Logger::getLogger ( "scidb4geo.eo_setsrs" ) );

    /*! @copydoc LogicalSetSRS
     */
    class PhysicalSetSRS : public PhysicalOperator
    {
    public:
        PhysicalSetSRS ( const string &logicalName, const string &physicalName, const Parameters &parameters, const ArrayDesc &schema ) :
            PhysicalOperator ( logicalName, physicalName, parameters, schema ) {
            _arrayName = ( ( std::shared_ptr<OperatorParamReference> & ) parameters[0] )->getObjectName();
        }

        virtual void preSingleExecute ( shared_ptr<Query> query ) {
            if ( _schema.isTransient() ) {
                shared_ptr<const InstanceMembership> membership ( Cluster::getInstance()->getInstanceMembership() );

                if ( ( membership->getViewId() != query->getCoordinatorLiveness()->getViewId() ) ||
                        ( membership->getInstances().size() != query->getInstancesCount() ) ) {

                    throw USER_EXCEPTION ( SCIDB_SE_EXECUTION, SCIDB_LE_NO_QUORUM2 );
                }
            }

            // Construct SRS object out of parameters
            AffineTransform A ( ( ( std::shared_ptr<OperatorParamPhysicalExpression> & ) _parameters[_parameters.size() - 1] )->getExpression()->evaluate().getString() ); // create affine transform based on string
            string auth_name = ( ( std::shared_ptr<OperatorParamPhysicalExpression> & ) _parameters[3] )->getExpression()->evaluate().getString();
            int auth_srid = ( ( std::shared_ptr<OperatorParamPhysicalExpression> & ) _parameters[4] )->getExpression()->evaluate().getInt32();
            string dim_x = ( ( std::shared_ptr<OperatorParamPhysicalExpression> & ) _parameters[1] )->getExpression()->evaluate().getString();
            string dim_y = ( ( std::shared_ptr<OperatorParamPhysicalExpression> & ) _parameters[2] )->getExpression()->evaluate().getString();

            // Check whether dimension exists
//             ArrayID arrayId = SystemCatalog::getInstance()->findArrayByName ( _arrayName );
//             std::shared_ptr<ArrayDesc> arrayDesc = SystemCatalog::getInstance()->getArrayDesc ( arrayId );

	    ArrayDesc arrayDesc;
	    SystemCatalog::getInstance()->getArrayDesc(_arrayName, query->getCatalogVersion(_arrayName), LAST_VERSION, arrayDesc);

            Dimensions dims = arrayDesc.getDimensions();
            bool dimOK_x = false;
            bool dimOK_y = false;
            for ( size_t i = 0; i < dims.size(); ++i ) {
                if ( dims[i].getBaseName().compare ( dim_x ) == 0 ) dimOK_x = true;
                else if ( dims[i].getBaseName().compare ( dim_y ) == 0 ) dimOK_y = true;
                if ( dimOK_x && dimOK_y ) break;
            }
            if ( !dimOK_x ) {
                if ( !dimOK_y ) {
                    stringstream serr;
                    serr << "Dimensions " << dim_x << " and " << dim_y  << " do not exist in target array.";
                    SCIDB4GEO_ERROR ( serr.str() , SCIDB4GEO_ERR_UNDECLARED_DIMENSION );
                }
                else {
                    stringstream serr;
                    serr << "Dimension " << dim_x << " does not exist in target array.";
                    SCIDB4GEO_ERROR ( serr.str()  , SCIDB4GEO_ERR_UNDECLARED_DIMENSION );
                }

            }
            else if ( !dimOK_y ) {
                stringstream serr;
                serr << "Dimension " << dim_y << " does not exist in target array.";
                SCIDB4GEO_ERROR ( serr.str() , SCIDB4GEO_ERR_UNDECLARED_DIMENSION );
            }

            // Add to system catalog
            PostgresWrapper::instance()->dbSetSpatialRef ( _arrayName, dim_x, dim_y, auth_name, auth_srid, A );

        }

        std::shared_ptr< Array> execute ( std::vector< std::shared_ptr< Array> > &inputArrays,
                                            std::shared_ptr<Query> query ) {
            return std::shared_ptr< Array>();
        }

    private:
        string _arrayName;
    };

    REGISTER_PHYSICAL_OPERATOR_FACTORY ( PhysicalSetSRS, "eo_setsrs", "PhysicalSetSRS" );
    typedef PhysicalSetSRS PhysicalSetSRS_depr;
    REGISTER_PHYSICAL_OPERATOR_FACTORY ( PhysicalSetSRS_depr, "st_setsrs", "PhysicalSetSRS_depr" ); // Backward compatibility
}


#include "util/static_rtree.hpp"
#include "extractor/edge_based_node_segment.hpp"
#include "engine/geospatial_query.hpp"
#include "util/coordinate.hpp"
#include "util/coordinate_calculation.hpp"
#include "util/exception.hpp"
#include "util/rectangle.hpp"
#include "util/typedefs.hpp"

#include "../common/temporary_file.hpp"
#include "mocks/mock_datafacade.hpp"

#include <boost/functional/hash.hpp>
#include <boost/test/unit_test.hpp>

#include <cmath>
#include <cstdint>

#include <algorithm>
#include <filesystem>
#include <memory>
#include <random>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

// explicit TBB scheduler init to register resources cleanup at exit
#if TBB_VERSION_MAJOR == 2020
#include <tbb/global_control.h>
tbb::global_control scheduler(tbb::global_control::max_allowed_parallelism, 2);
#else
#include <tbb/task_scheduler_init.h>
tbb::task_scheduler_init init(2);
#endif

BOOST_AUTO_TEST_SUITE(static_rtree)

using namespace osrm;
using namespace osrm::util;
using namespace osrm::test;

constexpr uint32_t TEST_BRANCHING_FACTOR = 8;
constexpr uint32_t TEST_LEAF_NODE_SIZE = 64;

using TestData = extractor::EdgeBasedNodeSegment;
using TestStaticRTree = StaticRTree<TestData,
                                    osrm::storage::Ownership::Container,
                                    TEST_BRANCHING_FACTOR,
                                    TEST_LEAF_NODE_SIZE>;
using MiniStaticRTree = StaticRTree<TestData, osrm::storage::Ownership::Container, 2, 128>;
using TestDataFacade = MockDataFacade<osrm::engine::routing_algorithms::ch::Algorithm>;

// Choosen by a fair W20 dice roll (this value is completely arbitrary)
constexpr unsigned RANDOM_SEED = 42;
static const int32_t WORLD_MIN_LAT = -85 * COORDINATE_PRECISION;
static const int32_t WORLD_MAX_LAT = 85 * COORDINATE_PRECISION;
static const int32_t WORLD_MIN_LON = -180 * COORDINATE_PRECISION;
static const int32_t WORLD_MAX_LON = 180 * COORDINATE_PRECISION;

template <typename DataT> class LinearSearchNN
{
  public:
    LinearSearchNN(const std::vector<Coordinate> &coords, const std::vector<DataT> &edges)
        : coords(coords), edges(edges)
    {
    }

    std::vector<DataT> Nearest(const Coordinate &input_coordinate, const unsigned num_results)
    {
        std::vector<DataT> local_edges(edges);

        auto projected_input = web_mercator::fromWGS84(input_coordinate);
        const auto segment_comparator = [this, &projected_input](const DataT &lhs,
                                                                 const DataT &rhs) {
            using web_mercator::fromWGS84;
            const auto lhs_result = coordinate_calculation::projectPointOnSegment(
                fromWGS84(coords[lhs.u]), fromWGS84(coords[lhs.v]), projected_input);
            const auto rhs_result = coordinate_calculation::projectPointOnSegment(
                fromWGS84(coords[rhs.u]), fromWGS84(coords[rhs.v]), projected_input);
            const auto lhs_squared_dist = coordinate_calculation::squaredEuclideanDistance(
                lhs_result.second, projected_input);
            const auto rhs_squared_dist = coordinate_calculation::squaredEuclideanDistance(
                rhs_result.second, projected_input);
            return lhs_squared_dist < rhs_squared_dist;
        };

        std::nth_element(local_edges.begin(),
                         local_edges.begin() + num_results,
                         local_edges.end(),
                         segment_comparator);
        local_edges.resize(num_results);

        return local_edges;
    }

  private:
    const std::vector<Coordinate> &coords;
    const std::vector<TestData> &edges;
};

template <unsigned NUM_NODES, unsigned NUM_EDGES> struct RandomGraphFixture
{
    struct TupleHash
    {
        typedef std::pair<unsigned, unsigned> argument_type;
        typedef std::size_t result_type;

        result_type operator()(const argument_type &t) const
        {
            std::size_t val{0};
            boost::hash_combine(val, t.first);
            boost::hash_combine(val, t.second);
            return val;
        }
    };

    RandomGraphFixture()
    {
        std::mt19937 g(RANDOM_SEED);

        std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT);
        std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON);

        for (unsigned i = 0; i < NUM_NODES; i++)
        {
            int lon = lon_udist(g);
            int lat = lat_udist(g);
            coords.emplace_back(Coordinate(FixedLongitude{lon}, FixedLatitude{lat}));
        }

        std::uniform_int_distribution<> edge_udist(0, coords.size() - 1);

        std::unordered_set<std::pair<unsigned, unsigned>, TupleHash> used_edges;

        while (edges.size() < NUM_EDGES)
        {
            TestData data;
            data.u = edge_udist(g);
            data.v = edge_udist(g);
            data.is_startpoint = true;
            if (used_edges.find(std::pair<unsigned, unsigned>(
                    std::min(data.u, data.v), std::max(data.u, data.v))) == used_edges.end())
            {
                edges.emplace_back(data);
                used_edges.emplace(std::min(data.u, data.v), std::max(data.u, data.v));
            }
        }
    }

    std::vector<Coordinate> coords;
    std::vector<TestData> edges;
};

struct GraphFixture
{
    GraphFixture(const std::vector<std::pair<FloatLongitude, FloatLatitude>> &input_coords,
                 const std::vector<std::tuple<unsigned, unsigned, bool>> &input_edges)
    {

        for (unsigned i = 0; i < input_coords.size(); i++)
        {
            coords.emplace_back(input_coords[i].first, input_coords[i].second);
        }

        for (const auto &pair : input_edges)
        {
            TestData d;
            d.u = std::get<0>(pair);
            d.v = std::get<1>(pair);
            // We set the forward nodes to the target node-based-node IDs, just
            // so we have something to test against.  Because this isn't a real
            // graph, the actual values aren't important, we just need something
            // to examine during tests.
            d.forward_segment_id = {std::get<1>(pair), true};
            d.reverse_segment_id = {std::get<0>(pair), true};
            d.fwd_segment_position = 0;
            d.is_startpoint = std::get<2>(pair);
            edges.emplace_back(d);
        }
    }

    std::vector<Coordinate> coords;
    std::vector<TestData> edges;
};

typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 3, TEST_LEAF_NODE_SIZE / 2>
    TestRandomGraphFixture_LeafHalfFull;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 5, TEST_LEAF_NODE_SIZE>
    TestRandomGraphFixture_LeafFull;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * 10, TEST_LEAF_NODE_SIZE * 2>
    TestRandomGraphFixture_TwoLeaves;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
                           TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR>
    TestRandomGraphFixture_Branch;
typedef RandomGraphFixture<TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 3,
                           TEST_LEAF_NODE_SIZE * TEST_BRANCHING_FACTOR * 2>
    TestRandomGraphFixture_MultipleLevels;
typedef RandomGraphFixture<10, 30> TestRandomGraphFixture_10_30;

template <typename RTreeT>
void simple_verify_rtree(RTreeT &rtree,
                         const std::vector<Coordinate> &coords,
                         const std::vector<TestData> &edges)
{
    for (const auto &e : edges)
    {
        const Coordinate &pu = coords[e.u];
        const Coordinate &pv = coords[e.v];
        auto result_u = rtree.Nearest(pu, 1);
        auto result_v = rtree.Nearest(pv, 1);
        BOOST_CHECK(result_u.size() == 1 && result_v.size() == 1);
        BOOST_CHECK(result_u.front().u == e.u || result_u.front().v == e.u);
        BOOST_CHECK(result_v.front().u == e.v || result_v.front().v == e.v);
    }
}

template <typename RTreeT>
void sampling_verify_rtree(RTreeT &rtree,
                           LinearSearchNN<TestData> &lsnn,
                           const std::vector<Coordinate> &coords,
                           unsigned num_samples)
{
    std::mt19937 g(RANDOM_SEED);
    std::uniform_int_distribution<> lat_udist(WORLD_MIN_LAT, WORLD_MAX_LAT);
    std::uniform_int_distribution<> lon_udist(WORLD_MIN_LON, WORLD_MAX_LON);
    std::vector<Coordinate> queries;
    for (unsigned i = 0; i < num_samples; i++)
    {
        queries.emplace_back(FixedLongitude{lon_udist(g)}, FixedLatitude{lat_udist(g)});
    }

    for (const auto &q : queries)
    {
        auto result_rtree = rtree.Nearest(q, 1);
        auto result_lsnn = lsnn.Nearest(q, 1);
        BOOST_CHECK(result_rtree.size() == 1);
        BOOST_CHECK(result_lsnn.size() == 1);
        auto rtree_u = result_rtree.back().u;
        auto rtree_v = result_rtree.back().v;
        auto lsnn_u = result_lsnn.back().u;
        auto lsnn_v = result_lsnn.back().v;

        const double rtree_dist =
            coordinate_calculation::perpendicularDistance(coords[rtree_u], coords[rtree_v], q);
        const double lsnn_dist =
            coordinate_calculation::perpendicularDistance(coords[lsnn_u], coords[lsnn_v], q);

        BOOST_CHECK_CLOSE(rtree_dist, lsnn_dist, 0.0001);
    }
}

template <typename RTreeT, typename FixtureT>
auto make_rtree(const std::filesystem::path &path, FixtureT &fixture)
{
    return RTreeT(fixture.edges, fixture.coords, path);
}

template <typename RTreeT = TestStaticRTree, typename FixtureT>
void construction_test(const std::string &path, FixtureT &fixture)
{
    std::string leaves_path;
    std::string nodes_path;
    auto rtree = make_rtree<RTreeT>(path, fixture);
    LinearSearchNN<TestData> lsnn(fixture.coords, fixture.edges);

    simple_verify_rtree(rtree, fixture.coords, fixture.edges);
    sampling_verify_rtree(rtree, lsnn, fixture.coords, 100);
}

BOOST_FIXTURE_TEST_CASE(construct_tiny, TestRandomGraphFixture_10_30)
{
    using TinyTestTree = StaticRTree<TestData, osrm::storage::Ownership::Container, 2, 64>;
    construction_test<TinyTestTree>("test_tiny", *this);
}

BOOST_FIXTURE_TEST_CASE(construct_half_leaf_test, TestRandomGraphFixture_LeafHalfFull)
{
    construction_test("test_1", *this);
}

BOOST_FIXTURE_TEST_CASE(construct_full_leaf_test, TestRandomGraphFixture_LeafFull)
{
    construction_test("test_2", *this);
}

BOOST_FIXTURE_TEST_CASE(construct_two_leaves_test, TestRandomGraphFixture_TwoLeaves)
{
    construction_test("test_3", *this);
}

BOOST_FIXTURE_TEST_CASE(construct_branch_test, TestRandomGraphFixture_Branch)
{
    construction_test("test_4", *this);
}

BOOST_FIXTURE_TEST_CASE(construct_multiple_levels_test, TestRandomGraphFixture_MultipleLevels)
{
    construction_test("test_5", *this);
}

// Bug: If you querry a point that lies between two BBs that have a gap,
// one BB will be pruned, even if it could contain a nearer match.
BOOST_AUTO_TEST_CASE(regression_test)
{
    using Coord = std::pair<FloatLongitude, FloatLatitude>;
    using Edge = std::tuple<unsigned, unsigned, bool>;
    GraphFixture fixture(
        {
            Coord{FloatLongitude{0.0}, FloatLatitude{40.0}},   //
            Coord{FloatLongitude{5.0}, FloatLatitude{35.0}},   //
            Coord{FloatLongitude{5.0}, FloatLatitude{5.0}},    //
            Coord{FloatLongitude{10.0}, FloatLatitude{0.0}},   //
            Coord{FloatLongitude{10.0}, FloatLatitude{20.0}},  //
            Coord{FloatLongitude{5.0}, FloatLatitude{20.0}},   //
            Coord{FloatLongitude{100.0}, FloatLatitude{40.0}}, //
            Coord{FloatLongitude{105.0}, FloatLatitude{35.0}}, //
            Coord{FloatLongitude{105.0}, FloatLatitude{5.0}},  //
            Coord{FloatLongitude{110.0}, FloatLatitude{0.0}},  //
        },
        {Edge(0, 1, true), Edge(2, 3, true), Edge(4, 5, true), Edge(6, 7, true), Edge(8, 9, true)});

    TemporaryFile tmp;
    auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
    LinearSearchNN<TestData> lsnn(fixture.coords, fixture.edges);

    // query a node just right of the center of the gap
    Coordinate input(FloatLongitude{55.1}, FloatLatitude{20.0});
    auto result_rtree = rtree.Nearest(input, 1);
    auto result_ls = lsnn.Nearest(input, 1);

    BOOST_CHECK(result_rtree.size() == 1);
    BOOST_CHECK(result_ls.size() == 1);

    BOOST_CHECK_EQUAL(result_ls.front().u, result_rtree.front().u);
    BOOST_CHECK_EQUAL(result_ls.front().v, result_rtree.front().v);
}

// Bug: If you querry a point with a narrow radius, no result should be returned
BOOST_AUTO_TEST_CASE(radius_regression_test)
{
    using Coord = std::pair<FloatLongitude, FloatLatitude>;
    using Edge = std::tuple<unsigned, unsigned, bool>;
    GraphFixture fixture(
        {
            Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
            Coord(FloatLongitude{10.0}, FloatLatitude{10.0}),
        },
        {Edge(0, 1, true), Edge(1, 0, true)});

    TemporaryFile tmp;
    auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
    TestDataFacade mockfacade;
    engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
        rtree, fixture.coords, mockfacade);

    Coordinate input(FloatLongitude{5.2}, FloatLatitude{5.0});

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 0.01, osrm::engine::Approach::UNRESTRICTED, true);
        BOOST_CHECK_EQUAL(results.size(), 0);
    }
}

BOOST_AUTO_TEST_CASE(permissive_edge_snapping)
{
    using Coord = std::pair<FloatLongitude, FloatLatitude>;
    using Edge = std::tuple<unsigned, unsigned, bool>;
    GraphFixture fixture(
        {
            Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
            Coord(FloatLongitude{0.001}, FloatLatitude{0.001}),
        },
        {Edge(0, 1, true), Edge(1, 0, false)});

    TemporaryFile tmp;
    auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
    TestDataFacade mockfacade;
    engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
        rtree, fixture.coords, mockfacade);

    Coordinate input(FloatLongitude{0.0005}, FloatLatitude{0.0005});

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 1000, osrm::engine::Approach::UNRESTRICTED, false);
        BOOST_CHECK_EQUAL(results.size(), 1);
    }

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 1000, osrm::engine::Approach::UNRESTRICTED, true);
        BOOST_CHECK_EQUAL(results.size(), 2);
    }
}

BOOST_AUTO_TEST_CASE(bearing_tests)
{
    using Coord = std::pair<FloatLongitude, FloatLatitude>;
    using Edge = std::tuple<unsigned, unsigned, bool>;
    GraphFixture fixture(
        {
            Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
            Coord(FloatLongitude{10.0}, FloatLatitude{10.0}),
        },
        {Edge(0, 1, true), Edge(1, 0, true)});

    TemporaryFile tmp;
    auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
    TestDataFacade mockfacade;
    engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
        rtree, fixture.coords, mockfacade);

    Coordinate input(FloatLongitude{5.1}, FloatLatitude{5.0});

    {
        auto results = query.NearestPhantomNodes(input, 5, osrm::engine::Approach::UNRESTRICTED);
        BOOST_CHECK_EQUAL(results.size(), 2);
        BOOST_CHECK_EQUAL(results.back().phantom_node.forward_segment_id.id, 0);
        BOOST_CHECK_EQUAL(results.back().phantom_node.reverse_segment_id.id, 1);
    }

    {
        auto results =
            query.NearestPhantomNodes(input, 5, 270, 10, osrm::engine::Approach::UNRESTRICTED);
        BOOST_CHECK_EQUAL(results.size(), 0);
    }

    {
        auto results =
            query.NearestPhantomNodes(input, 5, 45, 10, osrm::engine::Approach::UNRESTRICTED);
        BOOST_CHECK_EQUAL(results.size(), 2);

        BOOST_CHECK(results[0].phantom_node.forward_segment_id.enabled);
        BOOST_CHECK(!results[0].phantom_node.reverse_segment_id.enabled);
        BOOST_CHECK_EQUAL(results[0].phantom_node.forward_segment_id.id, 1);

        BOOST_CHECK(!results[1].phantom_node.forward_segment_id.enabled);
        BOOST_CHECK(results[1].phantom_node.reverse_segment_id.enabled);
        BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_segment_id.id, 1);
    }

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 11000, osrm::engine::Approach::UNRESTRICTED, true);
        BOOST_CHECK_EQUAL(results.size(), 2);
    }

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 11000, 270, 10, osrm::engine::Approach::UNRESTRICTED, true);
        BOOST_CHECK_EQUAL(results.size(), 0);
    }

    {
        auto results = query.NearestPhantomNodesInRange(
            input, 11000, 45, 10, osrm::engine::Approach::UNRESTRICTED, true);
        BOOST_CHECK_EQUAL(results.size(), 2);

        BOOST_CHECK(results[0].phantom_node.forward_segment_id.enabled);
        BOOST_CHECK(!results[0].phantom_node.reverse_segment_id.enabled);
        BOOST_CHECK_EQUAL(results[0].phantom_node.forward_segment_id.id, 1);

        BOOST_CHECK(!results[1].phantom_node.forward_segment_id.enabled);
        BOOST_CHECK(results[1].phantom_node.reverse_segment_id.enabled);
        BOOST_CHECK_EQUAL(results[1].phantom_node.reverse_segment_id.id, 1);
    }
}

BOOST_AUTO_TEST_CASE(bbox_search_tests)
{
    using Coord = std::pair<FloatLongitude, FloatLatitude>;
    using Edge = std::tuple<unsigned, unsigned, bool>;

    GraphFixture fixture(
        {
            Coord(FloatLongitude{0.0}, FloatLatitude{0.0}),
            Coord(FloatLongitude{1.0}, FloatLatitude{1.0}),
            Coord(FloatLongitude{2.0}, FloatLatitude{2.0}),
            Coord(FloatLongitude{3.0}, FloatLatitude{3.0}),
            Coord(FloatLongitude{4.0}, FloatLatitude{4.0}),
        },
        {Edge(0, 1, true), Edge(1, 2, true), Edge(2, 3, true), Edge(3, 4, true)});

    TemporaryFile tmp;
    auto rtree = make_rtree<MiniStaticRTree>(tmp.path, fixture);
    TestDataFacade mockfacade;
    engine::GeospatialQuery<MiniStaticRTree, TestDataFacade> query(
        rtree, fixture.coords, mockfacade);

    {
        RectangleInt2D bbox = {
            FloatLongitude{0.5}, FloatLongitude{1.5}, FloatLatitude{0.5}, FloatLatitude{1.5}};
        auto results = query.Search(bbox);
        BOOST_CHECK_EQUAL(results.size(), 2);
    }

    {
        RectangleInt2D bbox = {
            FloatLongitude{1.5}, FloatLongitude{3.5}, FloatLatitude{1.5}, FloatLatitude{3.5}};
        auto results = query.Search(bbox);
        BOOST_CHECK_EQUAL(results.size(), 3);
    }
}

BOOST_AUTO_TEST_SUITE_END()


#include <cassert>
#include <cmath>
#include <algorithm>
#include <boost/functional/hash.hpp>

#include "whisker.hh"

using namespace std;

vector< Whisker > Whisker::bisect( void ) const
{
  vector< Whisker > ret;
  for ( auto &x : _domain.bisect() ) {
    Whisker new_whisker( *this );
    new_whisker._domain = x;
    ret.push_back( new_whisker );
  }
  return ret;
}

Whisker::Whisker( const unsigned int s_window_increment, const double s_window_multiple, const double s_intersend, const MemoryRange & s_domain )
  : _generation( 0 ),
    _window_increment( s_window_increment ),
    _window_multiple( s_window_multiple ),
    _intersend( s_intersend ),
    _domain( s_domain )
{
}

Whisker::Whisker( const Whisker & other )
  : _generation( other._generation ),
    _window_increment( other._window_increment ),
    _window_multiple( other._window_multiple ),
    _intersend( other._intersend ),
    _domain( other._domain )
{
}

template < typename T >
bool Whisker::OptimizationSetting< T >::eligible_value( const T & value ) const
{
  return value >= min_value and value <= max_value;
}

template < typename T >
vector< T > Whisker::OptimizationSetting< T >::alternatives( const T & value ) const
{
  assert( eligible_value( value ) );

  vector< T > ret( 1, value );

  for ( T proposed_change = min_change;
	proposed_change <= max_change;
	proposed_change *= multiplier ) {
    /* explore positive change */
    const T proposed_value_up = value + proposed_change;
    const T proposed_value_down = value - proposed_change;

    if ( eligible_value( proposed_value_up ) ) {
      ret.push_back( proposed_value_up );
    }

    if ( eligible_value( proposed_value_down ) ) {
      ret.push_back( proposed_value_down );
    }
  }

  return ret;
}

vector< Whisker > Whisker::next_generation( void ) const
{
  vector< Whisker > ret;

  for ( const auto & alt_window : get_optimizer().window_increment.alternatives( _window_increment ) ) {
    for ( const auto & alt_multiple : get_optimizer().window_multiple.alternatives( _window_multiple ) ) {
      for ( const auto & alt_intersend : get_optimizer().intersend.alternatives( _intersend ) ) {
	Whisker new_whisker { *this };
	new_whisker._generation++;

	new_whisker._window_increment = alt_window;
	new_whisker._window_multiple = alt_multiple;
	new_whisker._intersend = alt_intersend;

	new_whisker.round();

	ret.push_back( new_whisker );
      }
    }
  }

  return ret;
}

void Whisker::promote( const unsigned int generation )
{
  _generation = max( _generation, generation );
}

string Whisker::str( const unsigned int total ) const
{
  char tmp[ 256 ];
  snprintf( tmp, 256, "{%s} gen=%u usage=%.4f => (win=%d + %f * win, intersend=%f)",
	    _domain.str().c_str(), _generation, double( _domain.count() ) / double( total ), _window_increment, _window_multiple, _intersend );
  return tmp;
}

RemyBuffers::Whisker Whisker::DNA( void ) const
{
  RemyBuffers::Whisker ret;

  ret.set_window_increment( _window_increment );
  ret.set_window_multiple( _window_multiple );
  ret.set_intersend( _intersend );
  ret.mutable_domain()->CopyFrom( _domain.DNA() );

  return ret;
}

Whisker::Whisker( const RemyBuffers::Whisker & dna )
  : _generation( 0 ),
    _window_increment( dna.window_increment() ),
    _window_multiple( dna.window_multiple() ),
    _intersend( dna.intersend() ),
    _domain( dna.domain() )
{
}

void Whisker::round( void )
{
  _window_multiple = (1.0/10000.0) * int( 10000 * _window_multiple );
  _intersend = (1.0/10000.0) * int( 10000 * _intersend );
}

size_t hash_value( const Whisker & whisker )
{
  size_t seed = 0;
  boost::hash_combine( seed, whisker._window_increment );
  boost::hash_combine( seed, whisker._window_multiple );
  boost::hash_combine( seed, whisker._intersend );
  boost::hash_combine( seed, whisker._domain );

  return seed;
}


//    Copyright (C) Dimitrios Michail 2019 - 2021.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

#include <iostream>
#include <fstream>
#include <map>
#include <list>

#include <boost/config.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/program_options.hpp>
#include <boost/timer/timer.hpp>
#include <boost/thread.hpp>

#include <parmcb/parmcb.hpp>
#include <parmcb/util.hpp>
#include <parmcb/detail/cycles.hpp>
#include <parmcb/sptrees.hpp>

using namespace boost;
namespace po = boost::program_options;

#define USAGE "Computes statistics on FVS and ISO cycle collections given a weighted undirected graph in DIMACS format."

template<class Graph, class WeightMap>
class CandidateCycleBuilder {
public:
    typedef typename boost::graph_traits<Graph>::vertex_descriptor Vertex;
    typedef typename boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef typename boost::property_traits<WeightMap>::value_type WeightType;

    CandidateCycleBuilder(const Graph &g, const WeightMap &weight_map) :
            g(g), weight_map(weight_map) {
    }

    std::tuple<std::set<Edge>, WeightType> operator()(const std::vector<parmcb::SPTree<Graph, WeightMap>> &trees,
            const parmcb::CandidateCycle<Graph, WeightMap> &c) const {

        std::shared_ptr<parmcb::SPNode<Graph, WeightMap>> v = trees[c.tree()].node(boost::source(c.edge(), g));
        std::shared_ptr<parmcb::SPNode<Graph, WeightMap>> u = trees[c.tree()].node(boost::target(c.edge(), g));

        Edge e = c.edge();
        bool valid = true;
        WeightType cycle_weight = boost::get(weight_map, e);
        std::set<Edge> result;
        result.insert(e);

        // first part
        Vertex w = boost::source(c.edge(), g);
        std::shared_ptr<parmcb::SPNode<Graph, WeightMap>> ws = trees[c.tree()].node(w);
        while (ws->has_pred()) {
            Edge a = ws->pred();
            if (result.insert(a).second == false) {
                valid = false;
                break;
            }
            cycle_weight += boost::get(weight_map, a);
            w = boost::opposite(a, w, g);
            ws = trees[c.tree()].node(w);
        }

        if (!valid) {
            return std::make_tuple(std::set<Edge> { }, 0.0);
        }

        // second part
        w = boost::target(c.edge(), g);
        ws = trees[c.tree()].node(w);
        while (ws->has_pred()) {
            Edge a = ws->pred();
            if (result.insert(a).second == false) {
                valid = false;
                break;
            }
            cycle_weight += boost::get(weight_map, a);
            w = boost::opposite(a, w, g);
            ws = trees[c.tree()].node(w);
        }

        if (!valid) {
            return std::make_tuple(std::set<Edge> { }, 0.0);
        }

        return std::make_tuple(result, cycle_weight);
    }

private:
    const Graph &g;
    const WeightMap &weight_map;
};

template<class Graph, class WeightMap, class CyclesBuilder>
void print_tree_stats(const char *name, const Graph &g, WeightMap weight_map) {
    std::vector<parmcb::SPTree<Graph, WeightMap>> trees;
    std::vector<parmcb::CandidateCycle<Graph, WeightMap>> cycles;
    CyclesBuilder cycles_builder;
    cycles_builder(g, weight_map, trees, cycles);
    std::cout << name << " cycles: " << cycles.size() << std::endl;

    CandidateCycleBuilder<Graph, WeightMap> candidate_cycle_builder(g, weight_map);

    typedef typename boost::graph_traits<Graph>::edge_descriptor Edge;
    typedef typename boost::property_traits<WeightMap>::value_type WeightType;

    double total_weight = 0.0;
    long total_card = 0;
    for (parmcb::CandidateCycle<Graph, WeightMap> c : cycles) {
        std::tuple<std::set<Edge>, WeightType> cc = candidate_cycle_builder(trees, c);

        total_weight += std::get<1>(cc);
        total_card += std::get<0>(cc).size();
    }

    std::cout << name << " total cycles weight: " << total_weight << std::endl;
    std::cout << name << " total cycles cardinality: " << total_card << std::endl;
}

template <class Graph, class WeightMap>
void print_all_tree_stats(const Graph& g, WeightMap weight_map) {
    print_tree_stats<Graph, WeightMap, parmcb::detail::FVSCyclesBuilder<Graph, WeightMap>>("FVS", g, weight_map);
    print_tree_stats<Graph, WeightMap, parmcb::detail::ISOCyclesBuilder<Graph, WeightMap>>("ISO", g, weight_map);
    print_tree_stats<Graph, WeightMap, parmcb::detail::HortonCyclesBuilder<Graph, WeightMap>>("HORTON", g, weight_map);
}

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

    po::variables_map vm;
    try {
        po::options_description desc(USAGE);
        // @formatter:off
        desc.add_options()
                ("help,h", "Help")
                ("verbose,v", po::value<bool>()->default_value(false)->implicit_value(true), "Verbose")
                ("input-file,I",po::value<std::string>(), "Input filename");
        // @formatter:on
        po::positional_options_description pos_desc;
        pos_desc.add("input-file", 1);
        po::command_line_parser parser { argc, argv };
        parser.options(desc).positional(pos_desc).allow_unregistered();
        po::parsed_options parsed_options = parser.run();
        po::store(parsed_options, vm);

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

        if (!vm.count("input-file")) {
            std::cerr << "Input file missing. See usage by calling with -h ." << std::endl;
            exit(EXIT_FAILURE);
        }

    } catch (const po::error &ex) {
        std::cerr << "Invalid arguments:" << ex.what() << std::endl;
        return EXIT_FAILURE;
    }

    typedef adjacency_list<vecS, vecS, undirectedS, no_property, property<edge_weight_t, double> > graph_t;

    // create graph
    graph_t graph;
    FILE *fp = fopen(vm["input-file"].as<std::string>().c_str(), "r");
    if (fp == NULL) {
        std::cerr << "Failed to open input file." << std::endl;
        exit(EXIT_FAILURE);
    }
    parmcb::read_dimacs_from_file(fp, graph);
    fclose(fp);

    if (parmcb::has_loops(graph)) {
        std::cerr << "Graph has loops, aborting.." << std::endl;
        return EXIT_FAILURE;
    }
    if (parmcb::has_multiple_edges(graph)) {
        std::cerr << "Graph has multiple edges, aborting.." << std::endl;
        return EXIT_FAILURE;
    }
    if (parmcb::has_non_positive_weights(graph, get(boost::edge_weight, graph))) {
        std::cerr << "Graph has negative or zero weight edges, aborting.." << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "graph n: " << num_vertices(graph) << std::endl;
    std::cout << "graph m: " << num_edges(graph) << std::endl;
    std::cout << "graph n*m: " << num_vertices(graph)*num_edges(graph) << std::endl;
    std::cout << std::flush;

    print_all_tree_stats(graph, get(boost::edge_weight, graph));

    return EXIT_SUCCESS;
}


/* -*- c++ -*- */
/*
 * Copyright 2003,2008,2011,2012 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * GNU Radio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * GNU Radio 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
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Radio; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include <gnuradio/fft/fft.h>
#include <gnuradio/sys_paths.h>
#include <gnuradio/gr_complex.h>
#include <volk/volk.h>
#include <fftw3.h>

#ifdef _WIN32 //http://www.fftw.org/install/windows.html#DLLwisdom
static void my_fftw_write_char(char c, void *f) { fputc(c, (FILE *) f); }
#define fftw_export_wisdom_to_file(f) fftw_export_wisdom(my_fftw_write_char, (void*) (f))
#define fftwf_export_wisdom_to_file(f) fftwf_export_wisdom(my_fftw_write_char, (void*) (f))
#define fftwl_export_wisdom_to_file(f) fftwl_export_wisdom(my_fftw_write_char, (void*) (f))

static int my_fftw_read_char(void *f) { return fgetc((FILE *) f); }
#define fftw_import_wisdom_from_file(f) fftw_import_wisdom(my_fftw_read_char, (void*) (f))
#define fftwf_import_wisdom_from_file(f) fftwf_import_wisdom(my_fftw_read_char, (void*) (f))
#define fftwl_import_wisdom_from_file(f) fftwl_import_wisdom(my_fftw_read_char, (void*) (f))
#include <fcntl.h>
#include <io.h>
#define O_NOCTTY 0
#define O_NONBLOCK 0
#endif //_WIN32

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <cassert>
#include <stdexcept>

#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/interprocess/sync/file_lock.hpp>
namespace fs = boost::filesystem;

namespace gr {
  namespace fft {
    static boost::mutex wisdom_thread_mutex;
    boost::interprocess::file_lock wisdom_lock;
    static bool wisdom_lock_init_done = false; // Modify while holding 'wisdom_thread_mutex'

    gr_complex *
    malloc_complex(int size)
    {
      return (gr_complex*) volk_malloc (sizeof (gr_complex)*size, volk_get_alignment ());
    }

    float *
    malloc_float(int size)
    {
      return (float*) volk_malloc (sizeof (float)*size, volk_get_alignment ());
    }

    double *
    malloc_double(int size)
    {
      return (double*) volk_malloc (sizeof (double)*size, volk_get_alignment ());
    }

    void
    free(void *b)
    {
      volk_free(b);
    }

    boost::mutex &
    planner::mutex()
    {
      static boost::mutex  s_planning_mutex;

      return s_planning_mutex;
    }

    static std::string
    wisdom_filename()
    {
      static fs::path path;
      path = fs::path(gr::appdata_path()) / ".gr_fftw_wisdom";
      return path.string();
    }

    static void
    wisdom_lock_init()
    {
      if (wisdom_lock_init_done)
        return;

      const std::string wisdom_lock_file = wisdom_filename() + ".lock";
      // std::cerr << "Creating FFTW wisdom lockfile: " << wisdom_lock_file << std::endl;
      int fd = open(wisdom_lock_file.c_str(),
        O_WRONLY | O_CREAT | O_NOCTTY | O_NONBLOCK,
        0666);
      if (fd < 0) {
        throw std::runtime_error("Failed to create FFTW wisdom lockfile: " + wisdom_lock_file);
      }
      close(fd);
      wisdom_lock = boost::interprocess::file_lock(wisdom_lock_file.c_str());
      wisdom_lock_init_done = true;
    }

    static void
    lock_wisdom()
    {
      wisdom_thread_mutex.lock();
      wisdom_lock_init();
      wisdom_lock.lock();
    }

    static void
    unlock_wisdom()
    {
      // Assumes 'lock_wisdom' has already been called (i.e. this file_lock is valid)
      wisdom_lock.unlock();
      wisdom_thread_mutex.unlock();
    }

    static void
    import_wisdom()
    {
      const std::string filename = wisdom_filename ();
      FILE *fp = fopen (filename.c_str(), "r");
      if (fp != 0){
        int r = fftwf_import_wisdom_from_file (fp);
        fclose (fp);
        if (!r){
          fprintf (stderr, "gr::fft: can't import wisdom from %s\n", filename.c_str());
        }
      }
    }

    static void
    config_threading(int nthreads)
    {
      static int fftw_threads_inited = 0;

#ifdef FFTW3F_THREADS
      if (fftw_threads_inited == 0)
    {
      fftw_threads_inited = 1;
      fftwf_init_threads();
    }

      fftwf_plan_with_nthreads(nthreads);
#endif
    }

    static void
    export_wisdom()
    {
      const std::string filename = wisdom_filename ();
      FILE *fp = fopen (filename.c_str(), "w");
      if (fp != 0){
        fftwf_export_wisdom_to_file (fp);
        fclose (fp);
      }
      else {
        fprintf (stderr, "fft_impl_fftw: ");
        perror (filename.c_str());
      }
    }

// ----------------------------------------------------------------

    fft_complex::fft_complex(int fft_size, bool forward, int nthreads)
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      assert (sizeof (fftwf_complex) == sizeof (gr_complex));

      if (fft_size <= 0){
        throw std::out_of_range ("fft_impl_fftw: invalid fft_size");
      }

      d_fft_size = fft_size;
      d_inbuf = (gr_complex *) volk_malloc (sizeof (gr_complex) * inbuf_length (), volk_get_alignment ());
      if (d_inbuf == 0) {
        throw std::runtime_error ("volk_malloc");
      }
      d_outbuf = (gr_complex *) volk_malloc (sizeof (gr_complex) * outbuf_length (), volk_get_alignment ());
      if (d_outbuf == 0) {
        volk_free (d_inbuf);
        throw std::runtime_error ("volk_malloc");
      }

      d_nthreads = nthreads;
      config_threading(nthreads);
      lock_wisdom();
      import_wisdom();	// load prior wisdom from disk

      d_plan = fftwf_plan_dft_1d (fft_size,
                  reinterpret_cast<fftwf_complex *>(d_inbuf),
                  reinterpret_cast<fftwf_complex *>(d_outbuf),
                  forward ? FFTW_FORWARD : FFTW_BACKWARD,
                  FFTW_MEASURE);

      if (d_plan == NULL) {
        fprintf(stderr, "gr::fft: error creating plan\n");
        throw std::runtime_error ("fftwf_plan_dft_1d failed");
      }
      export_wisdom();	// store new wisdom to disk
      unlock_wisdom();
    }

    fft_complex::~fft_complex()
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      fftwf_destroy_plan ((fftwf_plan) d_plan);
      volk_free (d_inbuf);
      volk_free (d_outbuf);
    }

    void
    fft_complex::set_nthreads(int n)
    {
      if (n <= 0) {
        throw std::out_of_range ("gr::fft: invalid number of threads");
      }
      d_nthreads = n;

#ifdef FFTW3F_THREADS
      fftwf_plan_with_nthreads(d_nthreads);
#endif
    }

    void
    fft_complex::execute()
    {
      fftwf_execute((fftwf_plan) d_plan);
    }

// ----------------------------------------------------------------

    fft_real_fwd::fft_real_fwd (int fft_size, int nthreads)
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      assert (sizeof (fftwf_complex) == sizeof (gr_complex));

      if (fft_size <= 0) {
        throw std::out_of_range ("gr::fft: invalid fft_size");
      }

      d_fft_size = fft_size;
      d_inbuf = (float *) volk_malloc (sizeof (float) * inbuf_length (), volk_get_alignment ());
      if (d_inbuf == 0) {
        throw std::runtime_error ("volk_malloc");
      }

      d_outbuf = (gr_complex *) volk_malloc (sizeof (gr_complex) * outbuf_length (), volk_get_alignment ());
      if (d_outbuf == 0) {
        volk_free (d_inbuf);
        throw std::runtime_error ("volk_malloc");
      }

      d_nthreads = nthreads;
      config_threading(nthreads);
      lock_wisdom();
      import_wisdom();	// load prior wisdom from disk

      d_plan = fftwf_plan_dft_r2c_1d (fft_size,
                      d_inbuf,
                      reinterpret_cast<fftwf_complex *>(d_outbuf),
                      FFTW_MEASURE);

      if (d_plan == NULL) {
        fprintf(stderr, "gr::fft::fft_real_fwd: error creating plan\n");
        throw std::runtime_error ("fftwf_plan_dft_r2c_1d failed");
      }
      export_wisdom();	// store new wisdom to disk
      unlock_wisdom();
    }

    fft_real_fwd::~fft_real_fwd()
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      fftwf_destroy_plan ((fftwf_plan) d_plan);
      volk_free (d_inbuf);
      volk_free (d_outbuf);
    }

    void
    fft_real_fwd::set_nthreads(int n)
    {
      if (n <= 0) {
        throw std::out_of_range ("gr::fft::fft_real_fwd::set_nthreads: invalid number of threads");
      }
      d_nthreads = n;

#ifdef FFTW3F_THREADS
      fftwf_plan_with_nthreads(d_nthreads);
#endif
    }

    void
    fft_real_fwd::execute()
    {
      fftwf_execute ((fftwf_plan) d_plan);
    }

    // ----------------------------------------------------------------

    fft_real_rev::fft_real_rev(int fft_size, int nthreads)
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      assert (sizeof (fftwf_complex) == sizeof (gr_complex));

      if (fft_size <= 0) {
        throw std::out_of_range ("gr::fft::fft_real_rev: invalid fft_size");
      }

      d_fft_size = fft_size;
      d_inbuf = (gr_complex *) volk_malloc (sizeof (gr_complex) * inbuf_length (), volk_get_alignment ());
      if (d_inbuf == 0) {
        throw std::runtime_error ("volk_malloc");
      }

      d_outbuf = (float *) volk_malloc (sizeof (float) * outbuf_length (), volk_get_alignment ());
      if (d_outbuf == 0) {
        volk_free (d_inbuf);
        throw std::runtime_error ("volk_malloc");
      }

      d_nthreads = nthreads;
      config_threading(nthreads);
      lock_wisdom();
      import_wisdom();	// load prior wisdom from disk

      // FIXME If there's ever a chance that the planning functions
      // will be called in multiple threads, we've got to ensure single
      // threaded access.  They are not thread-safe.
      d_plan = fftwf_plan_dft_c2r_1d (fft_size,
                      reinterpret_cast<fftwf_complex *>(d_inbuf),
                      d_outbuf,
                      FFTW_MEASURE);

      if (d_plan == NULL) {
        fprintf(stderr, "gr::fft::fft_real_rev: error creating plan\n");
        throw std::runtime_error ("fftwf_plan_dft_c2r_1d failed");
      }
      export_wisdom ();	// store new wisdom to disk
      unlock_wisdom();
    }

    fft_real_rev::~fft_real_rev ()
    {
      // Hold global mutex during plan construction and destruction.
      planner::scoped_lock lock(planner::mutex());

      fftwf_destroy_plan ((fftwf_plan) d_plan);
      volk_free (d_inbuf);
      volk_free (d_outbuf);
    }

    void
    fft_real_rev::set_nthreads(int n)
    {
      if (n <= 0) {
        throw std::out_of_range ("gr::fft::fft_real_rev::set_nthreads: invalid number of threads");
      }
      d_nthreads = n;

#ifdef FFTW3F_THREADS
      fftwf_plan_with_nthreads(d_nthreads);
#endif
    }

    void
    fft_real_rev::execute ()
    {
      fftwf_execute ((fftwf_plan) d_plan);
    }

  } /* namespace fft */
} /* namespace gr */


/**
 * 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.
 */
/**
 * @file signal.hpp
 * @author Ondrej Prochazka <ondrej.prochazka@citationtech.net>
 *
 * 2d signal with forward signal reconstruction capability.
 */

#ifndef MATH_SIGNAL_HPP
#define MATH_SIGNAL_HPP

#include <ostream>

#include "boost_gil_all.hpp"
#include <boost/scoped_array.hpp>
#include <boost/numeric/ublas/vector.hpp>

#include "filters.hpp"

namespace gil = boost::gil;
namespace ublas = boost::numeric::ublas;

namespace math {

class Signal2Base {

public :

    enum Type_t {
        Nonperiodic = 0x00,
        Xperiodic = 0x01
    };

    Signal2Base( int sizeX, int sizeY,
        const double llx, const double lly, const double urx,
        const double ury, const Type_t type = Nonperiodic );

    int width() const { return sizeX; }
    int height() const { return sizeY; }

    double pixelX() { return pixelx; }
    double pixelY() { return pixely; }

    struct Cell_s {

        enum {

            QUADRANT_LL = 0x01,
            QUADRANT_LR = 0x02,
            QUADRANT_UL = 0x04,
            QUADRANT_UR = 0x08,

            QUADRANT_NONE = 0x00,
            QUADRANT_ALL = 0x0f

        };

        double valueSum, pwsum, nwsum;
        unsigned char quadrants;

        Cell_s() : valueSum( 0.0 ), pwsum( 0.0 ), nwsum( 0.0 ),
            quadrants( QUADRANT_NONE ) {};

        bool defined() const {

            if ( pwsum - nwsum < 1E-15 ) return false;
            if ( pwsum <= 3 * nwsum ) return false;
            if ( quadrants != QUADRANT_ALL ) return false;

            return true;
        }

        double value() const {
            assert( defined() );
            return valueSum / ( pwsum - nwsum );
        }

        void add( double value, double weight, unsigned char & quadrant ) {
            valueSum += weight * value;
            quadrants |= quadrant;
            if ( weight > 0 ) pwsum += weight; else nwsum -= weight;
        };
    };

    typedef Cell_s * iterator;
    typedef const Cell_s * const_iterator;
    typedef const Cell_s * const_row_iterator;

    class const_col_iterator {

    public:

        const Cell_s & operator[] ( int i ) const {
            return *( cell + step * i ); }
        const_col_iterator & operator++()  { cell += step; return *this; }
        const_col_iterator operator + ( int i ) const {
            return const_col_iterator( cell + step * i, step ); }
        const Cell_s & operator* () const { return *cell; }
        const Cell_s * operator -> () const { return cell; }
        bool operator < ( const const_col_iterator & s ) {
            return cell < s.cell; }
        const_col_iterator( const Cell_s * cell, int step )
            : cell( cell ), step( step ) {}

    private:
        const Cell_s * cell;
        int step;
    };

    Cell_s & at( int i, int j ) { return cells[sizeX * j + i]; }
    const Cell_s & at( int i, int j ) const { return cells[ sizeX * j + i]; }

    iterator begin() { return iterator( cells.get() ); }
    const_iterator begin() const { return iterator( cells.get() ); }
    iterator end() { return iterator( cells.get() + sizeX * sizeY ); }
    const_iterator end() const {
        return const_iterator( cells.get() + sizeX * sizeY ); }
    const_iterator row_begin( int j ) const {
        return iterator( cells.get() + j * sizeX ); }
    const_iterator row_end( int j ) const {
        return const_iterator( cells.get() + ( j + 1 ) * sizeX );  };
    const_col_iterator col_begin( int i ) const {
        return const_col_iterator( cells.get() + i, sizeX ); }
    const_col_iterator col_end( int i ) const {
        return const_col_iterator( cells.get() + sizeX * sizeY + i, sizeX ); };

     /**
      * Provide a signal visualisation in form of an image.
      * White pixels correspond to the maximum value, black pixels to the
      * minimum.
      * The alpha channel is used as a binary indication of the part of the
      * signal that has been reconstructed.
      * The input image needs to have the same dimensions as the sampled signal.
      * This function is intended for diagnostics. */
     void visualize( gil::rgba8_image_t & output ) const;

     /** Debug dump in the form of a Matlab matrix. Undefined cells are stored
      *  as "nan"s.
      */
     void dump(std::ostream &f) const;

     class Transform_t {
     public :
         virtual ublas::vector<double> operator () (
            const ublas::vector<double> & v ) const { return v; }

         virtual ~Transform_t() {}
     };

     typedef std::vector<ublas::vector<double> > QuadList_t;

     void getQuads( QuadList_t & quads, const Transform_t & trafo =
         Transform_t() );

protected:
    int sizeX, sizeY;
    double llx, lly, urx, ury, pixelx, pixely;
    Type_t type;
    boost::scoped_array<Cell_s> cells;
};

template <typename FilterType = math::SincHamming2>
class Signal2 : public Signal2Base {
public :
    Signal2( int sizeX, int sizeY,
        const double llx, const double lly, const double urx,
        const double ury, const Type_t type = Nonperiodic )
        : Signal2Base(sizeX, sizeY, llx, lly,  urx, ury, type)
    {}

    /**
     * provide a signal sample at given position, of given value, and with
     * given sampling period hint. */
    void sample( double x, double y, double value,
        double periodX, double periodY );

    /**
     * return interpolated value of the signal at given position.
     */
    double operator () ( const double x, const double y ) const;
};

} // namespace math

// templated functions implementation
#include "signal.impl.hpp"

#endif // MATH_SIGNAL_HPP


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T.Lapreste

  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_DEFINITION_SHUFFLE_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_DEFINITION_SHUFFLE_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/dispatch/function/make_callable.hpp>
#include <boost/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    BOOST_DISPATCH_MAKE_TAG(ext, shuffle_, boost::dispatch::elementwise_<shuffle_>);
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, shuffle_);
  }

  BOOST_DISPATCH_CALLABLE_DEFINITION(tag::shuffle_,shuffle);

} }

#endif