//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
 * @date 16/08/2017
 * @file ActivityMap.h
 * @brief Contains the ActivityMap, which inherits from Matrix and adds a few extra parameters.
 *
 * @copyright <a href="https://opensource.org/licenses/MIT"> MIT Licence.</a>
 */

#ifndef ACTIVITYMAP_H
#define ACTIVITYMAP_H

#include <cstring>
#include <string>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <memory>
#include "Map.h"
#include "NRrand.h"

/**
 * @brief Contains the routines for importing activity maps and getting a cell value from the map.
 *
 * Activity maps can be reproduction probability maps or death maps.
 */
class ActivityMap
{
protected:
	// Matrix containing the relative activity probabilities
	Map<double> activity_map;
	// Path to the map file
	string map_file;
	// Maximum value across the map
	double max_val;
	// If true,
	bool null_map;
	// The fine map offsets and the sample map dimensions
	unsigned long offset_x, offset_y, x_dim, y_dim;
	// Random number generator
	shared_ptr<NRrand> random;

	// Function pointer for our reproduction map checker
	typedef bool (ActivityMap::*rep_ptr)(const unsigned long &x, const unsigned long &y, const long &xwrap,
										 const long &ywrap);

	// once setup will contain the end check function to use for this simulation.
	rep_ptr activity_map_checker_fptr;
public:
	ActivityMap() : activity_map(), offset_x(0), offset_y(0), x_dim(0), y_dim(0), random(make_shared<NRrand>()),
					activity_map_checker_fptr(nullptr)
	{
		map_file = "none";
		max_val = 0;
		null_map = true;
	}

	/**
	 * @brief Gets if the map is a null reproduction map, with no matrix storage in memory necessary.
	 * @return true if the map is "null" or "none"
	 */
	bool isNull();

	/**
	 * @brief Imports the map file from the given path
	 * Requires the dimensions to be identical as the fine map file dimensions
	 * @param file_name the path to the reproduction map to import
	 * @param size_x the x dimensions of the map file
	 * @param size_y the y dimensions of the map file
	 */
	void import(string file_name, unsigned long size_x, unsigned long size_y, shared_ptr<NRrand> random_in);

	/**
	 * @brief Correctly sets the reproduction function to either rejectionSampleNull or rejectionSample
	 * depending on if a reproduction map is used or not.
	 */
	void setActivityFunction();

	/**
	 * @brief Sets the offsets for the reproduction map from the sample grid.
	 * @param x_offset the x offset from the sample grid
	 * @param y_offset the y offset from the sample grid
	 * @param xdim the x dimension of the sample grid
	 * @param ydim the y dimension of the sample grid
	 */
	void setOffsets(const unsigned long &x_offset, const unsigned long &y_offset,
					const unsigned long &xdim, const unsigned long &ydim);

	/**
	 * @brief Returns true for all cell values
	 * Function to be pointed to in cases where there is no reproduction map
	 * @param random_number random number object to pass forward
	 * @param x x coordinate of the lineage on the sample grid
	 * @param y y coordinate of the lineage on the sample grid
	 * @param xwrap x wrapping of the lineage
	 * @param ywrap y wrapping of the lineage
	 * @return true always
	 */
	bool rejectionSampleNull(const unsigned long &x, const unsigned long &y, const long &xwrap, const long &ywrap);

	/**
	 * @brief Returns true for all cell values
	 * Function to be pointed to in cases where there is no reproduction map
	 * @param random_number random number object to pass forward
	 * @param x x coordinate of the lineage on the sample grid
	 * @param y y coordinate of the lineage on the sample grid
	 * @param xwrap x wrapping of the lineage
	 * @param ywrap y wrapping of the lineage
	 * @return true always
	 */
	bool rejectionSample(const unsigned long &x, const unsigned long &y, const long &xwrap, const long &ywrap);

	/**
	 * @brief Gets the value of the reproduction map at that location
	 * @param x x coordinate of the lineage on the sample grid
	 * @param y y coordinate of the lineage on the sample grid
	 * @param xwrap x wrapping of the lineage
	 * @param ywrap y wrapping of the lineage
	 * @return value of the reproduction map at the required location
	 */
	double getVal(const unsigned long &x, const unsigned long &y, const long &xwrap, const long &ywrap);

	/**
	 * @brief Tests if the random action occurs
	 * @param x x coordinate of the lineage on the sample grid
	 * @param y y coordinate of the lineage on the sample grid
	 * @param xwrap x wrapping of the lineage
	 * @param ywrap y wrapping of the lineage
	 * @return value of the reproduction map at the required location
	 */
	bool actionOccurs(const unsigned long &x, const unsigned long &y, const long &xwrap, const long &ywrap);

	/**
	 * @brief Standardises probability values from 0-1.
	 */
	void standardiseValues();

	/**
	 * @brief Operator [] for getting values directly from the reproduction map.
	 * @param index the index to get the row of
	 * @return the row present at that index
	 */
	Row<double> operator[](long index)
	{
		return activity_map[index];
	}

	/**
	 * @brief Equality operator
	 * @param rm the ActivityMap object to copy from
	 * @return the self ActivityMap object
	 */
	ActivityMap &operator=(const ActivityMap &rm)
	{
		this->activity_map = rm.activity_map;
		this->map_file = rm.map_file;
		this->max_val = rm.max_val;
		this->null_map = rm.null_map;
		this->offset_x = rm.offset_x;
		this->offset_y = rm.offset_y;
		this->x_dim = rm.x_dim;
		this->y_dim = rm.y_dim;
		this->activity_map_checker_fptr = rm.activity_map_checker_fptr;
		return *this;
	}

	/**
	 * @brief Operator for outputting to an ostream.
	 * @param os the ostream to output to
	 * @param r the ActivityMap to read from
	 * @return the os object
	 */
	friend ostream &operator<<(ostream &os, ActivityMap &r)
	{
		os << r.map_file << "\n";
		os << r.activity_map.getCols() << "\n";
		os << r.activity_map.getRows() << "\n";
		os << r.offset_x << "\n";
		os << r.offset_y << "\n";
		os << r.x_dim << "\n";
		os << r.y_dim << "\n";
		return os;
	}

	/**
	 * @brief Operator for inputting from an istream.
	 * @param is the istream to input from
	 * @param r the ActivityMap to input to
	 * @return the is object
	 */
	friend istream &operator>>(istream &is, ActivityMap &r)
	{
		is.ignore();
		getline(is, r.map_file);
		unsigned long col, row;
		is >> col >> row;
		is >> r.offset_x >> r.offset_y >> r.x_dim >> r.y_dim;
		r.import(r.map_file, col, row, shared_ptr<NRrand>());
		return is;
	}

};

#endif //ACTIVITYMAP_H
