//
// Copyright 2019 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
//
// Use, modification and distribution are subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BOOST_GIL_IMAGE_PROCESSING_SCALING_HPP
#define BOOST_GIL_IMAGE_PROCESSING_SCALING_HPP

#include <boost/gil/image_view.hpp>
#include <boost/gil/rgb.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/image_processing/numeric.hpp>

namespace boost { namespace gil {

/// \defgroup ScalingAlgorithms
/// \brief Algorthims suitable for rescaling
///
/// These algorithms are used to improve image quality after image resizing is made.
///
/// \defgroup DownScalingAlgorithms
/// \ingroup ScalingAlgorithms
/// \brief Algorthims suitable for downscaling
///
/// These algorithms provide best results when used for downscaling. Using for upscaling will
/// probably provide less than good results.
///
/// \brief a single step of lanczos downscaling
/// \ingroup DownScalingAlgorithms
///
/// Use this algorithm to scale down source image into a smaller image with reasonable quality.
/// Do note that having a look at the output once is a good idea, since it might have ringing
/// artifacts.
template <typename ImageView>
void lanczos_at(
    ImageView input_view,
    ImageView output_view,
    typename ImageView::x_coord_t source_x,
    typename ImageView::y_coord_t source_y,
    typename ImageView::x_coord_t target_x,
    typename ImageView::y_coord_t target_y,
    std::ptrdiff_t a)
{
    using x_coord_t = typename ImageView::x_coord_t;
    using y_coord_t = typename ImageView::y_coord_t;
    using pixel_t = typename std::remove_reference<decltype(std::declval<ImageView>()(0, 0))>::type;

    // C++11 doesn't allow auto in lambdas
    using channel_t = typename std::remove_reference
        <
            decltype(std::declval<pixel_t>().at(std::integral_constant<int, 0>{}))
        >::type;

    pixel_t result_pixel;
    static_transform(result_pixel, result_pixel, [](channel_t) {
        return static_cast<channel_t>(0);
    });
    auto x_zero = static_cast<x_coord_t>(0);
    auto x_one = static_cast<x_coord_t>(1);
    auto y_zero = static_cast<y_coord_t>(0);
    auto y_one = static_cast<y_coord_t>(1);

    for (y_coord_t y_i = (std::max)(source_y - static_cast<y_coord_t>(a) + y_one, y_zero);
         y_i <= (std::min)(source_y + static_cast<y_coord_t>(a), input_view.height() - y_one);
         ++y_i)
    {
        for (x_coord_t x_i = (std::max)(source_x - static_cast<x_coord_t>(a) + x_one, x_zero);
             x_i <= (std::min)(source_x + static_cast<x_coord_t>(a), input_view.width() - x_one);
             ++x_i)
        {
            double lanczos_response = lanczos(source_x - x_i, a) * lanczos(source_y - y_i, a);
            auto op = [lanczos_response](channel_t prev, channel_t next)
            {
                return static_cast<channel_t>(prev + next * lanczos_response);
            };
            static_transform(result_pixel, input_view(source_x, source_y), result_pixel, op);
        }
    }

    output_view(target_x, target_y) = result_pixel;
}

/// \brief Complete Lanczos algorithm
/// \ingroup DownScalingAlgorithms
///
/// This algorithm does full pass over resulting image and convolves pixels from
/// original image. Do note that it might be a good idea to have a look at test
/// output as there might be ringing artifacts.
/// Based on wikipedia article:
/// https://en.wikipedia.org/wiki/Lanczos_resampling
/// with standardinzed cardinal sin (sinc)
template <typename ImageView>
void scale_lanczos(ImageView input_view, ImageView output_view, std::ptrdiff_t a)
{
    double scale_x = (static_cast<double>(output_view.width()))
                     / static_cast<double>(input_view.width());
    double scale_y = (static_cast<double>(output_view.height()))
                     / static_cast<double>(input_view.height());

    using x_coord_t = typename ImageView::x_coord_t;
    using y_coord_t = typename ImageView::y_coord_t;
    for (y_coord_t y = 0; y < output_view.height(); ++y)
    {
        for (x_coord_t x = 0; x < output_view.width(); ++x)
        {
            lanczos_at(input_view, output_view, x / scale_x, y / scale_y, x, y, a);
        }
    }
}

}} // namespace boost::gil

#endif


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

#include "../core.h"
#include "../utility.h"

#include <streamer/extended.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/intrusive_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
#include <boost/variant.hpp>

#include <Eigen/Core>

STREAMER_BEGIN_NS

int CreateDynamicObjectEx(
	int modelid,
	float x,
	float y,
	float z,
	float rx,
	float ry,
	float rz,
	float streamDistance,
	float drawDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_OBJECT) == core->getData()->objects.size()) {
		return 0;
	}

	int objectId = Item::Object::identifier.get();
	Item::SharedObject object(new Item::Object);
	//object->amx = amx;
	object->objectId = objectId;
	object->inverseAreaChecking = false;
	object->noCameraCollision = false;
	object->originalComparableStreamDistance = -1.0f;
	object->positionOffset = Eigen::Vector3f::Zero();
	object->streamCallbacks = false;
	object->modelId = modelid;
	object->position = Eigen::Vector3f(x, y, z);
	object->rotation = Eigen::Vector3f(rx, ry, rz);
	object->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	object->streamDistance = streamDistance;
	object->drawDistance = drawDistance;

	worlds = object->worlds;
	interiors = object->interiors;
	players = object->players;
	areas = object->areas;

	object->priority = priority;
	core->getGrid()->addObject(object);
	core->getData()->objects.insert(std::make_pair(objectId, object));
	return objectId;
}

int CreateDynamicPickupEx(
	int modelid,
	int type,
	float x,
	float y,
	float z,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_PICKUP) == core->getData()->pickups.size()) {
		return 0;
	}
	int pickupId = Item::Pickup::identifier.get();
	Item::SharedPickup pickup(new Item::Pickup);
	//pickup->amx = amx;
	pickup->pickupId = pickupId;
	pickup->inverseAreaChecking = false;
	pickup->originalComparableStreamDistance = -1.0f;
	pickup->modelId = modelid;
	pickup->positionOffset = Eigen::Vector3f::Zero();
	pickup->streamCallbacks = false;
	pickup->type = type;
	pickup->position = Eigen::Vector3f(x, y, z);
	pickup->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	pickup->streamDistance = streamDistance;

	worlds = pickup->worlds;
	interiors = pickup->interiors;
	players = pickup->players;
	areas = pickup->areas;

	pickup->priority = priority;
	core->getGrid()->addPickup(pickup);
	core->getData()->pickups.insert(std::make_pair(pickupId, pickup));
	return pickupId;
}

int CreateDynamicCPEx(
	float x,
	float y,
	float z,
	float size,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_CP) == core->getData()->checkpoints.size()) {
		return 0;
	}
	int checkpointId = Item::Checkpoint::identifier.get();
	Item::SharedCheckpoint checkpoint(new Item::Checkpoint);
	//checkpoint->amx = amx;
	checkpoint->checkpointId = checkpointId;
	checkpoint->inverseAreaChecking = false;
	checkpoint->originalComparableStreamDistance = -1.0f;
	checkpoint->positionOffset = Eigen::Vector3f::Zero();
	checkpoint->position = Eigen::Vector3f(x, y, z);
	checkpoint->size = size;
	checkpoint->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	checkpoint->streamDistance = streamDistance;

	worlds = checkpoint->worlds;
	interiors = checkpoint->interiors;
	players = checkpoint->players;
	areas = checkpoint->areas;

	checkpoint->priority = priority;
	core->getGrid()->addCheckpoint(checkpoint);
	core->getData()->checkpoints.insert(std::make_pair(checkpointId, checkpoint));
	return checkpointId;
}

int CreateDynamicRaceCPEx(
	int type,
	float x,
	float y,
	float z,
	float nx,
	float ny,
	float nz,
	float size,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_RACE_CP) == core->getData()->raceCheckpoints.size()) {
		return 0;
	}
	int raceCheckpointId = Item::RaceCheckpoint::identifier.get();
	Item::SharedRaceCheckpoint raceCheckpoint(new Item::RaceCheckpoint);
	//raceCheckpoint->amx = amx;
	raceCheckpoint->raceCheckpointId = raceCheckpointId;
	raceCheckpoint->inverseAreaChecking = false;
	raceCheckpoint->originalComparableStreamDistance = -1.0f;
	raceCheckpoint->positionOffset = Eigen::Vector3f::Zero();
	raceCheckpoint->type = type;
	raceCheckpoint->position = Eigen::Vector3f(x, y, z);
	raceCheckpoint->next = Eigen::Vector3f(nx, ny, nz);
	raceCheckpoint->size = size;
	raceCheckpoint->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	raceCheckpoint->streamDistance = streamDistance;

	worlds = raceCheckpoint->worlds;
	interiors = raceCheckpoint->interiors;
	players = raceCheckpoint->players;
	areas = raceCheckpoint->areas;

	raceCheckpoint->priority = priority;
	core->getGrid()->addRaceCheckpoint(raceCheckpoint);
	core->getData()->raceCheckpoints.insert(std::make_pair(raceCheckpointId, raceCheckpoint));
	return raceCheckpointId;
}

int CreateDynamicMapIconEx(
	float x,
	float y,
	float z,
	int type,
	int color,
	int style,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_MAP_ICON) == core->getData()->mapIcons.size()) {
		return 0;
	}
	int mapIconId = Item::MapIcon::identifier.get();
	Item::SharedMapIcon mapIcon(new Item::MapIcon);
	//mapIcon->amx = amx;
	mapIcon->mapIconId = mapIconId;
	mapIcon->inverseAreaChecking = false;
	mapIcon->originalComparableStreamDistance = -1.0f;
	mapIcon->positionOffset = Eigen::Vector3f::Zero();
	mapIcon->position = Eigen::Vector3f(x, y, z);
	mapIcon->type = type;
	mapIcon->color = color;
	mapIcon->style = style;
	mapIcon->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	mapIcon->streamDistance = streamDistance;

	worlds = mapIcon->worlds;
	interiors = mapIcon->interiors;
	players = mapIcon->players;
	areas = mapIcon->areas;

	mapIcon->priority = priority;
	core->getGrid()->addMapIcon(mapIcon);
	core->getData()->mapIcons.insert(std::make_pair(mapIconId, mapIcon));
	return mapIconId;
}

int CreateDynamic3DTextLabelEx(
	std::string text,
	int color,
	float x,
	float y,
	float z,
	float drawDistance,
	int attachedPlayer,
	int attachedVehicle,
	bool testlos,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_3D_TEXT_LABEL) == core->getData()->textLabels.size()) {
		return 0;
	}
	int textLabelId = Item::TextLabel::identifier.get();
	Item::SharedTextLabel textLabel(new Item::TextLabel);
	//textLabel->amx = amx;
	textLabel->textLabelId = textLabelId;
	textLabel->inverseAreaChecking = false;
	textLabel->originalComparableStreamDistance = -1.0f;
	textLabel->positionOffset = Eigen::Vector3f::Zero();
	textLabel->text = text;
	textLabel->color = color;
	textLabel->position = Eigen::Vector3f(x, y, z);
	textLabel->drawDistance = drawDistance;
	if (attachedPlayer != INVALID_OBJECT_ID || attachedVehicle != INVALID_OBJECT_ID)
	{
		textLabel->attach = boost::intrusive_ptr<Item::TextLabel::Attach>(new Item::TextLabel::Attach);
		textLabel->attach->player = attachedPlayer;
		textLabel->attach->vehicle = attachedVehicle;
		if (textLabel->position.cwiseAbs().maxCoeff() > 50.0f)
		{
			textLabel->position.setZero();
		}
		core->getStreamer()->attachedTextLabels.insert(textLabel);
	}
	textLabel->testLOS = testlos;
	textLabel->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	textLabel->streamDistance = streamDistance;

	worlds = textLabel->worlds;
	interiors = textLabel->interiors;
	players = textLabel->players;
	areas = textLabel->areas;

	textLabel->priority = priority;
	core->getGrid()->addTextLabel(textLabel);
	core->getData()->textLabels.insert(std::make_pair(textLabelId, textLabel));
	return textLabelId;
}

int CreateDynamicCircleEx(
	float x,
	float y,
	float size,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}
	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_CIRCLE;
	area->position = Eigen::Vector2f(x, y);
	area->comparableSize = size * size;
	area->size = size;

	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;

	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicCylinderEx(
	float x,
	float y,
	float minz,
	float maxz,
	float size,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}
	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_CYLINDER;
	area->position = Eigen::Vector2f(x, y);
	area->height = Eigen::Vector2f(minz, maxz);
	area->comparableSize = size * size;
	area->size = size;

	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;

	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicSphereEx(
	float x,
	float y,
	float z,
	float size,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}
	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_SPHERE;
	area->position = Eigen::Vector3f(x, y, z);
	area->comparableSize = size * size;
	area->size = size;

	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;

	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicRectangleEx(
	float minx,
	float miny,
	float maxx,
	float maxy,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}
	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_RECTANGLE;
	area->position = Box2d(Eigen::Vector2f(minx, miny), Eigen::Vector2f(maxx, maxy));
	boost::geometry::correct(boost::get<Box2d>(area->position));
	area->comparableSize = static_cast<float>(boost::geometry::comparable_distance(boost::get<Box2d>(area->position).min_corner(), boost::get<Box2d>(area->position).max_corner()));
	area->size = static_cast<float>(boost::geometry::distance(boost::get<Box2d>(area->position).min_corner(), boost::get<Box2d>(area->position).max_corner()));

	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;

	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicCuboidEx(
	float minx,
	float miny,
	float minz,
	float maxx,
	float maxy,
	float maxz,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}
	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_CUBOID;
	area->position = Box3d(Eigen::Vector3f(minx, miny, minz), Eigen::Vector3f(maxx, maxy, maxz));
	boost::geometry::correct(boost::get<Box3d>(area->position));
	area->comparableSize = static_cast<float>(boost::geometry::comparable_distance(Eigen::Vector2f(boost::get<Box3d>(area->position).min_corner()[0], boost::get<Box3d>(area->position).min_corner()[1]), Eigen::Vector2f(boost::get<Box3d>(area->position).max_corner()[0], boost::get<Box3d>(area->position).max_corner()[1])));
	area->size = static_cast<float>(boost::geometry::distance(Eigen::Vector2f(boost::get<Box3d>(area->position).min_corner()[0], boost::get<Box3d>(area->position).min_corner()[1]), Eigen::Vector2f(boost::get<Box3d>(area->position).max_corner()[0], boost::get<Box3d>(area->position).max_corner()[1])));
	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;
	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicPolygonEx(
	std::vector<Eigen::Vector2f> points,
	float minz,
	float maxz,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_AREA) == core->getData()->areas.size()) {
		return 0;
	}

	int areaId = Item::Area::identifier.get();
	Item::SharedArea area(new Item::Area);
	//area->amx = amx;
	area->areaId = areaId;
	area->type = STREAMER_AREA_TYPE_POLYGON;

	Polygon2d polygon = boost::get<Polygon2d>(area->position);
	boost::geometry::assign_points(polygon, points);
	boost::geometry::correct(polygon);

	area->height = Eigen::Vector2f(minz, maxz);
	Box2d box = boost::geometry::return_envelope<Box2d>(boost::get<Polygon2d>(area->position));
	area->comparableSize = static_cast<float>(boost::geometry::comparable_distance(box.min_corner(), box.max_corner()));
	area->size = static_cast<float>(boost::geometry::distance(box.min_corner(), box.max_corner()));

	worlds = area->worlds;
	interiors = area->interiors;
	players = area->players;

	area->priority = priority;
	core->getGrid()->addArea(area);
	core->getData()->areas.insert(std::make_pair(areaId, area));
	return areaId;
}

int CreateDynamicActorEx(
	int modelid,
	float x,
	float y,
	float z,
	float r,
	bool invulnerable,
	float health,
	float streamDistance,
	boost::unordered_set<int> &worlds,
	boost::unordered_set<int> &interiors,
	std::bitset<MAX_PLAYERS> &players,
	boost::unordered_set<int> &areas,
	int priority
) {
	if (core->getData()->getGlobalMaxItems(STREAMER_TYPE_ACTOR) == core->getData()->actors.size()) {
		return 0;
	}
	int actorId = Item::Actor::identifier.get();
	Item::SharedActor actor(new Item::Actor);
	//actor->amx = amx;
	actor->actorId = actorId;
	actor->inverseAreaChecking = false;
	actor->originalComparableStreamDistance = -1.0f;
	actor->modelId = modelid;
	actor->position = Eigen::Vector3f(x, y, z);
	actor->rotation = r;
	actor->invulnerable = invulnerable;
	actor->health = health;
	actor->comparableStreamDistance = streamDistance < STREAMER_STATIC_DISTANCE_CUTOFF ? streamDistance : streamDistance * streamDistance;
	actor->streamDistance = streamDistance;

	worlds = actor->worlds;
	interiors = actor->interiors;
	players = actor->players;
	areas = actor->areas;

	actor->priority = priority;
	core->getGrid()->addActor(actor);
	core->getData()->actors.insert(std::make_pair(actorId, actor));
	return actorId;
}

STREAMER_END_NS


// #include <core/scoring/motif/motif_hash_stuff.hh>
// #include <core/import_pose/import_pose.hh>
#include <basic/options/option_macros.hh>

// #include <core/chemical/ChemicalManager.hh>
// #include <core/pose/PDBInfo.hh>

// #include <core/scoring/ScoreFunctionFactory.hh>
// #include <core/pose/Pose.hh>
// #include <core/io/pdb/pose_io.hh>
// #include <core/import_pose/import_pose.hh>
// #include <core/id/AtomID.hh>
#include <devel/init.hh>

#include <utility/io/izstream.hh>
#include <utility/io/ozstream.hh>
#include <utility/file/file_sys_util.hh>
#include <ObjexxFCL/format.hh>

#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include <map>

#include <riflib/RifFactory.hh>

#include <riflib/util.hh>

using std::cout;
using std::endl;
using devel::scheme::KMGT;

		// Float lever_radius,
		// Float lever_dis,
		// XformHashNeighbors<Hasher> & nbcache

OPT_1GRP_KEY( StringVector, scheme, base_xmap_files             )
	OPT_1GRP_KEY( Real              , scheme, base_xmap_cart_resl )
	OPT_1GRP_KEY( Real              , scheme, base_xmap_ang_resl )
	OPT_1GRP_KEY( RealVector        , scheme, hash_cart_resls        )
	OPT_1GRP_KEY( RealVector        , scheme, hash_cart_bounds       )
	OPT_1GRP_KEY( RealVector        , scheme, hash_ang_resls         )
	OPT_1GRP_KEY( RealVector        , scheme, lever_radii      )
	OPT_1GRP_KEY( RealVector        , scheme, lever_bounds     )
	OPT_1GRP_KEY( String            , scheme, nbcache_path )
	OPT_1GRP_KEY( Boolean           , scheme, verbose          )
	OPT_1GRP_KEY( Integer           , scheme, nbcache_rotation_sample_factor          )
	OPT_1GRP_KEY( Boolean           , scheme, make_hacky_grids )

	void REGISTER_OPTIONS() {
		using namespace basic::options;
		using namespace basic::options::OptionKeys;
		NEW_OPT( scheme::base_xmap_files, "xmap files to make 'coarse' grids for"     , utility::vector1<std::string>() );
		NEW_OPT( scheme::base_xmap_cart_resl, "cart resl for input xmap", 0 );
		NEW_OPT( scheme::base_xmap_ang_resl, "ang resl for input xmap", 0 );
		NEW_OPT( scheme::hash_cart_resls, "cartesian resolution(s) of hash table(s)"      , utility::vector1<double>() );
		NEW_OPT( scheme::hash_cart_bounds, "bound on cartesian coordinates"               , utility::vector1<double>() );
		NEW_OPT( scheme::hash_ang_resls,  "ang reslolution(s) of hash table(s) in degrees", utility::vector1<double>() );
		NEW_OPT( scheme::lever_radii      , ""                                      , utility::vector1<double>() );
		NEW_OPT( scheme::lever_bounds     , ""                                      , utility::vector1<double>() );
		NEW_OPT( scheme::nbcache_path , ""                                      , ".");
		NEW_OPT( scheme::verbose, "", false );
		NEW_OPT( scheme::nbcache_rotation_sample_factor, "", 1000 );
		NEW_OPT( scheme::make_hacky_grids, "", true );
	}

void
make_bounding_grids(){

	using namespace basic::options;
	using ObjexxFCL::format::F;
	namespace sopt = basic::options::OptionKeys::scheme;
	using namespace devel::scheme;

	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::lever_bounds     ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_ang_resls   ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_cart_resls  ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_cart_bounds ]().size() );

	runtime_assert( option[sopt::base_xmap_files]().size() == 1 );

	std::string base_xmap_file = option[sopt::base_xmap_files]().front();
	runtime_assert( utility::file::file_exists( base_xmap_file ) );
	runtime_assert( base_xmap_file.substr(base_xmap_file.size()-8) == ".xmap.gz" ||
	                base_xmap_file.substr(base_xmap_file.size()-7) == ".rif.gz"  ||
	                base_xmap_file.substr(base_xmap_file.size()-5) == ".xmap"  ||
	                base_xmap_file.substr(base_xmap_file.size()-4) == ".rif"
	                );
	cout << "reading " << base_xmap_file << endl;

	std::string rif_type = get_rif_type_from_file( base_xmap_file );
	std::cout << "======================================================================" << std::endl;
	std::cout << "read RIF type: " << rif_type << std::endl;
	std::cout << "======================================================================" << std::endl;
	devel::scheme::RifFactoryConfig rif_factory_config;
	rif_factory_config.rif_type = rif_type;
	shared_ptr<RifFactory> rif_factory = ::devel::scheme::create_rif_factory( rif_factory_config );

	std::string ref_description;
	RifConstPtr ref_rif = rif_factory->create_rif_from_file( base_xmap_file, ref_description );
	std::cout << "read description: " << std::endl << ref_description << std::endl;
	std::cout << "======================================================================" << std::endl;

	std::cout << "RIF size: " << KMGT( ref_rif->mem_use() ) << " load: " << ref_rif->load_factor() << std::endl;

	int nbase = ref_rif->size();
	std::cout <<"read full xmap, size: " << KMGT(nbase) << std::endl;

	for( int ibound = 1; ibound <= option[sopt::lever_bounds]().size(); ++ibound ){

		double const lever_radius      = option[sopt::lever_radii      ]().at( ibound );
		double const lever_bound       = option[sopt::lever_bounds     ]().at( ibound );
		double const hash_ang_resl     = option[sopt::hash_ang_resls   ]().at( ibound );
		double const hash_cart_resl    = option[sopt::hash_cart_resls  ]().at( ibound );
		double const hash_cart_bound   = option[sopt::hash_cart_bounds ]().at( ibound );

		double const cart_bound = lever_bound;
		double const  ang_bound_rad = lever_bound / lever_radius;
		double const  ang_bound = ang_bound_rad * 180.0 / M_PI;
		cout << "========================================= make bounding grids for resl. " << lever_bound << " ==============================================" << endl;
		cout << "cart_bound: " << cart_bound << ", ang_bound: " << ang_bound << endl;
		cout << "cart_hash_resl: " << hash_cart_resl << ", hash_ang_resl: " << hash_ang_resl << std::endl;

		RifPtr new_rif = rif_factory->create_rif_from_rif( ref_rif, hash_cart_resl, hash_ang_resl, hash_cart_bound );
		std::cout << "new map size " << KMGT(new_rif->size()) << " ratio: " <<  (float)new_rif->size() / (float)ref_rif->size() << std::endl;

		{
			cout << "before resize, bounding grid size: " << KMGT( new_rif->mem_use() )
			     << " load: " << new_rif->load_factor()
				 << ", sizeof(value_type) " << new_rif->sizeof_value_type() << endl;
			 cout << "NOT RESIZING bounding grid" << endl;
			// bounding_xmapfull.map_.max_load_factor(0.8);
			// bounding_xmapfull.map_.min_load_factor(0.4);
			// bounding_xmapfull.map_.resize(0);
			// cout << "after  resize, bounding grid size: " << KMGT( bounding_xmapfull.mem_use() )
			     // << " load: " << bounding_xmapfull.map_.size()*1.f/bounding_xmapfull.map_.bucket_count() << endl;
			std::string digits = boost::lexical_cast<std::string>(lever_bound);
			if( digits.size() == 1 ) digits = "0" + digits;
			std::string tag = "_BOUNDING_";
			if( option[sopt::make_hacky_grids]() ) tag += "HACK_";
			else                                   tag += "RANDHACK_";
			std::ostringstream oss_description;
			oss_description << "==== bounding xmap ====" << endl;
			if( option[sopt::make_hacky_grids]() ){
				oss_description << "!!!!!!!!!!!!!!!!!!!!!!!!!!!! USING_HACKY_GRIDS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
			}
			oss_description << "hash_cart_bound: " << hash_cart_bound << endl;
			oss_description << "lever_radius:  " << lever_radius << endl;
			oss_description << "lever_bound:   " << lever_bound << endl;
			oss_description << "==== source oss_description ====\n" << ref_description;
			std::string description = oss_description.str();

			utility::io::ozstream out( base_xmap_file+tag+"RIF_"+digits + ".xmap.gz" );
			new_rif->save( out, description );
			out.close();
		}

	}

}

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

	using namespace ::devel::scheme;

	REGISTER_OPTIONS();
	devel::init(argc,argv);

	make_bounding_grids();

	return 0;
}

/*

template<class T>
float fullval_to_numeric( T const & t ){
	return t;
}

template< int N >
float fullval_to_numeric( ::scheme::objective::storage::RotamerScores<N> const & rs ){
	return rs.score(0);
}

////////////// depricated... left for possible future reference
template<
	class EigenXform,
	class XMapValFull,
	template<class Xform> class HasherFull ,
	template<class Xform> class HasherBounding
>
void
make_bounding_grids_failed(){

	int const ArrayBits1 = 0;
	int const ArrayBits2 = 0;
	typedef float XMapValBounding;

	typedef ::scheme::objective::hash::XformMap< EigenXform, XMapValFull, HasherFull > XMapFull;

	using namespace basic::options;
	using namespace core::scoring::motif;
	namespace sopt = basic::options::OptionKeys::scheme;
	using ObjexxFCL::format::F;
	using devel::scheme::omp_max_threads;
	using devel::scheme::omp_thread_num;

	boost::random::mt19937 rng((unsigned int)time(0) + 736495684);
	int const num_threads = omp_max_threads();

	typedef scheme::objective::hash::XformMap< EigenXform, XMapValBounding, HasherBounding > XMapBounding;
	typedef scheme::objective::hash::XformHashNeighbors<typename XMapBounding::Hasher> XHNB;

	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::lever_bounds     ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_ang_resls   ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_cart_resls  ]().size() );
	runtime_assert( option[sopt::lever_radii     ]().size() == option[sopt::hash_cart_bounds ]().size() );

	XMapFull full_xmap; // read resls from file unless artificial
	std::string full_xmap_description;
	std::string base_xmap_file = "artificial_test";
	if( option[sopt::base_xmap_files]().size() > 0 ){
		base_xmap_file = option[sopt::base_xmap_files]().front();
		cout << "reading " << base_xmap_file << endl;
		runtime_assert( option[sopt::base_xmap_files]().size() == 1 );
		runtime_assert( utility::file::file_exists( base_xmap_file ) )
		runtime_assert( base_xmap_file.substr(base_xmap_file.size()-8) == ".xmap.gz" ||
		                base_xmap_file.substr(base_xmap_file.size()-7) == ".rif.gz"  ||
		                base_xmap_file.substr(base_xmap_file.size()-5) == ".xmap"  ||
		                base_xmap_file.substr(base_xmap_file.size()-4) == ".rif"
		                );
		utility::io::izstream in( base_xmap_file );
		runtime_assert( full_xmap.load( in, full_xmap_description ) );
		in.close();
		std::cout << "RIF size: " << KMGT( full_xmap.mem_use() ) << " load: " << full_xmap.map_.size()*1.f/full_xmap.map_.bucket_count() << std::endl;
	} else if( option[sopt::test_structure].user() ) {
		cout << "make artificial test RIF from: " << option[sopt::test_structure]() << endl;
		full_xmap.init( option[sopt::base_xmap_cart_resl](), option[sopt::base_xmap_ang_resl]() );
		core::pose::Pose pose;
		core::import_pose::pose_from_pdb( pose, option[sopt::test_structure]() );
		for( int itest = 0; itest < option[sopt::num_test_points](); ++itest ){
			{
				numeric::xyzVector<double> cen   = protocols::sic_dock::center_of_geom( pose );
				numeric::xyzVector<double> trans = numeric::random::random_vector_unit_cube() * 20.0 - 10.0;
				numeric::xyzMatrix<double> rot   = numeric::random::random_rotation();
				protocols::sic_dock::trans_pose( pose, -cen );
				protocols::sic_dock::rot_pose( pose, rot );
				protocols::sic_dock::trans_pose( pose, trans );
			}
			std::vector<int> picked_res;
			for( int ir = 1; ir <= pose.size(); ++ir ) picked_res.push_back(ir);
			numeric::random::random_permutation( picked_res, numeric::random::rg() );
			std::vector<EigenXform> target_frames;
			float score = 2.0 - (double)itest / ((double)option[sopt::num_test_points]()-1);
			std::cout << itest << " " << score << std::endl;
			for( int i = 0; i < option[sopt::num_target_frames](); ++i ){
				int ir = picked_res[i];
				scheme::actor::BackboneActor<EigenXform> bbactor( pose.residue(ir).xyz("N"), pose.residue(ir).xyz("CA"), pose.residue(ir).xyz("C") );
				::scheme::objective::storage::RotamerScores< 12 > rs;
				rs.add_rotamer( 1, score );
				full_xmap.insert( bbactor.position(), rs );
			}
			pose.dump_pdb( "test"+ObjexxFCL::format::I(4,itest+1)+".pdb" );
		}
	} else {
		utility_exit_with_message( "must specify base_xmap_files OR test_structure" );
	}
	// int nbase = full_xmap.count_not( 0.0 );
	// int nbase = full_xmap.count_not( XMapValFull() );
	int nbase = full_xmap.size();
	std::cout <<"read full xmap, size: " << KMGT(nbase) << std::endl;

	for( int ibound = 1; ibound <= option[sopt::lever_bounds]().size(); ++ibound ){

		double const lever_radius      = option[sopt::lever_radii      ]().at( ibound );
		double const lever_bound       = option[sopt::lever_bounds     ]().at( ibound );
		double const hash_ang_resl     = option[sopt::hash_ang_resls   ]().at( ibound );
		double const hash_cart_resl    = option[sopt::hash_cart_resls  ]().at( ibound );
		double const hash_cart_bound   = option[sopt::hash_cart_bounds ]().at( ibound );
		std::string nbcache_file = option[sopt::nbcache_path]() + "/__scheme_make_bounding_grids_"
			+"cr"+F(5,2,hash_cart_resl)+"_"
			+"ar"+F(5,2,hash_ang_resl)+"_"
			+"lr"+F(5,2,lever_radius)+"_"
			+"lb"+F(5,2,lever_bound)+".nbcache.gz";

		double const cart_bound = lever_bound;
		double const  ang_bound_rad = lever_bound / lever_radius;
		double const  ang_bound = ang_bound_rad * 180.0 / M_PI;
		cout << "========================================= make bounding grids for resl. " << lever_bound << " ==============================================" << endl;
		cout << "cart_bound: " << cart_bound << ", ang_bound: " << ang_bound << endl;
		cout << "cart_hash_resl: " << hash_cart_resl << ", hash_ang_resl: " << hash_ang_resl << std::endl;

		std::cout << "insert all values into coarse_xmap "; std::cout.flush();
		// XMapBounding coarse_xmap( hash_cart_resl, hash_ang_resl, hash_cart_bound );

		XMapFull * coarse_xmapfull_p;

		if( option[sopt::make_hacky_grids]() || lever_bound > 3.0 ){

			coarse_xmapfull_p = new XMapFull( hash_cart_resl, hash_ang_resl, hash_cart_bound );
			int progress0 = 0;
			BOOST_FOREACH( typename XMapFull::Map::value_type const & v, full_xmap.map_ ){
				if( ++progress0 % std::max((size_t)1,(full_xmap.size()/100)) == 0 ){
					std::cout << '*'; std::cout.flush();
				}
				EigenXform x = full_xmap.hasher_.get_center( v.first );
				XMapValBounding val = fullval_to_numeric( v.second );
				if( val != 0.0 ){
					// coarse_xmap.insert( x, val );
					uint64_t k = coarse_xmapfull_p->hasher_.get_key(x);
					typename XMapFull::Map::iterator iter = coarse_xmapfull_p->map_.find(k);
					if( iter == coarse_xmapfull_p->map_.end() ){
						coarse_xmapfull_p->map_.insert( std::make_pair(k,v.second) );
					} else {
						iter->second.merge( v.second );
					}
					// bounding_xmap.insert_sphere( x, lever_radius, lever_bound, val, nbcache );
				}
			}
			std::cout << endl;
			std::cout << "coarse_map size " << KMGT(coarse_xmapfull_p->size()) << " ratio: " <<  (float)coarse_xmapfull_p->size() / (float)full_xmap.size() << std::endl;

		} else {
			coarse_xmapfull_p = & full_xmap;
		}

		// XMapBounding bounding_xmap( hash_cart_resl, hash_ang_resl, hash_cart_bound );
		XMapFull bounding_xmapfull( hash_cart_resl, hash_ang_resl, hash_cart_bound );

		if( option[sopt::make_hacky_grids]() ){

			// bounding_xmap = coarse_xmap;
			bounding_xmapfull = *coarse_xmapfull_p;
			std::cout << "make_hacky_grids, using coarse_xmap as bounding map" << std::endl;

		} else if( true ) {
			std::cout << "using better hack to make bounding grids" << std::endl;

			float cart_rand_spread = cart_bound - hash_cart_resl / 2.0; // no justification for this...
			float ang_rand_spread  =  ang_bound -  hash_ang_resl / 2.0;
			std::cout << "cart_rand_spread: " << cart_rand_spread << ", " << "ang_rand_spread: " << ang_rand_spread << endl;

			float cratio = cart_bound / hash_cart_resl;
			float aratio = ang_bound / hash_ang_resl;

			int Nrandxform = 100.0  *  cratio*cratio*cratio  *  aratio*aratio*aratio;
			std::cout << "N insertion samples: " << KMGT(Nrandxform) << std::endl;
			if( lever_radius < 3.0 ) Nrandxform /= 3.0;

			std::vector<EigenXform> rand_xforms(Nrandxform);
			rand_xforms[0] = EigenXform::Identity();
			for( int i = 1; i < Nrandxform; ++i ){
				::scheme::numeric::rand_xform_sphere( rng, rand_xforms[i], cart_rand_spread, float(ang_rand_spread * M_PI/180.0) );
			}

			std::vector<XMapFull> bounding_xmaps( omp_max_threads(), XMapFull( hash_cart_resl, hash_ang_resl, hash_cart_bound ) );

			std::vector< std::pair< typename XMapFull::Map::key_type, typename XMapFull::Map::data_type > > coarse_xmap_values;
			// std::vector< typename XMapFull::Map::value_type > coarse_xmap_values;
			coarse_xmap_values.reserve(coarse_xmapfull_p->size());
			BOOST_FOREACH( typename XMapFull::Map::value_type const & v, coarse_xmapfull_p->map_ ){
				coarse_xmap_values.push_back( v );
			}

			std::cout << "make grid with " << KMGT(Nrandxform) << " rand nbr samp. ";
			float avg_nbcount = 0;
			#ifdef USE_OPENMP
			#pragma omp parallel for schedule(dynamic,1)
			#endif
			for( int i = 0; i < coarse_xmap_values.size(); ++i ){
				int ithread = omp_thread_num();
				XMapFull & bounding_xmap( bounding_xmaps[ithread] );
				typename XMapFull::Map::data_type const val = coarse_xmap_values[i].second;
				EigenXform x0 = coarse_xmapfull_p->hasher_.get_center( coarse_xmap_values[i].first );
				// std::set<uint64_t> seenit;
				google::dense_hash_set<uint64_t> seenit;
				seenit.set_empty_key(0);
				for( int irand = 0; irand < Nrandxform; ++irand ){
					EigenXform const x = rand_xforms[irand] * x0;
					uint64_t const k = bounding_xmap.hasher_.get_key(x);
					if( seenit.find(k) == seenit.end() ){
						seenit.insert(k);
						typename XMapFull::Map::iterator iter = bounding_xmap.map_.find(k);
						if( iter == bounding_xmap.map_.end() ){
							bounding_xmap.map_.insert( std::make_pair( k, val ) );
						} else {
							iter->second.merge( val );
						}
					}
				}
				#pragma omp critical
				avg_nbcount += seenit.size();

				if( i % std::max((size_t)1,(coarse_xmapfull_p->size()/100)) == 0 ){
					std::cout << "*"; std::cout.flush();
				}
			}
			std::cout << std::endl;
			avg_nbcount /= coarse_xmap_values.size();
			std::cout << "avg_nbcount " << avg_nbcount << ", nsamp / nbinfound = " << Nrandxform / avg_nbcount << std::endl;

			if( lever_radius > 3.0 ) delete coarse_xmapfull_p;

// utility_exit_with_message("testing rand neighbor gen");

			int n = 1; while( n < bounding_xmaps.size() ) n *= 2;
			while( n > 1 ){
				std::cout << "condense per-thread maps, to merge: " << n << std::endl;
				#ifdef USE_OPENMP
				#pragma omp parallel for schedule(dynamic,1)
				#endif
				for( int i = 0; i < n; ++i ){
					if( i < n/2 && i + n/2 < bounding_xmaps.size() ){
						XMapFull       & merge_to   = bounding_xmaps[i];
						XMapFull const & merge_from = bounding_xmaps[n/2+i];
						BOOST_FOREACH( typename XMapFull::Map::value_type const & v, merge_from.map_ ){
							typename XMapFull::Map::iterator iter = merge_to.map_.find( v.first );
							if( iter == merge_to.map_.end() ){
								merge_to.map_.insert( v );
							} else {
								iter->second.merge( v.second );
							}
						}
						// std::cout << "merged " << i+n/2 << " into " << i << std::endl;
					}
				}
				n /= 2;
			}
			bounding_xmapfull = bounding_xmaps[0];

		} else { // the way that doesn't work yet, using nb cells from library funcs

	// 		std::cout << "now do insert_sphere for all " << KMGT( coarse_xmap.size() ) << " values in coarse xmap with " << num_threads << " threads" << std::endl;

	// 		std::vector<XMapBounding> bounding_xmaps( omp_max_threads(), XMapBounding( hash_cart_resl, hash_ang_resl, hash_cart_bound ) );
	// 		XHNB nbcache_tmp( cart_bound, ang_bound, bounding_xmaps.front().hasher_, option[sopt::nbcache_rotation_sample_factor]() );
	// 		if( utility::file::file_exists( nbcache_file ) ){
	// 			cout << "reading nbcache file: " << nbcache_file << endl;
	// 			utility::io::izstream in( nbcache_file );
	// 			nbcache_tmp.load(in);
	// 			in.close();
	// 		}
	// 		std::vector<XHNB> nbcaches( num_threads, nbcache_tmp );

	// 		std::vector< std::pair< typename XMapBounding::Map::key_type, typename XMapBounding::Map::data_type > > coarse_xmap_values;
	// 		coarse_xmap_values.reserve(coarse_xmap.size());
	// 		BOOST_FOREACH( typename XMapBounding::Map::value_type const & v, coarse_xmap.map_ ){
	// 			coarse_xmap_values.push_back(v);
	// 		}
	// 		std::vector<double> avg_nbcounts(omp_max_threads(),0.0);

	// 		#ifdef USE_OPENMP
	// 		#pragma omp parallel for schedule(dynamic,1)
	// 		#endif
	// 		for( int i = 0; i < coarse_xmap_values.size(); ++i ){
	// 			int ithread = omp_thread_num();
	// 			XMapBounding & bounding_xmap( bounding_xmaps[ithread] );
	// 			XHNB & nbcache( nbcaches[ithread] );
	// 			double avg_nbcount = 0;
	// 			XMapValBounding val = coarse_xmap_values[i].second;
	// 			EigenXform x = coarse_xmap.hasher_.get_center( coarse_xmap_values[i].first );
	// 			avg_nbcounts[ithread] += bounding_xmap.insert_sphere( x, lever_radius, lever_bound, val, nbcache );
	// 			if( i % std::max((size_t)1,(coarse_xmap.size()/100)) == 0 ){
	// 				std::cout << "progress " << (float)i / (float)coarse_xmap_values.size() *100.0 << "% " << std::endl;
	// 			}
	// 		}
	// 		double avg_nbcount = 0;
	// 		BOOST_FOREACH( double d, avg_nbcounts ) avg_nbcount += d;
	// 		avg_nbcount /= double( coarse_xmap_values.size() );

	// // test = [(str(i)+" ") for i in range(12)]
	// // print
	// // n = 1
	// // while n < len(test): n *= 2
	// // while True:
	// // 	print "=======",n,"======="
	// // 	for i in range(n):
	// // 		if i < n/2 and i+n/2 < len(test):
	// // 			print i,n/2+i
	// // 			test[i] += test[n/2+i]
	// // 	n /= 2

	// // 	if n == 1: break

	// // #for i in test: print i
	// 		// hacky parallel merge
	// 		int n = 1; while( n < bounding_xmaps.size() ) n *= 2;
	// 		while( n > 1 ){
	// 			std::cout << "condense per-thread maps, to merge: " << n << std::endl;
	// 			#ifdef USE_OPENMP
	// 			#pragma omp parallel for schedule(dynamic,1)
	// 			#endif
	// 			for( int i = 0; i < n; ++i ){
	// 				if( i < n/2 && i + n/2 < bounding_xmaps.size() ){
	// 					XMapBounding       & merge_to   = bounding_xmaps[i];
	// 					XMapBounding const & merge_from = bounding_xmaps[n/2+i];
	// 					BOOST_FOREACH( typename XMapBounding::Map::value_type const & v, merge_from.map_ ){
	// 						typename XMapBounding::Map::iterator iter = merge_to.map_.find( v.first );
	// 						if( iter == merge_to.map_.end() ){
	// 							merge_to.map_.insert( v );
	// 						} else {
	// 							iter->second = std::min( v.second, iter->second );
	// 						}
	// 					}
	// 					std::cout << "merged " << i+n/2 << " into " << i << std::endl;
	// 				}
	// 			}
	// 			n /= 2;
	// 		}
	// 		bounding_xmap = bounding_xmaps[0];

	// 		// std::cout << "condense per-thread maps" << std::endl;
	// 		// XMapBounding bounding_xmap( hash_cart_resl, hash_ang_resl, hash_cart_bound );
	// 		// BOOST_FOREACH( XMapBounding const & xm, bounding_xmaps ){
	// 		// 	BOOST_FOREACH( typename XMapBounding::Map::value_type const & v, xm.map_ ){
	// 		// 		typename XMapBounding::Map::iterator iter = bounding_xmap.map_.find( v.first );
	// 		// 		if( iter == bounding_xmap.map_.end() ){
	// 		// 			bounding_xmap.map_.insert( v );
	// 		// 		} else {
	// 		// 			iter->second = std::min( v.second, iter->second );
	// 		// 		}
	// 		// 	}
	// 		// }

	// 		std::cout << endl << "average nbcount: " << avg_nbcount << " hash mem use: " << KMGT(bounding_xmap.mem_use()) << std::endl;

	// 		std::cout << "save nbcache_file " << nbcache_file << std::endl;
	// 		{
	// 			BOOST_FOREACH( XHNB const & nbcache, nbcaches ){
	// 				nbcache_tmp.merge( nbcache );
	// 			}
	// 			utility::io::ozstream out( nbcache_file );
	// 			nbcache_tmp.save( out );
	// 			out.close();
	// 		}

		} // end if hacky

		{
			cout << "before resize, bounding grid size: " << KMGT( bounding_xmapfull.mem_use() )
			     << " load: " << bounding_xmapfull.map_.size()*1.f/bounding_xmapfull.map_.bucket_count()
				 << ", sizeof(value_type) " << sizeof(typename XMapFull::Map::value_type) << endl;
			 cout << "NOT RESIZING bounding grid" << endl;
			// bounding_xmapfull.map_.max_load_factor(0.8);
			// bounding_xmapfull.map_.min_load_factor(0.4);
			// bounding_xmapfull.map_.resize(0);
			// cout << "after  resize, bounding grid size: " << KMGT( bounding_xmapfull.mem_use() )
			     // << " load: " << bounding_xmapfull.map_.size()*1.f/bounding_xmapfull.map_.bucket_count() << endl;
			std::string digits = boost::lexical_cast<std::string>(lever_bound);
			if( digits.size() == 1 ) digits = "0" + digits;
			std::string tag = "_BOUNDING_";
			if( option[sopt::make_hacky_grids]() ) tag += "HACK_";
			else                                   tag += "RANDHACK_";
			std::ostringstream description;
			description << "==== bounding xmap ====" << endl;
			if( option[sopt::make_hacky_grids]() ){
				description << "!!!!!!!!!!!!!!!!!!!!!!!!!!!! USING_HACKY_GRIDS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
			}
			description << "hash_cart_bound: " << hash_cart_bound << endl;
			description << "lever_radius:  " << lever_radius << endl;
			description << "lever_bound:   " << lever_bound << endl;
			description << "==== source description ====\n" << full_xmap_description;

			// utility::io::ozstream out( utility::file_basename(base_xmap_file)+tag+digits + ".xmap.gz" );
			// bounding_xmapfull.save( out, description.str() );
			// out.close();

			utility::io::ozstream out2( base_xmap_file+tag+"RIF_"+digits + ".xmap.gz" );
			bounding_xmapfull.save( out2, description.str() );
			out2.close();
		}

		// int nbounding = bounding_xmapfull.count_not(0.0);
		// cout << "Bounding size cost " << (float)nbounding/nbase << endl;
		// float fracfill = (float)nbounding/bounding_xmapfull.map_.size() / (float)(1<<ArrayBits2);
		// float memper = (float)((1<<ArrayBits2)+8.0) / (1<<ArrayBits2);

		// cout << "array fill frac " << fracfill << " zorder array[" << (1<<ArrayBits2) << "] mem_saving = " << 9.0 / memper * fracfill << "-fold " << endl;
		// some very quick test data:
		//            1   2    4    8   16   32   64   128  256  512  1024
		// R2.0/1.0: 1.0 1.53 2.05 2.54 2.79 2.34 1.97 1.53 1.17 0.90 0.66
		// R3.0/1.0:               2.23 2.39 1.99      1.28           0.52
		// R1.0/0.5                3.20 3.60 3.05
		// R1.5/0.5                3.40 4.00 3.45
		// R2.5/0.5                3.11 3.68

	}

}

*/


//==================================================================================================
/**
  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_COMMON_SIMD_FUNCTION_REC_RAW_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_REC_RAW_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/detail/traits.hpp>
#include <boost/simd/function/raw.hpp>

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

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// raw_(rec) for floating types: 'take all' version when no speedier exists
  /// on some architectures an intrinsic is called providing few bits and/or not getting correct
  /// results for zeros denormals  and infinities (sse1, sse2, avx, vmx)
  /// These version are used as bases for improvement using Newton Raphson and correcting limits
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  BOOST_DISPATCH_OVERLOAD_IF( rec_
                            , (typename T, typename X)
                            , (detail::is_native<X>)
                            , bd::cpu_
                            , bs::raw_tag
                            , bs::pack_<bd::unspecified_<T>, X>
                            )
  {
    BOOST_FORCEINLINE T operator()(const raw_tag &, T const& a) const BOOST_NOEXCEPT
    {
      return rec(a);
    }
  };
} } }

#endif


#include <pybind11/pybind11.h>
#include <pybind11/eigen.h>

#include <Eigen/Core>

#ifndef NO_AUTODIFF
#include <cmath>
#ifdef USE_STAN_MATH
#include <vector>
#include <stan/math.hpp>
#else
#include <cfloat>
#include <unsupported/Eigen/AutoDiff>
#endif
#endif

#include "celerite/celerite.h"
#include "celerite/carma.h"

namespace py = pybind11;

//
// By sub-classing CholeskySolver here, we can make it picklable but not make
// C++11 a requirement for the C++ library.
//
// The pybind11 pickle docs are here:
// http://pybind11.readthedocs.io/en/master/advanced/classes.html#pickling-support
// but the gist is that class just needs to expose __getstate__ and __setstate__.
// These will just directly call serialize and deserialize below.
//
class PicklableCholeskySolver : public celerite::solver::CholeskySolver<double> {
public:
  PicklableCholeskySolver () : celerite::solver::CholeskySolver<double>() {};

  auto serialize () const {
    return std::make_tuple(
      this->computed_, this->N_, this->J_, this->log_det_,
      this->phi_, this->u_, this->W_, this->D_
    );
  };

  void deserialize (
      bool computed, int n, int J, double log_det,
      Eigen::MatrixXd phi,
      Eigen::MatrixXd u,
      Eigen::MatrixXd W,
      Eigen::VectorXd D) {
    this->computed_ = computed;
    this->N_        = n;
    this->J_        = J;
    this->log_det_  = log_det;
    this->phi_      = phi;
    this->u_        = u;
    this->W_        = W;
    this->D_        = D;
  };
};

//
// Below is the boilerplate code for the pybind11 extension module.
//
PYBIND11_PLUGIN(solver) {
  typedef Eigen::MatrixXd matrix_t;
  typedef Eigen::VectorXd vector_t;

  py::module m("solver", R"delim(
This is the low-level interface to the C++ implementation of the celerite
algorithm. These methods do most of the heavy lifting but most users shouldn't
need to call these directly. This interface was built using `pybind11
<http://pybind11.readthedocs.io/>`_.

)delim");

  m.def("get_library_version", []() { return CELERITE_VERSION_STRING; },
        "The version of the linked C++ library");

  m.def("has_autodiff", []() {
#ifdef NO_AUTODIFF
    return false;
#else
    return true;
#endif
  }, "Returns True if celerite was compiled with autodiff support");

  py::register_exception<celerite::linalg_exception>(m, "LinAlgError");

  m.def("get_kernel_value",
    [](
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp,
      py::array_t<double> tau
    ) {
      auto get_kernel_value_closure = [a_real, c_real, a_comp, b_comp, c_comp, d_comp] (double t) {
        return celerite::get_kernel_value(
          a_real, c_real, a_comp, b_comp, c_comp, d_comp, t
        );
      };
      return py::vectorize(get_kernel_value_closure)(tau);
    },
    R"delim(
Get the value of the kernel for given parameters and lags

Args:
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    tau (array[n]): The time lags where the kernel should be evaluated.

Returns:
    array[n]: The kernel evaluated at ``tau``.

)delim");

  m.def("get_psd_value",
    [](
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp,
      py::array_t<double> omega
    ) {
      auto get_psd_value_closure = [a_real, c_real, a_comp, b_comp, c_comp, d_comp] (double t) {
        return celerite::get_psd_value(
          a_real, c_real, a_comp, b_comp, c_comp, d_comp, t
        );
      };
      return py::vectorize(get_psd_value_closure)(omega);
    },
    R"delim(
Get the PSD of the kernel for given parameters and angular frequencies

Args:
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    omega (array[n]): The frequencies where the PSD should be evaluated.

Returns:
    array[n]: The PSD evaluated at ``omega``.

)delim");

  m.def("check_coefficients",
    [](
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp
    ) {
      return celerite::check_coefficients(a_real, c_real, a_comp, b_comp, c_comp, d_comp);
    },
    R"delim(
Apply Sturm's theorem to check if parameters yield a positive PSD

Args:
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.

Returns:
    bool: ``True`` if the PSD is everywhere positive.

)delim");

  //
  // ------ CARMA ------
  //
  py::class_<celerite::carma::CARMASolver> carma_solver(m, "CARMASolver", R"delim(
A thin wrapper around the C++ CARMASolver class

This solver is parameterized following carma_pack:
https://github.com/brandonckelly/carma_pack

Args:
    log_sigma (float): The log of the variance of the process.
    arparams (array[p]): The parameters of the autoregressive component.
    maparams (array[q]): The parameters of the moving average component.

)delim");
  carma_solver.def(py::init<double, vector_t, vector_t>());

  carma_solver.def("log_likelihood",
    [](celerite::carma::CARMASolver& solver, const vector_t& t, const vector_t& y, const vector_t& yerr) {
      return solver.log_likelihood(t, y, yerr);
    }, R"delim(
Compute the log likelihood using a Kalman filter

Args:
    t (array[n]): The input coordinates of the observations.
    y (array[n]): The observations.
    yerr (array[n]): The measurement uncertainties of the observations.

)delim");

  carma_solver.def("get_celerite_coeffs",
    [](celerite::carma::CARMASolver& solver) {
      vector_t a_real, c_real, a_comp, b_comp, c_comp, d_comp;
      solver.get_celerite_coeffs(a_real, c_real, a_comp, b_comp, c_comp, d_comp);
      return std::make_tuple(a_real, c_real, a_comp, b_comp, c_comp, d_comp);
    }, R"delim(
Compute the coefficients of the celerite model for the given CARMA model

)delim");

  //
  // ------ CHOLESKY ------
  //
  py::class_<PicklableCholeskySolver> cholesky_solver(m, "CholeskySolver", R"delim(
A thin wrapper around the C++ CholeskySolver class
)delim");
  cholesky_solver.def(py::init<>());

#ifdef USE_STAN_MATH
  cholesky_solver.def("grad_log_likelihood",
    [](
        PicklableCholeskySolver& nothing,
        double jitter,
        const vector_t& a_real,
        const vector_t& c_real,
        const vector_t& a_comp,
        const vector_t& b_comp,
        const vector_t& c_comp,
        const vector_t& d_comp,
        const vector_t& A,
        const matrix_t& U,
        const matrix_t& V,
        const vector_t& x,
        const vector_t& y,
        const vector_t& diag
    ) {

#ifndef NO_AUTODIFF
      typedef stan::math::var g_t;
      typedef Eigen::Matrix<g_t, Eigen::Dynamic, 1> v_t;

      int J_real = a_real.rows();
      int J_comp = a_comp.rows();

      // Set up the solver to track the gradients
      celerite::solver::CholeskySolver<g_t> solver;
      g_t jitter_ = g_t(jitter);
      v_t a_real_(J_real), c_real_(J_real),
          a_comp_(J_comp), b_comp_(J_comp), c_comp_(J_comp), d_comp_(J_comp);
      a_real_ << a_real;
      c_real_ << c_real;
      a_comp_ << a_comp;
      b_comp_ << b_comp;
      c_comp_ << c_comp;
      d_comp_ << d_comp;

      // Factorize the matrix while propagating the gradients
      solver.compute(
        jitter_, a_real_, c_real_, a_comp_, b_comp_, c_comp_, d_comp_,
        A, U, V, x, diag
      );

      // Compute the likelihood
      g_t ll = -0.5 * (solver.dot_solve(y) + solver.log_determinant() + M_PI * log(x.rows()));
      double ll_val = ll.val();

      // Evaluate the backpropagated gradients
      std::vector<g_t> params;
      params.push_back(jitter_);
      for (int i = 0; i < J_real; ++i) params.push_back(a_real_(i));
      for (int i = 0; i < J_real; ++i) params.push_back(c_real_(i));
      for (int i = 0; i < J_comp; ++i) params.push_back(a_comp_(i));
      for (int i = 0; i < J_comp; ++i) params.push_back(b_comp_(i));
      for (int i = 0; i < J_comp; ++i) params.push_back(c_comp_(i));
      for (int i = 0; i < J_comp; ++i) params.push_back(d_comp_(i));
      std::vector<double> g;
      ll.grad(params, g);

      // Copy the results to a numpy array
      auto result = py::array_t<double>(g.size());
      auto buf = result.request();
      double* ptr = (double *) buf.ptr;
      for (size_t i = 0; i < g.size(); ++i) ptr[i] = g[i];

      // Tell stan that we don't need these gradients anymore
      stan::math::recover_memory();

      return std::make_tuple(ll_val, result);
#else
      throw std::exception();
#endif
    }, R"delim(
Compute the gradient of the log likelihood of the model using autodiff

The returned gradient is with respect to the jitter and the coefficients.

Args:
    jitter (float): The jitter of the kernel.
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    x (array[n]): The _sorted_ array of input coordinates.
    y (array[n]): The observations at ``x``.
    diag (array[n]): An array that should be added to the diagonal of the
        matrix. This often corresponds to measurement uncertainties and in
        that case, ``diag`` should be the measurement _variance_
        (i.e. sigma^2).

)delim");

#else

  cholesky_solver.def("grad_log_likelihood",
    [](
      PicklableCholeskySolver& nothing,
      double jitter,
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp,
      const vector_t& A,
      const matrix_t& U,
      const matrix_t& V,
      const vector_t& x,
      const vector_t& y,
      const vector_t& diag
    ) {

#ifndef NO_AUTODIFF
      typedef Eigen::AutoDiffScalar<Eigen::VectorXd> g_t;
      typedef Eigen::Matrix<g_t, Eigen::Dynamic, 1> v_t;

      int J_real = a_real.rows();
      int J_comp = a_comp.rows();
      int g_tot = 2 * J_real + 4 * J_comp;

      celerite::solver::CholeskySolver<g_t> solver;
      v_t a_real_(J_real), c_real_(J_real),
          a_comp_(J_comp), b_comp_(J_comp), c_comp_(J_comp), d_comp_(J_comp);

      // This hack is needed because, if jitter is zero, we end up with
      // numerically unstable gradients in all dimensions.
      bool compute_jitter = false;
      int i0 = 0, i = 0;
      g_t jitter_ = g_t(jitter);
      if (jitter > DBL_EPSILON) {
        compute_jitter = true;
        i0 = 1;
        g_tot ++;
        jitter_ = g_t(jitter, g_tot, 0);
      } else {
        jitter_ = g_t(jitter);
      }

      // Keep track of the coordinates of each gradient
      if (J_real) {
        for (i = 0; i < J_real; ++i) a_real_(i) = g_t(a_real(i), g_tot, i0+i);
        i0 += i;
        for (i = 0; i < J_real; ++i) c_real_(i) = g_t(c_real(i), g_tot, i0+i);
        i0 += i;
      }
      if (J_comp) {
        for (i = 0; i < J_comp; ++i) a_comp_(i) = g_t(a_comp(i), g_tot, i0+i);
        i0 += i;
        for (i = 0; i < J_comp; ++i) b_comp_(i) = g_t(b_comp(i), g_tot, i0+i);
        i0 += i;
        for (i = 0; i < J_comp; ++i) c_comp_(i) = g_t(c_comp(i), g_tot, i0+i);
        i0 += i;
        for (i = 0; i < J_comp; ++i) d_comp_(i) = g_t(d_comp(i), g_tot, i0+i);
      }

      // Factorize and track the gradients
      solver.compute(
        jitter_, a_real_, c_real_, a_comp_, b_comp_, c_comp_, d_comp_,
        A, U, V, x, diag
      );

      // Compute the likelihood and the gradients
      g_t ll = -0.5 * (solver.dot_solve(y) + solver.log_determinant() + M_PI * log(x.rows()));
      double ll_val = ll.value();

      // Deal with our zero jitter hack
      Eigen::VectorXd g;
      if (compute_jitter) {
        g = ll.derivatives();
      } else {
        g.resize(g_tot + 1);
        g(0) = 0.0;
        g.tail(g_tot) = ll.derivatives();
      }

      // Copy the result to a numpy array
      auto result = py::array_t<double>(g.size());
      auto buf = result.request();
      double* ptr = (double *) buf.ptr;
      for (int i = 0; i < g.rows(); ++i) ptr[i] = g(i);

      return std::make_tuple(ll_val, result);
#else
      throw std::exception();
#endif

    }, R"delim(
Compute the gradient of the log likelihood of the model using autodiff

The returned gradient is with respect to the jitter and the coefficients.

Args:
    jitter (float): The jitter of the kernel.
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    x (array[n]): The _sorted_ array of input coordinates.
    y (array[n]): The observations at ``x``.
    diag (array[n]): An array that should be added to the diagonal of the
        matrix. This often corresponds to measurement uncertainties and in
        that case, ``diag`` should be the measurement _variance_
        (i.e. sigma^2).

)delim");

#endif

  cholesky_solver.def("compute", [](PicklableCholeskySolver& solver,
      double jitter,
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp,
      const vector_t& A,
      const matrix_t& U,
      const matrix_t& V,
      const vector_t& x,
      const vector_t& diag) {
    return solver.compute(
      jitter, a_real, c_real, a_comp, b_comp, c_comp, d_comp, A, U, V, x, diag
    );
  },
  R"delim(
Compute the Cholesky factorization of the matrix

Args:
    jitter (float): The jitter of the kernel.
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    x (array[n]): The _sorted_ array of input coordinates.
    diag (array[n]): An array that should be added to the diagonal of the
        matrix. This often corresponds to measurement uncertainties and in
        that case, ``diag`` should be the measurement _variance_
        (i.e. sigma^2).

)delim");

  cholesky_solver.def("solve", [](PicklableCholeskySolver& solver, const matrix_t& b) {
    return solver.solve(b);
  },
  R"delim(
Solve a linear system for the matrix defined in ``compute``

A previous call to :func:`solver.CholeskySolver.compute` defines a matrix
``A`` and this method solves for ``x`` in the matrix equation ``A.x = b``.

Args:
    b (array[n] or array[n, nrhs]): The right hand side of the linear system.

Returns:
    array[n] or array[n, nrhs]: The solution of the linear system.

Raises:
    ValueError: For mismatched dimensions.

)delim");

  cholesky_solver.def("dot_solve", [](PicklableCholeskySolver& solver, const matrix_t& b) {
    return solver.dot_solve(b);
  },
  R"delim(
Solve the system ``b^T . A^-1 . b``

A previous call to :func:`solver.Solver.compute` defines a matrix ``A``
and this method solves ``b^T . A^-1 . b`` for a vector ``b``.

Args:
    b (array[n]): The right hand side of the linear system.

Returns:
    float: The solution of ``b^T . A^-1 . b``.

Raises:
    ValueError: For mismatched dimensions.

)delim");

  cholesky_solver.def("dot_L", [](PicklableCholeskySolver& solver, const matrix_t& z) {
    return solver.dot_L(z);
  },
  R"delim(
Compute the dot product of the square root of a ``celerite`` matrix

This method computes ``L.z`` where ``A = L.L^T`` is the matrix defined in
``compute``.

Args:
    z (array[n] or array[n, neq]): The matrix ``z`` described above.

Returns:
    array[n] or array[n, neq]: The dot product ``L.b`` as described above.

Raises:
    ValueError: For mismatched dimensions.

)delim");

  cholesky_solver.def("dot", [](PicklableCholeskySolver& solver,
      double jitter,
      const vector_t& a_real,
      const vector_t& c_real,
      const vector_t& a_comp,
      const vector_t& b_comp,
      const vector_t& c_comp,
      const vector_t& d_comp,
      const vector_t& A,
      const matrix_t& U,
      const matrix_t& V,
      const vector_t& x,
      const matrix_t& b) {
    return solver.dot(jitter, a_real, c_real, a_comp, b_comp, c_comp, d_comp, A, U, V, x, b);
  },
  R"delim(
Compute the dot product of a ``celerite`` matrix and another arbitrary matrix

This method computes ``A.b`` where ``A`` is defined by the parameters and
``b`` is an arbitrary matrix of the correct shape.

Args:
    jitter (float): The jitter of the kernel.
    a_real (array[j_real]): The coefficients of the real terms.
    c_real (array[j_real]): The exponents of the real terms.
    a_comp (array[j_complex]): The real part of the coefficients of the
        complex terms.
    b_comp (array[j_complex]): The imaginary part of the coefficients of
        the complex terms.
    c_comp (array[j_complex]): The real part of the exponents of the
        complex terms.
    d_comp (array[j_complex]): The imaginary part of the exponents of the
        complex terms.
    x (array[n]): The _sorted_ array of input coordinates.
    b (array[n] or array[n, neq]): The matrix ``b`` described above.

Returns:
    array[n] or array[n, neq]: The dot product ``A.b`` as described above.

Raises:
    ValueError: For mismatched dimensions.

)delim");

  cholesky_solver.def("predict", [](PicklableCholeskySolver& solver,
      const vector_t& y,
      const vector_t& x) {
    return solver.predict(y, x);
  },
  R"delim(

)delim");

  cholesky_solver.def("log_determinant", [](PicklableCholeskySolver& solver) {
    return solver.log_determinant();
  },
  R"delim(
Get the log-determinant of the matrix defined by ``compute``

Returns:
    float: The log-determinant of the matrix defined by
    :func:`solver.CholeskySolver.compute`.

)delim");

  cholesky_solver.def("computed", [](PicklableCholeskySolver& solver) {
      return solver.computed();
  },
  R"delim(
A flag that indicates if ``compute`` has been executed

Returns:
    bool: ``True`` if :func:`solver.CholeskySolver.compute` was previously
    executed successfully.

)delim");

  cholesky_solver.def("__getstate__", [](const PicklableCholeskySolver& solver) {
    return solver.serialize();
  });

  cholesky_solver.def("__setstate__", [](PicklableCholeskySolver& solver, py::tuple t) {
    if (t.size() != 8) throw std::runtime_error("Invalid state!");
    new (&solver) PicklableCholeskySolver();
    solver.deserialize(
      t[0].cast<bool>(),
      t[1].cast<int>(),
      t[2].cast<int>(),
      t[3].cast<double>(),

      t[4].cast<matrix_t>(),
      t[5].cast<matrix_t>(),
      t[6].cast<matrix_t>(),

      t[7].cast<vector_t>()
    );
  });

  return m.ptr();
}


// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin developers
// Copyright (c) 2014 The Mini-Blockchain Project
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "script.h"

#include "bignum.h"
#include "core.h"
#include "hash.h"
#include "key.h"
#include "keystore.h"
#include "sync.h"
#include "uint256.h"
#include "util.h"

#include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>

using namespace std;
using namespace boost;

typedef vector<unsigned char> valtype;

const char* GetTxnOutputType(txnouttype t)
{
    switch (t)
    {
    case TX_NONSTANDARD: return "nonstandard";
    case TX_PUBKEY: return "pubkey";
    case TX_PUBKEYHASH: return "pubkeyhash";
    case TX_SCRIPTHASH: return "scripthash";
    case TX_MULTISIG: return "multisig";
    case TX_NULL_DATA: return "nulldata";
    }
    return NULL;
}

namespace {
/** Wrapper that serializes like CTransaction, but with the modifications
 *  required for the signature hash done in-place
 */
class CTransactionSignatureSerializer {
private:
    const CTransaction &txTo;  // reference to the spending transaction (the one being serialized)

public:
    CTransactionSignatureSerializer(const CTransaction &txToIn) :
        txTo(txToIn) {}

    /** Serialize an input of txTo */
    template<typename S>
    void SerializeInput(S &s, unsigned int nInput, int nType, int nVersion) const {
        // Serialize the prevout
        ::Serialize(s, txTo.vin[nInput].pubKey, nType, nVersion);
	::Serialize(s, txTo.vin[nInput].nValue, nType, nVersion);
    }

    /** Serialize an output of txTo */
    template<typename S>
    void SerializeOutput(S &s, unsigned int nOutput, int nType, int nVersion) const {
        ::Serialize(s, txTo.vout[nOutput], nType, nVersion);
    }

    /** Serialize txTo */
    template<typename S>
    void Serialize(S &s, int nType, int nVersion) const {
        // Serialize nVersion
        ::Serialize(s, txTo.nVersion, nType, nVersion);
        // Serialize vin
        unsigned int nInputs = txTo.vin.size();
        ::WriteCompactSize(s, nInputs);
        for (unsigned int nInput = 0; nInput < nInputs; nInput++)
             SerializeInput(s, nInput, nType, nVersion);
        // Serialize vout
        unsigned int nOutputs = txTo.vout.size();
        ::WriteCompactSize(s, nOutputs);
        for (unsigned int nOutput = 0; nOutput < nOutputs; nOutput++)
             SerializeOutput(s, nOutput, nType, nVersion);
	// Serialize msg
	::WriteCompactSize(s, txTo.msg.size());
	::Serialize(s, txTo.msg, nType, nVersion);
        // Serialie nLockTime
        ::Serialize(s, txTo.nLockHeight, nType, nVersion);
    }
};
}

uint256 SignatureHash(const CTransaction& txTo)
{
    // Wrapper to serialize only the necessary parts of the transaction being signed
    CTransactionSignatureSerializer txTmp(txTo);

    // Serialize and hash
    CHashWriter ss(SER_GETHASH, 0);
    ss << txTmp;
    return ss.GetHash();
}

// Valid signature cache, to avoid doing expensive ECDSA signature checking
// twice for every transaction (once when accepted into memory pool, and
// again when accepted into the block chain)

class CSignatureCache
{
private:
     // sigdata_type is (signature hash, signature, public key):
    typedef boost::tuple<uint256, std::vector<unsigned char>, CPubKey> sigdata_type;
    std::set< sigdata_type> setValid;
    boost::shared_mutex cs_sigcache;

public:
    bool
    Get(const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubKey)
    {
        boost::shared_lock<boost::shared_mutex> lock(cs_sigcache);

        sigdata_type k(hash, vchSig, pubKey);
        std::set<sigdata_type>::iterator mi = setValid.find(k);
        if (mi != setValid.end())
            return true;
        return false;
    }

    void Set(const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubKey)
    {
        // DoS prevention: limit cache size to less than 10MB
        // (~200 bytes per cache entry times 50,000 entries)
        // Since there are a maximum of 20,000 signature operations per block
        // 50,000 is a reasonable default.
        int64_t nMaxCacheSize = GetArg("-maxsigcachesize", 50000);
        if (nMaxCacheSize <= 0) return;

        boost::unique_lock<boost::shared_mutex> lock(cs_sigcache);

        while (static_cast<int64_t>(setValid.size()) > nMaxCacheSize)
        {
            // Evict a random entry. Random because that helps
            // foil would-be DoS attackers who might try to pre-generate
            // and re-use a set of valid signatures just-slightly-greater
            // than our cache size.
            uint256 randomHash = GetRandHash();
            std::vector<unsigned char> unused;
            std::set<sigdata_type>::iterator it =
                setValid.lower_bound(sigdata_type(randomHash, unused, unused));
            if (it == setValid.end())
                it = setValid.begin();
            setValid.erase(*it);
        }

        sigdata_type k(hash, vchSig, pubKey);
        setValid.insert(k);
    }
};

bool Sign1(const CKeyID& address, const CKeyStore& keystore, uint256 hash, CScript& scriptSigRet)
{
    CKey key;
    if (!keystore.GetKey(address, key))
        return false;

    vector<unsigned char> vchSig;
    if (!key.SignCompact(hash, vchSig))
        return false;
    scriptSigRet << vchSig;

    //printf("Size: %ld %ld\n", scriptSigRet.size(), vchSig.size());

    return true;
}

bool SignN(const vector<valtype>& multisigdata, const CKeyStore& keystore, uint256 hash, CScript& scriptSigRet)
{
    int nSigned = 0;
    int nRequired = multisigdata.front()[0];
    for (unsigned int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++)
    {
        const valtype& pubkey = multisigdata[i];
        CKeyID keyID = CPubKey(pubkey).GetID();
        if (Sign1(keyID, keystore, hash, scriptSigRet))
            ++nSigned;
    }
    return nSigned==nRequired;
}

unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore)
{
    unsigned int nResult = 0;
    BOOST_FOREACH(const valtype& pubkey, pubkeys)
    {
        CKeyID keyID = CPubKey(pubkey).GetID();
        if (keystore.HaveKey(keyID))
            ++nResult;
    }
    return nResult;
}

bool VerifyScript(const CScript& scriptSig, const uint160& pubKey, const CTransaction& txTo, unsigned int nIn)
{
    assert(nIn < txTo.vin.size());
    // Leave out the signature from the hash, since a signature can't sign itself.
    // The checksig op will also drop the signatures from its hash.
    uint256 hash = SignatureHash(txTo);
    //cout << "Tx Hash: " << hash.GetHex() << endl;
    //cout << "Key: " << pubKey.GetHex() << endl;
    bool found=false;
    BOOST_FOREACH(const CTxIn &txin, txTo.vin){
	if(txin.pubKey!=pubKey)
	    continue;
#if 0
	printf("Size: %ld\n", txin.scriptSig.size());
	for(int i=0; i < txin.scriptSig.size(); i++){
		printf("%2.2X", txin.scriptSig[i]);
	}
	printf("\n");
#endif
	if(!txin.scriptSig.size())
	    return false;

	//Signature format is 1 byte for number of signatures.
	//Signatures are 65 bytes each
	//Public hashs of (m-n) of m are 20 bytes
	uint32_t nSigs = txin.scriptSig[0];
	if(!nSigs)
	    return false;

	uint32_t nHashStart = nSigs*65 + 1;

	if(txin.scriptSig.size() < nHashStart)
	    return false;

	uint32_t nHashArea = txin.scriptSig.size() - nHashStart;
	if(nHashArea%20)
	    return false;

	uint32_t nHashSigs = nHashArea/20;

	CPubKey key;
    	vector<uint160> recoveredKeys;
	for(uint32_t i=0; i < nSigs; i++){
	    if(!key.RecoverCompact(hash,&txin.scriptSig[1+i*65])){
		printf("Could not recover key\n");
		return false;
	    }
	    recoveredKeys.push_back(key.GetID());
	}

	vector<uint160> explicitKeys;
	for(uint32_t i=0; i < nHashSigs; i++){
	    uint160 temp;
	    memcpy(&temp, &txin.scriptSig[nHashStart + i * 20], 20);
	    explicitKeys.push_back(temp);
	}

	//printf("Recovered: %s for %s\n", recoveredKeys[0].GetHex().c_str(), pubKey.GetHex().c_str());

	//We must enforce that the keys are sorted in order to remove malleability
	if(!is_sorted(recoveredKeys.begin(),recoveredKeys.end()))
	    return false;

	if(!is_sorted(explicitKeys.begin(),explicitKeys.end()))
	    return false;

	//combine the vectors
	vector<uint160> allKeys;
	allKeys.insert(allKeys.end(), recoveredKeys.begin(), recoveredKeys.end());
	allKeys.insert(allKeys.end(), explicitKeys.begin(), explicitKeys.end());

	//Special case for single sigs
	if(nSigs==1 && nHashSigs==0){
	    //printf("RecoveredKeys:s %lu\n", recoveredKeys.size());
	    if(recoveredKeys[0] != pubKey){
		printf("Fail!!!!\n");
		return false;
	    }
	    found=true;
	    continue;
	}

	//Sort the complete key collection
	sort(allKeys.begin(),allKeys.end());
	char data[allKeys.size()*20 + 1];
	for(uint32_t i=0; i < allKeys.size(); i++){
	    memcpy(&data[i*20],&allKeys[i],20);
	}
	data[sizeof(data)-1] = nSigs;

	uint160 hash = Hash160(data,data+sizeof(data));
	if(hash!=pubKey){
	    printf("Multisig fail\n");
	    return false;
	}
	found=true;
    }
    return found;
}

bool SignSignature(const CKeyStore &keystore, uint160 pubKey, CTransaction& txTo, uint32_t nIn)
{
    assert(nIn < txTo.vin.size());
    // Leave out the signature from the hash, since a signature can't sign itself.
    // The checksig op will also drop the signatures from its hash.
    uint256 hash = SignatureHash(txTo);
    //cout << "Tx Hash: " << hash.GetHex() << endl;
    //cout << "Key: " << pubKey.GetHex() << endl;
    BOOST_FOREACH(CTxIn &txin, txTo.vin){
	if(txin.pubKey!=pubKey)
	    continue;
	txin.scriptSig.push_back(1);
	return Sign1(CKeyID(pubKey),keystore,hash,txin.scriptSig);
    }
    return false;
}
void CScript::SetMultisig(int nRequired, const std::vector<CPubKey>& keys)
{
#if 0
    this->clear();

    *this << EncodeOP_N(nRequired);
    BOOST_FOREACH(const CPubKey& key, keys)
        *this << key;
    *this << EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;
#else
   assert(0);
#endif
}