// This file is part of necsim project which is released under MIT license.
// See file **LICENSE.txt** or visit https://opensource.org/licenses/MIT) for full license details.

/**
 * @author Samuel Thompson
 * @file SimulatedSpeciesAbundancesHandler.cpp
 *
 * @copyright <a href="https://opensource.org/licenses/MIT"> MIT Licence.</a>
 * @brief Class for repeatedly selecting random species from a distribution of species abundances.
 *
 * Contact: samuel.thompson14@imperial.ac.uk or thompsonsed@gmail.com
 */

#include "SimulatedSpeciesAbundancesHandler.h"

SimulatedSpeciesAbundancesHandler::SimulatedSpeciesAbundancesHandler()
		: species_abundances(), species_richness_per_abundance(),
		  cumulative_abundance_map(
				  make_shared<map<unsigned long, unsigned long>>()), total_species_number(0), number_of_individuals(0){}

unsigned long SimulatedSpeciesAbundancesHandler::getRandomSpeciesID()
{
	unsigned long random_abundance = getRandomAbundanceOfIndividual();
	if(species_abundances.count(random_abundance) == 0)
	{
		stringstream ss;
		ss << "No species abundances found for " << random_abundance << ". Please report this bug." << endl;
		throw FatalException(ss.str());
	}
	if(species_richness_per_abundance[random_abundance] == 0)
	{
		stringstream ss;
		ss << "Richness is 0 for abundance of " << random_abundance << endl;
		throw FatalException(ss.str());
	}
	if(species_richness_per_abundance[random_abundance] == 1)
	{
		return species_abundances[random_abundance][0];
	}
	unsigned long random_species_index = random->i0(species_richness_per_abundance[random_abundance] - 1);
	if(random_species_index >= species_abundances[random_abundance].size())
	{
		stringstream ss;
		ss << "Random species index larger than species abundance size: " << random_species_index << ">=";
		ss << species_abundances[random_abundance].size() << endl;
		throw FatalException(ss.str());
	}
	return species_abundances[random_abundance][random_species_index];
}

void SimulatedSpeciesAbundancesHandler::setAbundanceList(
		const shared_ptr<map<unsigned long, unsigned long>> &abundance_list_in)
{
	shared_ptr<vector<unsigned long>> abundance_list = make_shared<vector<unsigned long>>();
	abundance_list->reserve(abundance_list_in->size());
	community_size = 0;
	for(const auto &item: *abundance_list_in)
	{
		abundance_list->push_back(item.second);
		community_size += item.second;
	}
	generateAbundanceTable(abundance_list);
	generateCumulativeAbundances(abundance_list);
}

void SimulatedSpeciesAbundancesHandler::setAbundanceList(shared_ptr<vector<unsigned long>> abundance_list_in)
{
	max_species_id = 0;
	number_of_individuals = 0;
	generateAbundanceTable(abundance_list_in);
	generateCumulativeAbundances(abundance_list_in);
}

void SimulatedSpeciesAbundancesHandler::generateAbundanceTable(shared_ptr<vector<unsigned long>> abundance_list)
{
	writeInfo("Generating abundance table...");
	max_species_id = 0;
	for(const auto &item: (*abundance_list))
	{
		max_species_id++;
		species_abundances[item].push_back(max_species_id);
	}
	for(const auto &item: species_abundances)
	{
		species_richness_per_abundance[item.first] = item.second.size();
	}
	writeInfo("done!\n");
}

void SimulatedSpeciesAbundancesHandler::generateCumulativeAbundances(shared_ptr<vector<unsigned long>> abundance_list)
{
	writeInfo("Generating cumulative abundances...");
	if(abundance_list->empty())
	{
		throw FatalException("Abundance list is empty - please report this bug.");
	}
	// Holds a sorted abundance list (as doubles).
	vector<unsigned long> temp_cumulative_abundances;
	temp_cumulative_abundances.resize(abundance_list->size(), 0);
	partial_sum(abundance_list->begin(), abundance_list->end(), temp_cumulative_abundances.begin());
#ifdef DEBUG
	unsigned long total_sum = accumulate(abundance_list->begin(), abundance_list->end(), (unsigned long) 0);
	if(temp_cumulative_abundances.empty())
	{
		throw FatalException("Temporary cumulative abundance list is empty - please report this bug.");
	}
	if(total_sum == 0)
	{
		throw FatalException("Total sum of abundances is 0 - please report this bug.");
	}
#endif // DEBUG

	for(unsigned long i = 0; i < temp_cumulative_abundances.size(); i++)
	{
		(*cumulative_abundance_map)[temp_cumulative_abundances[i]] = (*abundance_list)[i];
	}
	if(cumulative_abundance_map->begin()->second == 0)
	{
#ifdef DEBUG
		if(cumulative_abundance_map->begin()->first != 0)
		{
			stringstream ss;
			ss << "Species abundance generated for " << cumulative_abundance_map->begin()->first;
			ss << " has zero abundance. Please report this bug." << endl;
			throw FatalException(ss.str());
		}
#endif // DEBUG
		// Remove the first 0 (if there is one)
		cumulative_abundance_map->erase(cumulative_abundance_map->begin());
	}
#ifdef DEBUG
	if(cumulative_abundance_map->rbegin()->first != community_size)
	{
		stringstream ss;
		ss << "Last cumulative abundance value (" << cumulative_abundance_map->rbegin()->first;
		ss << ") is not equal to community size (" << community_size << "). Please report this bug." << endl;
		throw FatalException(ss.str());
	}
	if(community_size == 1 && cumulative_abundance_map->size() != 1)
	{
		stringstream ss;
		ss << "Community size is 1, but cumulative abundance map has " << cumulative_abundance_map->size();
		ss << " elements. Please report this bug." << endl;
		throw FatalException(ss.str());
	}
#endif // DEBUG
	writeInfo("done!\n");
}

unsigned long SimulatedSpeciesAbundancesHandler::getRandomAbundanceOfIndividual()
{
#ifdef DEBUG
	if(cumulative_abundance_map->size() == 1)
	{
		if(cumulative_abundance_map->begin()->second == 0)
		{
			throw FatalException("Only one abundance found in abundance list, and it is 0. Please report this bug.");
		}
		return cumulative_abundance_map->begin()->second;
	}
	if(cumulative_abundance_map->empty())
	{
		throw FatalException("Cumulative abundance map is empty. Please report this bug.");
	}
	if(community_size == 1)
	{
		stringstream ss;
		ss << "Community size is 1 with cumulative abundance map: " << endl;
		for(const auto &item: *cumulative_abundance_map)
		{
			ss << item.first << ": " << item.second << endl;
		}
		ss << "Please report this bug." << endl;
		throw FatalException(ss.str());
	}
#endif // DEBUG
	return cumulative_abundance_map->upper_bound(random->i0(community_size - 1))->second;
}

unsigned long SimulatedSpeciesAbundancesHandler::getSpeciesRichnessOfAbundance(const unsigned long &abundance)
{
	if(species_richness_per_abundance.count(abundance) == 0)
	{
		stringstream ss;
		ss << "Cannot find species of abundance " << abundance << " - please report this bug." << endl;
		throw FatalException(ss.str());
	}
	return species_richness_per_abundance[abundance];
}
