#include "wheelcontroller.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <thread>

#define deg150 (150.0 * PI / 180.0)
#define deg30 (30.0 * PI / 180.0)
#define deg270 (270.0 * PI / 180.0)
//#define LIMIT_ACCELERATION

WheelController::WheelController() : ThreadedClass("WheelController")
{
	w_left = NULL;
	w_right = NULL;
	w_back = NULL;
	targetSpeed = { 0, 0, 0 };
};

void WheelController::InitWheels(boost::asio::io_service &io, bool useDummyPorts/* = false*/)
{
	using boost::property_tree::ptree;
	ptree pt;
	if (useDummyPorts) {
		w_left = new SoftwareWheel("left");
		w_right = new SoftwareWheel("right");
		w_back = new SoftwareWheel("back");

	}
	else {
		read_ini("conf/ports.ini", pt);

		std::cout << "left wheel " << std::to_string(ID_WHEEL_LEFT) << " " << pt.get<std::string>(std::to_string(ID_WHEEL_LEFT)) << std::endl;
		w_left = new SerialWheel(io, pt.get<std::string>(std::to_string(ID_WHEEL_LEFT)), 115200, "left");

		std::cout << "right wheel " << std::to_string(ID_WHEEL_RIGHT) << " " << pt.get<std::string>(std::to_string(ID_WHEEL_RIGHT)) << std::endl;
		w_right = new SerialWheel(io, pt.get<std::string>(std::to_string(ID_WHEEL_RIGHT)), 115200, "right");

		std::cout << "back wheel " << std::to_string(ID_WHEEL_BACK) << " " << pt.get<std::string>(std::to_string(ID_WHEEL_BACK)) << std::endl;
		w_back = new SerialWheel(io, pt.get<std::string>(std::to_string(ID_WHEEL_BACK)), 115200, "back");
		std::cout << "wheels done" << std::endl;

	}
	w_left->Start();
	w_right->Start();
	w_back->Start();
	Start();

}

void WheelController::DestroyWheels()
{
	if (w_left != NULL) {
		delete w_left;
		w_left = NULL;
	}
	if (w_right != NULL) {
		delete w_right;
		w_right = NULL;
	}
	if (w_back != NULL) {
		delete w_back;
		w_back = NULL;

	}
};
WheelController::~WheelController()
{
	Stop();
	WaitForStop();
	DestroyWheels();
}
void WheelController::Forward(int speed) {

	DriveRotate(speed * 1.1547, 0, 0);

}
void WheelController::Rotate(bool direction, double speed)
{
	DriveRotate(0,0, direction ? speed : -speed);
}
void WheelController::Drive(double velocity, double direction)
{
	DriveRotate(velocity, direction, 0);
}

void WheelController::DriveRotate(double velocity, double direction, double rotate)
{
	//std::cout << "DriveRotate: " << velocity << std::endl;

	if (abs(velocity) > 190){
		if (velocity > 0){
			velocity = 190;
		}
		else{
			velocity = -190;
		}
	}

	if ((abs(rotate) + velocity) > 190){
		if (rotate > 0){
			velocity = velocity - rotate;
		}
		else{
			velocity = velocity + rotate;
		}

	}
	//double sign = (velocity > 0) - (velocity < 0);
	//velocity = sign*std::min(190.0, abs(velocity + rotate)) - rotate;

	targetSpeed.velocity = velocity; // sin(direction* PI / 180.0)* velocity + rotate;
	targetSpeed.heading = direction; //cos(direction* PI / 180.0)* velocity + rotate,
	targetSpeed.rotation = rotate;
#ifndef LIMIT_ACCELERATION
		auto speeds = CalculateWheelSpeeds(targetSpeed.velocity, targetSpeed.heading, targetSpeed.rotation);
	if (w_left != NULL) w_left->SetSpeed(speeds.x);
	if (w_right != NULL) w_right->SetSpeed(speeds.y);
	if (w_back != NULL) w_back->SetSpeed(speeds.z);
#endif
	directControl = false;
	updateSpeed = true;
	lastUpdate = boost::posix_time::microsec_clock::local_time();

}
cv::Point3d WheelController::CalculateWheelSpeeds(double velocity, double direction, double rotate)
{
	return cv::Point3d(
		(velocity*cos((150 - direction) * PI / 180.0)) + rotate,
		((velocity*cos((30 - direction)  * PI / 180.0)) + rotate),
		(velocity*cos((270 - direction)  * PI / 180.0)) + rotate
	);
}
void WheelController::Stop()
{
	Drive(0,0);
}

bool WheelController::IsStalled()
{
	if (directControl) return false;

	if (targetSpeed.velocity < 0.01 || actualSpeed.velocity > 90){
		stallTime = boost::posix_time::microsec_clock::local_time();
		return false;
	}
	boost::posix_time::ptime time = boost::posix_time::microsec_clock::local_time();
	boost::posix_time::time_duration::tick_type stallDuration = (time - stallTime).total_milliseconds();
	//std::cout << "stall? " << velocity << ", " << velocity2 << "t: " << stallDuration << std::endl;

	if (abs(actualSpeed.velocity - targetSpeed.velocity) < 10) {
		// reset timer
		stallTime = boost::posix_time::microsec_clock::local_time();
	}else {
		//std::cout << "stall? " << velocity << ", " << velocity2 << "t: " << stallDuration << std::endl;
		return stallDuration > 600;

	}
	return false;

	//return w_left->IsStalled() || w_right->IsStalled() || w_back->IsStalled();
}
bool WheelController::HasError()
{
	return w_left->HasError() || w_right->HasError() || w_back->HasError();
}

cv::Point3d WheelController::GetWheelSpeeds()
{
	return cv::Point3d(w_left->GetSpeed(), w_right->GetSpeed(), w_back->GetSpeed());
}

void WheelController::CalculateRobotSpeed()
{
	lastSpeed = actualSpeed;
	cv::Point3d speeds = GetWheelSpeeds();
	double a, b, c, u, v, w;
	/*
	a = x *[cos(u) * cos(y) + sin(u) * sin(y)] + z
	b = x *[cos(v) * cos(y) + sin(v) * sin(y)] + z
	c = x *[cos(w) * cos(y) + sin(w) * sin(y)] + z
	*/
	if (abs(speeds.z - speeds.x) > 0.0000001) { // c - a == 0
		a = speeds.x; b = speeds.y; c = speeds.z;
		u = deg150; v = deg30; w = deg270;
	}
	else if (abs(speeds.x - speeds.y) > 0.0000001) {
		a = speeds.y; b = speeds.z; c = speeds.x;
		u = deg30; v = deg270; w = deg30;
	}
	else if (abs(speeds.z - speeds.y) > 0.0000001) {
		a = speeds.z; b = speeds.x; c = speeds.y;
		u = deg270; v = deg30; w = deg150;
	}
	else {
		// all equal, rotation only
		actualSpeed.velocity = 0;
		actualSpeed.heading = 0;
		actualSpeed.rotation = speeds.x;
		return;

	}
	double s = (b - a) / (c - a);
	double directionInRad = atan(((cos(v) - cos(u)) - s * (cos(w) - cos(u))) / (s * (sin(w) - sin(u)) - (sin(v) - sin(u))));
	//if (directionInRad < 0) directionInRad += 2 * PI;
	actualSpeed.heading = directionInRad / PI * 180;
	actualSpeed.velocity = (a - c) / ((cos(u) - cos(w)) * cos(directionInRad) + (sin(u) - sin(w)) * sin(directionInRad));
	actualSpeed.rotation = c - (actualSpeed.velocity  * cos(w - directionInRad));

}

const Speed &  WheelController::GetTargetSpeed()
{
	return targetSpeed;
}

const Speed &  WheelController::GetActualSpeed()
{
	return actualSpeed;
}

std::string WheelController::GetDebugInfo(){

	std::ostringstream oss;
	oss.precision(4);
	oss << "[WheelController] target: " << "velocity: " << targetSpeed.velocity << ", heading: " << targetSpeed.heading << ", rotate: " << targetSpeed.rotation << "|";
	oss << "[WheelController] actual: " << "velocity: " << actualSpeed.velocity << ", heading: " << actualSpeed.heading << ", rotate: " << actualSpeed.rotation << "|";
	cv::Point3d speeds = GetWheelSpeeds();
	auto speeds2 = CalculateWheelSpeeds(targetSpeed.velocity, targetSpeed.heading, targetSpeed.rotation);
	oss << "[Wheels] target: " << "left  : " << speeds2.x << ", right: " << speeds2.y << ", back: " << speeds2.z << "|";
	oss << "[Wheels] actual: " << "left  : " << speeds.x << ", right: " << speeds.y << ", back: " << speeds.x << "|";
	//oss << "[WheelController] pos: " << "x: " << robotPos.x << ", y: " << robotPos.y << ", r: " << robotPos.z;
	return oss.str();
}

void WheelController::Run()
{
	while (!stop_thread) {
		CalculateRobotSpeed();
#ifdef LIMIT_ACCELERATION
		boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
		if (!updateSpeed && (now - lastUpdate).total_milliseconds() > 500) {
			targetSpeed = {0,0,0};
		}
		updateSpeed = false;
		Speed speed = targetSpeed;
		double dt = (double)(now - lastStep).total_milliseconds() / 1000.0;
		if (dt < 0.0000001) continue;

		if (abs(actualSpeed.velocity) > 1000) {
			std::cout << "to big actual velocity: " << actualSpeed.velocity << "wheel speeds: " << GetWheelSpeeds() << std::endl;
			actualSpeed.velocity = 0;

		}
		double dv = targetSpeed.velocity - actualSpeed.velocity;
		double sign = (dv > 0) - (dv < 0);
		double acc = dv / dt;

		acc = sign * std::min(fabs(acc), 500.0);
		speed.velocity = acc*dt + actualSpeed.velocity;

		auto speeds = CalculateWheelSpeeds(speed.velocity, speed.heading, speed.rotation);
		//std::cout << "wheel speeds, left: " << speeds.x << ", right: " << speeds.y << ", back: " << speeds.z << std::endl;
		w_left->SetSpeed(speeds.x);
		w_right->SetSpeed(speeds.y);
		w_back->SetSpeed(speeds.z);
		lastStep = now;
#endif
		std::this_thread::sleep_for(std::chrono::milliseconds(10)); // do not poll serial to fast
	}
	std::cout << "WheelController stoping" << std::endl;

}


#include <boost/test/unit_test.hpp>

#include "util.h"
#include "scrypt.h"
#include "main.h"

BOOST_AUTO_TEST_SUITE(scrypt_tests)

BOOST_AUTO_TEST_CASE(scrypt_hashtest)
{
    // Test Scrypt hash with known inputs against expected outputs for all
    // supported Nfactors, only goes up to N = 1048576 due to current 32 bit
    // limit on nTime. Blocktime is packed in the pseudoheaders in inputhex
    #define HASHCOUNT 50
    const char* inputhex[HASHCOUNT] = {
        ""
    };
    const char* expected[HASHCOUNT] = {
        ""
    };
    uint256 scrypthash;
    std::vector<unsigned char> inputbytes;

    for (int i = 0; i < HASHCOUNT; i++) {
        inputbytes = ParseHex(inputhex[i]);
        unsigned int blocktime = *(unsigned int*)(&inputbytes[68]);
        printf("%i\n", blocktime);
        unsigned char Nfactor = GetNfactor(blocktime);
        unsigned int scratchpad_size = 128 * (1 << (Nfactor+1)) + 512;
        std::vector<char> scratchpad = std::vector<char>(scratchpad_size);
#if defined(USE_SSE2)
        // Test SSE2 scrypt
        scrypt_N_1_1_256_sp_sse2((const char*)&inputbytes[0],
                                BEGIN(scrypthash), &scratchpad[0],
                                Nfactor);
#endif
        // Test generic scrypt
        scrypt_N_1_1_256_sp_generic((const char*)&inputbytes[0],
                                    BEGIN(scrypthash), &scratchpad[0],
                                    Nfactor);
        BOOST_CHECK_EQUAL(scrypthash.ToString().c_str(), expected[i]);
    }
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) 2017, Adam Allevato
// Copyright (c) 2017, The University of Texas at Austin
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "orp/core/nn_classifier.h"

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

NNClassifier::NNClassifier() :
  Classifier3D()
{
  srand (static_cast <unsigned> (time(0)));

  node_private_.param<std::string>("data_folder", dataFolder, "/");
  node_private_.param<std::string>("file_extension", fileExtension, "/");
  node_private_.param<float>("threshold", threshold, 10000.0f);
}

void NNClassifier::init() {
  //parse the params on the parameter server
  loadTypeList();
  if(!ros::isShuttingDown()) {

    loadModelsRecursive(dataFolder, fileExtension, loadedModels);
    ROS_INFO("Loaded %d models.\n", (int)loadedModels.size());

    if(loadedModels.size() < 1)
    {
      ROS_WARN ("No models loaded from %s.", dataFolder.c_str());
    } else {
      subModels = loadedModels;

      // Convert data into FLANN format
      kData = new flann::Matrix<float>(
        new float[subModels.size() * subModels[0].second.size()],
        subModels.size(),
        subModels[0].second.size());

      ROS_INFO_STREAM("data size: [" << kData->rows <<
                      " , " << kData->cols << "]");
      for(size_t i = 0; i < kData->rows; ++i)
      {
        for(size_t j = 0; j < kData->cols; ++j)
        {
          *(kData->ptr()+(i*kData->cols + j)) = subModels[i].second[j];
        }
      }

      kIndex = new flann::Index<flann::ChiSquareDistance<float> >(
          *kData, flann::LinearIndexParams ());
      kIndex->buildIndex();
    }
    ROS_INFO("Training data loaded.");
  }
  Classifier::init();
}

void NNClassifier::loadTypeList() {
  XmlRpc::XmlRpcValue paramMap;
  std::vector<WorldObjectType> objects;
  while(!node_.getParam("items", paramMap)) {
    ROS_INFO_DELAYED_THROTTLE(5.0,
                      "Waiting for object type list on parameter server...");
    ros::Duration(1.0).sleep();
  }
  for(XmlRpc::XmlRpcValue::iterator it = paramMap.begin();
      it != paramMap.end(); ++it) {
    fullTypeList.push_back(it->first);
  }
}

NNClassifier::~NNClassifier() {
  delete[] kData->ptr();
  delete kIndex;
}

int NNClassifier::nearestKSearch(
  flann::Index<flann::ChiSquareDistance<float> > &index,
  const FeatureVector &homeFeature,
  int k,
  flann::Matrix<int> &indices,
  flann::Matrix<float> &distances)
{
  int rows = 1;
  // Query point
  //flann::Matrix<float> home =
  //  flann::Matrix<float>(
  //    const_cast<float*>(&(homeFeature.second[0])),
  //                       home.rows,
  //                       homeFeature.second.size ());
  flann::Matrix<float> home =
      flann::Matrix<float>(
        new float[homeFeature.second.size()],
        rows,
        homeFeature.second.size());
  memcpy(&home.ptr ()[0],
         &homeFeature.second.at(0),
         home.cols * home.rows * sizeof (int));

  indices =
      flann::Matrix<int>(new int[home.rows*k], home.rows, k);
  distances =
      flann::Matrix<float>(new float[home.rows*k], home.rows, k);
  int foundCount =
      index.knnSearch (home, indices, distances, k, flann::SearchParams (128));

  return foundCount;
}

void NNClassifier::loadModelsRecursive(
  const boost::filesystem::path &base_dir,
  const std::string &extension,
  FeatureVectorVector &loadedModels)
{
  ROS_INFO("loading files from %s", base_dir.string().c_str());
  if(!boost::filesystem::is_directory (base_dir)) {
    ROS_FATAL("target path %s is not a directory!", base_dir.string().c_str());
    return;
  }
  if(!boost::filesystem::exists (base_dir)) {
    ROS_FATAL("target folder %s does not exist", base_dir.string().c_str());
    return;
  }

  for(boost::filesystem::directory_iterator it (base_dir);
      it != boost::filesystem::directory_iterator (); ++it) {
    if(boost::filesystem::is_directory(it->status ())) {
      std::stringstream ss;
      ss << it->path();
      ROS_INFO("NOT traversing into directory %s.", ss.str().c_str());
    }
    if(boost::filesystem::is_regular_file(it->status()) &&
       boost::filesystem::extension(it->path()) == extension) {
      FeatureVector m;
      boost::regex pattern("");
      boost::cmatch what;
      for(std::vector<std::string>::iterator types = fullTypeList.begin();
          types != fullTypeList.end(); ++types) {
        //match just the filename against the regex
        pattern = boost::regex("(" + *types + ")(.*)");
        if(boost::regex_match(
           it->path().filename().string().c_str(), what, pattern))
        {
          if(loadHist(it->path(), m))
          {
            loadedModels.push_back(m);
            ROS_INFO("Loading file %s",
                      it->path().filename().string().c_str());
          } else
          {
            ROS_INFO_STREAM("histogram loader rejected file " <<
                            it->path().filename().string().c_str());
          }
        }
      }
    }
  }
}


#define BOOST_TEST_MODULE pcraster model_engine dynamicwave
#include <boost/test/unit_test.hpp>

#include "calc_globallibdefs.h"
#include "geo_filecreatetester.h"
#include "com_csfcell.h"
#include "com_directory.h"
#include "com_file.h"
#include "calc_field.h"
#include "calc_p5stack.h"

// NOTE use string failureExpected in files expected to fail, see style guide

struct Fixture
{

    Fixture()
    {
        calc::globalInit();
    }

    ~Fixture()
    {
        calc::globalEnd();
    }

};

BOOST_FIXTURE_TEST_SUITE(dynamicwave, Fixture)

BOOST_AUTO_TEST_CASE(testSimpleInput)
{
  using namespace calc;

  // pcrcalc507 basic dynamicwave
  // profileId=0,H=1,A=2,P=3
  com::write("1 0 0 1\n1 1 2000 2000","tmp507.tbl");

  // first run no tests
  execTest("pcrcalc507");
/*
  // next some alterations:
  const char* pcrcalc507a=" \
      report tmp.state,tmp.flux=\n \
      dynwavestate,dynwaveflux( \n \
      tmp507.tbl,                  \n \
      inp1n.map,  # profile id  \n \
      inpldd.map,               \n \
      %1%, # oldState            \n \
      0, # inflow               \n \
      0, # bottomLevel          \n \
      1, # roughness            \n \
      1, # segmentLength        \n \
      10, # nrTimeSlices        \n \
      1, # timestepInSecs       \n \
      0  # constantState        \n \
      );                        \n \
      report p = lookuppotential(tmp507.tbl,inp1n.map,0,1,tmp.state); \n \
      report s = lookupstate(tmp507.tbl,inp1n.map,0,1,p); \n \
      report tmp.res = (tmp.state==s) && %2%;     \n";

  { // state == 5 everywhere
    geo::FileCreateTester state("tmp.state");
    geo::FileCreateTester  flux("tmp.flux");
    std::string testCmd((boost::format(pcrcalc507a)
          % "5" % "1==1").str());
    execTest(testCmd);

    BOOST_CHECK(flux.equalTo("inp0s.map",false));
    BOOST_CHECK(state.equalTo("inp5s.map",false));
  }
  { // state == distance ==> drop in the network
    geo::FileCreateTester  res("tmp.res");
    std::string testCmd((boost::format(pcrcalc507a) %
          "ldddist(inpldd.map,inpldd.map==5,1)" %
          "tmp.state >= 0 && tmp.flux >= 0"
          ).str());
    execTest(testCmd);

    BOOST_CHECK(res.equalTo("inp1b.map",false));
  }
  { // state == 300-distance ==> only uphill
    geo::FileCreateTester  res("tmp.res");
    std::string testCmd((boost::format(pcrcalc507a) %
     "300-ldddist(inpldd.map,inpldd.map==5,1)" %
     "tmp.flux == 0 && tmp.state==(300-ldddist(inpldd.map,inpldd.map==5,1))"
     ).str());
    execTest(testCmd);

    BOOST_CHECK(res.equalTo("inp1b.map",false));
  }

  { // state == 100000, to generate DomainError
    geo::FileCreateTester state("tmp.state");
    geo::FileCreateTester  flux("tmp.flux");
    std::string testCmd((boost::format(pcrcalc507a)
          % "100000" % "1==1").str());
    TRY_TEST_MSG {
      execTest(testCmd);
    } CATCH_TEST_MSG("pcrcalc508");
    BOOST_CHECK(catched);
  }
*/
}

BOOST_AUTO_TEST_SUITE_END()


#include "stdafx.h"
#include "hmac_sha_base64.h"
#include "openssl/sha.h"

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string.hpp>

using namespace core;
using namespace tools;

uint8_t *coding=(uint8_t*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";                   //52...63

enum
{
    UNABLE_TO_OPEN_INPUT_FILE,
    UNABLE_TO_OPEN_OUTPUT_FILE,
    UNABLE_TO_CREATE_OUTPUTBUFFER
};

uint8_t base64::lobyte(uint16_t w)
{
    return ((uint8_t)(((uint32_t)(w)) & 0xff));
}

uint8_t base64::hibyte(uint16_t w)
{
    return ((uint8_t)((((uint32_t)(w)) >> 8) & 0xff));
}

uint16_t base64::loword(uint32_t l)
{
    return ((uint16_t)(((uint32_t)(l)) & 0xffff));
}

uint16_t base64::hiword(uint32_t l)
{
    return ((uint16_t)((((uint32_t)(l)) >> 16) & 0xffff));
}

int32_t fchr(uint8_t c)
{
    int32_t i=0;
    for (i=0;i < 64;i++) if (coding[i]==c) return (i);
    return (-1);
}

int32_t base64::base64_decode(uint8_t *source,int32_t length, uint8_t *dst)
{
    int32_t temp =0;
    int32_t i=0;
    int32_t size =0;
    int32_t cursor = 0;

    for (;i<length/4;)
    {
        int32_t zeroes;
        int32_t j;
        cursor = 0;
        zeroes = 0;

        *((uint8_t*)&cursor+3) = source[i*4];
        *((uint8_t*)&cursor+2) = source[i*4+1];
        *((uint8_t*)&cursor+1) = source[i*4+2];
        *((uint8_t*)&cursor) = source[i*4+3];
        for (j =0 ;j <4; j++)
        {
            if (*((uint8_t*)&cursor +j) == '=')
            {
                *((uint8_t*)&cursor +j) =0;
                zeroes++;
                continue;
            }
            temp  = fchr( *((uint8_t*)&cursor +j) );
            temp <<=6*j;
            cursor &= ~(0xFF << j*8);
            cursor ^= temp;
        }
        for (j = 0; j<3; j++)
        {
            dst[i*3+j] = *((uint8_t*)&cursor+2);
            cursor <<=8;
            size++;
        }
        i++;
        size-=zeroes;
    }
    return size;
}

int32_t base64::base64_encode(uint8_t* source, int32_t length, uint8_t* dst)
{
    uint32_t cursor =0x0;
    int32_t i=0,size =0, j =0,ostatok = (length%3);

    for (;i<length - (length%3);)
    {
        *((uint8_t*)&cursor) = source[i+2];
        *((uint8_t*)&cursor+1) = source[i+1];
        *((uint8_t*)&cursor+2) = source[i];
        i+=3;

        //shift
        for (j =0; j<4; j++ )
        {
            uint8_t zna4; // delete after debug
            cursor <<=6;
            zna4 = core::tools::base64::hibyte(core::tools::base64::hiword(cursor));
            dst[size+j] = coding[zna4];
            cursor &= 0x0FFFFFF;
        }
        size +=j;
    }
    if (ostatok)
    {
        cursor =0;
        if (ostatok>1)
        {
            *((uint8_t*)&cursor) = source[i+1];
            *((uint8_t*)&cursor+1) = source[i];
        }
        else
            *((uint8_t*)&cursor) = source[i];

        for (i = 0; i<ostatok+1; i++)
        {
            cursor<<=6;
            if (ostatok == 1)
            {
                dst[size+i] = coding[core::tools::base64::hibyte(core::tools::base64::loword(cursor))];
                cursor &= 0xFF;
            }
            else
            {
                dst[size+i] = coding[core::tools::base64::lobyte(core::tools::base64::hiword(cursor))];
                cursor &= 0xFFFF;
            }

        }
        size +=i;
        for (j=0; j < 4 - i;j++)
        {
            dst[size] = '=';
            size++;
        }
    }
    return (size);
}

std::string base64::hmac_base64(std::vector<uint8_t>& data, std::vector<uint8_t>& secret)
{
    const unsigned SHA_BLOCKSIZE = 64;

    uint8_t* text = &data[0];
    uint32_t textlen = (uint32_t)data.size();
    uint8_t* key = &secret[0];
    uint32_t keylen = (uint32_t)secret.size();

#define MIR_SHA256_HASH_SIZE 32

    unsigned char mdkey[MIR_SHA256_HASH_SIZE];
    unsigned char k_ipad[SHA_BLOCKSIZE], k_opad[SHA_BLOCKSIZE];

    SHA256_CTX ctx;

    uint8_t res[MIR_SHA256_HASH_SIZE];

    if ( keylen > SHA_BLOCKSIZE )
    {
        SHA256_Init( &ctx );
        SHA256_Update( &ctx, key, keylen );
        SHA256_Final( mdkey, &ctx );

        keylen = 32;
        key = mdkey;
    }

    memcpy( k_ipad, key, keylen );
    memcpy( k_opad, key, keylen );
    memset( k_ipad+keylen, 0x36, SHA_BLOCKSIZE - keylen );
    memset( k_opad+keylen, 0x5c, SHA_BLOCKSIZE - keylen );

    for ( uint32_t i = 0; i < keylen; i++ )
    {
        k_ipad[i] ^= 0x36;
        k_opad[i] ^= 0x5c;
    }

    SHA256_Init( &ctx );
    SHA256_Update( &ctx, k_ipad, SHA_BLOCKSIZE );
    SHA256_Update( &ctx, text, (int32_t) textlen );
    SHA256_Final( res, &ctx );

    SHA256_Init( &ctx );
    SHA256_Update( &ctx, k_opad,SHA_BLOCKSIZE );
    SHA256_Update( &ctx, res, (int32_t)MIR_SHA256_HASH_SIZE );
    SHA256_Final( res, &ctx );

    std::vector<uint8_t> temp_buffer(MIR_SHA256_HASH_SIZE*2);
    int32_t encoded = core::tools::base64::base64_encode((uint8_t*) res, MIR_SHA256_HASH_SIZE, &temp_buffer[0]);

    return std::string((char*) &temp_buffer[0], encoded);
}

using namespace boost::archive::iterators;

std::string base64::decode64(const std::string& val)
{
    typedef transform_width<binary_from_base64<std::string::const_iterator>, 8, 6> it;
    return boost::algorithm::trim_right_copy_if(std::string(it(std::begin(val)), it(std::end(val))), [](char c) {
        return c == '\0';
    });
}

std::string base64::encode64(const std::string& val)
{
    typedef base64_from_binary<transform_width<std::string::const_iterator, 6, 8>> it;
    auto tmp = std::string(it(std::begin(val)), it(std::end(val)));
    return tmp.append((3 - val.size() % 3) % 3, '=');
}

void core::tools::sha256(std::string_view _string, char _sha[65])
{
    unsigned char hash[SHA256_DIGEST_LENGTH];

    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, _string.data(), _string.size());
    SHA256_Final(hash, &sha256);

    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
    {
        sprintf(_sha + (i * 2), "%02x", hash[i]);
    }

    _sha[64] = 0;
}


#ifdef USE_DARKNET

#include <boost/filesystem.hpp>
#include <fstream>
#include <glog/logging.h>
#include <opencv2/opencv.hpp>
#include <sstream>
#include <unordered_set>

#include "nexus/backend/darknet_model.h"
#include "nexus/backend/slice.h"
#include "nexus/backend/utils.h"
#include "nexus/common/image.h"
#include "nexus/proto/control.pb.h"

namespace fs = boost::filesystem;

namespace nexus {
namespace backend {

namespace {

image cvmat_to_image(const cv::Mat& cv_img_rgb) {
  int c = cv_img_rgb.channels();
  int h = cv_img_rgb.rows;
  int w = cv_img_rgb.cols;
  image img = make_image(w, h, c);
  int count = 0;
  for (int k = 0; k < c; ++k) {
    for (int i = 0; i < h; ++i) {
      for (int j = 0; j < w; ++j) {
        cv::Vec3b pixel = cv_img_rgb.at<cv::Vec3b>(i, j);
        img.data[count++] = pixel.val[k] / 255.;
      }
    }
  }
  return img;
}

}

DarknetModel::DarknetModel(int gpu_id, const ModelInstanceConfig& config) :
    ModelInstance(gpu_id, config),
    first_input_array_(true) {
  // load darknet model
  CHECK(model_info_["cfg_file"]) << "Missing cfg_file in the model info";
  CHECK(model_info_["weight_file"]) << "Missing weight_file in the model info";
  fs::path model_dir = fs::path(model_info_["model_dir"].as<std::string>());
  fs::path cfg_path = model_dir / model_info_["cfg_file"].as<std::string>();
  fs::path weight_path = model_dir / model_info_["weight_file"].
                         as<std::string>();
  CHECK(fs::exists(cfg_path)) << "Config file " << cfg_path <<
      " doesn't exist";
  CHECK(fs::exists(weight_path)) << "Weight file " << weight_path <<
      " doesn't exist";
  image_height_ = 0;
  image_width_ = 0;
  if (model_session_.image_height() > 0) {
    CHECK_GT(model_session_.image_width(), 0) << "image_height and " <<
        "image_width must be set at same time";
    image_height_ = model_session_.image_height();
    image_width_ = model_session_.image_width();
  }

  // switch the current directory to the model directory as required
  // for loading a model in the darknet
  fs::path curr_dir = fs::current_path();
  fs::current_path(weight_path.parent_path());
  net_ = parse_network_cfg_spec(const_cast<char*>(cfg_path.string().c_str()),
                                gpu_id, max_batch_, image_width_,
                                image_height_);
  load_weights(net_, const_cast<char*>(weight_path.string().c_str()));
  fs::current_path(curr_dir);

  // Get input and output's shape and size
  auto input_layer = net_->layers[0];
  input_shape_.set_dims({max_batch_, input_layer.c, input_layer.h,
          input_layer.w});
  input_size_ = input_shape_.NumElements(1);
  auto output_layer = get_network_output_layer(net_);
  output_shape_.set_dims({max_batch_, output_layer.out_c, output_layer.out_h,
          output_layer.out_w});
  output_size_ = output_shape_.NumElements(1);
  LOG(INFO) << "Model " << model_session_id_ << ": input shape " <<
      input_shape_ << " (" << input_size_ << "), output shape " <<
      output_shape_ << " (" << output_size_ << ")";
  // find the output layer id
  for (int i = net_->n - 1; i > 0; --i) {
    if (net_->layers[i].type != COST) {
      output_layer_id_ = i;
      break;
    }
  }
  // Darknet doesn't have name for layers, so hardcode name as "output"
  output_name_ = "output";
  // load classnames
  if (model_info_["class_names"]) {
    fs::path cns_path = model_dir / model_info_["class_names"].
                        as<std::string>();
    LoadClassnames(cns_path.string(), &classnames_);
  }
}

DarknetModel::~DarknetModel() {
  free_network(net_);
}

Shape DarknetModel::InputShape() {
  return input_shape_;
}

std::unordered_map<std::string, Shape> DarknetModel::OutputShapes() {
  return {{ output_name_, output_shape_ }};
}

ArrayPtr DarknetModel::CreateInputGpuArray() {
  std::shared_ptr<Array> arr;
  if (first_input_array_) {
    auto buf = std::make_shared<Buffer>(
        net_->input_gpu, max_batch_ * input_size_ * sizeof(float),
        gpu_device_);
    arr = std::make_shared<Array>(DT_FLOAT, max_batch_ * input_size_, buf);
    first_input_array_ = false;
  } else {
    arr = std::make_shared<Array>(DT_FLOAT, max_batch_ * input_size_,
                                  gpu_device_);
  }
  return arr;
}

std::unordered_map<std::string, ArrayPtr> DarknetModel::GetOutputGpuArrays() {
  auto buf = std::make_shared<Buffer>(net_->layers[output_layer_id_].output_gpu,
                                      max_batch_ * output_size_ * sizeof(float),
                                      gpu_device_);
  auto arr = std::make_shared<Array>(DT_FLOAT, max_batch_ * output_size_, buf);
  return {{ output_name_, arr }};
}

void DarknetModel::Preprocess(std::shared_ptr<Task> task) {
  auto prepare_image = [&](cv::Mat& cv_img) {
#if 0
    image img = cvmat_to_image(cv_img);
    //image resized = resize_image(img, net_->w, net_->h);
    image resized = letterbox_image(img, net_->w, net_->h);
    size_t nfloats = net_->w * net_->h * 3;
    auto buf = std::make_shared<Buffer>(
        resized.data, nfloats * sizeof(float), cpu_device_, true);
    free_image(img);
#else
    cv::Mat resized_image;
    cv::resize(cv_img, resized_image, cv::Size(net_->w, net_->h));
    image input = cvmat_to_image(resized_image);
    size_t nfloats = net_->w * net_->h * 3;
    auto buf = std::make_shared<Buffer>(
        input.data, nfloats * sizeof(float), cpu_device_, true);
#endif
    auto in_arr = std::make_shared<Array>(DT_FLOAT, nfloats, buf);
    task->AppendInput(in_arr);
  };

  const auto& query = task->query;
  const auto& input_data = query.input();
  switch (input_data.data_type()) {
    case DT_IMAGE: {
      cv::Mat cv_img_rgb = DecodeImage(input_data.image(), CO_RGB);
      task->attrs["im_height"] = cv_img_rgb.rows;
      task->attrs["im_width"] = cv_img_rgb.cols;
      if (query.window_size() > 0) {
        for (int i = 0; i < query.window_size(); ++i) {
          auto rect = query.window(i);
          cv::Mat crop_img = cv_img_rgb(cv::Rect(
              rect.left(), rect.top(), rect.right() - rect.left(),
              rect.bottom() - rect.top()));
          prepare_image(crop_img);
        }
      } else {
        prepare_image(cv_img_rgb);
      }
      break;
    }
    default:
      task->result.set_status(INPUT_TYPE_INCORRECT);
      task->result.set_error_message("Input type incorrect: " +
                                     DataType_Name(input_data.data_type()));
      break;
  }
}

void DarknetModel::Forward(std::shared_ptr<BatchTask> batch_task) {
  size_t batch_size = batch_task->batch_size();
  net_->input_gpu = batch_task->GetInputArray()->Data<float>();
  set_batch_network_lightweight(net_, batch_size);
  network_predict_gpu_nocopy(net_);
  layer l = net_->layers[output_layer_id_];
  auto out_arr = batch_task->GetOutputArray(output_name_);
  Memcpy(out_arr->Data<void>(), cpu_device_, l.output_gpu, gpu_device_,
         batch_size * output_size_ * sizeof(float));
  batch_task->SliceOutputBatch({{
        output_name_, Slice(batch_size, output_size_) }});
}

void DarknetModel::Postprocess(std::shared_ptr<Task> task) {
  const auto& query = task->query;
  auto* result = &task->result;
  result->set_status(CTRL_OK);
  for (auto& output : task->outputs) {
    auto out_arr = output->arrays.at(output_name_);
    float* out_data = out_arr->Data<float>();
    // TODO: check predicates in the query
    if (type() == "detection") {
      layer l = net_->layers[net_->n - 1];
      size_t nboxes = l.w * l.h * l.n;
      size_t nprobs = nboxes * (l.classes + 1);
      int* boxes = new int[nboxes * 4];
      float* probs = new float[nprobs];
      int only_objectness = 0;
      float tree_threshold = 0.5;
      int relative = 1;
      float nms = 0.3;
      float threshold = 0.24;
      int im_height = task->attrs["im_height"].as<int>();
      int im_width = task->attrs["im_width"].as<int>();
      output_detection_results(
          out_data, l, im_width, im_height, net_->w, net_->h, threshold, probs,
          nprobs, boxes, nboxes * 4, only_objectness, nullptr, tree_threshold,
          relative, nms);
      MarshalDetectionResult(query, probs, nprobs, boxes, nboxes, result);
      delete[] boxes;
      delete[] probs;
    } else if (type() == "classification") {
      if (classnames_.empty()) {
        PostprocessClassification(query, out_data, output_size_, result);
      } else {
        PostprocessClassification(query, out_data, output_size_, result,
                                  &classnames_);
      }
    } else {
      std::ostringstream oss;
      oss << "Unsupported model type " << type() << " for " << framework();
      result->set_status(MODEL_TYPE_NOT_SUPPORT);
      result->set_error_message(oss.str());
      break;
    }
  }
}

void DarknetModel::MarshalDetectionResult(
    const QueryProto& query, const float* probs, size_t nprobs,
    const int* boxes, size_t nboxes, QueryResultProto* result) {
  std::vector<std::string> output_fields(query.output_field().begin(),
                                         query.output_field().end());
  if (output_fields.size() == 0) {
    output_fields.push_back("rect");
    output_fields.push_back("class_name");
  }
  size_t nclasses_plus_1 = nprobs / nboxes;
  for (size_t i = 0; i < nboxes; ++i) {
    const float* ps = &probs[i * nclasses_plus_1];
    const int* bs = &boxes[i * 4];
    bool allzero = true;
    for (size_t j = 0; j < 4; ++j) {
      if (bs[j] != 0) {
        allzero = false;
        break;
      }
    }
    if (allzero) {
      continue;
    }
    float max_prob = 0.;
    int max_idx = -1;
    for (size_t j = 0; j < nclasses_plus_1 - 1; ++j) {
      float p = float(ps[j]);
      if (p > 0) {
        if (p > max_prob) {
          max_prob = p;
          max_idx = j;
        }
      }
    }
    // fill in detection result
    auto record = result->add_output();
    for (auto field : output_fields) {
      if (field == "rect") {
        auto value = record->add_named_value();
        value->set_name("rect");
        value->set_data_type(DT_RECT);
        auto rect = value->mutable_rect();
        rect->set_left(bs[0]);
        rect->set_right(bs[1]);
        rect->set_top(bs[2]);
        rect->set_bottom(bs[3]);
      } else if (field == "objectness") {
        auto value = record->add_named_value();
        value->set_name("objectness");
        value->set_data_type(DT_FLOAT);
        value->set_f(ps[nclasses_plus_1 - 1]);
      } else if (field == "class_id") {
        auto value = record->add_named_value();
        value->set_name("class_id");
        value->set_data_type(DT_INT32);
        value->set_i(max_idx);
      } else if (field == "class_prob") {
        auto value = record->add_named_value();
        value->set_name("class_prob");
        value->set_data_type(DT_FLOAT);
        value->set_f(max_prob);
      } else if (field == "class_name") {
        auto value = record->add_named_value();
        value->set_name("class_name");
        value->set_data_type(DT_STRING);
        if (classnames_.size() > max_idx) {
          value->set_s(classnames_.at(max_idx));
        }
      }
    }
  }
}

} // namespace backend
} // namespace nexus

#endif // USE_DARKNET


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

#include <fstream>
#include <iomanip>

#include <cif++/CifUtils.hpp>
#include <cif++/Structure.hpp>

#include <boost/algorithm/string.hpp>
#include <boost/date_time.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/program_options.hpp>

#include <zeep/json/element.hpp>

#include "blast.hpp"
#include "queue.hpp"
#include "revision.hpp"

namespace po = boost::program_options;
namespace fs = std::filesystem;
namespace io = boost::iostreams;
namespace ba = boost::algorithm;

using json = zeep::json::element;

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

using mmcif::Point;

struct CAtom
{
	CAtom(const CAtom &) = default;
	CAtom(CAtom &&) = default;

	CAtom(mmcif::AtomType type, Point pt, int charge)
		: type(type), pt(pt)
	{
		radius = charge == 0 ?
			mmcif::AtomTypeTraits(type).radius(mmcif::RadiusType::VanderWaals) :
			mmcif::AtomTypeTraits(type).effective_ionic_radius(charge);

		if (std::isnan(radius))
			throw std::runtime_error("Unknown radius for atom " + mmcif::AtomTypeTraits(type).symbol() + " with charge " + std::to_string(charge));
	}

	CAtom(const mmcif::Atom &atom)
		: CAtom(atom.type(), atom.location(), atom.charge())
	{
	}

	mmcif::AtomType type;
	Point pt;
	float radius;
};

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

json CalculateClashScore(const std::vector<CAtom> &polyAtoms, const std::vector<CAtom> &resAtoms, float maxDistance)
{
	auto maxDistanceSq = maxDistance * maxDistance;

	json result;

	auto &dp = result["distance"];

	int n = 0, o = 0;
	double sumOverlapSq = 0;

	for (auto &pa : polyAtoms)
	{
		bool near = false;

		for (auto &ra : resAtoms)
		{
			auto d = DistanceSquared(pa.pt, ra.pt);

			if (d >= maxDistanceSq)
				continue;

			near = true;

			d = std::sqrt(d);

			auto overlap = pa.radius + ra.radius - d;
			if (overlap < 0)
				overlap = 0;

			if (overlap > 0)
			{
				++n;
				sumOverlapSq += overlap * overlap;
			}

			json d_pair;
			d_pair.push_back(d);
			d_pair.push_back(overlap);

			dp.push_back(std::move(d_pair));
		}

		if (near)
			++o;
	}

	result["score"] = o ? sumOverlapSq / o : 0;
	result["clash_count"] = n;
	result["poly_atom_count"] = o;
	result["ligand_atom_count"] = resAtoms.size();

	return result;
}

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

int a_main(int argc, const char *argv[])
{
	using namespace std::literals;
	using namespace cif::literals;

	po::options_description visible_options(argv[0] + " [options] input-file [output-file]"s);

	visible_options.add_options()
		("distance-cutoff", po::value<float>()->default_value(4), "The max distance between polymer atoms and ligand atoms used in calculating clash scores")
		("config", po::value<std::string>(), "Config file")
		("help,h", "Display help message")
		("version", "Print version")
		("verbose,v", "Verbose output")
		("quiet", "Do not produce warnings");

	po::options_description hidden_options("hidden options");
	hidden_options.add_options()
		("xyzin,i", po::value<std::string>(), "coordinates file")
		("output,o", po::value<std::string>(), "Output to this file")
		("debug,d", po::value<int>(), "Debug level (for even more verbose output)");

	po::options_description cmdline_options;
	cmdline_options.add(visible_options).add(hidden_options);

	po::positional_options_description p;
	p.add("xyzin", 1);
	p.add("output", 1);

	po::variables_map vm;
	po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(p).run(), vm);

	fs::path configFile = "alphafill.conf";
	if (vm.count("config"))
		configFile = vm["config"].as<std::string>();

	if (not fs::exists(configFile) and getenv("HOME") != nullptr)
		configFile = fs::path(getenv("HOME")) / ".config" / configFile;

	if (fs::exists(configFile))
	{
		std::ifstream cfgFile(configFile);
		if (cfgFile.is_open())
			po::store(po::parse_config_file(cfgFile, visible_options, true), vm);
	}

	po::notify(vm);

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

	if (vm.count("version"))
	{
		write_version_string(std::cout, vm.count("verbose"));
		exit(0);
	}

	if (vm.count("help") or vm.count("xyzin") == 0)
	{
		std::cout << visible_options << std::endl;
		exit(vm.count("help") ? 0 : 1);
	}

	if (vm.count("quiet"))
		cif::VERBOSE = -1;

	if (vm.count("verbose"))
		cif::VERBOSE = 1;

	if (vm.count("debug"))
		cif::VERBOSE = vm["debug"].as<int>();

	float maxDistance = vm["distance-cutoff"].as<float>();

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

	fs::path xyzin = vm["xyzin"].as<std::string>();
	mmcif::File f(xyzin);
	mmcif::Structure structure(f, 1, mmcif::StructureOpenOptions::SkipHydrogen);

	auto &db = f.data();

	auto &polies = structure.polymers();
	if (polies.size() != 1)
		throw std::runtime_error("Number of polymers in this file is not exactly 1");

	auto &poly = polies.front();

	std::vector<CAtom> polyAtoms;

	for (auto &res : poly)
	{
		for (auto &atom : res.atoms())
			polyAtoms.emplace_back(atom);
	}

	std::vector<std::string> ligandIDs;
	for (auto &&[id] : db["struct_asym"].rows<std::string>("id"))
	{
		if (id == poly.asymID())
			continue;
		ligandIDs.emplace_back(id);
	}

	for (auto &asymID : ligandIDs)
	{
		std::vector<CAtom> resAtoms;

		// auto &res = structure.getResidue(asymID);

		// for (auto &atom : res.atoms())
		// 	resAtoms.emplace_back(atom);

		// assert(not resAtoms.empty());

		for (const auto &[type_symbol, x, y, z, charge, comp_id] :
			db["atom_site"].find<std::string,float,float,float,std::optional<int>,std::string>(
				"label_asym_id"_key == asymID,
				"type_symbol", "Cartn_x", "Cartn_y", "Cartn_z", "pdbx_formal_charge", "label_comp_id"))
		{
			mmcif::AtomTypeTraits att(type_symbol);

			int formal_charge = charge.value_or(0);

			if (not charge.has_value() and att.isMetal())
			{
				auto compound = mmcif::CompoundFactory::instance().create(comp_id);
				if (compound)
					formal_charge = compound->formalCharge();
			}

			resAtoms.emplace_back(att.type(), Point{ x, y, z }, formal_charge);
		}

		json clash{
			{ "asym_id", asymID },
			{ "clash", CalculateClashScore(polyAtoms, resAtoms, maxDistance) }
		};

		std::cout << clash << std::endl;
	}

	return 0;
}

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

// recursively print exception whats:
void print_what(const std::exception &e)
{
	std::cerr << e.what() << std::endl;
	try
	{
		std::rethrow_if_nested(e);
	}
	catch (const std::exception &nested)
	{
		std::cerr << " >> ";
		print_what(nested);
	}
}

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

int main(int argc, const char *argv[])
{
	int result = 0;

	try
	{
#if defined(DATA_DIR)
		cif::addDataDirectory(DATA_DIR);
#endif
		result = a_main(argc, argv);
	}
	catch (const std::exception &ex)
	{
		print_what(ex);
		exit(1);
	}

	return result;
}


#include "OwnedItems.hpp"
#include "SerializeUtility.hpp"
#include <boost/test/auto_unit_test.hpp>
#include <iostream>

void OwnedItemsDataLibrary()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(DATA_LIBRARY);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 1 );
  BOOST_CHECK ( oi.GetCostSum() == 15 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 10 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 10 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(DATA_LIBRARY);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(DATA_LIBRARY);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 2 );
  BOOST_CHECK ( oi.GetCostSum() == 30 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 20 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 20 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(DATA_LIBRARY);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsWarehouse()
{
  OwnedItems oi;
  std::vector<ItemType> it;
  FactoryType fac;

  fac = oi.AddItem(WAREHOUSE);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 1 );
  BOOST_CHECK ( oi.GetCostSum() == 25 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 3 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(WAREHOUSE);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(WAREHOUSE);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 2 );
  BOOST_CHECK ( oi.GetCostSum() == 50 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 6 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(WAREHOUSE);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsHeavyEquipment()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(HEAVY_EQUIPMENT);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 1 );
  BOOST_CHECK ( oi.GetCostSum() == 30 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 5 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 5 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 10 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == true );

  it.push_back(HEAVY_EQUIPMENT);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(HEAVY_EQUIPMENT);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 2 );
  BOOST_CHECK ( oi.GetCostSum() == 60 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 10 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 10 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 20 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == true );

  it.push_back(HEAVY_EQUIPMENT);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsNodule()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(NODULE);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 2 );
  BOOST_CHECK ( oi.GetCostSum() == 25 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 3 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(NODULE);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(NODULE);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 4 );
  BOOST_CHECK ( oi.GetCostSum() == 50 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 6 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(NODULE);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsScientists()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(SCIENTISTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 2 );
  BOOST_CHECK ( oi.GetCostSum() == 40 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 1 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(SCIENTISTS);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(SCIENTISTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 4 );
  BOOST_CHECK ( oi.GetCostSum() == 80 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 2 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(SCIENTISTS);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsOrbitalLab()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(ORBITAL_LAB);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 3 );
  BOOST_CHECK ( oi.GetCostSum() == 50 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 1 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ORBITAL_LAB);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(ORBITAL_LAB);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 6 );
  BOOST_CHECK ( oi.GetCostSum() == 100 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 2 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ORBITAL_LAB);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsRobots()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(ROBOTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 3 );
  BOOST_CHECK ( oi.GetCostSum() == 50 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == UNLIMITED_ROBOTS );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 1 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 1 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ROBOTS);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(ROBOTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 6 );
  BOOST_CHECK ( oi.GetCostSum() == 100 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == UNLIMITED_ROBOTS );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == UNLIMITED_ROBOTS );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 2 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ROBOTS);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsLaboratory()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(LABORATORY);

  BOOST_CHECK ( fac == RESEARCH_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 5 );
  BOOST_CHECK ( oi.GetCostSum() == 100 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == true );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(LABORATORY);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(LABORATORY);

  BOOST_CHECK ( fac == RESEARCH_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 10 );
  BOOST_CHECK ( oi.GetCostSum() == 200 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == true );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(LABORATORY);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsEcoplants()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(ECOPLANTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 5 );
  BOOST_CHECK ( oi.GetCostSum() == 50 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 10 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 5 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ECOPLANTS);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(ECOPLANTS);

  BOOST_CHECK ( fac == NO_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 10 );
  BOOST_CHECK ( oi.GetCostSum() == 100 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 20 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 5 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(ECOPLANTS);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsOutpost()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(OUTPOST);

  BOOST_CHECK ( fac == TITANIUM_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 5 );
  BOOST_CHECK ( oi.GetCostSum() == 100 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 3 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 5 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(OUTPOST);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(OUTPOST);

  BOOST_CHECK ( fac == TITANIUM_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 10 );
  BOOST_CHECK ( oi.GetCostSum() == 200 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 6 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 10 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(OUTPOST);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsSpaceStation()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(SPACE_STATION);

  BOOST_CHECK ( fac == SPACE_STATION_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 120 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(SPACE_STATION);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(SPACE_STATION);

  BOOST_CHECK ( fac == SPACE_STATION_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 240 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(SPACE_STATION);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsPlanetaryCruiser()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(PLANETARY_CRUISER);

  BOOST_CHECK ( fac == PLANETARY_CRUISER_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 160 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(PLANETARY_CRUISER);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(PLANETARY_CRUISER);

  BOOST_CHECK ( fac == PLANETARY_CRUISER_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 320 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(PLANETARY_CRUISER);
  BOOST_CHECK ( oi.GetItemList() == it);
}

void OwnedItemsMoonBase()
{
  OwnedItems oi;
  FactoryType fac;
  std::vector<ItemType> it;

  fac = oi.AddItem(MOON_BASE);

  BOOST_CHECK ( fac == MOON_BASE_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 200 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(MOON_BASE);
  BOOST_CHECK ( oi.GetItemList() == it);

  fac = oi.AddItem(MOON_BASE);

  BOOST_CHECK ( fac == MOON_BASE_FACTORY );
  BOOST_CHECK ( oi.GetItemVPs() == 0 );
  BOOST_CHECK ( oi.GetCostSum() == 400 );

  BOOST_CHECK ( oi.GetDiscount(DATA_LIBRARY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(WAREHOUSE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(HEAVY_EQUIPMENT) == 0 );
  BOOST_CHECK ( oi.GetDiscount(NODULE) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SCIENTISTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ORBITAL_LAB) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ROBOTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(LABORATORY) == 0 );
  BOOST_CHECK ( oi.GetDiscount(ECOPLANTS) == 0 );
  BOOST_CHECK ( oi.GetDiscount(OUTPOST) == 0 );
  BOOST_CHECK ( oi.GetDiscount(SPACE_STATION) == 0 );
  BOOST_CHECK ( oi.GetDiscount(PLANETARY_CRUISER) == 0 );
  BOOST_CHECK ( oi.GetDiscount(MOON_BASE) == 0 );

  BOOST_CHECK ( oi.GetHandIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleIncrease() == 0 );
  BOOST_CHECK ( oi.GetPeopleCost() == 10 );
  BOOST_CHECK ( oi.GetResearchProduction() == 0 );
  BOOST_CHECK ( oi.GetMicrobioticProduction() == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( FIRST_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( SECOND_UNLIMITED ) == 0 );
  BOOST_CHECK ( oi.GetNumRobotsPerPerson( ALWAYS_MULTIPLICATIVE ) == 0 );
  BOOST_CHECK ( oi.CanBuildResearch() == false );
  BOOST_CHECK ( oi.CanBuildTitanium() == false );

  it.push_back(MOON_BASE);
  BOOST_CHECK ( oi.GetItemList() == it);
}

BOOST_AUTO_TEST_CASE( OwnedItemsConstructor )
{
  OwnedItemsDataLibrary();
  OwnedItemsWarehouse();
  OwnedItemsHeavyEquipment();
  OwnedItemsNodule();
  OwnedItemsScientists();
  OwnedItemsOrbitalLab();
  OwnedItemsRobots();
  OwnedItemsLaboratory();
  OwnedItemsEcoplants();
  OwnedItemsOutpost();
  OwnedItemsSpaceStation();
  OwnedItemsPlanetaryCruiser();
  OwnedItemsMoonBase();

  OwnedItems oi;
  std::vector<ItemType> it;
  oi.AddItem(LABORATORY);      it.push_back(LABORATORY);
  oi.AddItem(HEAVY_EQUIPMENT); it.push_back(HEAVY_EQUIPMENT);
  oi.AddItem(ROBOTS);          it.push_back(ROBOTS);
  oi.AddItem(ROBOTS);          it.push_back(ROBOTS);

  BOOST_CHECK( oi.GetItemList() == it);

  OwnedItems noi;
  SerialTransfer(oi,noi);

  BOOST_CHECK( noi.GetItemList() == it);
}