/***************************************************************************
 * Copyright 1998-2018 by authors (see AUTHORS.txt)                        *
 *                                                                         *
 *   This file is part of LuxCoreRender.                                   *
 *                                                                         *
 * 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 <stdexcept>
#include <boost/foreach.hpp>
#include <boost/regex.hpp>

#include "luxrays/kernels/kernels.h"
#include "slg/kernels/kernels.h"
#include "slg/film/film.h"
#include "slg/film/imagepipeline/plugins/cameraresponse.h"
#include "slg/film/imagepipeline/plugins/cameraresponsefunctions.h"
#include "slg/utils/filenameresolver.h"

using namespace std;
using namespace luxrays;
using namespace slg;

//------------------------------------------------------------------------------
// CameraResponse filter plugin
//------------------------------------------------------------------------------

BOOST_CLASS_EXPORT_IMPLEMENT(slg::CameraResponsePlugin)

// Estimates gamma in y = x^gamma using Gauss-Newton iterations
static float EstimateGamma(const vector<float> &x, const vector<float> &y, float *rmse) {
	const size_t n = x.size();

	double gamma = 1.0; // Initial guess
	int iter = 0;

	double last_ssr = 1e30;

	while (iter < 100) {
		double ssr = 0;
		double JTr = 0.0;
		double JTJ = 0.0;

		size_t i;

		for (i = 0; i < n; i++) {
			// log() doesn't behave nice near zero
			if (x[i] < 1e-12)
				continue;

			// pow() has issues with small but negative values of gamma
			// this is more stable
			const double lx = log(x[i]);
			const double xg = exp(gamma * lx);

			// residual
			double r = y[i] - xg;
			ssr += r*r;

			// jacobian
			double J = lx * xg;

			JTr += J * r;
			JTJ += J * J;
		}

		if (rmse)
			*rmse = static_cast<float>(sqrt(ssr/i));

		if (fabs(ssr - last_ssr) < 1e-6)
			break;

		const double delta = JTr / JTJ;

		if (fabs(delta) < 1e-9)
			break;

		gamma = gamma + delta;

		last_ssr = ssr;

		iter++;
	}

	return static_cast<float>(gamma);
}

static void AdjustGamma(const vector<float> &from, vector<float> &to, float gamma = 2.2f) {
	for (size_t i = 0; i < from.size(); i++)
		to[i] = powf(to[i], gamma);
}

CameraResponsePlugin::CameraResponsePlugin(const string &name) {
	if (!LoadPreset(name))
		LoadFile(name);

	// Compensate for built-in gamma. Use Y for gamma estimation.
	vector<float> YI, YB;

	// use all unique input values
	YI.insert(YI.end(), redI.begin(), redI.end());
	YI.insert(YI.end(), greenI.begin(), greenI.end());
	YI.insert(YI.end(), blueI.begin(), blueI.end());

	sort(YI.begin(), YI.end());
	const size_t n = unique(YI.begin(), YI.end()) - YI.begin();

	YI.resize(n);
	YB.resize(n);

	for (size_t i = 0; i < n; i++) {
		RGBColor c(YI[i]);

		// Map handles color / monochrome and interpolation
		Map(c);

		YB[i] = c.Y();
	}

	float rmse;
	const float sourceGamma = EstimateGamma(YI, YB, &rmse);

	// Compensate for built-in gamma
	AdjustGamma(redI, redB, 1.f / sourceGamma);
	AdjustGamma(greenI, greenB, 1.f / sourceGamma);
	AdjustGamma(blueI, blueB, 1.f / sourceGamma);

#if !defined(LUXRAYS_DISABLE_OPENCL)
	oclIntersectionDevice = NULL;
	oclRedI = NULL;
	oclRedB = NULL;
	oclGreenI = NULL;
	oclGreenB = NULL;
	oclBlueI = NULL;
	oclBlueB = NULL;

	applyKernel = NULL;
#endif
}

CameraResponsePlugin::~CameraResponsePlugin() {
#if !defined(LUXRAYS_DISABLE_OPENCL)
	delete applyKernel;

	if (oclIntersectionDevice){
		oclIntersectionDevice->FreeBuffer(&oclRedI);
		oclIntersectionDevice->FreeBuffer(&oclRedB);
		oclIntersectionDevice->FreeBuffer(&oclGreenI);
		oclIntersectionDevice->FreeBuffer(&oclGreenB);
		oclIntersectionDevice->FreeBuffer(&oclBlueI);
		oclIntersectionDevice->FreeBuffer(&oclBlueB);
	}
#endif
}

CameraResponsePlugin::CameraResponsePlugin() {
#if !defined(LUXRAYS_DISABLE_OPENCL)
	oclIntersectionDevice = NULL;
	oclRedI = NULL;
	oclRedB = NULL;
	oclGreenI = NULL;
	oclGreenB = NULL;
	oclBlueI = NULL;
	oclBlueB = NULL;

	applyKernel = NULL;
#endif
}

ImagePipelinePlugin *CameraResponsePlugin::Copy() const {
	CameraResponsePlugin *crp = new CameraResponsePlugin();
	crp->color = color;
	crp->redI = redI;
	crp->redB = redB;
	crp->greenI = greenI;
	crp->greenB = greenB;
	crp->blueI = blueI;
	crp->blueB = blueB;

	return crp;
}

//------------------------------------------------------------------------------
// CPU version
//------------------------------------------------------------------------------

void CameraResponsePlugin::Apply(Film &film, const u_int index) {
	Spectrum *pixels = (Spectrum *)film.channel_IMAGEPIPELINEs[index]->GetPixels();
	const u_int pixelCount = film.GetWidth() * film.GetHeight();

	const bool hasPN = film.HasChannel(Film::RADIANCE_PER_PIXEL_NORMALIZED);
	const bool hasSN = film.HasChannel(Film::RADIANCE_PER_SCREEN_NORMALIZED);

	#pragma omp parallel for
	for (
		// Visual C++ 2013 supports only OpenMP 2.5
#if _OPENMP >= 200805
		unsigned
#endif
		int i = 0; i < pixelCount; ++i) {
		if (film.HasSamples(hasPN, hasSN, i))
			Map(pixels[i]);
	}
}

void CameraResponsePlugin::Map(RGBColor &rgb) const {
	if (color) {
		rgb.c[0] = ApplyCrf(rgb.c[0], redI, redB);
		rgb.c[1] = ApplyCrf(rgb.c[1], greenI, greenB);
		rgb.c[2] = ApplyCrf(rgb.c[2], blueI, blueB);
	} else {
		const float y = rgb.Y();
		rgb.c[0] = rgb.c[1] = rgb.c[2] = ApplyCrf(y, redI, redB);
	}
}

float CameraResponsePlugin::ApplyCrf(float point, const vector<float> &from, const vector<float> &to) const {
	if (point <= from.front())
		return to.front();
	if (point >= from.back())
		return to.back();

	int index = upper_bound(from.begin(), from.end(), point) -
		from.begin();
	float x1 = from[index - 1];
	float x2 = from[index];
	float y1 = to[index - 1];
	float y2 = to[index];
	return Lerp((point - x1) / (x2 - x1), y1, y2);
}

//------------------------------------------------------------------------------
// OpenCL version
//------------------------------------------------------------------------------

#if !defined(LUXRAYS_DISABLE_OPENCL)
void CameraResponsePlugin::ApplyOCL(Film &film, const u_int index) {
	if (!applyKernel) {
		oclIntersectionDevice = film.oclIntersectionDevice;

		// Allocate OpenCL buffers
		film.ctx->SetVerbose(true);
		oclIntersectionDevice->AllocBufferRO(&oclRedI, &redI[0], redI.size() * sizeof(float), "Camera response redI");
		oclIntersectionDevice->AllocBufferRO(&oclRedB, &redB[0], redI.size() * sizeof(float), "Camera response redB");
		if (color) {
			oclIntersectionDevice->AllocBufferRO(&oclGreenI, &greenI[0], greenI.size() * sizeof(float), "Camera response greenI");
			oclIntersectionDevice->AllocBufferRO(&oclGreenB, &greenB[0], greenB.size() * sizeof(float), "Camera response greenB");
			oclIntersectionDevice->AllocBufferRO(&oclBlueI, &blueI[0], blueI.size() * sizeof(float), "Camera response blueI");
			oclIntersectionDevice->AllocBufferRO(&oclBlueB, &blueB[0], blueB.size() * sizeof(float), "Camera response blueB");
		}
		film.ctx->SetVerbose(false);

		// Compile sources
		const double tStart = WallClockTime();

		// Set #define symbols
		stringstream ssParams;
		ssParams.precision(6);
		ssParams << scientific <<
				" -D LUXRAYS_OPENCL_KERNEL" <<
				" -D SLG_OPENCL_KERNEL";
		if (color)
			ssParams << " -D PARAM_CAMERARESPONSE_COLOR";

		cl::Program *program = ImagePipelinePlugin::CompileProgram(
				film,
				ssParams.str(),
				luxrays::ocl::KernelSource_color_types +
				luxrays::ocl::KernelSource_color_funcs +
				slg::ocl::KernelSource_plugin_cameraresponse_funcs,
				"CameraResponsePlugin");

		//----------------------------------------------------------------------
		// CameraResponsePlugin_Apply kernel
		//----------------------------------------------------------------------

		SLG_LOG("[CameraResponsePlugin] Compiling CameraResponsePlugin_Apply Kernel");
		applyKernel = new cl::Kernel(*program, "CameraResponsePlugin_Apply");

		// Set kernel arguments
		u_int argIndex = 0;
		applyKernel->setArg(argIndex++, film.GetWidth());
		applyKernel->setArg(argIndex++, film.GetHeight());
		applyKernel->setArg(argIndex++, *(film.ocl_IMAGEPIPELINE));
		applyKernel->setArg(argIndex++, *oclRedI);
		applyKernel->setArg(argIndex++, *oclRedB);
		applyKernel->setArg(argIndex++, (u_int)redI.size());
		if (color) {
			applyKernel->setArg(argIndex++, *oclGreenI);
			applyKernel->setArg(argIndex++, *oclGreenB);
			applyKernel->setArg(argIndex++, (u_int)greenI.size());
			applyKernel->setArg(argIndex++, *oclBlueI);
			applyKernel->setArg(argIndex++, *oclBlueB);
			applyKernel->setArg(argIndex++, (u_int)blueI.size());
		}

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

		delete program;

		// Because imgMapDesc is a local variable
		oclIntersectionDevice->GetOpenCLQueue().flush();

		const double tEnd = WallClockTime();
		SLG_LOG("[CameraResponsePlugin] Kernels compilation time: " << int((tEnd - tStart) * 1000.0) << "ms");
	}

	oclIntersectionDevice->GetOpenCLQueue().enqueueNDRangeKernel(*applyKernel,
			cl::NullRange, cl::NDRange(RoundUp(film.GetWidth() * film.GetHeight(), 256u)), cl::NDRange(256));
}
#endif

//------------------------------------------------------------------------------
// Camera response definition
//------------------------------------------------------------------------------

// A database of 201 response curves can be downloaded from http://www.cs.columbia.edu/CAVE//software/softlib/dorf.php
// Direct link: http://www.cs.columbia.edu/CAVE//software/dorf/response/dorfCurves.zip

void CameraResponsePlugin::LoadFile(const string &filmName) {
	const string resolvedFileName = SLG_FileNameResolver.ResolveFile(filmName);

	ifstream file(resolvedFileName.c_str());
	if (!file)
		throw runtime_error("Unable to open Camera Response Function file: " + resolvedFileName);

	string crfdata;
	{
		// Read file into memory
		stringstream ss("");
		ss << file.rdbuf();
		crfdata = ss.str();
	}

	boost::regex func_expr("(?-s)(?<funcname>\\S+)\\s+graph.+\\s+I\\s*=\\s*(?<I>.+$)\\s+B\\s*=\\s*(?<B>.+$)");
	boost::regex channel_expr("^(?<name>.*)(?<channel>Red|Green|Blue)$");
	boost::regex float_expr("-?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?");

	boost::sregex_iterator rit(crfdata.begin(), crfdata.end(), func_expr);
	boost::sregex_iterator rend;

	bool red, green, blue;
	red = green = blue = false;

	string crfname("");

	for (; rit != rend; ++rit) {
		const boost::smatch &m = *rit;
		boost::smatch cm;

		// Smatch references input string, keep copy
		const string funcname = m["funcname"].str();
		string name = funcname;
		string channel = "Red";

		if (boost::regex_match(funcname, cm, channel_expr)) {
			name = cm["name"].str();
			channel = cm["channel"].str();
		}
		const string Istr = m["I"].str();
		const string Bstr = m["B"].str();

		if (crfname == "")
			crfname = name;
		else if (crfname != name)
			throw runtime_error("Expected Camera Response Function name '" + crfname + "' but found '" + name + "'");

		vector<float> *I = NULL;
		vector<float> *B = NULL;
		bool *channel_flag = NULL;

		if (channel == "Red") {
			I = &redI;
			B = &redB;
			channel_flag = &red;
		} else if (channel == "Green") {
			I = &greenI;
			B = &greenB;
			channel_flag = &green;
		} else if (channel == "Blue") {
			I = &blueI;
			B = &blueB;
			channel_flag = &blue;
		} else
			throw runtime_error("Error parsing Camera Response file");

		if (*channel_flag) {
			SLG_LOG("WARNING: " << channel << " channel already specified in '" << resolvedFileName << "', ignoring");
			continue;
		}

		// Parse functions
		for (boost::sregex_iterator rit(Istr.begin(), Istr.end(), float_expr); rit != rend; ++rit) {
			I->push_back(boost::lexical_cast<float>(rit->str(0)));
		}

		for (boost::sregex_iterator rit(Bstr.begin(), Bstr.end(), float_expr); rit != rend; ++rit) {
			B->push_back(boost::lexical_cast<float>(rit->str(0)));
		}

		*channel_flag = I->size() == B->size();

		if (!(*channel_flag) || I->empty()) {
			SLG_LOG("WARNING: Inconsistent " << channel << " data for '" << resolvedFileName << "', ignoring");
			I->clear();
			B->clear();
			*channel_flag = false;
		}

		color = red && green && blue;
		if (color)
			break;
	}

	// Need at least red for either monochrome or color
	if (!red)
		throw runtime_error("No valid Camera Response Functions in: " + filmName);
}

bool CameraResponsePlugin::LoadPreset(const string &filmName) {
/*
	Valid preset names (case sensitive):

	Advantix_100CD
	Advantix_200CD
	Advantix_400CD
	Agfachrome_ctpecisa_200CD
	Agfachrome_ctprecisa_100CD
	Agfachrome_rsx2_050CD
	Agfachrome_rsx2_100CD
	Agfachrome_rsx2_200CD
	Agfacolor_futura_100CD
	Agfacolor_futura_200CD
	Agfacolor_futura_400CD
	Agfacolor_futuraII_100CD
	Agfacolor_futuraII_200CD
	Agfacolor_futuraII_400CD
	Agfacolor_hdc_100_plusCD
	Agfacolor_hdc_200_plusCD
	Agfacolor_hdc_400_plusCD
	Agfacolor_optimaII_100CD
	Agfacolor_optimaII_200CD
	Agfacolor_ultra_050_CD
	Agfacolor_vista_100CD
	Agfacolor_vista_200CD
	Agfacolor_vista_400CD
	Agfacolor_vista_800CD
	Agfapan_apx_025CD
	Agfapan_apx_100CD
	Agfapan_apx_400CD
	Ektachrome_100_plusCD
	Ektachrome_100CD
	Ektachrome_320TCD
	Ektachrome_400XCD
	Ektachrome_64CD
	Ektachrome_64TCD
	Ektachrome_E100SCD
	F125CD
	F250CD
	F400CD
	FCICD
	Gold_100CD
	Gold_200CD
	Kodachrome_200CD
	Kodachrome_25CD
	Kodachrome_64CD
	Max_Zoom_800CD
	Portra_100TCD
	Portra_160NCCD
	Portra_160VCCD
	Portra_400NCCD
	Portra_400VCCD
	Portra_800CD
*/

	// Generated by make_embedded_crfs.py
	if (filmName == "Advantix_100CD") {
		const size_t nRed = sizeof(Advantix_100CDRed_I) / sizeof(Advantix_100CDRed_I[0]);
		redI.assign(Advantix_100CDRed_I, Advantix_100CDRed_I + nRed);
		redB.assign(Advantix_100CDRed_B, Advantix_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Advantix_100CDGreen_I) / sizeof(Advantix_100CDGreen_I[0]);
		greenI.assign(Advantix_100CDGreen_I, Advantix_100CDGreen_I + nGreen);
		greenB.assign(Advantix_100CDGreen_B, Advantix_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Advantix_100CDBlue_I) / sizeof(Advantix_100CDBlue_I[0]);
		blueI.assign(Advantix_100CDBlue_I, Advantix_100CDBlue_I + nBlue);
		blueB.assign(Advantix_100CDBlue_B, Advantix_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Advantix_200CD") {
		const size_t nRed = sizeof(Advantix_200CDRed_I) / sizeof(Advantix_200CDRed_I[0]);
		redI.assign(Advantix_200CDRed_I, Advantix_200CDRed_I + nRed);
		redB.assign(Advantix_200CDRed_B, Advantix_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Advantix_200CDGreen_I) / sizeof(Advantix_200CDGreen_I[0]);
		greenI.assign(Advantix_200CDGreen_I, Advantix_200CDGreen_I + nGreen);
		greenB.assign(Advantix_200CDGreen_B, Advantix_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Advantix_200CDBlue_I) / sizeof(Advantix_200CDBlue_I[0]);
		blueI.assign(Advantix_200CDBlue_I, Advantix_200CDBlue_I + nBlue);
		blueB.assign(Advantix_200CDBlue_B, Advantix_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Advantix_400CD") {
		const size_t nRed = sizeof(Advantix_400CDRed_I) / sizeof(Advantix_400CDRed_I[0]);
		redI.assign(Advantix_400CDRed_I, Advantix_400CDRed_I + nRed);
		redB.assign(Advantix_400CDRed_B, Advantix_400CDRed_B + nRed);
		const size_t nGreen = sizeof(Advantix_400CDGreen_I) / sizeof(Advantix_400CDGreen_I[0]);
		greenI.assign(Advantix_400CDGreen_I, Advantix_400CDGreen_I + nGreen);
		greenB.assign(Advantix_400CDGreen_B, Advantix_400CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Advantix_400CDBlue_I) / sizeof(Advantix_400CDBlue_I[0]);
		blueI.assign(Advantix_400CDBlue_I, Advantix_400CDBlue_I + nBlue);
		blueB.assign(Advantix_400CDBlue_B, Advantix_400CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfachrome_ctpecisa_200CD") {
		const size_t nRed = sizeof(Agfachrome_ctpecisa_200CDRed_I) / sizeof(Agfachrome_ctpecisa_200CDRed_I[0]);
		redI.assign(Agfachrome_ctpecisa_200CDRed_I, Agfachrome_ctpecisa_200CDRed_I + nRed);
		redB.assign(Agfachrome_ctpecisa_200CDRed_B, Agfachrome_ctpecisa_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfachrome_ctpecisa_200CDGreen_I) / sizeof(Agfachrome_ctpecisa_200CDGreen_I[0]);
		greenI.assign(Agfachrome_ctpecisa_200CDGreen_I, Agfachrome_ctpecisa_200CDGreen_I + nGreen);
		greenB.assign(Agfachrome_ctpecisa_200CDGreen_B, Agfachrome_ctpecisa_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfachrome_ctpecisa_200CDBlue_I) / sizeof(Agfachrome_ctpecisa_200CDBlue_I[0]);
		blueI.assign(Agfachrome_ctpecisa_200CDBlue_I, Agfachrome_ctpecisa_200CDBlue_I + nBlue);
		blueB.assign(Agfachrome_ctpecisa_200CDBlue_B, Agfachrome_ctpecisa_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfachrome_ctprecisa_100CD") {
		const size_t nRed = sizeof(Agfachrome_ctprecisa_100CDRed_I) / sizeof(Agfachrome_ctprecisa_100CDRed_I[0]);
		redI.assign(Agfachrome_ctprecisa_100CDRed_I, Agfachrome_ctprecisa_100CDRed_I + nRed);
		redB.assign(Agfachrome_ctprecisa_100CDRed_B, Agfachrome_ctprecisa_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfachrome_ctprecisa_100CDGreen_I) / sizeof(Agfachrome_ctprecisa_100CDGreen_I[0]);
		greenI.assign(Agfachrome_ctprecisa_100CDGreen_I, Agfachrome_ctprecisa_100CDGreen_I + nGreen);
		greenB.assign(Agfachrome_ctprecisa_100CDGreen_B, Agfachrome_ctprecisa_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfachrome_ctprecisa_100CDBlue_I) / sizeof(Agfachrome_ctprecisa_100CDBlue_I[0]);
		blueI.assign(Agfachrome_ctprecisa_100CDBlue_I, Agfachrome_ctprecisa_100CDBlue_I + nBlue);
		blueB.assign(Agfachrome_ctprecisa_100CDBlue_B, Agfachrome_ctprecisa_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfachrome_rsx2_050CD") {
		const size_t nRed = sizeof(Agfachrome_rsx2_050CDRed_I) / sizeof(Agfachrome_rsx2_050CDRed_I[0]);
		redI.assign(Agfachrome_rsx2_050CDRed_I, Agfachrome_rsx2_050CDRed_I + nRed);
		redB.assign(Agfachrome_rsx2_050CDRed_B, Agfachrome_rsx2_050CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfachrome_rsx2_050CDGreen_I) / sizeof(Agfachrome_rsx2_050CDGreen_I[0]);
		greenI.assign(Agfachrome_rsx2_050CDGreen_I, Agfachrome_rsx2_050CDGreen_I + nGreen);
		greenB.assign(Agfachrome_rsx2_050CDGreen_B, Agfachrome_rsx2_050CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfachrome_rsx2_050CDBlue_I) / sizeof(Agfachrome_rsx2_050CDBlue_I[0]);
		blueI.assign(Agfachrome_rsx2_050CDBlue_I, Agfachrome_rsx2_050CDBlue_I + nBlue);
		blueB.assign(Agfachrome_rsx2_050CDBlue_B, Agfachrome_rsx2_050CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfachrome_rsx2_100CD") {
		const size_t nRed = sizeof(Agfachrome_rsx2_100CDRed_I) / sizeof(Agfachrome_rsx2_100CDRed_I[0]);
		redI.assign(Agfachrome_rsx2_100CDRed_I, Agfachrome_rsx2_100CDRed_I + nRed);
		redB.assign(Agfachrome_rsx2_100CDRed_B, Agfachrome_rsx2_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfachrome_rsx2_100CDGreen_I) / sizeof(Agfachrome_rsx2_100CDGreen_I[0]);
		greenI.assign(Agfachrome_rsx2_100CDGreen_I, Agfachrome_rsx2_100CDGreen_I + nGreen);
		greenB.assign(Agfachrome_rsx2_100CDGreen_B, Agfachrome_rsx2_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfachrome_rsx2_100CDBlue_I) / sizeof(Agfachrome_rsx2_100CDBlue_I[0]);
		blueI.assign(Agfachrome_rsx2_100CDBlue_I, Agfachrome_rsx2_100CDBlue_I + nBlue);
		blueB.assign(Agfachrome_rsx2_100CDBlue_B, Agfachrome_rsx2_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfachrome_rsx2_200CD") {
		const size_t nRed = sizeof(Agfachrome_rsx2_200CDRed_I) / sizeof(Agfachrome_rsx2_200CDRed_I[0]);
		redI.assign(Agfachrome_rsx2_200CDRed_I, Agfachrome_rsx2_200CDRed_I + nRed);
		redB.assign(Agfachrome_rsx2_200CDRed_B, Agfachrome_rsx2_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfachrome_rsx2_200CDGreen_I) / sizeof(Agfachrome_rsx2_200CDGreen_I[0]);
		greenI.assign(Agfachrome_rsx2_200CDGreen_I, Agfachrome_rsx2_200CDGreen_I + nGreen);
		greenB.assign(Agfachrome_rsx2_200CDGreen_B, Agfachrome_rsx2_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfachrome_rsx2_200CDBlue_I) / sizeof(Agfachrome_rsx2_200CDBlue_I[0]);
		blueI.assign(Agfachrome_rsx2_200CDBlue_I, Agfachrome_rsx2_200CDBlue_I + nBlue);
		blueB.assign(Agfachrome_rsx2_200CDBlue_B, Agfachrome_rsx2_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futura_100CD") {
		const size_t nRed = sizeof(Agfacolor_futura_100CDRed_I) / sizeof(Agfacolor_futura_100CDRed_I[0]);
		redI.assign(Agfacolor_futura_100CDRed_I, Agfacolor_futura_100CDRed_I + nRed);
		redB.assign(Agfacolor_futura_100CDRed_B, Agfacolor_futura_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futura_100CDGreen_I) / sizeof(Agfacolor_futura_100CDGreen_I[0]);
		greenI.assign(Agfacolor_futura_100CDGreen_I, Agfacolor_futura_100CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futura_100CDGreen_B, Agfacolor_futura_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futura_100CDBlue_I) / sizeof(Agfacolor_futura_100CDBlue_I[0]);
		blueI.assign(Agfacolor_futura_100CDBlue_I, Agfacolor_futura_100CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futura_100CDBlue_B, Agfacolor_futura_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futura_200CD") {
		const size_t nRed = sizeof(Agfacolor_futura_200CDRed_I) / sizeof(Agfacolor_futura_200CDRed_I[0]);
		redI.assign(Agfacolor_futura_200CDRed_I, Agfacolor_futura_200CDRed_I + nRed);
		redB.assign(Agfacolor_futura_200CDRed_B, Agfacolor_futura_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futura_200CDGreen_I) / sizeof(Agfacolor_futura_200CDGreen_I[0]);
		greenI.assign(Agfacolor_futura_200CDGreen_I, Agfacolor_futura_200CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futura_200CDGreen_B, Agfacolor_futura_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futura_200CDBlue_I) / sizeof(Agfacolor_futura_200CDBlue_I[0]);
		blueI.assign(Agfacolor_futura_200CDBlue_I, Agfacolor_futura_200CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futura_200CDBlue_B, Agfacolor_futura_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futura_400CD") {
		const size_t nRed = sizeof(Agfacolor_futura_400CDRed_I) / sizeof(Agfacolor_futura_400CDRed_I[0]);
		redI.assign(Agfacolor_futura_400CDRed_I, Agfacolor_futura_400CDRed_I + nRed);
		redB.assign(Agfacolor_futura_400CDRed_B, Agfacolor_futura_400CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futura_400CDGreen_I) / sizeof(Agfacolor_futura_400CDGreen_I[0]);
		greenI.assign(Agfacolor_futura_400CDGreen_I, Agfacolor_futura_400CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futura_400CDGreen_B, Agfacolor_futura_400CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futura_400CDBlue_I) / sizeof(Agfacolor_futura_400CDBlue_I[0]);
		blueI.assign(Agfacolor_futura_400CDBlue_I, Agfacolor_futura_400CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futura_400CDBlue_B, Agfacolor_futura_400CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futuraII_100CD") {
		const size_t nRed = sizeof(Agfacolor_futuraII_100CDRed_I) / sizeof(Agfacolor_futuraII_100CDRed_I[0]);
		redI.assign(Agfacolor_futuraII_100CDRed_I, Agfacolor_futuraII_100CDRed_I + nRed);
		redB.assign(Agfacolor_futuraII_100CDRed_B, Agfacolor_futuraII_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futuraII_100CDGreen_I) / sizeof(Agfacolor_futuraII_100CDGreen_I[0]);
		greenI.assign(Agfacolor_futuraII_100CDGreen_I, Agfacolor_futuraII_100CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futuraII_100CDGreen_B, Agfacolor_futuraII_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futuraII_100CDBlue_I) / sizeof(Agfacolor_futuraII_100CDBlue_I[0]);
		blueI.assign(Agfacolor_futuraII_100CDBlue_I, Agfacolor_futuraII_100CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futuraII_100CDBlue_B, Agfacolor_futuraII_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futuraII_200CD") {
		const size_t nRed = sizeof(Agfacolor_futuraII_200CDRed_I) / sizeof(Agfacolor_futuraII_200CDRed_I[0]);
		redI.assign(Agfacolor_futuraII_200CDRed_I, Agfacolor_futuraII_200CDRed_I + nRed);
		redB.assign(Agfacolor_futuraII_200CDRed_B, Agfacolor_futuraII_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futuraII_200CDGreen_I) / sizeof(Agfacolor_futuraII_200CDGreen_I[0]);
		greenI.assign(Agfacolor_futuraII_200CDGreen_I, Agfacolor_futuraII_200CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futuraII_200CDGreen_B, Agfacolor_futuraII_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futuraII_200CDBlue_I) / sizeof(Agfacolor_futuraII_200CDBlue_I[0]);
		blueI.assign(Agfacolor_futuraII_200CDBlue_I, Agfacolor_futuraII_200CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futuraII_200CDBlue_B, Agfacolor_futuraII_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_futuraII_400CD") {
		const size_t nRed = sizeof(Agfacolor_futuraII_400CDRed_I) / sizeof(Agfacolor_futuraII_400CDRed_I[0]);
		redI.assign(Agfacolor_futuraII_400CDRed_I, Agfacolor_futuraII_400CDRed_I + nRed);
		redB.assign(Agfacolor_futuraII_400CDRed_B, Agfacolor_futuraII_400CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_futuraII_400CDGreen_I) / sizeof(Agfacolor_futuraII_400CDGreen_I[0]);
		greenI.assign(Agfacolor_futuraII_400CDGreen_I, Agfacolor_futuraII_400CDGreen_I + nGreen);
		greenB.assign(Agfacolor_futuraII_400CDGreen_B, Agfacolor_futuraII_400CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_futuraII_400CDBlue_I) / sizeof(Agfacolor_futuraII_400CDBlue_I[0]);
		blueI.assign(Agfacolor_futuraII_400CDBlue_I, Agfacolor_futuraII_400CDBlue_I + nBlue);
		blueB.assign(Agfacolor_futuraII_400CDBlue_B, Agfacolor_futuraII_400CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_hdc_100_plusCD") {
		const size_t nRed = sizeof(Agfacolor_hdc_100_plusCDRed_I) / sizeof(Agfacolor_hdc_100_plusCDRed_I[0]);
		redI.assign(Agfacolor_hdc_100_plusCDRed_I, Agfacolor_hdc_100_plusCDRed_I + nRed);
		redB.assign(Agfacolor_hdc_100_plusCDRed_B, Agfacolor_hdc_100_plusCDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_hdc_100_plusCDGreen_I) / sizeof(Agfacolor_hdc_100_plusCDGreen_I[0]);
		greenI.assign(Agfacolor_hdc_100_plusCDGreen_I, Agfacolor_hdc_100_plusCDGreen_I + nGreen);
		greenB.assign(Agfacolor_hdc_100_plusCDGreen_B, Agfacolor_hdc_100_plusCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_hdc_100_plusCDBlue_I) / sizeof(Agfacolor_hdc_100_plusCDBlue_I[0]);
		blueI.assign(Agfacolor_hdc_100_plusCDBlue_I, Agfacolor_hdc_100_plusCDBlue_I + nBlue);
		blueB.assign(Agfacolor_hdc_100_plusCDBlue_B, Agfacolor_hdc_100_plusCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_hdc_200_plusCD") {
		const size_t nRed = sizeof(Agfacolor_hdc_200_plusCDRed_I) / sizeof(Agfacolor_hdc_200_plusCDRed_I[0]);
		redI.assign(Agfacolor_hdc_200_plusCDRed_I, Agfacolor_hdc_200_plusCDRed_I + nRed);
		redB.assign(Agfacolor_hdc_200_plusCDRed_B, Agfacolor_hdc_200_plusCDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_hdc_200_plusCDGreen_I) / sizeof(Agfacolor_hdc_200_plusCDGreen_I[0]);
		greenI.assign(Agfacolor_hdc_200_plusCDGreen_I, Agfacolor_hdc_200_plusCDGreen_I + nGreen);
		greenB.assign(Agfacolor_hdc_200_plusCDGreen_B, Agfacolor_hdc_200_plusCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_hdc_200_plusCDBlue_I) / sizeof(Agfacolor_hdc_200_plusCDBlue_I[0]);
		blueI.assign(Agfacolor_hdc_200_plusCDBlue_I, Agfacolor_hdc_200_plusCDBlue_I + nBlue);
		blueB.assign(Agfacolor_hdc_200_plusCDBlue_B, Agfacolor_hdc_200_plusCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_hdc_400_plusCD") {
		const size_t nRed = sizeof(Agfacolor_hdc_400_plusCDRed_I) / sizeof(Agfacolor_hdc_400_plusCDRed_I[0]);
		redI.assign(Agfacolor_hdc_400_plusCDRed_I, Agfacolor_hdc_400_plusCDRed_I + nRed);
		redB.assign(Agfacolor_hdc_400_plusCDRed_B, Agfacolor_hdc_400_plusCDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_hdc_400_plusCDGreen_I) / sizeof(Agfacolor_hdc_400_plusCDGreen_I[0]);
		greenI.assign(Agfacolor_hdc_400_plusCDGreen_I, Agfacolor_hdc_400_plusCDGreen_I + nGreen);
		greenB.assign(Agfacolor_hdc_400_plusCDGreen_B, Agfacolor_hdc_400_plusCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_hdc_400_plusCDBlue_I) / sizeof(Agfacolor_hdc_400_plusCDBlue_I[0]);
		blueI.assign(Agfacolor_hdc_400_plusCDBlue_I, Agfacolor_hdc_400_plusCDBlue_I + nBlue);
		blueB.assign(Agfacolor_hdc_400_plusCDBlue_B, Agfacolor_hdc_400_plusCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_optimaII_100CD") {
		const size_t nRed = sizeof(Agfacolor_optimaII_100CDRed_I) / sizeof(Agfacolor_optimaII_100CDRed_I[0]);
		redI.assign(Agfacolor_optimaII_100CDRed_I, Agfacolor_optimaII_100CDRed_I + nRed);
		redB.assign(Agfacolor_optimaII_100CDRed_B, Agfacolor_optimaII_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_optimaII_100CDGreen_I) / sizeof(Agfacolor_optimaII_100CDGreen_I[0]);
		greenI.assign(Agfacolor_optimaII_100CDGreen_I, Agfacolor_optimaII_100CDGreen_I + nGreen);
		greenB.assign(Agfacolor_optimaII_100CDGreen_B, Agfacolor_optimaII_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_optimaII_100CDBlue_I) / sizeof(Agfacolor_optimaII_100CDBlue_I[0]);
		blueI.assign(Agfacolor_optimaII_100CDBlue_I, Agfacolor_optimaII_100CDBlue_I + nBlue);
		blueB.assign(Agfacolor_optimaII_100CDBlue_B, Agfacolor_optimaII_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_optimaII_200CD") {
		const size_t nRed = sizeof(Agfacolor_optimaII_200CDRed_I) / sizeof(Agfacolor_optimaII_200CDRed_I[0]);
		redI.assign(Agfacolor_optimaII_200CDRed_I, Agfacolor_optimaII_200CDRed_I + nRed);
		redB.assign(Agfacolor_optimaII_200CDRed_B, Agfacolor_optimaII_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_optimaII_200CDGreen_I) / sizeof(Agfacolor_optimaII_200CDGreen_I[0]);
		greenI.assign(Agfacolor_optimaII_200CDGreen_I, Agfacolor_optimaII_200CDGreen_I + nGreen);
		greenB.assign(Agfacolor_optimaII_200CDGreen_B, Agfacolor_optimaII_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_optimaII_200CDBlue_I) / sizeof(Agfacolor_optimaII_200CDBlue_I[0]);
		blueI.assign(Agfacolor_optimaII_200CDBlue_I, Agfacolor_optimaII_200CDBlue_I + nBlue);
		blueB.assign(Agfacolor_optimaII_200CDBlue_B, Agfacolor_optimaII_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_ultra_050_CD") {
		const size_t nRed = sizeof(Agfacolor_ultra_050_CDRed_I) / sizeof(Agfacolor_ultra_050_CDRed_I[0]);
		redI.assign(Agfacolor_ultra_050_CDRed_I, Agfacolor_ultra_050_CDRed_I + nRed);
		redB.assign(Agfacolor_ultra_050_CDRed_B, Agfacolor_ultra_050_CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_ultra_050_CDGreen_I) / sizeof(Agfacolor_ultra_050_CDGreen_I[0]);
		greenI.assign(Agfacolor_ultra_050_CDGreen_I, Agfacolor_ultra_050_CDGreen_I + nGreen);
		greenB.assign(Agfacolor_ultra_050_CDGreen_B, Agfacolor_ultra_050_CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_ultra_050_CDBlue_I) / sizeof(Agfacolor_ultra_050_CDBlue_I[0]);
		blueI.assign(Agfacolor_ultra_050_CDBlue_I, Agfacolor_ultra_050_CDBlue_I + nBlue);
		blueB.assign(Agfacolor_ultra_050_CDBlue_B, Agfacolor_ultra_050_CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_vista_100CD") {
		const size_t nRed = sizeof(Agfacolor_vista_100CDRed_I) / sizeof(Agfacolor_vista_100CDRed_I[0]);
		redI.assign(Agfacolor_vista_100CDRed_I, Agfacolor_vista_100CDRed_I + nRed);
		redB.assign(Agfacolor_vista_100CDRed_B, Agfacolor_vista_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_vista_100CDGreen_I) / sizeof(Agfacolor_vista_100CDGreen_I[0]);
		greenI.assign(Agfacolor_vista_100CDGreen_I, Agfacolor_vista_100CDGreen_I + nGreen);
		greenB.assign(Agfacolor_vista_100CDGreen_B, Agfacolor_vista_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_vista_100CDBlue_I) / sizeof(Agfacolor_vista_100CDBlue_I[0]);
		blueI.assign(Agfacolor_vista_100CDBlue_I, Agfacolor_vista_100CDBlue_I + nBlue);
		blueB.assign(Agfacolor_vista_100CDBlue_B, Agfacolor_vista_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_vista_200CD") {
		const size_t nRed = sizeof(Agfacolor_vista_200CDRed_I) / sizeof(Agfacolor_vista_200CDRed_I[0]);
		redI.assign(Agfacolor_vista_200CDRed_I, Agfacolor_vista_200CDRed_I + nRed);
		redB.assign(Agfacolor_vista_200CDRed_B, Agfacolor_vista_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_vista_200CDGreen_I) / sizeof(Agfacolor_vista_200CDGreen_I[0]);
		greenI.assign(Agfacolor_vista_200CDGreen_I, Agfacolor_vista_200CDGreen_I + nGreen);
		greenB.assign(Agfacolor_vista_200CDGreen_B, Agfacolor_vista_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_vista_200CDBlue_I) / sizeof(Agfacolor_vista_200CDBlue_I[0]);
		blueI.assign(Agfacolor_vista_200CDBlue_I, Agfacolor_vista_200CDBlue_I + nBlue);
		blueB.assign(Agfacolor_vista_200CDBlue_B, Agfacolor_vista_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_vista_400CD") {
		const size_t nRed = sizeof(Agfacolor_vista_400CDRed_I) / sizeof(Agfacolor_vista_400CDRed_I[0]);
		redI.assign(Agfacolor_vista_400CDRed_I, Agfacolor_vista_400CDRed_I + nRed);
		redB.assign(Agfacolor_vista_400CDRed_B, Agfacolor_vista_400CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_vista_400CDGreen_I) / sizeof(Agfacolor_vista_400CDGreen_I[0]);
		greenI.assign(Agfacolor_vista_400CDGreen_I, Agfacolor_vista_400CDGreen_I + nGreen);
		greenB.assign(Agfacolor_vista_400CDGreen_B, Agfacolor_vista_400CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_vista_400CDBlue_I) / sizeof(Agfacolor_vista_400CDBlue_I[0]);
		blueI.assign(Agfacolor_vista_400CDBlue_I, Agfacolor_vista_400CDBlue_I + nBlue);
		blueB.assign(Agfacolor_vista_400CDBlue_B, Agfacolor_vista_400CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfacolor_vista_800CD") {
		const size_t nRed = sizeof(Agfacolor_vista_800CDRed_I) / sizeof(Agfacolor_vista_800CDRed_I[0]);
		redI.assign(Agfacolor_vista_800CDRed_I, Agfacolor_vista_800CDRed_I + nRed);
		redB.assign(Agfacolor_vista_800CDRed_B, Agfacolor_vista_800CDRed_B + nRed);
		const size_t nGreen = sizeof(Agfacolor_vista_800CDGreen_I) / sizeof(Agfacolor_vista_800CDGreen_I[0]);
		greenI.assign(Agfacolor_vista_800CDGreen_I, Agfacolor_vista_800CDGreen_I + nGreen);
		greenB.assign(Agfacolor_vista_800CDGreen_B, Agfacolor_vista_800CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Agfacolor_vista_800CDBlue_I) / sizeof(Agfacolor_vista_800CDBlue_I[0]);
		blueI.assign(Agfacolor_vista_800CDBlue_I, Agfacolor_vista_800CDBlue_I + nBlue);
		blueB.assign(Agfacolor_vista_800CDBlue_B, Agfacolor_vista_800CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Agfapan_apx_025CD") {
		const size_t nRed = sizeof(Agfapan_apx_025CDRed_I) / sizeof(Agfapan_apx_025CDRed_I[0]);
		redI.assign(Agfapan_apx_025CDRed_I, Agfapan_apx_025CDRed_I + nRed);
		redB.assign(Agfapan_apx_025CDRed_B, Agfapan_apx_025CDRed_B + nRed);
		color = false;
	} else if (filmName == "Agfapan_apx_100CD") {
		const size_t nRed = sizeof(Agfapan_apx_100CDRed_I) / sizeof(Agfapan_apx_100CDRed_I[0]);
		redI.assign(Agfapan_apx_100CDRed_I, Agfapan_apx_100CDRed_I + nRed);
		redB.assign(Agfapan_apx_100CDRed_B, Agfapan_apx_100CDRed_B + nRed);
		color = false;
	} else if (filmName == "Agfapan_apx_400CD") {
		const size_t nRed = sizeof(Agfapan_apx_400CDRed_I) / sizeof(Agfapan_apx_400CDRed_I[0]);
		redI.assign(Agfapan_apx_400CDRed_I, Agfapan_apx_400CDRed_I + nRed);
		redB.assign(Agfapan_apx_400CDRed_B, Agfapan_apx_400CDRed_B + nRed);
		color = false;
	} else if (filmName == "Ektachrome_100_plusCD") {
		const size_t nRed = sizeof(Ektachrome_100_plusCDRed_I) / sizeof(Ektachrome_100_plusCDRed_I[0]);
		redI.assign(Ektachrome_100_plusCDRed_I, Ektachrome_100_plusCDRed_I + nRed);
		redB.assign(Ektachrome_100_plusCDRed_B, Ektachrome_100_plusCDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_100_plusCDGreen_I) / sizeof(Ektachrome_100_plusCDGreen_I[0]);
		greenI.assign(Ektachrome_100_plusCDGreen_I, Ektachrome_100_plusCDGreen_I + nGreen);
		greenB.assign(Ektachrome_100_plusCDGreen_B, Ektachrome_100_plusCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_100_plusCDBlue_I) / sizeof(Ektachrome_100_plusCDBlue_I[0]);
		blueI.assign(Ektachrome_100_plusCDBlue_I, Ektachrome_100_plusCDBlue_I + nBlue);
		blueB.assign(Ektachrome_100_plusCDBlue_B, Ektachrome_100_plusCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_100CD") {
		const size_t nRed = sizeof(Ektachrome_100CDRed_I) / sizeof(Ektachrome_100CDRed_I[0]);
		redI.assign(Ektachrome_100CDRed_I, Ektachrome_100CDRed_I + nRed);
		redB.assign(Ektachrome_100CDRed_B, Ektachrome_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_100CDGreen_I) / sizeof(Ektachrome_100CDGreen_I[0]);
		greenI.assign(Ektachrome_100CDGreen_I, Ektachrome_100CDGreen_I + nGreen);
		greenB.assign(Ektachrome_100CDGreen_B, Ektachrome_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_100CDBlue_I) / sizeof(Ektachrome_100CDBlue_I[0]);
		blueI.assign(Ektachrome_100CDBlue_I, Ektachrome_100CDBlue_I + nBlue);
		blueB.assign(Ektachrome_100CDBlue_B, Ektachrome_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_320TCD") {
		const size_t nRed = sizeof(Ektachrome_320TCDRed_I) / sizeof(Ektachrome_320TCDRed_I[0]);
		redI.assign(Ektachrome_320TCDRed_I, Ektachrome_320TCDRed_I + nRed);
		redB.assign(Ektachrome_320TCDRed_B, Ektachrome_320TCDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_320TCDGreen_I) / sizeof(Ektachrome_320TCDGreen_I[0]);
		greenI.assign(Ektachrome_320TCDGreen_I, Ektachrome_320TCDGreen_I + nGreen);
		greenB.assign(Ektachrome_320TCDGreen_B, Ektachrome_320TCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_320TCDBlue_I) / sizeof(Ektachrome_320TCDBlue_I[0]);
		blueI.assign(Ektachrome_320TCDBlue_I, Ektachrome_320TCDBlue_I + nBlue);
		blueB.assign(Ektachrome_320TCDBlue_B, Ektachrome_320TCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_400XCD") {
		const size_t nRed = sizeof(Ektachrome_400XCDRed_I) / sizeof(Ektachrome_400XCDRed_I[0]);
		redI.assign(Ektachrome_400XCDRed_I, Ektachrome_400XCDRed_I + nRed);
		redB.assign(Ektachrome_400XCDRed_B, Ektachrome_400XCDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_400XCDGreen_I) / sizeof(Ektachrome_400XCDGreen_I[0]);
		greenI.assign(Ektachrome_400XCDGreen_I, Ektachrome_400XCDGreen_I + nGreen);
		greenB.assign(Ektachrome_400XCDGreen_B, Ektachrome_400XCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_400XCDBlue_I) / sizeof(Ektachrome_400XCDBlue_I[0]);
		blueI.assign(Ektachrome_400XCDBlue_I, Ektachrome_400XCDBlue_I + nBlue);
		blueB.assign(Ektachrome_400XCDBlue_B, Ektachrome_400XCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_64CD") {
		const size_t nRed = sizeof(Ektachrome_64CDRed_I) / sizeof(Ektachrome_64CDRed_I[0]);
		redI.assign(Ektachrome_64CDRed_I, Ektachrome_64CDRed_I + nRed);
		redB.assign(Ektachrome_64CDRed_B, Ektachrome_64CDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_64CDGreen_I) / sizeof(Ektachrome_64CDGreen_I[0]);
		greenI.assign(Ektachrome_64CDGreen_I, Ektachrome_64CDGreen_I + nGreen);
		greenB.assign(Ektachrome_64CDGreen_B, Ektachrome_64CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_64CDBlue_I) / sizeof(Ektachrome_64CDBlue_I[0]);
		blueI.assign(Ektachrome_64CDBlue_I, Ektachrome_64CDBlue_I + nBlue);
		blueB.assign(Ektachrome_64CDBlue_B, Ektachrome_64CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_64TCD") {
		const size_t nRed = sizeof(Ektachrome_64TCDRed_I) / sizeof(Ektachrome_64TCDRed_I[0]);
		redI.assign(Ektachrome_64TCDRed_I, Ektachrome_64TCDRed_I + nRed);
		redB.assign(Ektachrome_64TCDRed_B, Ektachrome_64TCDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_64TCDGreen_I) / sizeof(Ektachrome_64TCDGreen_I[0]);
		greenI.assign(Ektachrome_64TCDGreen_I, Ektachrome_64TCDGreen_I + nGreen);
		greenB.assign(Ektachrome_64TCDGreen_B, Ektachrome_64TCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_64TCDBlue_I) / sizeof(Ektachrome_64TCDBlue_I[0]);
		blueI.assign(Ektachrome_64TCDBlue_I, Ektachrome_64TCDBlue_I + nBlue);
		blueB.assign(Ektachrome_64TCDBlue_B, Ektachrome_64TCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Ektachrome_E100SCD") {
		const size_t nRed = sizeof(Ektachrome_E100SCDRed_I) / sizeof(Ektachrome_E100SCDRed_I[0]);
		redI.assign(Ektachrome_E100SCDRed_I, Ektachrome_E100SCDRed_I + nRed);
		redB.assign(Ektachrome_E100SCDRed_B, Ektachrome_E100SCDRed_B + nRed);
		const size_t nGreen = sizeof(Ektachrome_E100SCDGreen_I) / sizeof(Ektachrome_E100SCDGreen_I[0]);
		greenI.assign(Ektachrome_E100SCDGreen_I, Ektachrome_E100SCDGreen_I + nGreen);
		greenB.assign(Ektachrome_E100SCDGreen_B, Ektachrome_E100SCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Ektachrome_E100SCDBlue_I) / sizeof(Ektachrome_E100SCDBlue_I[0]);
		blueI.assign(Ektachrome_E100SCDBlue_I, Ektachrome_E100SCDBlue_I + nBlue);
		blueB.assign(Ektachrome_E100SCDBlue_B, Ektachrome_E100SCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "F125CD") {
		const size_t nRed = sizeof(F125CDRed_I) / sizeof(F125CDRed_I[0]);
		redI.assign(F125CDRed_I, F125CDRed_I + nRed);
		redB.assign(F125CDRed_B, F125CDRed_B + nRed);
		const size_t nGreen = sizeof(F125CDGreen_I) / sizeof(F125CDGreen_I[0]);
		greenI.assign(F125CDGreen_I, F125CDGreen_I + nGreen);
		greenB.assign(F125CDGreen_B, F125CDGreen_B + nGreen);
		const size_t nBlue = sizeof(F125CDBlue_I) / sizeof(F125CDBlue_I[0]);
		blueI.assign(F125CDBlue_I, F125CDBlue_I + nBlue);
		blueB.assign(F125CDBlue_B, F125CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "F250CD") {
		const size_t nRed = sizeof(F250CDRed_I) / sizeof(F250CDRed_I[0]);
		redI.assign(F250CDRed_I, F250CDRed_I + nRed);
		redB.assign(F250CDRed_B, F250CDRed_B + nRed);
		const size_t nGreen = sizeof(F250CDGreen_I) / sizeof(F250CDGreen_I[0]);
		greenI.assign(F250CDGreen_I, F250CDGreen_I + nGreen);
		greenB.assign(F250CDGreen_B, F250CDGreen_B + nGreen);
		const size_t nBlue = sizeof(F250CDBlue_I) / sizeof(F250CDBlue_I[0]);
		blueI.assign(F250CDBlue_I, F250CDBlue_I + nBlue);
		blueB.assign(F250CDBlue_B, F250CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "F400CD") {
		const size_t nRed = sizeof(F400CDRed_I) / sizeof(F400CDRed_I[0]);
		redI.assign(F400CDRed_I, F400CDRed_I + nRed);
		redB.assign(F400CDRed_B, F400CDRed_B + nRed);
		const size_t nGreen = sizeof(F400CDGreen_I) / sizeof(F400CDGreen_I[0]);
		greenI.assign(F400CDGreen_I, F400CDGreen_I + nGreen);
		greenB.assign(F400CDGreen_B, F400CDGreen_B + nGreen);
		const size_t nBlue = sizeof(F400CDBlue_I) / sizeof(F400CDBlue_I[0]);
		blueI.assign(F400CDBlue_I, F400CDBlue_I + nBlue);
		blueB.assign(F400CDBlue_B, F400CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "FCICD") {
		const size_t nRed = sizeof(FCICDRed_I) / sizeof(FCICDRed_I[0]);
		redI.assign(FCICDRed_I, FCICDRed_I + nRed);
		redB.assign(FCICDRed_B, FCICDRed_B + nRed);
		const size_t nGreen = sizeof(FCICDGreen_I) / sizeof(FCICDGreen_I[0]);
		greenI.assign(FCICDGreen_I, FCICDGreen_I + nGreen);
		greenB.assign(FCICDGreen_B, FCICDGreen_B + nGreen);
		const size_t nBlue = sizeof(FCICDBlue_I) / sizeof(FCICDBlue_I[0]);
		blueI.assign(FCICDBlue_I, FCICDBlue_I + nBlue);
		blueB.assign(FCICDBlue_B, FCICDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Gold_100CD") {
		const size_t nRed = sizeof(Gold_100CDRed_I) / sizeof(Gold_100CDRed_I[0]);
		redI.assign(Gold_100CDRed_I, Gold_100CDRed_I + nRed);
		redB.assign(Gold_100CDRed_B, Gold_100CDRed_B + nRed);
		const size_t nGreen = sizeof(Gold_100CDGreen_I) / sizeof(Gold_100CDGreen_I[0]);
		greenI.assign(Gold_100CDGreen_I, Gold_100CDGreen_I + nGreen);
		greenB.assign(Gold_100CDGreen_B, Gold_100CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Gold_100CDBlue_I) / sizeof(Gold_100CDBlue_I[0]);
		blueI.assign(Gold_100CDBlue_I, Gold_100CDBlue_I + nBlue);
		blueB.assign(Gold_100CDBlue_B, Gold_100CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Gold_200CD") {
		const size_t nRed = sizeof(Gold_200CDRed_I) / sizeof(Gold_200CDRed_I[0]);
		redI.assign(Gold_200CDRed_I, Gold_200CDRed_I + nRed);
		redB.assign(Gold_200CDRed_B, Gold_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Gold_200CDGreen_I) / sizeof(Gold_200CDGreen_I[0]);
		greenI.assign(Gold_200CDGreen_I, Gold_200CDGreen_I + nGreen);
		greenB.assign(Gold_200CDGreen_B, Gold_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Gold_200CDBlue_I) / sizeof(Gold_200CDBlue_I[0]);
		blueI.assign(Gold_200CDBlue_I, Gold_200CDBlue_I + nBlue);
		blueB.assign(Gold_200CDBlue_B, Gold_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Kodachrome_200CD") {
		const size_t nRed = sizeof(Kodachrome_200CDRed_I) / sizeof(Kodachrome_200CDRed_I[0]);
		redI.assign(Kodachrome_200CDRed_I, Kodachrome_200CDRed_I + nRed);
		redB.assign(Kodachrome_200CDRed_B, Kodachrome_200CDRed_B + nRed);
		const size_t nGreen = sizeof(Kodachrome_200CDGreen_I) / sizeof(Kodachrome_200CDGreen_I[0]);
		greenI.assign(Kodachrome_200CDGreen_I, Kodachrome_200CDGreen_I + nGreen);
		greenB.assign(Kodachrome_200CDGreen_B, Kodachrome_200CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Kodachrome_200CDBlue_I) / sizeof(Kodachrome_200CDBlue_I[0]);
		blueI.assign(Kodachrome_200CDBlue_I, Kodachrome_200CDBlue_I + nBlue);
		blueB.assign(Kodachrome_200CDBlue_B, Kodachrome_200CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Kodachrome_25CD") {
		const size_t nRed = sizeof(Kodachrome_25CDRed_I) / sizeof(Kodachrome_25CDRed_I[0]);
		redI.assign(Kodachrome_25CDRed_I, Kodachrome_25CDRed_I + nRed);
		redB.assign(Kodachrome_25CDRed_B, Kodachrome_25CDRed_B + nRed);
		const size_t nGreen = sizeof(Kodachrome_25CDGreen_I) / sizeof(Kodachrome_25CDGreen_I[0]);
		greenI.assign(Kodachrome_25CDGreen_I, Kodachrome_25CDGreen_I + nGreen);
		greenB.assign(Kodachrome_25CDGreen_B, Kodachrome_25CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Kodachrome_25CDBlue_I) / sizeof(Kodachrome_25CDBlue_I[0]);
		blueI.assign(Kodachrome_25CDBlue_I, Kodachrome_25CDBlue_I + nBlue);
		blueB.assign(Kodachrome_25CDBlue_B, Kodachrome_25CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Kodachrome_64CD") {
		const size_t nRed = sizeof(Kodachrome_64CDRed_I) / sizeof(Kodachrome_64CDRed_I[0]);
		redI.assign(Kodachrome_64CDRed_I, Kodachrome_64CDRed_I + nRed);
		redB.assign(Kodachrome_64CDRed_B, Kodachrome_64CDRed_B + nRed);
		const size_t nGreen = sizeof(Kodachrome_64CDGreen_I) / sizeof(Kodachrome_64CDGreen_I[0]);
		greenI.assign(Kodachrome_64CDGreen_I, Kodachrome_64CDGreen_I + nGreen);
		greenB.assign(Kodachrome_64CDGreen_B, Kodachrome_64CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Kodachrome_64CDBlue_I) / sizeof(Kodachrome_64CDBlue_I[0]);
		blueI.assign(Kodachrome_64CDBlue_I, Kodachrome_64CDBlue_I + nBlue);
		blueB.assign(Kodachrome_64CDBlue_B, Kodachrome_64CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Max_Zoom_800CD") {
		const size_t nRed = sizeof(Max_Zoom_800CDRed_I) / sizeof(Max_Zoom_800CDRed_I[0]);
		redI.assign(Max_Zoom_800CDRed_I, Max_Zoom_800CDRed_I + nRed);
		redB.assign(Max_Zoom_800CDRed_B, Max_Zoom_800CDRed_B + nRed);
		const size_t nGreen = sizeof(Max_Zoom_800CDGreen_I) / sizeof(Max_Zoom_800CDGreen_I[0]);
		greenI.assign(Max_Zoom_800CDGreen_I, Max_Zoom_800CDGreen_I + nGreen);
		greenB.assign(Max_Zoom_800CDGreen_B, Max_Zoom_800CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Max_Zoom_800CDBlue_I) / sizeof(Max_Zoom_800CDBlue_I[0]);
		blueI.assign(Max_Zoom_800CDBlue_I, Max_Zoom_800CDBlue_I + nBlue);
		blueB.assign(Max_Zoom_800CDBlue_B, Max_Zoom_800CDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_100TCD") {
		const size_t nRed = sizeof(Portra_100TCDRed_I) / sizeof(Portra_100TCDRed_I[0]);
		redI.assign(Portra_100TCDRed_I, Portra_100TCDRed_I + nRed);
		redB.assign(Portra_100TCDRed_B, Portra_100TCDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_100TCDGreen_I) / sizeof(Portra_100TCDGreen_I[0]);
		greenI.assign(Portra_100TCDGreen_I, Portra_100TCDGreen_I + nGreen);
		greenB.assign(Portra_100TCDGreen_B, Portra_100TCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_100TCDBlue_I) / sizeof(Portra_100TCDBlue_I[0]);
		blueI.assign(Portra_100TCDBlue_I, Portra_100TCDBlue_I + nBlue);
		blueB.assign(Portra_100TCDBlue_B, Portra_100TCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_160NCCD") {
		const size_t nRed = sizeof(Portra_160NCCDRed_I) / sizeof(Portra_160NCCDRed_I[0]);
		redI.assign(Portra_160NCCDRed_I, Portra_160NCCDRed_I + nRed);
		redB.assign(Portra_160NCCDRed_B, Portra_160NCCDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_160NCCDGreen_I) / sizeof(Portra_160NCCDGreen_I[0]);
		greenI.assign(Portra_160NCCDGreen_I, Portra_160NCCDGreen_I + nGreen);
		greenB.assign(Portra_160NCCDGreen_B, Portra_160NCCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_160NCCDBlue_I) / sizeof(Portra_160NCCDBlue_I[0]);
		blueI.assign(Portra_160NCCDBlue_I, Portra_160NCCDBlue_I + nBlue);
		blueB.assign(Portra_160NCCDBlue_B, Portra_160NCCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_160VCCD") {
		const size_t nRed = sizeof(Portra_160VCCDRed_I) / sizeof(Portra_160VCCDRed_I[0]);
		redI.assign(Portra_160VCCDRed_I, Portra_160VCCDRed_I + nRed);
		redB.assign(Portra_160VCCDRed_B, Portra_160VCCDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_160VCCDGreen_I) / sizeof(Portra_160VCCDGreen_I[0]);
		greenI.assign(Portra_160VCCDGreen_I, Portra_160VCCDGreen_I + nGreen);
		greenB.assign(Portra_160VCCDGreen_B, Portra_160VCCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_160VCCDBlue_I) / sizeof(Portra_160VCCDBlue_I[0]);
		blueI.assign(Portra_160VCCDBlue_I, Portra_160VCCDBlue_I + nBlue);
		blueB.assign(Portra_160VCCDBlue_B, Portra_160VCCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_400NCCD") {
		const size_t nRed = sizeof(Portra_400NCCDRed_I) / sizeof(Portra_400NCCDRed_I[0]);
		redI.assign(Portra_400NCCDRed_I, Portra_400NCCDRed_I + nRed);
		redB.assign(Portra_400NCCDRed_B, Portra_400NCCDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_400NCCDGreen_I) / sizeof(Portra_400NCCDGreen_I[0]);
		greenI.assign(Portra_400NCCDGreen_I, Portra_400NCCDGreen_I + nGreen);
		greenB.assign(Portra_400NCCDGreen_B, Portra_400NCCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_400NCCDBlue_I) / sizeof(Portra_400NCCDBlue_I[0]);
		blueI.assign(Portra_400NCCDBlue_I, Portra_400NCCDBlue_I + nBlue);
		blueB.assign(Portra_400NCCDBlue_B, Portra_400NCCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_400VCCD") {
		const size_t nRed = sizeof(Portra_400VCCDRed_I) / sizeof(Portra_400VCCDRed_I[0]);
		redI.assign(Portra_400VCCDRed_I, Portra_400VCCDRed_I + nRed);
		redB.assign(Portra_400VCCDRed_B, Portra_400VCCDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_400VCCDGreen_I) / sizeof(Portra_400VCCDGreen_I[0]);
		greenI.assign(Portra_400VCCDGreen_I, Portra_400VCCDGreen_I + nGreen);
		greenB.assign(Portra_400VCCDGreen_B, Portra_400VCCDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_400VCCDBlue_I) / sizeof(Portra_400VCCDBlue_I[0]);
		blueI.assign(Portra_400VCCDBlue_I, Portra_400VCCDBlue_I + nBlue);
		blueB.assign(Portra_400VCCDBlue_B, Portra_400VCCDBlue_B + nBlue);
		color = true;
	} else if (filmName == "Portra_800CD") {
		const size_t nRed = sizeof(Portra_800CDRed_I) / sizeof(Portra_800CDRed_I[0]);
		redI.assign(Portra_800CDRed_I, Portra_800CDRed_I + nRed);
		redB.assign(Portra_800CDRed_B, Portra_800CDRed_B + nRed);
		const size_t nGreen = sizeof(Portra_800CDGreen_I) / sizeof(Portra_800CDGreen_I[0]);
		greenI.assign(Portra_800CDGreen_I, Portra_800CDGreen_I + nGreen);
		greenB.assign(Portra_800CDGreen_B, Portra_800CDGreen_B + nGreen);
		const size_t nBlue = sizeof(Portra_800CDBlue_I) / sizeof(Portra_800CDBlue_I[0]);
		blueI.assign(Portra_800CDBlue_I, Portra_800CDBlue_I + nBlue);
		blueB.assign(Portra_800CDBlue_B, Portra_800CDBlue_B + nBlue);
		color = true;
	} else
		return false;

	return true;
}


#include <boost/rational.hpp>

int main()
{
  boost::rational<int> half(1,2);
  if (boost::rational_cast<double>(half) != 0.5) {
    return 1;
  }

  return 0;
}


#define BOOST_TEST_MODULE SolutionTest

#include "solution.hpp"

//#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(SolutionSuite)

BOOST_AUTO_TEST_CASE(PlainTest1)
{
    int numRows = 5;
    vector<vector<int>> results = Solution().generate(numRows);

    vector<vector<int>> expected{
        {1},
        {1,1},
        {1,2,1},
        {1,3,3,1},
        {1,4,6,4,1}
    };

    BOOST_CHECK_EQUAL(results.size(), expected.size());
    for (int i = 0; i < results.size(); i++)
        BOOST_CHECK_EQUAL_COLLECTIONS(results[i].begin(), results[i].end(), expected[i].begin(), expected[i].end());
}

BOOST_AUTO_TEST_CASE(PlainTest2)
{
    int numRows = 0;
    vector<vector<int>> results = Solution().generate(numRows);

    vector<vector<int>> expected{};

    BOOST_CHECK_EQUAL(results.size(), expected.size());
    for (int i = 0; i < results.size(); i++)
        BOOST_CHECK_EQUAL_COLLECTIONS(results[i].begin(), results[i].end(), expected[i].begin(), expected[i].end());
}

BOOST_AUTO_TEST_CASE(PlainTest3)
{
    int numRows = 1;
    vector<vector<int>> results = Solution().generate(numRows);

    vector<vector<int>> expected{
        {1}
    };

    BOOST_CHECK_EQUAL(results.size(), expected.size());
    for (int i = 0; i < results.size(); i++)
        BOOST_CHECK_EQUAL_COLLECTIONS(results[i].begin(), results[i].end(), expected[i].begin(), expected[i].end());
}

BOOST_AUTO_TEST_CASE(PlainTest4)
{
    int numRows = 2;
    vector<vector<int>> results = Solution().generate(numRows);

    vector<vector<int>> expected{
        {1},
        {1,1}
    };

    BOOST_CHECK_EQUAL(results.size(), expected.size());
    for (int i = 0; i < results.size(); i++)
        BOOST_CHECK_EQUAL_COLLECTIONS(results[i].begin(), results[i].end(), expected[i].begin(), expected[i].end());
}

BOOST_AUTO_TEST_SUITE_END()


#include <Eigen/Eigen>
#include <array>
#include <utility>

#include "core/utils/log.h"

#include "DFISEParser.hpp"
#include "octree/Octree.hpp"

namespace mesh_converter {
    /**
     * @brief Tetrahedron class for the 3D barycentric interpolation
     */
    class MeshElement {
    public:
        /**
         * @brief Default constructor
         */
        MeshElement() = delete;

        /**
         * @brief Constructor using a vector to store the index of the nodes and a list for the mesh and electric field
         * points
         * @param dimension Dimension of the nodes
         * @param index List of indices of the points for debugging purposes
         * @param vertices_tetrahedron List containing 4 mesh node points
         * @param efield_vertices_tetrahedron List containing 4 points with the component of the electric field at the mesh
         * node
         */
        MeshElement(size_t dimension,
                    const std::array<Point, 4>& vertices_tetrahedron,
                    const std::array<Point, 4>& efield_vertices_tetrahedron)
            : dimension_(dimension), vertices_(vertices_tetrahedron), e_field_(efield_vertices_tetrahedron) {
            calculate_volume();
        }

        /**
         * @brief Checks if the tetrahedron is valid for the interpolation
         * @param volume_cut Threshold for the minimum tetrahedron volume
         * @param qp Desired point for the interpolation
         */
        bool validElement(double volume_cut, Point& qp) const;

        /**
         * @brief Barycentric interpolation implementation
         * @param qp Point where the interpolation is being done
         */
        Point getObservable(Point& qp) const;

        /**
         * @brief Print tetrahedron information for debugging
         * @return String describing the mesh element
         */
        std::string print(Point& qp) const;

    private:
        /**
         * @brief Get the distance from vertex to a random point
         * @param index Vertex for which the distance will be calculated
         * @param qp Point from which will be calculated the distance
         */
        double get_distance(size_t index, Point& qp) const;

        void calculate_volume();

        double get_sub_volume(size_t index, Point& p) const;

        size_t dimension_{3};
        std::array<Point, 4> vertices_{};
        std::array<Point, 4> e_field_{};

        double volume_{0};
    };

    /**
     * @brief Functor class to be used by the for_each_combination algorithm.
     *
     * It receives pointers to the point and field vectors and its operator() member is called for every combination of
     * results found. It constructs a new MeshElement, checks for its validity and returns true to stop the iteration and
     * false to continue to the next combination of results.
     */
    class Combination {
        const std::vector<Point>* grid_;
        const std::vector<Point>* field_;
        Point reference_;
        Point result_;
        bool valid_{};
        double cut_;

        std::array<Point, 4> grid_elements;
        std::array<Point, 4> field_elements;

    public:
        /**
         * @brief constructor for functor
         * @param  points     Pointer to mesh point vector
         * @param  field      Pointer to field vector
         * @param  q          Reference point to interpolate at
         * @param  volume_cut Volume cut to be used
         */
        explicit Combination(const std::vector<Point>* points,
                             const std::vector<Point>* field,
                             const Point& q,
                             const double volume_cut)
            : grid_(points), field_(field), reference_(q), cut_(volume_cut) {}

        /**
         * @brief Operator called for each permutation
         * @param begin First iterator element of current combination
         * @param end Last iterator element of current combination
         * @return True if valid mesh element was found, stops the loop, or False if not found, continuing loop through
         * combinations.
         */
        template <class It> bool operator()(It begin, It end) {
            // Dimensionality is number of iterator elements minus one:
            size_t dimensions = static_cast<size_t>(end - begin) - 1;
            size_t idx = 0;
            for(; begin < end; begin++) {
                grid_elements[idx] = (*grid_)[*begin];
                field_elements[idx++] = (*field_)[*begin];
            }

            LOG(TRACE) << "Constructing element with dim " << dimensions << " at " << reference_;
            MeshElement element(dimensions, grid_elements, field_elements);
            valid_ = element.validElement(cut_, reference_);
            if(valid_) {
                LOG(DEBUG) << element.print(reference_);
                result_ = element.getObservable(reference_);
            }

            return valid_; // Don't break out of the loop if element is invalid
        }

        /**
         * @brief Member to check for validity of returned mesh element:
         * @return True if element is valid and can be used, False if no valid element was found but iteration ended without
         * result
         */
        bool valid() const { return valid_; }

        /**
         * @brief Member to retrieve interpolated result from valid mesh element
         * @return Interpolated result from valid mesh element
         */
        Point result() const { return result_; }
    };

} // namespace mesh_converter


// Copyright (c) 2021 Harderthan
//
// 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.

#ifndef NAV2_MPC_CONTROLLER__MPC_CONTROLLER_HPP_
#define NAV2_MPC_CONTROLLER__MPC_CONTROLLER_HPP_

#include <algorithm>
#include <memory>
#include <string>
#include <vector>

#include "geometry_msgs/msg/pose2_d.hpp"
#include "nav2_core/controller.hpp"
#include "nav2_util/odometry_utils.hpp"
#include "pluginlib/class_list_macros.hpp"
#include "pluginlib/class_loader.hpp"
#include "rclcpp/rclcpp.hpp"

#include "mpc_core.hpp"
#include <Eigen/Core>
#include <Eigen/QR>

namespace nav2_mpc_controller {
/**
 * @class nav2_mpc_controller::MPCController
 * @brief mpc controller plugin
 */
class MPCController : public nav2_core::Controller {
public:
  /**
   * @brief Constructor for nav2_mpc_controller::MPCController
   */
  MPCController() = default;

  /**
   * @brief Destrructor for nav2_mpc_controller::MPCController
   */
  ~MPCController() override = default;

  /**
   * @brief Configure controller state machine
   * @param parent WeakPtr to node
   * @param name Name of plugin
   * @param tf TF buffer
   * @param costmap_ros Costmap2DROS object of environment
   */
  void configure(const rclcpp_lifecycle::LifecycleNode::SharedPtr &parent,
                 std::string name, const std::shared_ptr<tf2_ros::Buffer> &tf,
                 const std::shared_ptr<nav2_costmap_2d::Costmap2DROS>
                     &costmap_ros) override;

  /**
   * @brief Cleanup controller state machine
   */
  void cleanup() override;

  /**
   * @brief Activate controller state machine
   */
  void activate() override;

  /**
   * @brief Deactivate controller state machine
   */
  void deactivate() override;

  /**
   * @brief Compute the best command given the current pose and velocity, with
   * possible debug information
   *
   * Same as above computeVelocityCommands, but with debug results.
   * If the results pointer is not null, additional information about the twists
   * evaluated will be in results after the call.
   *
   * @param pose      Current robot pose
   * @param velocity  Current robot velocity
   * @param results   Output param, if not NULL, will be filled in with full
   * evaluation results
   * @return          Best command
   */
  geometry_msgs::msg::TwistStamped
  computeVelocityCommands(const geometry_msgs::msg::PoseStamped &pose,
                          const geometry_msgs::msg::Twist &velocity) override;

  /**
   * @brief nav2_core setPlan - Sets the global plan
   * @param path The global plan
   */
  void setPlan(const nav_msgs::msg::Path &path) override;

protected:
  Eigen::VectorXd& impCoefficients(const geometry_msgs::msg::PoseStamped &pose, const nav_msgs::msg::Path& global_plan);
  double impThetaError(double theta, const Eigen::VectorXd& coeffs,
                       int sample_size, int sample_ratio);
  void convertCoordinateSystem(Eigen::VectorXd& x_veh, Eigen::VectorXd y_veh);
  double polyeval(Eigen::VectorXd coeffs, double x);
  Eigen::VectorXd polyfit(Eigen::VectorXd xvals, Eigen::VectorXd yvals,
                          int order);

  std::shared_ptr<tf2_ros::Buffer> tf_;
  std::string plugin_name_;
  std::shared_ptr<nav2_costmap_2d::Costmap2DROS> costmap_ros_;
  nav2_costmap_2d::Costmap2D *costmap_;
  rclcpp::Logger logger_{rclcpp::get_logger("MPCController")};

  MPCCore _mpc;
  map<string, double> _mpc_params;
  double mpc_steps_, ref_cte_, ref_etheta_, ref_vel_, w_cte_, w_etheta_, w_vel_,
      w_angvel_, w_accel_, w_angvel_d_, w_accel_d_, max_angvel_, max_throttle_,
      bound_value_;

  double dt_, w_, throttle_, speed_, max_speed_;
  double pathLength_, goalRadius_, waypointsDist_;
  int controller_freq_, downSampling_, thread_numbers_;
  bool goal_received_, goal_reached_, path_computed_, pub_twist_flag_,
      debug_info_, delay_mode_;

  tf2::Duration transform_tolerance_;
  double control_duration_;
  bool use_cost_regulated_linear_velocity_scaling_;
  double inflation_cost_scaling_factor_;

  nav_msgs::msg::Path global_plan_;
  std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::Path>>
      global_path_pub_;
  std::shared_ptr<
      rclcpp_lifecycle::LifecyclePublisher<geometry_msgs::msg::PointStamped>>
      carrot_pub_;
  std::shared_ptr<rclcpp_lifecycle::LifecyclePublisher<nav_msgs::msg::Path>>
      carrot_arc_pub_;
};
} // namespace nav2_mpc_controller

#endif // NAV2_MPC_CONTROLLER__MPC_CONTROLLER_HPP_


/*
 * odeint_rk4_phase_lattice.cpp
 *
 * Copyright 2011 Mario Mulansky
 * Copyright 2012 Karsten Ahnert
 *
 * Distributed under 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)
 */

#include <cmath>

#include <boost/array.hpp>

#include <boost/numeric/odeint/stepper/runge_kutta4_classic.hpp>
#include <boost/numeric/odeint/stepper/runge_kutta4.hpp>
#include <boost/numeric/odeint/algebra/array_algebra.hpp>

#include "rk_performance_test_case.hpp"

#include "phase_lattice.hpp"

const size_t N = 1024;

typedef boost::array< double , N > state_type;
typedef boost::numeric::odeint::runge_kutta4_classic< state_type , double , state_type , double , boost::numeric::odeint::array_algebra> rk4_odeint_type;

class odeint_wrapper
{
public:
    void reset_init_cond()
    {
        for( size_t i = 0 ; i<N ; ++i )
            m_x[i] = 2.0*3.1415927*rand() / RAND_MAX;
        m_t = 0.0;
    }

    inline void do_step( const double dt )
    {
        m_stepper.do_step( phase_lattice<N>() , m_x , m_t , dt );
        //m_t += dt;
    }

    double state( const size_t i ) const
    { return m_x[i]; }

private:
    state_type m_x;
    double m_t;
    rk4_odeint_type m_stepper;
};

int main()
{
    srand( 12312354 );

    odeint_wrapper stepper;

    run( stepper , 10000 , 1E-6 );
}


#ifndef OITK_CHESSBOARDSAMPLECONSENSUS_H
#define OITK_CHESSBOARDSAMPLECONSENSUS_H

#include <cmath>
#include <Eigen/Core>
#include <pcl/sample_consensus/sac_model.h>

namespace oitk
{
    inline void compressChessboardModel(Eigen::VectorXf &model,
        const Eigen::Vector3f &plane, const Eigen::Vector3f &origin, const Eigen::Vector3f &xdirection)
    {
        model.resize(6);
        model.head(3) = origin;
        // model.data()[3] = plane.data()[0] / plane.data()[2];
        model[3] = plane[0] / plane[2];
        model[4] = plane[1] / plane[2];

        Eigen::Vector3f absolute;
        absolute << 1, 0, 0;
        Eigen::Vector3f globalxdir = plane.cross(absolute);
        Eigen::Vector3f globalydir = plane.cross(globalxdir);

        model[5] = atan2f(xdirection.dot(globalydir) / globalydir.norm(),
                          xdirection.dot(globalxdir) / globalxdir.norm());
    }

    inline void decompressChessboardModel(const Eigen::VectorXf &model,
        Eigen::Vector3f &plane, Eigen::Vector3f &origin, Eigen::Vector3f &xdirection)
    {
        origin = model.head(3);
        plane[0] = model[3];
        plane[1] = model[4];
        plane[2] = 1;
        plane /= plane.norm();

        Eigen::Vector3f absolute;
        absolute << 1, 0, 0;
        Eigen::Vector3f globalxdir = plane.cross(absolute);
        Eigen::Vector3f globalydir = plane.cross(globalxdir);
        xdirection = globalxdir * cos(model[5]) + globalydir * sin(model[5]);
        xdirection /= xdirection.norm();
    }

    template <typename PointT>
    class SampleConsensusChessboard : public pcl::SampleConsensusModel<PointT>
    {
    public:
        typedef typename pcl::SampleConsensusModel<PointT>::PointCloud PointCloud;
        typedef typename pcl::SampleConsensusModel<PointT>::PointCloudPtr PointCloudPtr;
        typedef typename pcl::SampleConsensusModel<PointT>::PointCloudConstPtr PointCloudConstPtr;
        typedef boost::shared_ptr<SampleConsensusChessboard> Ptr;

    private:
        int _pattern_size;
        float _board_size, _edge_size, _rand_rate, _hole_size;
        Eigen::Vector3f _plane_coefficients;

        virtual bool isSampleGood(const std::vector<int> &samples) const override
        {
            if (samples.size() < 2)
                return false;

            // Get the values at the two points
            const Eigen::Map<const Eigen::Vector3f> p0 = input_->points[samples[0]].getVector3fMap();
            const Eigen::Map<const Eigen::Vector3f> p1 = input_->points[samples[1]].getVector3fMap();
            Eigen::Vector3f segment = (p1 - p0);

            constexpr float sq2 = 1.5; // a bit larger than sqrt(2)
            if (segment.norm() > _board_size * sq2)
                return false;

            return true;
        }

        inline float rndHalf()
        {
            constexpr int mod = 1024;
            return (rnd() % mod - (mod >> 1)) / (float)mod;
        }

        inline float distanceToHoleEdge(const float &relative_coord)
        {
            // XXX: Assert relative_coord is positive
            float times = relative_coord / (_hole_size * 2)
                + ((_pattern_size / 2 + 1) % 2 == 0 ? 0.25 : -0.25);
            times = times - (int)times;
            if (times < 0.25)
                return times * _hole_size * 2;
            else if (times < 0.5)
                return (0.5 - times) * _hole_size * 2;
            else return 0;
        }

        inline float distanceToBoard(const float &board_x, const float &board_y)
        {
            // XXX: Assert board_x and board_y is positive
            float outer_edge = _board_size / 2;
            float dx = board_x - outer_edge;
            float dy = board_y - outer_edge;
            if (dx > 0)
            {
                if (dy > 0)
                    return sqrt(dx * dx + dy * dy);
                else
                    return dx;
            }
            else if (board_y > outer_edge)
                return dy;
            else
                return std::min(distanceToHoleEdge(board_x),
                           distanceToHoleEdge(board_y));
        }

    protected:
        using pcl::SampleConsensusModel<PointT>::model_name_;
        using pcl::SampleConsensusModel<PointT>::input_;
        using pcl::SampleConsensusModel<PointT>::indices_;
        using pcl::SampleConsensusModel<PointT>::error_sqr_dists_;
        using pcl::SampleConsensusModel<PointT>::sample_size_;
        using pcl::SampleConsensusModel<PointT>::model_size_;
        using pcl::SampleConsensusModel<PointT>::rnd;

    private:
        /** \brief Functor for the optimization function */
        struct OptimizationFunctor : pcl::Functor<float>
        {
            OptimizationFunctor(int n_data_points, SampleConsensusChessboard<PointT> *model) :
                pcl::Functor<float>(n_data_points), model_(model) {}

            int operator() (const Eigen::VectorXf &x, Eigen::VectorXf &fvec) const
            {
                // TODO: Implement
            }

            SampleConsensusChessboard<PointT> *model_;
        };

    public:
        SampleConsensusChessboard(const PointCloudConstPtr &cloud,
            int pattern_size, float board_size, float edge_size,
            Eigen::VectorXf plane_coeffs, bool random = false)
            : pcl::SampleConsensusModel<PointT>(cloud, random),
            _pattern_size(pattern_size), _board_size(board_size), _edge_size(edge_size),
            _hole_size((board_size - 2 * edge_size) / (pattern_size - 1)), _rand_rate(1)
        {
            model_name_ = "SampleConsensusChessboard";
            sample_size_ = 2;
            model_size_ = 6;

            if (plane_coeffs.size() < 3)
                cerr << "Wrong plane coefficients input. Please estimate the plane before finding the board!" << endl;
            for (int i = 0; i < 3; i++)
                _plane_coefficients[i] = plane_coeffs[i];
            _plane_coefficients /= _plane_coefficients.norm();
        }

        SampleConsensusChessboard(const PointCloudConstPtr &cloud, const std::vector<int> &indices,
            int pattern_size, float board_size, float edge_size,
            Eigen::VectorXf plane_coeffs, bool random = false)
            : pcl::SampleConsensusModel<PointT>(cloud, indices, random),
            _pattern_size(pattern_size), _board_size(board_size), _edge_size(edge_size),
            _hole_size((board_size - 2 * edge_size) / (pattern_size - 1)), _rand_rate(1)
        {
            model_name_ = "SampleConsensusChessboard";
            sample_size_ = 2;
            model_size_ = 6;

            if (plane_coeffs.size() < 3)
                cerr << "Wrong plane coefficients input. Please estimate the plane before finding the board!" << endl;
            for (int i = 0; i < 3; i++)
                _plane_coefficients[i] = plane_coeffs[i];
            _plane_coefficients /= _plane_coefficients.norm();
        }

        virtual bool computeModelCoefficients(const std::vector<int> &samples,
            Eigen::VectorXf &model_coefficients) override
        {
            pcl::Array3fMapConst p0 = input_->points[samples[0]].getArray3fMap();
            pcl::Array3fMapConst p1 = input_->points[samples[1]].getArray3fMap();

            Eigen::Array3f origin = (p0 + p1) / 2;
            origin += (p0 - origin) * rndHalf() * _rand_rate;
            Eigen::Vector3f diameter = p1 - p0;
            Eigen::Vector3f adiameter = _plane_coefficients.cross(diameter);

            Eigen::Vector3f xdirection = diameter / diameter.norm() * rndHalf() * _rand_rate
                                       + adiameter / adiameter.norm() * rndHalf() * _rand_rate;
            xdirection /= xdirection.norm();

            compressChessboardModel(model_coefficients, _plane_coefficients, origin, xdirection);
            return true;
        }

        virtual void optimizeModelCoefficients(const std::vector<int> &inliers,
            const Eigen::VectorXf &model_coefficients,
            Eigen::VectorXf &optimized_coefficients) override
        {
            // TODO: Implement
            cerr << "Not Implemented yet" << endl;
        }

        virtual void getDistancesToModel(const Eigen::VectorXf &model_coefficients,
            std::vector<double> &distances) override
        {
            distances.resize(indices_->size());

            Eigen::Vector3f plane_normal, origin, xdirection, ydirection;
            decompressChessboardModel(model_coefficients, plane_normal, origin, xdirection);
            ydirection = plane_normal.cross(xdirection);

            for (int idx = 0; idx < indices_->size(); idx++)
            {
                // Project to plane
                Eigen::Vector3f direct_dist =
                    input_->points[(*indices_)[idx]].getArray3fMap() - origin.array();
                float zdist = direct_dist.dot(plane_normal);
                direct_dist -= zdist * plane_normal;
                float board_x = abs(direct_dist.dot(xdirection));
                float board_y = abs(direct_dist.dot(ydirection));

                // Calculate distance
                float bdist = distanceToBoard(board_x, board_y);
                // distances[idx] = sqrt(bdist * bdist + zdist * zdist);
                distances[idx] = bdist; // Just consider the distance in board
            }
        }

        virtual void selectWithinDistance(const Eigen::VectorXf &model_coefficients,
            const double threshold, std::vector<int> &inliers) override
        {
            std::vector<double> distances;
            getDistancesToModel(model_coefficients, distances);

            int counter = 0;
            inliers.resize(indices_->size());
            error_sqr_dists_.resize(indices_->size());
            for (int idx = 0;idx < distances.size(); idx++)
            {
                if (distances[idx] >= threshold) continue;
                inliers[counter] = (*indices_)[idx];
                error_sqr_dists_[counter] = distances[idx];
                counter++;
            }
            inliers.resize(counter);
            error_sqr_dists_.resize(counter);
        }

        virtual int countWithinDistance(const Eigen::VectorXf &model_coefficients,
            const double threshold) override
        {
            std::vector<double> distances;
            getDistancesToModel(model_coefficients, distances);

            int counter = 0;
            for (double distance : distances)
                if (distance < threshold) counter++;
            return counter;
        }

        virtual void projectPoints(const std::vector<int> &inliers,
            const Eigen::VectorXf &model_coefficients,
            PointCloud &projected_points,
            bool copy_data_fields = true) override
        {
            // TODO: Implement
            cerr << "Not Implemented yet" << endl;
        }

        virtual bool doSamplesVerifyModel(const std::set<int> &indices,
            const Eigen::VectorXf &model_coefficients,
            const double threshold) override
        {
            // TODO: Implement
            cerr << "Not Implemented yet" << endl;
            return false;
        }

        virtual pcl::SacModel getModelType() const override
        {
            cerr << "SampleConsensusChessboard is not a sample consensus type built in pcl!" << endl;
            return pcl::SACMODEL_PLANE;
        }

        /** \brief Set the randomization range of the board location
          * \param[in] rate the randomization range (0~1)
          */
        inline void setRandomRate(const float &rate)
        {
            _rand_rate = rate;
        }

        /** \brief Get the randomization range of the board location
          * \param[out] rate the randomization range (0~1)
          */
        inline void getRandomRate(float &rate)
        {
            rate = _rand_rate;
        }
    };
}

#endif // OITK_CHESSBOARDSAMPLECONSENSUS_H


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

  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)
*/
//==================================================================================================
#include <boost/simd/pack.hpp>
#include <boost/simd/function/arg.hpp>
#include <boost/simd/function/bits.hpp>
#include <boost/simd/meta/cardinal_of.hpp>
#include <simd_test.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>

template <typename T, std::size_t N, typename Env>
void test(Env& $)
{
  namespace bs = boost::simd;
  using p_t = bs::pack<T, N>;

  T a1[N], b[N];
  for(std::size_t i = 0; i < N; ++i)
  {
    a1[i] = (i%2) ? T(i) : T(-i);
    b[i] = bs::arg(a1[i]) ;
  }
  p_t aa1(&a1[0], &a1[0]+N);
  p_t bb (&b[0], &b[0]+N);
  STF_IEEE_EQUAL(bs::arg(aa1), bb);
}

STF_CASE_TPL("Check arg on pack" , STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  using p_t = bs::pack<T>;
  static const std::size_t N = bs::cardinal_of<p_t>::value;
  test<T, N>($);
  test<T, N/2>($);
  test<T, N*2>($);
}

STF_CASE_TPL (" arg real",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  using bs::arg;
 using p_t = bs::pack<T>;
 using r_t = decltype(arg(p_t()));

  // return type conformity test
  STF_TYPE_IS(r_t, p_t);

  // specific values tests
#ifndef BOOST_SIMD_NO_INVALIDS
  STF_EQUAL(arg(bs::Inf<p_t>()), bs::Zero<r_t>());
  STF_EQUAL(arg(bs::Minf<p_t>()), bs::Pi<r_t>());
  STF_IEEE_EQUAL(bs::pedantic_(arg)(bs::Nan<p_t>()), bs::Nan<r_t>());
  STF_IEEE_EQUAL(arg(bs::Nan<p_t>()), bs::Pi<r_t>());
#endif
  STF_EQUAL(arg(bs::Mone<p_t>()), bs::Pi<r_t>());
  STF_EQUAL(arg(bs::One<p_t>()), bs::Zero<r_t>());
  STF_EQUAL(arg(bs::Zero<p_t>()), bs::Zero<r_t>());
} // end of test for floating_


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <nt2/trigonometric/include/functions/rem_pio2_medium.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/simd/sdk/simd/io.hpp>
#include <boost/fusion/include/std_pair.hpp>
#include <nt2/include/functions/splat.hpp>
#include <nt2/include/constants/pio_2.hpp>
#include <nt2/include/constants/pio_4.hpp>
#include <nt2/include/constants/half.hpp>

#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>

NT2_TEST_CASE_TPL ( rem_pio2_medium_real__1_0,  NT2_SIMD_REAL_TYPES)
{
  using nt2::rem_pio2_medium;
  using nt2::tag::rem_pio2_medium_;
  using boost::simd::native;
  typedef typename nt2::meta::as_integer<T>::type           iT;
  typedef native<T,BOOST_SIMD_DEFAULT_EXTENSION>            vT;
  typedef native<iT,BOOST_SIMD_DEFAULT_EXTENSION>          viT;
  typedef std::pair<viT, vT>  r_t;

  {
    r_t res = rem_pio2_medium(nt2::Pio_2<vT>());
    vT r1;
    NT2_TEST_EQUAL( rem_pio2_medium(nt2::Pio_2<vT>(), r1), nt2::One<viT>());
    NT2_TEST_ULP_EQUAL( r1, nt2::Zero<vT>(), 0.5);
    NT2_TEST_EQUAL( rem_pio2_medium(nt2::Pio_4<vT>()*nt2::Half<T>(), r1), nt2::Zero<viT>());
    NT2_TEST_ULP_EQUAL( r1, nt2::Pio_4<vT>()*nt2::Half<vT>(), 0.5);
  }

}


#ifdef COMPILATION_INSTRUCTIONS
/usr/local/cuda-11.1/bin/nvcc -x cu -std=c++17  -use_fast_math -lpthread -D_REENTRANT -DBOOST_PP_VARIADICS  -Xcudafe "--diag_suppress=implicit_return_from_non_void_function" --extended-lambda --expt-relaxed-constexpr $0 -o $0x `pkg-config --cflags --libs cudart-11.0 cublas-11.0 blas` -lboost_unit_test_framework -DBOOST_LOG_DYN_LINK -lboost_log -lboost_thread -lboost_system -lboost_log_setup -lpthread -lboost_timer&&$0x&&rm $0x; exit
#endif
// © Alfredo A. Correa 2020-2021

#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuBLAS gemm"
#define BOOST_TEST_DYN_LINK
#include<boost/test/unit_test.hpp>
#include <boost/timer/timer.hpp>

//#include"boost/log/trivial.hpp"
//#define MULTI_MARK_SCOPE(MsG) BOOST_LOG_TRIVIAL(trace)<<MsG

//#include "../../../../adaptors/cublas/context.hpp"

#include "../../../cuda/cublas.hpp"
#include "../../../../array.hpp"

#include "../../../../adaptors/cuda.hpp"
#include "../../../../adaptors/blas.hpp"

#include<random>

namespace multi = boost::multi;

BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_complex_3x2_3x2){
	using complex = std::complex<double>; complex const I{0, 1};
	namespace blas = multi::blas;
	multi::array<complex, 2> const a = {
		{1. + 2.*I, 5. + 2.*I},
		{9. - 1.*I, 9. + 1.*I},
		{1. + 1.*I, 2. + 2.*I}
	};
	multi::array<complex, 2> const b = {
		{ 11. - 2.*I, 5. + 2.*I},
		{  7. - 3.*I, 2. + 1.*I},
		{  8. - 1.*I, 1. + 1.*I}
	};
	{
		{
			multi::array<complex, 2> c({2, 2});
			c = blas::gemm(1., blas::H(a), b); // c=ab, c⸆=b⸆a⸆
			BOOST_REQUIRE( c[1][0] == 125.-84.*I );
		}
	}
	{
		multi::cuda::array<complex, 2> const a_gpu = a;
		multi::cuda::array<complex, 2> const b_gpu = b;
		{
			multi::cuda::array<complex, 2> c_gpu({2, 2});
			c_gpu = blas::gemm(1., blas::H(a_gpu), b_gpu); // c=ab, c⸆=b⸆a⸆
			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
		}
		{
			auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu);
			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
		}
	}
	{
		multi::cuda::managed::array<complex, 2> const a_gpu = a;
		multi::cuda::managed::array<complex, 2> const b_gpu = b;
		{
			multi::cuda::managed::array<complex, 2> c_gpu({2, 2});
			blas::gemm(1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆
			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
		}
		{
			auto c_gpu =+ blas::gemm(1.0, blas::H(a_gpu), b_gpu);
			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
		}
	}
}

//BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_complex_3x2_3x2_with_context){
//	using complex = std::complex<double>; complex const I{0, 1};
//	namespace blas = multi::blas;
//	multi::array<complex, 2> const a = {
//		{1. + 2.*I, 5. + 2.*I},
//		{9. - 1.*I, 9. + 1.*I},
//		{1. + 1.*I, 2. + 2.*I}
//	};
//	multi::array<complex, 2> const b = {
//		{ 11. - 2.*I, 5. + 2.*I},
//		{  7. - 3.*I, 2. + 1.*I},
//		{  8. - 1.*I, 1. + 1.*I}
//	};
//	{
//		{
//			multi::blas::context ctx;
//			multi::array<complex, 2> c({2, 2});
//			blas::gemm(ctx, 1., blas::H(a), b, 0., c); // c=ab, c⸆=b⸆a⸆
//			BOOST_REQUIRE( c[1][0] == 125.-84.*I );
//		}
//	}
//	{
//		multi::cublas::context ctx;
//		multi::cuda::array<complex, 2> const a_gpu = a;
//		multi::cuda::array<complex, 2> const b_gpu = b;
//		{
//			multi::cuda::array<complex, 2> c_gpu({2, 2});
//			blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆
//			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
//		}
//		{
//			auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu);
//			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
//		}
//	}
//	{
//		multi::cublas::context ctx;
//		multi::cuda::managed::array<complex, 2> const a_gpu = a;
//		multi::cuda::managed::array<complex, 2> const b_gpu = b;
//		{
//			multi::cuda::managed::array<complex, 2> c_gpu({2, 2});
//			blas::gemm(ctx, 1., blas::H(a_gpu), b_gpu, 0., c_gpu); // c=ab, c⸆=b⸆a⸆
//			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
//		}
//		{
//			auto c_gpu =+ blas::gemm(&ctx, blas::H(a_gpu), b_gpu);
//			BOOST_REQUIRE( c_gpu[1][0] == 125.-84.*I );
//		}
//	}
//}

#if 0
BOOST_AUTO_TEST_CASE(multi_adaptors_blas_cuda_gemm_context_timing){
	using complex = std::complex<double>;//complex const I{0, 1};

	multi::array<complex, 2> A({1000, 1000});
	multi::array<complex, 2> B(      {1000, 1000});
	multi::array<complex, 2> C({size(A), size(~B)});
	A[99][99] = B[11][22] = C[33][44] = 1.0;
	std::cerr<< "memory " << (A.num_elements()+ B.num_elements() + C.num_elements())*sizeof(complex)/1e6 <<" MB"<<std::endl;

	{
		auto rand = [d=std::uniform_real_distribution<>{0., 10.}, g=std::mt19937{}]() mutable{return complex{d(g), d(g)};};
		std::generate(A.elements().begin(), A.elements().end(), rand);
		std::generate(B.elements().begin(), B.elements().end(), rand);
	}
	namespace blas = multi::blas;
	{
		boost::timer::auto_cpu_timer t; // 2.398206s
		for(auto i = 0; i != 10; ++i){
			blas::context ctx;
			blas::gemm(ctx, 1, A, B, 0, C);
		}
	}
	using device_array = multi::cuda::array<complex, 2>;
	{
		device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)});

		boost::timer::auto_cpu_timer t; // 0.707426s
		for(auto i = 0; i != 10; ++i){
			multi::cublas::context ctx;
			blas::gemm(ctx, 1, A_gpu, B_gpu, 0, C_gpu);
		}
	}
	{
		device_array A_gpu = A, B_gpu = B, C_gpu({size(A), size(~B)});

		boost::timer::auto_cpu_timer t; // 0.613534s
		multi::cublas::context ctx;
		for(auto i = 0; i != 10; ++i) blas::gemm(ctx, 1, A_gpu, B_gpu, 0, C_gpu);
	}
}
#endif


//  (C) Copyright John Maddock 2008.
//  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_TR1_CMATH_HPP_INCLUDED
#  define BOOST_TR1_CMATH_HPP_INCLUDED
#  include <boost/tr1/detail/config.hpp>

#ifdef BOOST_HAS_TR1_CMATH

#  if defined(BOOST_HAS_INCLUDE_NEXT) && !defined(BOOST_TR1_DISABLE_INCLUDE_NEXT)
#     include_next BOOST_TR1_HEADER(cmath)
#  else
#     include <boost/tr1/detail/config_all.hpp>
#     include BOOST_TR1_HEADER(cmath)
#  endif

#else

#include <boost/math/tr1.hpp>

namespace std{ namespace tr1{

using boost::math::tr1::assoc_laguerre;
using boost::math::tr1::assoc_laguerref;
using boost::math::tr1::assoc_laguerrel;
// [5.2.1.2] associated Legendre functions:
using boost::math::tr1::assoc_legendre;
using boost::math::tr1::assoc_legendref;
using boost::math::tr1::assoc_legendrel;
// [5.2.1.3] beta function:
using boost::math::tr1::beta;
using boost::math::tr1::betaf;
using boost::math::tr1::betal;
// [5.2.1.4] (complete) elliptic integral of the first kind:
using boost::math::tr1::comp_ellint_1;
using boost::math::tr1::comp_ellint_1f;
using boost::math::tr1::comp_ellint_1l;
// [5.2.1.5] (complete) elliptic integral of the second kind:
using boost::math::tr1::comp_ellint_2;
using boost::math::tr1::comp_ellint_2f;
using boost::math::tr1::comp_ellint_2l;
// [5.2.1.6] (complete) elliptic integral of the third kind:
using boost::math::tr1::comp_ellint_3;
using boost::math::tr1::comp_ellint_3f;
using boost::math::tr1::comp_ellint_3l;
#if 0
// [5.2.1.7] confluent hypergeometric functions:
using boost::math::tr1::conf_hyperg;
using boost::math::tr1::conf_hypergf;
using boost::math::tr1::conf_hypergl;
#endif
// [5.2.1.8] regular modified cylindrical Bessel functions:
using boost::math::tr1::cyl_bessel_i;
using boost::math::tr1::cyl_bessel_if;
using boost::math::tr1::cyl_bessel_il;
// [5.2.1.9] cylindrical Bessel functions (of the first kind):
using boost::math::tr1::cyl_bessel_j;
using boost::math::tr1::cyl_bessel_jf;
using boost::math::tr1::cyl_bessel_jl;
// [5.2.1.10] irregular modified cylindrical Bessel functions:
using boost::math::tr1::cyl_bessel_k;
using boost::math::tr1::cyl_bessel_kf;
using boost::math::tr1::cyl_bessel_kl;
// [5.2.1.11] cylindrical Neumann functions;
// cylindrical Bessel functions (of the second kind):
using boost::math::tr1::cyl_neumann;
using boost::math::tr1::cyl_neumannf;
using boost::math::tr1::cyl_neumannl;
// [5.2.1.12] (incomplete) elliptic integral of the first kind:
using boost::math::tr1::ellint_1;
using boost::math::tr1::ellint_1f;
using boost::math::tr1::ellint_1l;
// [5.2.1.13] (incomplete) elliptic integral of the second kind:
using boost::math::tr1::ellint_2;
using boost::math::tr1::ellint_2f;
using boost::math::tr1::ellint_2l;
// [5.2.1.14] (incomplete) elliptic integral of the third kind:
using boost::math::tr1::ellint_3;
using boost::math::tr1::ellint_3f;
using boost::math::tr1::ellint_3l;
// [5.2.1.15] exponential integral:
using boost::math::tr1::expint;
using boost::math::tr1::expintf;
using boost::math::tr1::expintl;
// [5.2.1.16] Hermite polynomials:
using boost::math::tr1::hermite;
using boost::math::tr1::hermitef;
using boost::math::tr1::hermitel;
#if 0
// [5.2.1.17] hypergeometric functions:
using boost::math::tr1::hyperg;
using boost::math::tr1::hypergf;
using boost::math::tr1::hypergl;
#endif
// [5.2.1.18] Laguerre polynomials:
using boost::math::tr1::laguerre;
using boost::math::tr1::laguerref;
using boost::math::tr1::laguerrel;
// [5.2.1.19] Legendre polynomials:
using boost::math::tr1::legendre;
using boost::math::tr1::legendref;
using boost::math::tr1::legendrel;
// [5.2.1.20] Riemann zeta function:
using boost::math::tr1::riemann_zeta;
using boost::math::tr1::riemann_zetaf;
using boost::math::tr1::riemann_zetal;
// [5.2.1.21] spherical Bessel functions (of the first kind):
using boost::math::tr1::sph_bessel;
using boost::math::tr1::sph_besself;
using boost::math::tr1::sph_bessell;
// [5.2.1.22] spherical associated Legendre functions:
using boost::math::tr1::sph_legendre;
using boost::math::tr1::sph_legendref;
using boost::math::tr1::sph_legendrel;
// [5.2.1.23] spherical Neumann functions;
// spherical Bessel functions (of the second kind):
using boost::math::tr1::sph_neumann;
using boost::math::tr1::sph_neumannf;
using boost::math::tr1::sph_neumannl;

// types
using boost::math::tr1::double_t;
using boost::math::tr1::float_t;
// functions
using boost::math::tr1::acosh;
using boost::math::tr1::acoshf;
using boost::math::tr1::acoshl;
using boost::math::tr1::asinh;
using boost::math::tr1::asinhf;
using boost::math::tr1::asinhl;
using boost::math::tr1::atanh;
using boost::math::tr1::atanhf;
using boost::math::tr1::atanhl;
using boost::math::tr1::cbrt;
using boost::math::tr1::cbrtf;
using boost::math::tr1::cbrtl;
using boost::math::tr1::copysign;
using boost::math::tr1::copysignf;
using boost::math::tr1::copysignl;
using boost::math::tr1::erf;
using boost::math::tr1::erff;
using boost::math::tr1::erfl;
using boost::math::tr1::erfc;
using boost::math::tr1::erfcf;
using boost::math::tr1::erfcl;
#if 0
using boost::math::tr1::exp2;
using boost::math::tr1::exp2f;
using boost::math::tr1::exp2l;
#endif
using boost::math::tr1::expm1;
using boost::math::tr1::expm1f;
using boost::math::tr1::expm1l;
#if 0
using boost::math::tr1::fdim;
using boost::math::tr1::fdimf;
using boost::math::tr1::fdiml;
using boost::math::tr1::fma;
using boost::math::tr1::fmaf;
using boost::math::tr1::fmal;
#endif
using boost::math::tr1::fmax;
using boost::math::tr1::fmaxf;
using boost::math::tr1::fmaxl;
using boost::math::tr1::fmin;
using boost::math::tr1::fminf;
using boost::math::tr1::fminl;
using boost::math::tr1::hypot;
using boost::math::tr1::hypotf;
using boost::math::tr1::hypotl;
#if 0
using boost::math::tr1::ilogb;
using boost::math::tr1::ilogbf;
using boost::math::tr1::ilogbl;
#endif
using boost::math::tr1::lgamma;
using boost::math::tr1::lgammaf;
using boost::math::tr1::lgammal;
#if 0
using boost::math::tr1::llrint;
using boost::math::tr1::llrintf;
using boost::math::tr1::llrintl;
#endif
using boost::math::tr1::llround;
using boost::math::tr1::llroundf;
using boost::math::tr1::llroundl;
using boost::math::tr1::log1p;
using boost::math::tr1::log1pf;
using boost::math::tr1::log1pl;
#if 0
using boost::math::tr1::log2;
using boost::math::tr1::log2f;
using boost::math::tr1::log2l;
using boost::math::tr1::logb;
using boost::math::tr1::logbf;
using boost::math::tr1::logbl;
using boost::math::tr1::lrint;
using boost::math::tr1::lrintf;
using boost::math::tr1::lrintl;
#endif
using boost::math::tr1::lround;
using boost::math::tr1::lroundf;
using boost::math::tr1::lroundl;
#if 0
using boost::math::tr1::nan;
using boost::math::tr1::nanf;
using boost::math::tr1::nanl;
using boost::math::tr1::nearbyint;
using boost::math::tr1::nearbyintf;
using boost::math::tr1::nearbyintl;
#endif
using boost::math::tr1::nextafter;
using boost::math::tr1::nextafterf;
using boost::math::tr1::nextafterl;
using boost::math::tr1::nexttoward;
using boost::math::tr1::nexttowardf;
using boost::math::tr1::nexttowardl;
#if 0
using boost::math::tr1::remainder;
using boost::math::tr1::remainderf;
using boost::math::tr1::remainderl;
using boost::math::tr1::remquo;
using boost::math::tr1::remquof;
using boost::math::tr1::remquol;
using boost::math::tr1::rint;
using boost::math::tr1::rintf;
using boost::math::tr1::rintl;
#endif
using boost::math::tr1::round;
using boost::math::tr1::roundf;
using boost::math::tr1::roundl;
#if 0
using boost::math::tr1::scalbln;
using boost::math::tr1::scalblnf;
using boost::math::tr1::scalblnl;
using boost::math::tr1::scalbn;
using boost::math::tr1::scalbnf;
using boost::math::tr1::scalbnl;
#endif
using boost::math::tr1::tgamma;
using boost::math::tr1::tgammaf;
using boost::math::tr1::tgammal;
using boost::math::tr1::trunc;
using boost::math::tr1::truncf;
using boost::math::tr1::truncl;
// C99 macros defined as C++ templates
using boost::math::tr1::signbit;
using boost::math::tr1::fpclassify;
using boost::math::tr1::isfinite;
using boost::math::tr1::isinf;
using boost::math::tr1::isnan;
using boost::math::tr1::isnormal;
#if 0
using boost::math::tr1::isgreater;
using boost::math::tr1::isgreaterequal;
using boost::math::tr1::isless;
using boost::math::tr1::islessequal;
using boost::math::tr1::islessgreater;
using boost::math::tr1::isunordered;
#endif
} } // namespaces

#endif // BOOST_HAS_TR1_CMATH

#endif // BOOST_TR1_CMATH_HPP_INCLUDED