#define SHA1_CHECKSUM
#include "framework/serialization/providers/providerwithchecksum.h"
#include "framework/configfile.h"
#include "framework/logger.h"
#include "framework/trace.h"
#include "library/strings.h"
#include "library/strings_format.h"
#include <sstream>

#include "dependencies/pugixml/src/pugixml.hpp"

// Disable automatic #pragma linking for boost - only enabled in msvc and that should provide boost
// symbols as part of the module that uses it
#define BOOST_ALL_NO_LIB
#include <boost/crc.hpp>
#include <boost/version.hpp>
#if BOOST_VERSION >= 106600
#include <boost/uuid/detail/sha1.hpp>
#else
#include <boost/uuid/sha1.hpp>
#endif
#include <boost/uuid/uuid.hpp>

namespace OpenApoc
{
ConfigOptionBool useCRCChecksum("Framework.Serialization", "CRC",
                                "use a CRC checksum when saving files", true);
ConfigOptionBool useSHA1Checksum("Framework.Serialization", "SHA1",
                                 "use a SHA1 checksum when saving files", false);

static UString calculateSHA1Checksum(const std::string &str)
{
	TRACE_FN;
	UString hashString;

	boost::uuids::detail::sha1 sha;
	sha.process_bytes(str.c_str(), str.size());
	unsigned int hash[5];
	sha.get_digest(hash);
	for (int i = 0; i < 5; i++)
	{
		unsigned int v = hash[i];
		for (int j = 0; j < 4; j++)
		{
			// FIXME: Probably need to do the reverse for big endian?
			unsigned int byteHex = v & 0xff000000;
			byteHex >>= 24;
			hashString += format("%02x", byteHex).str();
			v <<= 8;
		}
	}

	return hashString;
}
static UString calculateCRCChecksum(const std::string &str)
{
	TRACE_FN;
	UString hashString;

	boost::crc_32_type crc;
	crc.process_bytes(str.c_str(), str.size());
	auto hash = crc.checksum();
	hashString = format("%08x", hash);
	return hashString;
}

static UString calculateChecksum(const UString &type, const std::string &str)
{
	if (type == "CRC")
	{
		return calculateCRCChecksum(str);
	}
	else if (type == "SHA1")
	{
		return calculateSHA1Checksum(str);
	}
	else
	{
		LogWarning("Unknown checksum type \"%s\"", type);
		return "";
	}
}

std::string ProviderWithChecksum::serializeManifest()
{
	pugi::xml_document manifestDoc;
	auto decl = manifestDoc.prepend_child(pugi::node_declaration);
	decl.append_attribute("version") = "1.0";
	decl.append_attribute("encoding") = "UTF-8";
	auto root = manifestDoc.root().append_child();
	root.set_name("checksums");
	for (auto &p : checksums)
	{
		auto node = root.append_child();
		node.set_name("file");
		node.text().set(p.first.cStr());
		for (auto &csum : p.second)
		{
			auto checksumNode = node.append_child();
			checksumNode.set_name(csum.first.cStr());
			checksumNode.text().set(csum.second.cStr());
		}
	}
	std::stringstream ss;
	manifestDoc.save(ss, "  ");
	return ss.str();
}

bool ProviderWithChecksum::parseManifest(const std::string &manifestData)
{
	std::stringstream ss(manifestData);
	pugi::xml_document manifestDoc;
	auto parse_result = manifestDoc.load(ss);
	if (!parse_result)
	{
		LogWarning("Failed to parse checksum.xml : \"%s\" at \"%llu\"", parse_result.description(),
		           (unsigned long long)parse_result.offset);
		return false;
	}
	auto rootNode = manifestDoc.child("checksums");
	if (!rootNode)
	{
		LogWarning("checksum.xml has invalid root node");
		return false;
	}
	auto fileNode = rootNode.child("file");
	while (fileNode)
	{
		UString fileName = fileNode.text().get();

		if (this->checksums.find(fileName) != this->checksums.end())
		{
			LogWarning("Multiple manifest entries for path \"%s\"", fileName);
		}

		this->checksums[fileName] = {};

		auto checksumNode = fileNode.first_child();
		while (checksumNode)
		{
			if (checksumNode.type() == pugi::xml_node_type::node_element)
			{
				UString checksumType = checksumNode.name();
				this->checksums[fileName][checksumType] = checksumNode.text().get();
			}
			checksumNode = checksumNode.next_sibling();
		}
		fileNode = fileNode.next_sibling("file");
	}

	return true;
}
bool ProviderWithChecksum::openArchive(const UString &path, bool write)
{

	if (!inner->openArchive(path, write))
	{
		return false;
	}

	if (!write)
	{
		UString result;
		if (!inner->readDocument("checksum.xml", result))
		{
			LogInfo("Missing manifest file in \"%s\"", path);
			return true;
		}
		parseManifest(result.str());
	}
	return true;
}
bool ProviderWithChecksum::readDocument(const UString &path, UString &result)
{
	if (inner->readDocument(path, result))
	{
		for (auto &csum : checksums[path.str()])
		{
			auto expectedCSum = csum.second;
			auto calculatedCSum = calculateChecksum(csum.first, result.str());
			if (expectedCSum != calculatedCSum)
			{
				LogWarning("File \"%s\" has incorrect \"%s\" checksum \"%s\", expected \"%s\"",
				           path, csum.first, calculatedCSum, expectedCSum);
			}
			else
			{
				LogDebug("File \"%s\" matches \"%s\" checksum \"%s\"", path, csum.first,
				         calculatedCSum);
			}
		}
		return true;
	}

	return false;
}
bool ProviderWithChecksum::saveDocument(const UString &path, const UString &contents)
{

	if (inner->saveDocument(path, contents))
	{
		if (this->checksums.find(path) != this->checksums.end())
		{
			LogWarning("Multiple document entries for path \"%s\"", path);
		}
		this->checksums[path.str()] = {};
		if (useCRCChecksum.get())
			this->checksums[path.str()]["CRC"] = calculateChecksum("CRC", contents.str()).str();
		if (useSHA1Checksum.get())
			this->checksums[path.str()]["SHA1"] = calculateChecksum("SHA1", contents.str()).str();
		return true;
	}
	return false;
}
bool ProviderWithChecksum::finalizeSave()
{
	UString manifest = serializeManifest();
	inner->saveDocument("checksum.xml", manifest);
	return inner->finalizeSave();
}
} // namespace OpenApoc


/**
*  @file    mdm_VolumeAnalysis.cxx
*  @brief   Implementation of mdm_VolumeAnalysis class
*
*  Original author MA Berks 24 Oct 2018
*  (c) Copyright QBI, University of Manchester 2020
*/

#ifndef MDM_API_EXPORTS
#define MDM_API_EXPORTS
#endif // !MDM_API_EXPORTS

#include "mdm_VolumeAnalysis.h"

#include <cmath>
#include <chrono>  // chrono::system_clock
#include <sstream> // stringstream
#include <algorithm>
#include <numeric>
#include <boost/format.hpp>

#include <mdm_exception.h>
#include <madym/mdm_ProgramLogger.h>
#include <madym/mdm_AIF.h>

//Names of output maps
const std::string mdm_VolumeAnalysis::MAP_NAME_IAUC = "IAUC"; //Appended with IAUC time
const std::string mdm_VolumeAnalysis::MAP_NAME_RESDIUALS = "residuals";
const std::string mdm_VolumeAnalysis::MAP_NAME_ENHANCING = "enhVox";
const std::string mdm_VolumeAnalysis::MAP_NAME_ROI = "ROI";
const std::string mdm_VolumeAnalysis::MAP_NAME_ERROR_TRACKER = "error_tracker";
const std::string mdm_VolumeAnalysis::MAP_NAME_T1 = "T1";
const std::string mdm_VolumeAnalysis::MAP_NAME_M0 = "M0";
//Signal derived concentration - appended with volume number
const std::string mdm_VolumeAnalysis::MAP_NAME_CT_SIG = "Ct_sig";
//Model estimated concentration - appended with volume number
const std::string mdm_VolumeAnalysis::MAP_NAME_CT_MOD = "Ct_mod";

MDM_API mdm_VolumeAnalysis::mdm_VolumeAnalysis()
	:
	T1Mapper_(errorTracker_, ROI_),
	testEnhancement_(false),
	useM0Ratio_(true),
  useB1correction_(false),
  outputCt_sig_(false),
  outputCt_mod_(false),
  useNoise_(false),
  StDataMaps_(0),
  CtDataMaps_(0),
  CtModelMaps_(0),
  dynamicTimes_(0),
  noiseVar_(0),
  firstImage_(0),
  lastImage_(0),
	maxIterations_(0),
  model_(NULL)
{
	setIAUCtimes({ 60.0, 90.0, 120.0 }, true, false);
}

MDM_API mdm_VolumeAnalysis::~mdm_VolumeAnalysis()
{

}

MDM_API void mdm_VolumeAnalysis::reset()
{
  ROI_.reset();
  AIFmap_.reset();
  StDataMaps_.clear();
  CtDataMaps_.clear();
  CtModelMaps_.clear();
  dynamicTimes_.clear();
  noiseVar_.clear();
  dynamicMetaData_.reset();

  T1Mapper_.reset();
  errorTracker_.resetErrorImage();

  /* Images for inputs and output */
  pkParamMaps_.clear();
  IAUCMaps_.clear();
  modelResidualsMap_.reset();
  enhVoxMap_.reset();
  initMapParams_.clear();
}

//
MDM_API mdm_ErrorTracker& mdm_VolumeAnalysis::errorTracker()
{
	return errorTracker_;
}

//
MDM_API mdm_T1Mapper& mdm_VolumeAnalysis::T1Mapper()
{
	return T1Mapper_;
}

//
MDM_API const mdm_T1Mapper& mdm_VolumeAnalysis::T1Mapper() const
{
  return T1Mapper_;
}

//
MDM_API void mdm_VolumeAnalysis::setROI(const mdm_Image3D ROI)
{
  errorTracker_.checkOrSetDimension(ROI, "ROI");
  ROI_ = ROI;
	}

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::ROI() const
{
	return ROI_;
}

MDM_API void mdm_VolumeAnalysis::setAIFmap(
  const mdm_Image3D map)
{
  errorTracker_.checkOrSetDimension(map, "AIF map");

  if (map.type() != mdm_Image3D::ImageType::TYPE_AIFVOXELMAP)
  {
    AIFmap_.copy(map);
    AIFmap_.setType(mdm_Image3D::ImageType::TYPE_AIFVOXELMAP);
    for (size_t idx = 0; idx < map.numVoxels(); idx++)
      if (map.voxel(idx) > 0)
        AIFmap_.setVoxel(idx, mdm_AIF::AIFmapVoxel::SELECTED);
  }
  else
    AIFmap_ = map;
}

//
MDM_API std::vector<double> mdm_VolumeAnalysis::AIFfromMap()
{
  if (!AIFmap_)
    throw mdm_exception(__func__, "AIF map not set.");

  checkDynamicsSet();

  std::vector<size_t> badVoxels;
  std::vector<double>baseAIF;
  computeMeanCt(AIFmap_, mdm_AIF::AIFmapVoxel::SELECTED, baseAIF, badVoxels);
  for (const auto vox : badVoxels)
    AIFmap_.setVoxel(vox, mdm_AIF::AIFmapVoxel::INVALID_CT);

  return baseAIF;
};

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::AIFmap() const
{
  return AIFmap_;
}

MDM_API void mdm_VolumeAnalysis::addStDataMap(const mdm_Image3D dynImg)
{
  //Check the image dimension match
  errorTracker_.checkOrSetDimension(dynImg,
    "dynamic image " + std::to_string(StDataMaps_.size()+1));

	//Add the image to the list
	StDataMaps_.push_back(dynImg);

  //First map we add, set the reference image
  if (!dynamicMetaData_)
    setDynamicMetaData(dynImg);

	//Extract the time from the header, converted to minutes
  setDynamicTime(dynImg);

  if (useNoise_)
  {
    double noise = dynImg.info().noiseSigma.value();
    if (noise != NAN)
      noiseVar_.push_back(noise);
  }

	if (outputCt_sig_ && (CtDataMaps_.size() == numSt() - 1))
	{
		mdm_Image3D ctMap;
		ctMap.copy(dynImg);
		ctMap.setTimeStampFromDoubleStr(dynImg.timeStamp());
		ctMap.setType(mdm_Image3D::ImageType::TYPE_CAMAP);
		CtDataMaps_.push_back(ctMap);
	}
  if (outputCt_mod_ && (CtModelMaps_.size() == numSt() - 1))
  {
    mdm_Image3D cModMap;
    cModMap.copy(dynImg);
		cModMap.setTimeStampFromDoubleStr(dynImg.timeStamp());
    cModMap.setType(mdm_Image3D::ImageType::TYPE_CAMAP);
    CtModelMaps_.push_back(cModMap);
  }
}

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::StDataMap(size_t i) const
{
  if (i >= StDataMaps_.size())
    throw mdm_exception(__func__, boost::format(
      "Attempting to access S(t) map at index %1% when there are only %2% S(t) maps")
      % i % StDataMaps_.size());

  return StDataMaps_[i];
}

//
MDM_API const std::vector<mdm_Image3D> & mdm_VolumeAnalysis::StDataMaps() const
{
  return StDataMaps_;
}

//
MDM_API size_t  mdm_VolumeAnalysis::numDynamics() const
{
	if (StDataMaps_.empty())
		return numCtSignal();

  return numSt();
}

//
MDM_API void mdm_VolumeAnalysis::computeMeanCt(
  const mdm_Image3D &map, double map_val,
  std::vector<double> &meanCt, std::vector<size_t> &badVoxels) const
{
  errorTracker_.checkDimension(map, "Ct ROI");

  auto nTimes = numDynamics();

  if (!nTimes)
    throw mdm_exception(__func__, "Trying to compute mean C(t) when no dynamic maps set");

  meanCt.resize(nTimes, 0);
  badVoxels.clear();
  double numVox = 0;
  for (size_t idx = 0; idx < map.numVoxels(); idx++)
  {
    if (map.voxel(idx) == map_val)
    {
      std::vector<double> Ct;
      if (computeCt_)
      {
        mdm_DCEVoxel vox(setUpVoxel(idx));
        if (vox.status() != mdm_DCEVoxel::OK)
        {
          badVoxels.push_back(idx);
          continue;
        }

        Ct = vox.CtData();
      }
      else
        voxelCtData(idx, Ct);

      for (size_t t = 0; t < nTimes; t++)
        meanCt[t] += Ct[t];

      numVox++;
    }
  }

  if (numVox)
    for (auto &v : meanCt)
      v /= numVox;

  return;
}

//
MDM_API void mdm_VolumeAnalysis::addCtDataMap(const mdm_Image3D ctMap)
{
  //Check the image dimension match
  errorTracker_.checkOrSetDimension(ctMap,
    "concentration image " + std::to_string(CtDataMaps_.size() + 1));

  //We don't allow mixed setting of Ct and St maps - so if St already set, throw error
  if (!StDataMaps_.empty())
    throw mdm_exception(__func__, "Attempting to add C(t) when S(t) maps already set");

	//Add the image to the list
	CtDataMaps_.push_back(ctMap);

  //First map we add, set the reference image
  if (!dynamicMetaData_)
    setDynamicMetaData(ctMap);

	//Extract the time from the header, converted to minutes
	dynamicTimes_.push_back(ctMap.minutesFromTimeStamp());

  //Check if there is a noise variance associated with the volume
  if (useNoise_)
  {
    double noise = ctMap.info().noiseSigma.value();
    if (!isnan(noise))
      noiseVar_.push_back(noise);
  }
}

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::CtDataMap(size_t i) const
{
  if (i >= CtDataMaps_.size())
    throw mdm_exception(__func__, boost::format(
      "Attempting to access C(t) map at index %1% when there are only %2% C(t) maps")
      % i % CtDataMaps_.size());

  return CtDataMaps_[i];
}

//
MDM_API const std::vector<mdm_Image3D> & mdm_VolumeAnalysis::CtDataMaps() const
{
  return CtDataMaps_;
}

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::CtModelMap(size_t i) const
{
  if (i >= CtModelMaps_.size())
    throw mdm_exception(__func__, boost::format(
      "Attempting to access Cm(t) map at index %1% when there are only %2% Cm(t) maps")
      % i % CtDataMaps_.size());

  return CtModelMaps_[i];
}

//
MDM_API const std::vector<mdm_Image3D> & mdm_VolumeAnalysis::CtModelMaps() const
{
  return CtModelMaps_;
}

//
MDM_API mdm_Image3D mdm_VolumeAnalysis::DCEMap(const std::string &mapName) const
{
  checkModelSet();

  for (int i = 0; i < model_->numParams(); i++)
  {
    if (mapName == model_->paramName(i))
      return pkParamMaps_[i];
  }

	for (size_t i = 0; i < IAUCTimes_.size(); i++)
	{
		if (mapName == (MAP_NAME_IAUC + std::to_string(int(IAUCTimes_[i]))))
			return IAUCMaps_[i];
	}

  if (mapName == (MAP_NAME_IAUC + "_peak"))
    return IAUCMaps_.back();

	if (mapName == MAP_NAME_RESDIUALS)
		return modelResidualsMap_;

	if (mapName == MAP_NAME_ENHANCING)
		return enhVoxMap_;

	//Error map name not recognised
	throw mdm_exception(__func__, boost::format("Map name %1% not recognised") % mapName);

}

//
MDM_API void mdm_VolumeAnalysis::setDCEMap(const std::string &mapName, const mdm_Image3D &map)
{
  errorTracker_.checkOrSetDimension(map, "param map " + mapName);

  checkModelSet();

  if (pkParamMaps_.size() != model_->numParams())
    pkParamMaps_.resize(model_->numParams());

  for (int i = 0; i < model_->numParams(); i++)
  {
    if (mapName == model_->paramName(i))
    {
      pkParamMaps_[i] = map;
      return;
    }
  }

  for (size_t i = 0; i < IAUCTimes_.size(); i++)
  {
    if (mapName == (MAP_NAME_IAUC + std::to_string(int(IAUCTimes_[i]))))
    {
      IAUCMaps_[i] = map;
      return;
    }
  }

  if (mapName == (MAP_NAME_IAUC + "_peak"))
  {
    IAUCMaps_[IAUCMaps_.size()-1] = map;
    return;
  }

  if (mapName == MAP_NAME_RESDIUALS)
  {
    modelResidualsMap_ = map;
    return;
  }
  if (mapName == MAP_NAME_ENHANCING)
  {
    enhVoxMap_ = map;
    return;
  }

  //Error map name not recognised
  throw mdm_exception(__func__, boost::format("Map name %1% not recognised") % mapName);
}

//
MDM_API std::string mdm_VolumeAnalysis::modelType() const
{
  if (model_)
    return model_->modelType();
  else
    return "";
}

//
MDM_API std::vector<double> mdm_VolumeAnalysis::dynamicTimes() const
{
	return dynamicTimes_;
}

//
MDM_API double mdm_VolumeAnalysis::dynamicTime(size_t i) const
{
  if (i >= dynamicTimes_.size())
    throw mdm_exception(__func__, boost::format(
      "Attempting to access timepoint %1% when there are only %2% timepoints")
      % i % dynamicTimes_.size());

  return dynamicTimes_[i];
}

//
MDM_API std::vector<std::string> mdm_VolumeAnalysis::paramNames() const
{
  checkModelSet();
	return model_->paramNames();
}

//
MDM_API std::vector<double> mdm_VolumeAnalysis::IAUCtimes() const
{
	return IAUCTimes_;
}

//
MDM_API bool mdm_VolumeAnalysis::IAUCAtpeak() const
{
  return IAUCAtPeak_;
}

//
MDM_API void mdm_VolumeAnalysis::setR1Const(double rc)
{
	r1Const_ = rc;
}

MDM_API void mdm_VolumeAnalysis::setPrebolusImage(int prebolus)
{
  prebolusImage_ = prebolus;
}

//
MDM_API void mdm_VolumeAnalysis::setModel(std::shared_ptr<mdm_DCEModelBase> model)
{
	model_ = model;
}

//
MDM_API void mdm_VolumeAnalysis::setTestEnhancement(bool flag)
{
	testEnhancement_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setM0Ratio(bool flag)
{
	useM0Ratio_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setB1correction(bool flag)
{
  useB1correction_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setComputeCt(bool flag)
{
	computeCt_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setOutputCtSig(bool flag)
{
	outputCt_sig_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setOutputCtMod(bool flag)
{
  outputCt_mod_ = flag;
}

//
MDM_API void mdm_VolumeAnalysis::setIAUCtimes(
  const std::vector<double> &times, bool convertToMins, bool IAUCAtPeak)
{
	IAUCTimes_ = times;
	std::sort(IAUCTimes_.begin(), IAUCTimes_.end());

	IAUCTMinutes_ = times;
	if (convertToMins)
	{
		for (auto &t : IAUCTMinutes_)
			t /= 60;
	}
	IAUCAtPeak_ = IAUCAtPeak;
}

//
MDM_API void mdm_VolumeAnalysis::setUseNoise(bool b)
{
  useNoise_ = b;
}

//
MDM_API void mdm_VolumeAnalysis::setFirstImage(size_t t)
{
  firstImage_ = t;
}

//
MDM_API void mdm_VolumeAnalysis::setLastImage(size_t t)
{
  lastImage_ = t;
}

//
MDM_API void mdm_VolumeAnalysis::setOptimisationType(const std::string& type)
{
  optimisationType_ = type;
}

//
MDM_API void mdm_VolumeAnalysis::setMaxIterations(int maxItr)
{
	maxIterations_ = maxItr;
}

//
MDM_API void mdm_VolumeAnalysis::setInitMapParams(const std::vector<int> &params)
{
  initMapParams_ = params;
}

//
MDM_API void  mdm_VolumeAnalysis::fitDCEModel(
  bool optimiseModel, const std::vector<int> initMapParams)
{
  checkDynamicsSet();
  checkModelSet();

  //
  initialiseParameterMaps(*model_);

  //Fit the model
  fitModel(*model_, optimiseModel);

}

//------------------------------------------------------------------
// Private
//------------------------------------------------------------------

//
size_t mdm_VolumeAnalysis::numSt() const
{
  return StDataMaps_.size();
}

//
size_t mdm_VolumeAnalysis::numCtSignal() const
{
  return CtDataMaps_.size();
}

//
size_t mdm_VolumeAnalysis::numCtModel() const
{
  return CtModelMaps_.size();
}

//
void mdm_VolumeAnalysis::checkModelSet() const
{
  if (!model_)
    throw mdm_exception(__func__, "Model not set");
}

void mdm_VolumeAnalysis::checkDynamicsSet() const
{
  if (!numDynamics())
    throw mdm_exception(__func__, "Dynamic maps not loaded.");
}

//
void mdm_VolumeAnalysis::setDynamicMetaData(const mdm_Image3D& img)
{
  dynamicMetaData_ = std::make_unique<mdm_Image3D::MetaData>(img.info());
  auto msg = boost::format(
    "Acquisition parameters for dynamic series set from %1%: \n"
    "    TR = %2% ms\n"
    "    FA = %3% deg")
    % dynamicMetaData_->xtrSource
    % dynamicMetaData_->TR.value()
    % dynamicMetaData_->flipAngle.value();

  mdm_ProgramLogger::logProgramMessage(msg.str());
}

//
void mdm_VolumeAnalysis::setDynamicTime(const mdm_Image3D& img)
{
  dynamicTimes_.push_back(img.minutesFromTimeStamp());
  auto msg = boost::format(
    "Time t(%1%) = %2% mins set from %3%")
    % dynamicTimes_.size()
    % dynamicTimes_.back()
    % dynamicMetaData_->xtrSource;

  mdm_ProgramLogger::logProgramMessage(msg.str());
}

//
void mdm_VolumeAnalysis::initialiseParameterMaps(
  const mdm_DCEModelBase &model)
{
  //Model parameter maps may already have been loaded
  if (pkParamMaps_.size() != model.numParams())
    pkParamMaps_.resize(model.numParams());

  //For each map not already created, create it
  for (auto &map : pkParamMaps_)
    if (!map)
      createMap(map);

  //Create IAUC maps
  IAUCMaps_.resize(IAUCTimes_.size() + int(IAUCAtPeak_));
  for (auto &map : IAUCMaps_)
    createMap(map);

  //Model residuals may already have been loaded
  if (!modelResidualsMap_)
    createMap(modelResidualsMap_);

  //Create enhancing map
  createMap(enhVoxMap_);

  //Create modelled Ct maps
  if (outputCt_mod_)
  {
    CtModelMaps_.resize(numDynamics());
    for (auto &map : CtModelMaps_)
      createMap(map);
  }
}

//
mdm_DCEVoxel mdm_VolumeAnalysis::setUpVoxel(size_t voxelIndex) const
{
  std::vector<double> St, Ct;
  if (computeCt_)
    voxelStData(voxelIndex, St);

  else
    voxelCtData(voxelIndex, Ct);

  mdm_DCEVoxel vox(
    St,//dynSignals
    Ct,//dynConc
    prebolusImage_,//bolus_time
    dynamicTimes_,//dynamicTimings
    IAUCTMinutes_,
    IAUCAtPeak_);//IAUC_times

  if (computeCt_)
  {
    if (!dynamicMetaData_)
      throw mdm_exception(__func__,
        "Attempting to convert to signal with no dynamic meta data set (eg TR, FA)");

    auto TR = dynamicMetaData_->TR.value();
    auto FA = dynamicMetaData_->flipAngle.value();

    auto T1 = T1Mapper_.T1(voxelIndex);
    auto M0 = useM0Ratio_ ? 0.0 : T1Mapper_.M0(voxelIndex);
    auto B1 = useB1correction_ ? T1Mapper_.B1(voxelIndex) : 1.0;

    //Convert signal (if already C(t) does nothing so can call regardless)
    vox.computeCtFromSignal(T1, FA, TR, r1Const_, M0, B1, firstImage_);
  }

  return vox;
}

//
void  mdm_VolumeAnalysis::voxelStData(size_t voxelIndex, std::vector<double> &data) const
{
	size_t n = numSt();

	data.resize(n);
  for (size_t k = 0; k < n; k++)
    data[k] = StDataMaps_[k].voxel(voxelIndex);
}

//
void  mdm_VolumeAnalysis::voxelCtData(size_t voxelIndex, std::vector<double> &data) const
{
	size_t n = CtDataMaps_.size();

	data.resize(n);
	for (size_t k = 0; k < n; k++)
		data[k] = CtDataMaps_[k].voxel(voxelIndex);
}

//
void  mdm_VolumeAnalysis::voxelCtModel(size_t voxelIndex, std::vector<double> &data) const
{
  size_t n = CtModelMaps_.size();

  data.resize(n);
  for (size_t k = 0; k < n; k++)
    data[k] = CtModelMaps_[k].voxel(voxelIndex);
}

//
void mdm_VolumeAnalysis::setVoxelErrors(size_t voxelIndex, const mdm_DCEVoxel &vox)
{
	//
  auto status = vox.status();
  if (status == mdm_DCEVoxel::CA_NAN)
    errorTracker_.updateVoxel(voxelIndex, mdm_ErrorTracker::CA_IS_NAN);

  else if (status == mdm_DCEVoxel::DYN_T1_BAD)
    errorTracker_.updateVoxel(voxelIndex, mdm_ErrorTracker::DYNT1_NEGATIVE);

  else if (status == mdm_DCEVoxel::M0_BAD)
    errorTracker_.updateVoxel(voxelIndex, mdm_ErrorTracker::M0_NEGATIVE);

  else if (status == mdm_DCEVoxel::NON_ENHANCING)
    errorTracker_.updateVoxel(voxelIndex, mdm_ErrorTracker::NON_ENH_IAUC);
}

//
void mdm_VolumeAnalysis::setVoxelPreFit(size_t voxelIndex,
  const mdm_DCEVoxel  &vox, const mdm_DCEModelFitter &fitter)
{
  //Set any error codes returned from setting up the voxel in the error codes map
  setVoxelErrors(voxelIndex, vox);

  //Set any IAUC values
  for (size_t i = 0; i < IAUCMaps_.size(); i++)
    IAUCMaps_[i].setVoxel(voxelIndex, vox.IAUCVal(i));

  //Set output C(t) maps
  if (outputCt_sig_)
    for (size_t i = 0; i < numDynamics(); i++)
      CtDataMaps_[i].setVoxel(voxelIndex, vox.CtData()[i]);

  if (outputCt_mod_)
    for (size_t i = 0; i < numDynamics(); i++)
      CtModelMaps_[i].setVoxel(voxelIndex, fitter.CtModel()[i]);

  //Set enhancing status
  enhVoxMap_.setVoxel(voxelIndex, vox.enhancing());
}

//
void mdm_VolumeAnalysis::setVoxelPostFit(size_t voxelIndex,
  const mdm_DCEModelBase &model, const mdm_DCEVoxel  &vox, const mdm_DCEModelFitter &fitter,
  int &numErrors)
{

  //Check if any model fitting error codes generated
  mdm_ErrorTracker::ErrorCode errorCode = model.getModelErrorCode();
  if (errorCode != mdm_ErrorTracker::OK)
  {
    errorTracker_.updateVoxel(voxelIndex, errorCode);
    numErrors++;
  }

  //Check if we have a target model residual to match
  auto residual = fitter.modelFitError();
  auto targetResidual = modelResidualsMap_.voxel(voxelIndex);
  if (targetResidual && targetResidual < residual)
    return; //Don't update parameter maps or model residuals

  //Otherwise, residual is accepted, set parameter maps, modelled C(t) residuals
  for (size_t i = 0; i < pkParamMaps_.size(); i++)
		pkParamMaps_[i].setVoxel(voxelIndex, model.params(int(i)));

  if (outputCt_mod_)
    for (size_t i = 0; i < numDynamics(); i++)
      CtModelMaps_[i].setVoxel(voxelIndex, fitter.CtModel()[i]);

  modelResidualsMap_.setVoxel(voxelIndex, residual);

}

//
std::vector <size_t> mdm_VolumeAnalysis::getVoxelsToFit() const
{
  std::vector<size_t> selectedVoxels;
  if (ROI_)
  {
    for (size_t idx = 0; idx < ROI_.numVoxels(); idx++)
    {
      if (ROI_.voxel(idx))
        selectedVoxels.push_back(idx);
    }
  }
  else
  {
    selectedVoxels.resize(errorTracker_.errorImage().numVoxels());
    std::iota(selectedVoxels.begin(), selectedVoxels.end(), 0);
  }
  return selectedVoxels;
}

//
void mdm_VolumeAnalysis::initialiseModelParams(
  const size_t voxelIndex,
  mdm_DCEModelBase &model)
{

  int n = model.numParams();
  std::vector<double> initialParams = model.initialParams();

  for (int i : initMapParams_)
    initialParams[i] = pkParamMaps_[i].voxel(voxelIndex);

  model.setInitialParams(initialParams);
}

//
void mdm_VolumeAnalysis::logProgress(
  double &numProcessed, const double numVoxels)
{
  //Increments the processed count, and logs a message at every 10th % complete
  numProcessed++;
  double pctComplete = 100.0*numProcessed / numVoxels;
  if (pctComplete >= pctTarget_)
  {
    mdm_ProgramLogger::logProgramMessage(std::to_string(int(pctComplete)) + "% voxels fitted.");
    pctTarget_ += 10;
  }

}

//
void  mdm_VolumeAnalysis::fitModel(
  mdm_DCEModelBase &model,
  bool optimiseModel)
{
  //Create a new fitter object
  mdm_DCEModelFitter modelFitter(
    model,
    firstImage_,
    lastImage_ ? lastImage_ : numDynamics(),
    noiseVar_,
    optimisationType_,
    maxIterations_
  );

  // Get list of voxels to fit
  std::vector<size_t> selectedVoxels = getVoxelsToFit();
  auto numVoxels = selectedVoxels.size();
	double numProcessed = 0;
	int numErrors = 0;
  pctTarget_ = 10;
  bool paramMapsInitialised = !initMapParams_.empty();

  //Away we go...
  mdm_ProgramLogger::logProgramMessage(
    "Fitting " + modelType() + " to " + std::to_string(numVoxels) + " voxels");
	auto fit_start = std::chrono::system_clock::now();
  for(const auto voxelIndex : selectedVoxels)
  {
    //If compute Ct from signal, skip voxels with invalid T1
    if (computeCt_ && T1Mapper_.T1(voxelIndex) <= 0.0)
      continue;

    //Check if we've got parameter maps with values to initialise each voxel
    //if not the existing values set in the model will be used
    if (paramMapsInitialised)
      initialiseModelParams(voxelIndex, model);

    //Set up the DCE voxel object
    mdm_DCEVoxel vox(setUpVoxel(voxelIndex));

    //Compute IAUC
    vox.computeIAUC();

    //Run an initial fit (does not optimise parameters, but
    //sets bounds on model parameters, and compute the model residual
    //for the initial model parameters
    modelFitter.initialiseModelFit(vox.CtData());

    //Test enhancement
    if (testEnhancement_)
      vox.testEnhancing();

    //Set values that don't depend on model fitting
    setVoxelPreFit(voxelIndex, vox, modelFitter);

    //The main event: If optimising the model fit, do so now
    if (optimiseModel)
      modelFitter.fitModel(vox.status());

    //Set all the necessary values in the output maps
    setVoxelPostFit(voxelIndex, model, vox, modelFitter, numErrors);

		logProgress(numProcessed, double(numVoxels));
  }

	// Get end time and log results
	auto fit_end = std::chrono::system_clock::now();
	std::chrono::duration<double> elapsed_seconds = fit_end - fit_start;

	std::stringstream ss;
	ss << "mdm_VolumeAnalysis: Processed " <<
		numProcessed << " voxels in " << elapsed_seconds.count() << "s.\n" <<
		numErrors << " voxels returned fit errors\n";
	mdm_ProgramLogger::logProgramMessage(ss.str());
}

//
void mdm_VolumeAnalysis::createMap(mdm_Image3D& img)
{
  if (!errorTracker_.errorImage())
    throw mdm_exception(__func__,
      "Attempting to create parameter maps before any other images have been set to"
      " to determine reference dimensions.");

  img.copy(errorTracker_.errorImage());
	img.setType(mdm_Image3D::ImageType::TYPE_KINETICMAP);
}


// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt
/*
This example program shows how to find frontal human faces in an image.  In
particular, this program shows how you can take a list of images from the
command line and display each on the screen with red boxes overlaid on each
human face.
The examples/faces folder contains some jpg images of people.  You can run
this program on them and see the detections by executing the following command:
./face_detection_ex faces/*.jpg
This face detector is made using the now classic Histogram of Oriented
Gradients (HOG) feature combined with a linear classifier, an image pyramid,
and sliding window detection scheme.  This type of object detector is fairly
general and capable of detecting many types of semi-rigid objects in
addition to human faces.  Therefore, if you are interested in making your
own object detectors then read the fhog_object_detector_ex.cpp example
program.  It shows how to use the machine learning tools which were used to
create dlib's face detector.
Finally, note that the face detector is fastest when compiled with at least
SSE2 instructions enabled.  So if you are using a PC with an Intel or AMD
chip then you should enable at least SSE2 instructions.  If you are using
cmake to compile this program you can enable them by using one of the
following commands when you create the build project:
cmake path_to_dlib_root/examples -DUSE_SSE2_INSTRUCTIONS=ON
cmake path_to_dlib_root/examples -DUSE_SSE4_INSTRUCTIONS=ON
cmake path_to_dlib_root/examples -DUSE_AVX_INSTRUCTIONS=ON
This will set the appropriate compiler options for GCC, clang, Visual
Studio, or the Intel compiler.  If you are using another compiler then you
need to consult your compiler's manual to determine how to enable these
instructions.  Note that AVX is the fastest but requires a CPU from at least
2011.  SSE4 is the next fastest and is supported by most current machines.
*/

#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/gui_widgets.h>
#include <dlib/image_io.h>
#include <iostream>

using namespace dlib;
using namespace std;

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

int main(int argc, char** argv)
{
    try
    {
        if (argc == 1)
        {
            cout << "Give some image files as arguments to this program." << endl;
            return 0;
        }

        frontal_face_detector detector = get_frontal_face_detector();
        image_window win;

        // Loop over all the images provided on the command line.
        for (int i = 1; i < argc; ++i)
        {
            cout << "processing image " << argv[i] << endl;
            array2d<unsigned char> img;
            load_image(img, argv[i]);
            // Make the image bigger by a factor of two.  This is useful since
            // the face detector looks for faces that are about 80 by 80 pixels
            // or larger.  Therefore, if you want to find faces that are smaller
            // than that then you need to upsample the image as we do here by
            // calling pyramid_up().  So this will allow it to detect faces that
            // are at least 40 by 40 pixels in size.  We could call pyramid_up()
            // again to find even smaller faces, but note that every time we
            // upsample the image we make the detector run slower since it must
            // process a larger image.
            pyramid_up(img);

            // Now tell the face detector to give us a list of bounding boxes
            // around all the faces it can find in the image.
            std::vector<rectangle> dets = detector(img);

            cout << "Number of faces detected: " << dets.size() << endl;
            // Now we show the image on the screen and the face detections as
            // red overlay boxes.
            win.clear_overlay();
            win.set_image(img);
            win.add_overlay(dets, rgb_pixel(255, 0, 0));

            cout << "Hit enter to process the next image..." << endl;
            cin.get();
        }
    }
    catch (exception& e)
    {
        cout << "\nexception thrown!" << endl;
        cout << e.what() << endl;
    }
}

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


#define INFO
#define TIMER
#define _USE_MATH_DEFINES
#include <GL/glew.h>

#include <cuda_gl_interop.h>
#include <cuda_runtime_api.h>

#ifdef __gnu_linux__
#include <sys/types.h>
#include <sys/stat.h>
#elif _WIN32
#include <direct.h>
#else
#error "OS not supported!"
#endif
#include <iostream>
#include <string>
#include <vector>

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

#include <opencv2/core.hpp>
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>

//camera calibration from a sequence of images
#include <Eigen/Dense>
#include <opencv2/cudaarithm.hpp>
#include <OpenNI.h>
#include "Kinect.h"
#include "GLUtil.hpp"
#include "Camera.h"
#include "VideoSourceKinect.hpp"

//Qt
#include <QGLViewer/qglviewer.h>
#include "Data4Viewer.h"

using namespace std;
using namespace cv;
using namespace Eigen;
using namespace qglviewer;

Data4Viewer::Data4Viewer(){
	_uResolution = 0;
	_uPyrHeight = 3;
	_Cw = Eigen::Vector3d(0.f,0.f,0.f);
	_nStatus = 01;//1 restart; 2 //recording continue 3://pause 4://dump
}
Data4Viewer::~Data4Viewer()
{
}

void Data4Viewer::init()
{
	GLenum eError = glewInit();
	if (GLEW_OK != eError){
		cout << ("glewInit() error.") << endl;
		cout << ( glewGetErrorString(eError) ) << endl;
	}
	loadFromYml();
	reset();

	return;
}//init()

void Data4Viewer::loadFromYml(){
	return;
}

void Data4Viewer::reset(){
	return;
}

void Data4Viewer::drawGlobalView()
{
	_pKinect->_pRGBCamera->setGLProjectionMatrix(0.1f, 100.f);

	glMatrixMode(GL_MODELVIEW);
	Eigen::Affine3d tmp; tmp.setIdentity();
	Eigen::Matrix4d mMat;
	mMat.row(0) = tmp.matrix().row(0);
	mMat.row(1) = -tmp.matrix().row(1);
	mMat.row(2) = -tmp.matrix().row(2);
	mMat.row(3) = tmp.matrix().row(3);

	glLoadMatrixd(mMat.data());
	GpuMat rgb(_pKinect->_cvmRGB);
	_pKinect->_pRGBCamera->renderCameraInLocal(rgb, _pGL.get(), false, NULL, 0.2f, true); //render in model coordinate
	//cout<<("drawRGBView");
	return;
}

#define INFO
#define TIMER


/*  gameplayutility %{Cpp:License:ClassName} - Yann BOUCHER (yann) 04/06/2016
**
**
**            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
**                    Version 2, December 2004
**
** Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
**
** Everyone is permitted to copy and distribute verbatim or modified
** copies of this license document, and changing it is allowed as long
** as the name is changed.
**
**            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
**   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
**
**  0. You just DO WHAT THE FUCK YOU WANT TO.
*/

#include "utils/gameplayglobals.hpp"

#include <atomic>
#include <thread>

#include <boost/optional.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point_xy.hpp>

#include <SFML/Audio/Sound.hpp>
#include <SFML/System/Sleep.hpp>

#include <Thor/Math/Trigonometry.hpp>
#include <Thor/Math/Random.hpp>

#include "actor.hpp"

#include "raycasting.hpp"
#include "utils/mathutility.hpp"
#include "utils/graphicsutility.hpp"
#include "map.hpp"

#include "constants.hpp"

#include <iostream>

namespace Rayfun
{

static std::atomic_size_t g_soundNumber { 0 };

boost::optional<Actor&> fireBullets(Actor &t_actor, const sf::Vector2d &t_src, const sf::Vector2d &t_dir,
                                    const sf::Vector2d &t_spread, Map& t_map, const State::Context &t_context,
                                    size_t t_damage, size_t t_num, const std::string &t_dmgType,
                                    size_t t_maxDistance)
{
    std::sort(t_map.sprites.begin(), t_map.sprites.end(), [&t_actor](std::unique_ptr<DrawableActor>& rhs,
              std::unique_ptr<DrawableActor>& lhs){
        return Utility::distance(rhs->pos, t_actor.pos) < Utility::distance(lhs->pos, t_actor.pos);
    });

    for (size_t i { 0 }; i < t_num; ++i)
    {
        sf::Vector2d unitVector = Utility::angleToVector<double>(thor::random(0, 360));
        double spreadIntensity = thor::random(0.f, 1.f);
        sf::Vector2d dev { t_spread.x * spreadIntensity, t_spread.y * spreadIntensity };
        dev = thor::cwiseProduct(unitVector, dev);

        DrawableActor* actor = nullptr;
        if (Raycasting::rayIntersectsSprite(t_src, Utility::angleToVector(dev.x + thor::polarAngle(t_dir)),
                                            t_map, actor))
        {
            if (actor && actor->ondamage)
            {
                actor->ondamage(t_actor, *actor, t_damage, t_dmgType);
            }
            //TODO: implement puffs ?
        }
        else
        {
            auto result = Raycasting::castRay(t_src, Utility::angleToVector(dev.x + thor::polarAngle(t_dir)),
                                              t_map, Raycasting::HitMode::Visibility).back();

            if (result.tileHit && result.tileHit->tex[result.side] != 0)
            {
                if (result.distance > t_maxDistance)
                {
                    return boost::none;
                }

                if (!result.tileHit->decals[result.side])
                {
                    result.tileHit->decals[result.side] = sf::Image();
                }
                auto& decal = *(result.tileHit->decals[result.side]);

                // FIXME : again find a way to get texture size by ID !

                if (true /*decal.getSize() != {64, 64 }*/)
                {
                    decal.create(64,
                            64, sf::Color::Transparent);
                }

                double wallX { 0 };
                if (result.side == Side::North || result.side == Side::South)
                {
                    wallX = result.hitPos.y - std::floor(result.hitPos.y);
                }
                else
                {
                    wallX = result.hitPos.x - std::floor(result.hitPos.x);
                }
                if (result.side == Side::South || result.side == Side::West)
                {
                    wallX = 1 - wallX;
                }
                wallX *= decal.getSize().x;
                double wallY = decal.getSize().y / 2.f + (result.distance / 2.f) * std::tan(thor::toRadian(dev.y))
                               * decal.getSize().y;
                if (result.tileHit && wallY >= 0 && wallY <= decal.getSize().y)
                {
                    Utility::addDecal(decal, t_context.resources.imageHolder["decals/bullet"],
                            sf::Vector2s(wallX, wallY), true);
                    if (result.tileHit->ondamage[result.side])
                    {
                        result.tileHit->ondamage[result.side](t_actor, *result.tileHit, t_damage, t_dmgType);
                    }
                }
            }
        }
    }

    return boost::none;
}

void playSoundAt(const Resources &t_res, const std::string &t_sound,
                 const sf::Vector2f& t_pos, float t_volume)
{
    if (g_soundNumber < c_maxSounds)
    {
        std::thread([&t_res, t_volume, t_sound, t_pos]{
            ++g_soundNumber;
            sf::Sound *sound = new sf::Sound;
            sound->setBuffer(t_res.soundHolder[t_sound]);
            sound->setVolume(t_volume);
            sound->setPosition(t_pos.x, t_pos.y, 0);
            sound->play();
            while (sound->getStatus() == sf::Sound::Playing)
            {sf::sleep(sf::seconds(0.1f));};
            delete sound;
            --g_soundNumber;
        }).detach();
    }
}

void playSoundUI(const Resources &t_res, const std::string &t_sound, float t_volume)
{
    if (g_soundNumber < c_maxSounds)
    {
        std::thread([&t_res, t_volume, t_sound]{
            ++g_soundNumber;
            sf::Sound *sound = new sf::Sound;
            sound->setBuffer(t_res.soundHolder[t_sound]);
            sound->setVolume(t_volume);
            sound->setRelativeToListener(true);
            sound->setPosition(0, 0, 0);
            sound->play();
            while (sound->getStatus() == sf::Sound::Playing)
            {sf::sleep(sf::seconds(0.1f));};
            delete sound;
            --g_soundNumber;
        }).detach();
    }
}

bool spriteClipAt(const sf::Vector2d &t_pos, Map &t_map, DrawableActor *&t_hitSprite)
{
    typedef boost::geometry::model::d2::point_xy<double> Point;
    Point origin = {t_pos.x, t_pos.y};

    for (auto& sprite : t_map.sprites)
    {
        sf::Image img = sprite->renderImage();
        boost::geometry::model::polygon<Point> spriteRect;
        double largestSide = std::max<double>(img.getSize().x, img.getSize().y);
        double side = img.getSize().x / largestSide;
        std::vector<Point> points =
        {
            {sprite->pos.x - side / 2, sprite->pos.x - side / 2},
            {sprite->pos.x - side / 2, sprite->pos.x + side / 2},
            {sprite->pos.x + side / 2, sprite->pos.x + side / 2},
            {sprite->pos.x + side / 2, sprite->pos.x - side / 2}
        };
        boost::geometry::assign_points(spriteRect, points);
        if (boost::geometry::intersects(origin, spriteRect))
        {
            if (sprite->clip)
            {
                t_hitSprite = sprite.get();
                return true;
            }
        }
    }

    return false;
}

}


// Copyright (c) 2015-2018 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "read_assigner.hpp"

#include <utility>
#include <iterator>
#include <algorithm>
#include <limits>
#include <random>
#include <stdexcept>
#include <cassert>

#include <boost/optional.hpp>

#include "utils/maths.hpp"
#include "utils/kmer_mapper.hpp"
#include "core/models/haplotype_likelihood_model.hpp"
#include "core/models/error/error_model_factory.hpp"

namespace octopus {

namespace {

using HaplotypeLikelihoods = std::vector<std::vector<double>>;

void find_max_likelihood_haplotypes(const std::vector<Haplotype>& haplotypes, const unsigned read,
                                    const HaplotypeLikelihoods& likelihoods,
                                    std::vector<unsigned>& result)
{
    assert(result.empty());
    auto max_likelihood = std::numeric_limits<double>::lowest();
    for (unsigned k {0}; k < haplotypes.size(); ++k) {
        const auto curr = likelihoods[k][read];
        if (maths::almost_equal(curr, max_likelihood)) {
            result.push_back(k);
        } else if (curr > max_likelihood) {
            result.assign({k});
            max_likelihood = curr;
        }
    }
    if (result.empty()) {
        result.resize(haplotypes.size());
        std::iota(std::begin(result), std::end(result), 0);
    }
}

template <typename ForwardIt, typename RandomGenerator>
ForwardIt random_select(ForwardIt first, ForwardIt last, RandomGenerator& g)
{
    if (first == last) return first;
    const auto max = static_cast<std::size_t>(std::distance(first, last));
    if (max == 1) return first;
    std::uniform_int_distribution<std::size_t> dist {0, max - 1};
    std::advance(first, dist(g));
    return first;
}

template <typename ForwardIt>
ForwardIt random_select(ForwardIt first, ForwardIt last)
{
    static thread_local std::mt19937 generator {42};
    return random_select(first, last, generator);
}

template <typename Range>
decltype(auto) random_select(const Range& values)
{
    assert(!values.empty());
    return *random_select(std::cbegin(values), std::cend(values));
}

auto calculate_support(const std::vector<Haplotype>& haplotypes,
                       const std::vector<AlignedRead>& reads,
                       const HaplotypeLikelihoods& likelihoods,
                       boost::optional<std::deque<AlignedRead>&> ambiguous,
                       AssignmentConfig config)
{
    HaplotypeSupportMap result {};
    std::vector<unsigned> top {};
    top.reserve(haplotypes.size());
    for (unsigned i {0}; i < reads.size(); ++i) {
        find_max_likelihood_haplotypes(haplotypes, i, likelihoods, top);
        if (top.size() == 1) {
            result[haplotypes[top.front()]].push_back(reads[i]);
        } else {
            using UA = AssignmentConfig::AmbiguousAction;
            switch (config.ambiguous_action) {
                case UA::first:
                    result[haplotypes[top.front()]].push_back(reads[i]);
                    break;
                case UA::all: {
                    for (auto idx : top) result[haplotypes[idx]].push_back(reads[i]);
                    break;
                }
                case UA::random: {
                    result[haplotypes[random_select(top)]].push_back(reads[i]);
                    break;
                }
                case UA::drop:
                default:
                    break;
            }
            if (ambiguous) ambiguous->push_back(reads[i]);
        }
        top.clear();
    }
    return result;
}

auto max_deletion_size(const std::vector<Haplotype>& haplotypes)
{
    unsigned result {0};
    for (const auto& haplotype : haplotypes) {
        if (region_size(haplotype) > sequence_size(haplotype)) {
            auto diff = static_cast<unsigned>(region_size(haplotype) - sequence_size(haplotype));
            result = std::max(result, diff);
        }
    }
    return result;
}

auto compute_read_hashes(const std::vector<AlignedRead>& reads)
{
    static constexpr unsigned char mapperKmerSize {6};
    std::vector<KmerPerfectHashes> result {};
    result.reserve(reads.size());
    std::transform(std::cbegin(reads), std::cend(reads), std::back_inserter(result),
                   [=] (const AlignedRead& read) { return compute_kmer_hashes<mapperKmerSize>(read.sequence()); });
    return result;
}

auto calculate_likelihoods(const std::vector<Haplotype>& haplotypes,
                           const std::vector<AlignedRead>& reads,
                           HaplotypeLikelihoodModel& model)
{
    assert(!haplotypes.empty());
    const auto& haplotype_region = mapped_region(haplotypes.front());
    const auto reads_region = encompassing_region(reads);
    const auto min_flank_pad = HaplotypeLikelihoodModel::pad_requirement();
    unsigned min_lhs_expansion{2 * min_flank_pad}, min_rhs_expansion{2 * min_flank_pad};
    if (begins_before(reads_region, haplotype_region)) {
        min_lhs_expansion += begin_distance(reads_region, haplotype_region);
    }
    if (ends_before(haplotype_region, reads_region)) {
        min_rhs_expansion += end_distance(haplotype_region, reads_region);
    }
    const auto min_expansion = std::max(min_lhs_expansion, min_rhs_expansion) + max_deletion_size(haplotypes);
    const auto read_hashes = compute_read_hashes(reads);
    static constexpr unsigned char mapperKmerSize {6};
    auto haplotype_hashes = init_kmer_hash_table<mapperKmerSize>();
    HaplotypeLikelihoods result{};
    result.reserve(haplotypes.size());
    for (const auto& haplotype : haplotypes) {
        const auto expanded_haplotype = expand(haplotype, min_expansion);
        populate_kmer_hash_table<mapperKmerSize>(expanded_haplotype.sequence(), haplotype_hashes);
        auto haplotype_mapping_counts = init_mapping_counts(haplotype_hashes);
        model.reset(expanded_haplotype);
        std::vector<double> likelihoods(reads.size());
        std::transform(std::cbegin(reads), std::cend(reads), std::cbegin(read_hashes), std::begin(likelihoods),
                       [&] (const auto& read, const auto& read_hash) {
                           auto mapping_positions = map_query_to_target(read_hash, haplotype_hashes, haplotype_mapping_counts);
                           reset_mapping_counts(haplotype_mapping_counts);
                           return model.evaluate(read, mapping_positions);
                       });
        clear_kmer_hash_table(haplotype_hashes);
        result.push_back(std::move(likelihoods));
    }
    return result;
}

} // namespace

HaplotypeSupportMap compute_haplotype_support(const Genotype<Haplotype>& genotype,
                                              const std::vector<AlignedRead>& reads,
                                              HaplotypeLikelihoodModel model,
                                              boost::optional<std::deque<AlignedRead>&> ambiguous,
                                              AssignmentConfig config)
{
    if (!genotype.is_homozygous() && !reads.empty()) {
        const auto unique_haplotypes = genotype.copy_unique();
        assert(unique_haplotypes.size() > 1);
        const auto likelihoods = calculate_likelihoods(unique_haplotypes, reads, model);
        return calculate_support(unique_haplotypes, reads, likelihoods, ambiguous, config);
    } else {
        return {};
    }
}

HaplotypeSupportMap compute_haplotype_support(const Genotype<Haplotype>& genotype,
                                              const std::vector<AlignedRead>& reads,
                                              std::deque<AlignedRead>& ambiguous,
                                              AssignmentConfig config)
{
    return compute_haplotype_support(genotype, reads, HaplotypeLikelihoodModel {nullptr, make_indel_error_model(), false}, ambiguous, config);
}

HaplotypeSupportMap compute_haplotype_support(const Genotype<Haplotype>& genotype,
                                              const std::vector<AlignedRead>& reads,
                                              AssignmentConfig config)
{
    return compute_haplotype_support(genotype, reads, HaplotypeLikelihoodModel {nullptr, make_indel_error_model(), false}, config);
}

HaplotypeSupportMap compute_haplotype_support(const Genotype<Haplotype>& genotype,
                                              const std::vector<AlignedRead>& reads,
                                              HaplotypeLikelihoodModel model,
                                              AssignmentConfig config)
{
    return compute_haplotype_support(genotype, reads, std::move(model), boost::none, config);
}

HaplotypeSupportMap compute_haplotype_support(const Genotype<Haplotype>& genotype,
                                              const std::vector<AlignedRead>& reads,
                                              std::deque<AlignedRead>& ambiguous,
                                              HaplotypeLikelihoodModel model,
                                              AssignmentConfig config)
{
    return compute_haplotype_support(genotype, reads, std::move(model), ambiguous, config);
}

AlleleSupportMap compute_allele_support(const std::vector<Allele>& alleles,
                                        const HaplotypeSupportMap& haplotype_support)
{
    return compute_allele_support(alleles, haplotype_support,
                                  [] (const Haplotype& haplotype, const Allele& allele) {
                                      return haplotype.includes(allele);
                                  });
}

} // namespace octopus