#include <utility>

#include <utility>

// 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 Sam Thompson
 * @file SimParameters.h
 * @brief Stores and parses simulation parameters from the command line or a config file.
 *
 * Contact: samuel.thompson14@imperial.ac.uk or thompsonsed@gmail.com
 * @copyright <a href="https://opensource.org/licenses/MIT"> MIT Licence.</a>
 */

#ifndef SPECIATIONCOUNTER_SIMPARAMETERS_H
#define SPECIATIONCOUNTER_SIMPARAMETERS_H

#include <string>
#include <utility>
#include <vector>
#include <queue>

#include "ConfigParser.h"
#include "Logging.h"
#include "custom_exceptions.h"
#include "file_system.h"
#include "double_comparison.h"



namespace necsim
{

    struct HistoricalMapParameters
    {
        string fine_map_file;
        string coarse_map_file;
        double habitat_change_rate;
        double generation;

        HistoricalMapParameters()
        {
            HistoricalMapParameters("", "", 0.0, 0.0);
        }

        HistoricalMapParameters(string f, string c, const double &h, const double &g) : fine_map_file(std::move(f)),
                                                                                        coarse_map_file(std::move(c)),
                                                                                        habitat_change_rate(h),
                                                                                        generation(g)
        {

        }

        bool operator<(const HistoricalMapParameters &mp) const
        {
            return generation < mp.generation;
        }

        bool operator>(const HistoricalMapParameters &mp) const
        {
            return !(generation < mp.generation);
        }
    };

    /**
     * @struct SimParameters
     * @brief Stores and imports the variables required by the Map object.
     * Used to setting the Map variables in a more elegant way.
     */
    struct SimParameters
    {
        string fine_map_file, coarse_map_file, output_directory;
        string historical_fine_map_file, historical_coarse_map_file, sample_mask_file;
        // for file naming purposes.
        long long task{}, seed{};
        // the variables for the grid containing the initial individuals.
        unsigned long grid_x_size{}, grid_y_size{};
        // The variables for the sample grid, which may or may not be the same as the main simulation grid
        unsigned long sample_x_size{}, sample_y_size{};
        unsigned long sample_x_offset{}, sample_y_offset{};
        // The fine map variables at the same resolution as the grid.
        unsigned long fine_map_x_size{}, fine_map_y_size{}, fine_map_x_offset{}, fine_map_y_offset{};
        // the coarse map variables at a scaled resolution of the fine map.
        unsigned long coarse_map_x_size{}, coarse_map_y_size{}, coarse_map_x_offset{}, coarse_map_y_offset{};
        unsigned long coarse_map_scale{};
        unsigned long desired_specnum{};
        // the relative cost of moving through non-forest
        double dispersal_relative_cost{};
        // the size of each square of habitat in numbers of individuals
        double deme{};
        // the sample proportion,
        double deme_sample{};
        // the speciation rate.
        long double spec{0.0};
        // the variance of the dispersal kernel.
        double sigma{};
        // max time to run for
        unsigned long max_time{};
        // the number of generations since a historical landscape was encountered.
        double gen_since_historical{};
        // the transform rate of the forest from historical to modern forest.
        double habitat_change_rate{};
        // the fatness of the dispersal kernel
        double tau{};
        // dispersal method - should be one of [normal, fat-tail, norm-uniform]
        string dispersal_method;
        // the probability of selecting from a uniform dispersal kernel (for uniformally-modified dispersals)
        double m_prob{};
        // the cutoff for the normal dispersal in cells.
        double cutoff{};
        // if true, restricts dispersal from the same cell.
        bool restrict_self{};
        // file containing the points to record data from
        string times_file;
        // vector of times
        vector<double> times;
        // Stores the full list of configs imported from file
        ConfigParser configs;
        // Set to true if the oldest historical state has been reached.
        bool is_historical{};
        // if the sample file is not null, this variable tells us whether different points in space require different
        // numbers of individuals to be sampled. If this is the case, the actual values are read from the sample mask as a
        // proportion of individuals sampled, from 0-1. Otherwise, it is treated as a boolean mask, with values > 0.5
        // representing sampling in the cell.
        bool uses_spatial_sampling{};
        // This can be closed, infinite and tiled (which is also infinite)
        string landscape_type;
        // The protracted speciation current_metacommunity_parameters - these DON'T need to be stored upon pausing simulations
        bool is_protracted{};
        double min_speciation_gen{}, max_speciation_gen{};

        // a map of dispersal values, where each row corresponds to the probability of moving from one cell
        // to any other.
        string dispersal_file;

        // a map of relative death probabilities.
        string death_file;

        // a map of relative reproduction probabilities.
        string reproduction_file;

        std::queue<HistoricalMapParameters> all_historical_map_parameters;
        bool has_parsed_historical;

        /**
         * @brief Default constructor
         */
        SimParameters() : times(), configs(), all_historical_map_parameters(), has_parsed_historical(false)
        {
            fine_map_file = "none";
            coarse_map_file = "none";
            output_directory = "none";
            historical_fine_map_file = "none";
            historical_coarse_map_file = "none";
            sample_mask_file = "none";
            times_file = "null";
            dispersal_method = "none";
            landscape_type = "none";
            death_file = "none";
            reproduction_file = "none";
            dispersal_file = "none";
            min_speciation_gen = 0.0;
            max_speciation_gen = 0.0;
            is_protracted = false;
            restrict_self = false;
            m_prob = 0;
            cutoff = 0;
            tau = 0;
        }

        /**
         * @brief Links to the provided ConfigOption.
         * Assumes that the parameters have already been parsed from the config file.
         * @param configOption the parsed ConfigOption object
         */
        void importParameters(ConfigParser configOption)
        {
            configs = std::move(configOption);
            importParameters();
        }

        /**
         * @brief Imports the spatial variables from a path to the config file..
         * @param config_in string of the path to the config file
         */
        void importParameters(const string &conf_in)
        {
            // do the import of the values from combination of command-line arguments and file.
            configs.setConfig(conf_in, false, true);
            configs.parseConfig();
            importParameters();
        }

        /**
         * @brief Main import of parameters from the config file option.
         */
        void importParameters()
        {
            sample_x_size = stoul(configs.getSectionOptions("sample_grid", "x", "0"));
            sample_y_size = stoul(configs.getSectionOptions("sample_grid", "y", "0"));
            sample_x_offset = stoul(configs.getSectionOptions("sample_grid", "x_off", "0"));
            sample_y_offset = stoul(configs.getSectionOptions("sample_grid", "y_off", "0"));
            uses_spatial_sampling = static_cast<bool>(stoi(configs.getSectionOptions("sample_grid",
                                                                                     "uses_spatial_sampling",
                                                                                     "0")));
            if(configs.hasSection("grid_map"))
            {
                grid_x_size = stoul(configs.getSectionOptions("grid_map", "x"));
                grid_y_size = stoul(configs.getSectionOptions("grid_map", "y"));
            }
            else
            {
                grid_x_size = sample_x_size;
                grid_y_size = sample_y_size;
            }
            sample_mask_file = configs.getSectionOptions("sample_grid", "path", "null");
            fine_map_file = configs.getSectionOptions("fine_map", "path", "none");
            fine_map_x_size = stoul(configs.getSectionOptions("fine_map", "x", "0"));
            fine_map_y_size = stoul(configs.getSectionOptions("fine_map", "y", "0"));
            fine_map_x_offset = stoul(configs.getSectionOptions("fine_map", "x_off", "0"));
            fine_map_y_offset = stoul(configs.getSectionOptions("fine_map", "y_off", "0"));
            coarse_map_file = configs.getSectionOptions("coarse_map", "path", "none");
            coarse_map_x_size = stoul(configs.getSectionOptions("coarse_map", "x", "0"));
            coarse_map_y_size = stoul(configs.getSectionOptions("coarse_map", "y", "0"));
            coarse_map_x_offset = stoul(configs.getSectionOptions("coarse_map", "x_off", "0"));
            coarse_map_y_offset = stoul(configs.getSectionOptions("coarse_map", "y_off", "0"));
            coarse_map_scale = stoul(configs.getSectionOptions("coarse_map", "scale", "0"));
            historical_fine_map_file = configs.getSectionOptions("historical_fine0", "path", "none");
            historical_coarse_map_file = configs.getSectionOptions("historical_coarse0", "path", "none");
            dispersal_method = configs.getSectionOptions("dispersal", "method", "none");
            m_prob = stod(configs.getSectionOptions("dispersal", "m_probability", "0"));
            cutoff = stod(configs.getSectionOptions("dispersal", "cutoff", "0.0"));
            // quick and dirty conversion for string to bool
            restrict_self = static_cast<bool>(stoi(configs.getSectionOptions("dispersal", "restrict_self", "0")));
            landscape_type = configs.getSectionOptions("dispersal", "landscape_type", "none");
            dispersal_file = configs.getSectionOptions("dispersal", "dispersal_file", "none");
            death_file = configs.getSectionOptions("death", "map", "none");
            reproduction_file = configs.getSectionOptions("reproduction", "map", "none");
            output_directory = configs.getSectionOptions("main", "output_directory", "Default");
            seed = stol(configs.getSectionOptions("main", "seed", "0"));
            task = stol(configs.getSectionOptions("main", "task", "0"));
            tau = stod(configs.getSectionOptions("main", "tau", "0.0"));
            sigma = stod(configs.getSectionOptions("main", "sigma", "0.0"));
            deme = stod(configs.getSectionOptions("main", "deme"));
            deme_sample = stod(configs.getSectionOptions("main", "sample_size"));
            max_time = stoul(configs.getSectionOptions("main", "max_time"));
            dispersal_relative_cost = stod(configs.getSectionOptions("main", "dispersal_relative_cost", "0"));
            spec = stod(configs.getSectionOptions("main", "min_spec_rate"));
            desired_specnum = stoul(configs.getSectionOptions("main", "min_species", "1"));
            if(configs.hasSection("protracted"))
            {
                is_protracted = static_cast<bool>(stoi(configs.getSectionOptions("protracted", "has_protracted", "0")));
                min_speciation_gen = stod(configs.getSectionOptions("protracted", "min_speciation_gen", "0.0"));
                max_speciation_gen = stod(configs.getSectionOptions("protracted", "max_speciation_gen"));
            }
            if(configs.hasSection("times"))
            {

                times_file = "set";
                auto times_str = configs.getSectionValues("times");
                for(auto i : times_str)
                {
                    times.push_back(stod(i));
                }
                if(times.size() == 0)
                {
                    times_file = "null";
                }
            }
            parseHistorical();
        }

        /**
         * @brief Sets the main simulation parameters
         * @param task the job type reference number, used for file referencing
         * @param seed the seed to set random number generation
         * @param output_directory the output directory
         * @param max_time the maximum time to simulate for
         * @param desired_specnum the desired number of species to aim towards (currently not functional)
         * @param times_file the file containing a list of temporal sampling points
         */
        void setKeyParameters(const long long &task,
                              const long long &seed,
                              const string &output_directory,
                              const unsigned long &max_time,
                              unsigned long desired_specnum,
                              const string &times_file)
        {
            this->task = task;
            this->seed = seed;
            this->output_directory = output_directory;
            this->max_time = max_time;
            this->desired_specnum = desired_specnum;
            this->times_file = times_file;

        }

        /**
         * @brief Sets the speciation parameters for the simulation.
         * @param spec_in the speciation rate to use
         * @param is_protracted_in if true, simulates as a protracted simulation
         * @param min_speciation_gen_in the minimum speciation generation for protracted simulations
         * @param max_speciation_gen_in the maximum speciation generation for protracted simulations
         */
        void setSpeciationParameters(const long double &spec_in,
                                     bool is_protracted_in,
                                     const double &min_speciation_gen_in,
                                     const double &max_speciation_gen_in)
        {
            spec = spec_in;
            is_protracted = is_protracted_in;
            min_speciation_gen = min_speciation_gen_in;
            max_speciation_gen = max_speciation_gen_in;
        }

        /**
         * @brief Sets the dispersal parameters for the simulation.
         * @param dispersal_method_in the method of individuals dispersing (normal, fat-tailed or norm-uniform)
         * @param sigma_in the sigma value for a normal distribution
         * @param tau_in the tau value for the fat-tailed distribution
         * @param m_prob_in the probability of uniform dispersal for the norm-uniform distribution
         * @param cutoff_in the maximum dispersal distance for the uniform distribution
         * @param dispersal_relative_cost_in the relative cost of dispersing through non-forest
         * @param restrict_self_in if true, prevents dispersal from the same cell
         * @param landscape_type_in the landscape type (infinite, tiled or closed)
         * @param dispersal_file_in a map of dispersal probabilities
         */
        void setDispersalParameters(const string &dispersal_method_in,
                                    const double &sigma_in,
                                    const double &tau_in,
                                    const double &m_prob_in,
                                    const double &cutoff_in,
                                    const double &dispersal_relative_cost_in,
                                    bool restrict_self_in,
                                    const string &landscape_type_in,
                                    const string &dispersal_file_in)
        {
            dispersal_method = dispersal_method_in;
            sigma = sigma_in;
            tau = tau_in;
            m_prob = m_prob_in;
            cutoff = cutoff_in;
            dispersal_relative_cost = dispersal_relative_cost_in;
            restrict_self = restrict_self_in;
            landscape_type = landscape_type_in;
            dispersal_file = dispersal_file_in;

        }

        /**
         * @brief Sets the parameters controlling demographic rates
         *
         * @param reproduction_file_in a map of relative reproduction rates
         * @param death_file_in a map of relative death (i.e. turnover) rates
         */
        void setDemographicParameters(const string &reproduction_file_in, const string &death_file_in)
        {
            reproduction_file = reproduction_file_in;
            death_file = death_file_in;
        }

        /**
         * @brief Sets the historical map parameters for the simulation.
         * @param historical_fine_file_map_in the fine resolution historical file
         * @param historical_coarse_map_file_in the coarse resolution historical file
         * @param gen_since_historical_in the number of generations since the historical state was achieved
         * @param habitat_change_rate_in the rate of habitat change towards the historical state
         */
        void setHistoricalMapParameters(const string &historical_fine_file_map_in,
                                        const string &historical_coarse_map_file_in,
                                        const double &gen_since_historical_in,
                                        const double &habitat_change_rate_in)
        {
            historical_fine_map_file = historical_fine_file_map_in;
            historical_coarse_map_file = historical_coarse_map_file_in;
            gen_since_historical = gen_since_historical_in;
            habitat_change_rate = habitat_change_rate_in;
        }

        void setHistoricalMapParameters(vector<string> path_fine,
                                        vector<unsigned long> number_fine,
                                        vector<double> rate_fine,
                                        vector<double> time_fine,
                                        vector<string> path_coarse,
                                        vector<unsigned long> number_coarse,
                                        vector<double> rate_coarse,
                                        vector<double> time_coarse)
        {
            habitat_change_rate = 0.0;
            if(!rate_fine.empty())
            {
                is_historical = true;
                habitat_change_rate = rate_fine[0];
            }
            gen_since_historical = 0.0;
            if(!time_fine.empty())
            {
                gen_since_historical = time_fine[0];
            }
            if(time_fine.size() != rate_fine.size() || rate_fine.size() != number_fine.size()
               || number_fine.size() != path_fine.size())
            {
                std::stringstream ss;
                ss << "Lengths of historical fine map variables lists must be the same: " << time_fine.size() << "!=";
                ss << rate_fine.size() << "!=" << number_fine.size() << "!=" << path_fine.size() << std::endl;
                throw FatalException(ss.str());
            }
            if(time_coarse.size() != rate_coarse.size() || rate_coarse.size() != number_coarse.size()
               || number_coarse.size() != path_coarse.size())
            {
                std::stringstream ss;
                ss << "Lengths of historical coarse map variables lists must be the same: " << time_coarse.size()
                   << "!=";
                ss << rate_coarse.size() << "!=" << number_coarse.size() << "!=" << path_coarse.size() << std::endl;
                throw FatalException(ss.str());
            }
            for(unsigned long i = 0; i < time_fine.size(); i++)
            {
                string tmp = "historical_fine" + std::to_string(number_fine[i]);
                configs.setSectionOption(tmp, "path", path_fine[i]);
                configs.setSectionOption(tmp, "number", std::to_string(number_fine[i]));
                configs.setSectionOption(tmp, "time", std::to_string(time_fine[i]));
                configs.setSectionOption(tmp, "rate", std::to_string(rate_fine[i]));
            }
            for(unsigned long i = 0; i < time_coarse.size(); i++)
            {
                string tmp = "historical_coarse" + std::to_string(number_fine[i]);
                configs.setSectionOption(tmp, "path", path_coarse[i]);
                configs.setSectionOption(tmp, "number", std::to_string(number_coarse[i]));
                configs.setSectionOption(tmp, "time", std::to_string(time_coarse[i]));
                configs.setSectionOption(tmp, "rate", std::to_string(rate_coarse[i]));
            }
        }

        /**
         * @brief Sets the map parameters for the simulation.
         * @param fine_map_file_in the fine resolution density map
         * @param coarse_map_file_in the coarse resolution density map
         * @param sample_mask_file_in the spatial sampling mask
         * @param grid_x_size_in the x dimension of the grid
         * @param grid_y_size_in the y dimension of the grid
         * @param sample_x_size_in the x dimension of the sample mask
         * @param sample_y_size_in the y dimension of the sample mask
         * @param sample_x_offset_in the x offset of the sample mask from the grid
         * @param sample_y_offset_in the y offset of the sample mask from the grid
         * @param fine_map_x_size_in the x dimension of the fine map
         * @param fine_map_y_size_in the y dimension of the fine map
         * @param fine_map_x_offset_in the x offset of the fine map from the sample mask
         * @param fine_map_y_offset_in the y offset of the fine map from the sample mask
         * @param coarse_map_x_size_in the x dimension of the coarse map
         * @param coarse_map_y_size_in the y dimension of the coarse map
         * @param coarse_map_x_offset_in the x offset of the coarse map from the fine map
         * @param coarse_map_y_offset_in the y offset of the coarse map from the fine map
         * @param coarse_map_scale_in the scale of the coarse map compared to the fine map
         * @param deme_in the number of individuals per cell
         * @param deme_sample_in the proportion of individuals to sample from each cell
         * @param uses_spatial_sampling_in if the sample mask denotes differing spatial sampling proportions
         */
        void setMapParameters(const string &fine_map_file_in,
                              const string &coarse_map_file_in,
                              const string &sample_mask_file_in,
                              const unsigned long &grid_x_size_in,
                              const unsigned long &grid_y_size_in,
                              const unsigned long &sample_x_size_in,
                              const unsigned long &sample_y_size_in,
                              const unsigned long &sample_x_offset_in,
                              const unsigned long &sample_y_offset_in,
                              const unsigned long &fine_map_x_size_in,
                              const unsigned long &fine_map_y_size_in,
                              const unsigned long &fine_map_x_offset_in,
                              const unsigned long &fine_map_y_offset_in,
                              const unsigned long &coarse_map_x_size_in,
                              const unsigned long &coarse_map_y_size_in,
                              const unsigned long &coarse_map_x_offset_in,
                              const unsigned long &coarse_map_y_offset_in,
                              const unsigned long &coarse_map_scale_in,
                              const double &deme_in,
                              const double &deme_sample_in,
                              bool uses_spatial_sampling_in)
        {
            fine_map_file = fine_map_file_in;
            coarse_map_file = coarse_map_file_in;
            sample_mask_file = sample_mask_file_in;
            grid_x_size = grid_x_size_in;
            grid_y_size = grid_y_size_in;
            sample_x_size = sample_x_size_in;
            sample_y_size = sample_y_size_in;
            sample_x_offset = sample_x_offset_in;
            sample_y_offset = sample_y_offset_in;
            fine_map_x_size = fine_map_x_size_in;
            fine_map_y_size = fine_map_y_size_in;
            fine_map_x_offset = fine_map_x_offset_in;
            fine_map_y_offset = fine_map_y_offset_in;
            coarse_map_x_size = coarse_map_x_size_in;
            coarse_map_y_size = coarse_map_y_size_in;
            coarse_map_x_offset = coarse_map_x_offset_in;
            coarse_map_y_offset = coarse_map_y_offset_in;
            coarse_map_scale = coarse_map_scale_in;
            deme = deme_in;
            deme_sample = deme_sample_in;
            uses_spatial_sampling = uses_spatial_sampling_in;
        }

        void setMapParameters(const string &fine_map_file_in,
                              const string &coarse_map_file_in,
                              const unsigned long &sample_x_size_in,
                              const unsigned long &sample_y_size_in,
                              const unsigned long &fine_map_x_size_in,
                              const unsigned long &fine_map_y_size_in,
                              const unsigned long &fine_map_x_offset_in,
                              const unsigned long &fine_map_y_offset_in,
                              const unsigned long &coarse_map_x_size_in,
                              const unsigned long &coarse_map_y_size_in,
                              const unsigned long &coarse_map_x_offset_in,
                              const unsigned long &coarse_map_y_offset_in,
                              const unsigned long &coarse_map_scale_in,
                              const double &deme_in,
                              const string &landscape_type_in)
        {
            fine_map_file = fine_map_file_in;
            coarse_map_file = coarse_map_file_in;
            grid_x_size = sample_x_size_in;
            grid_y_size = sample_y_size_in;
            sample_x_size = sample_x_size_in;
            sample_y_size = sample_y_size_in;
            sample_x_offset = 0;
            sample_y_offset = 0;
            fine_map_x_size = fine_map_x_size_in;
            fine_map_y_size = fine_map_y_size_in;
            fine_map_x_offset = fine_map_x_offset_in;
            fine_map_y_offset = fine_map_y_offset_in;
            coarse_map_x_size = coarse_map_x_size_in;
            coarse_map_y_size = coarse_map_y_size_in;
            coarse_map_x_offset = coarse_map_x_offset_in;
            coarse_map_y_offset = coarse_map_y_offset_in;
            coarse_map_scale = coarse_map_scale_in;
            deme = deme_in;
            deme_sample = 1.0;
            landscape_type = landscape_type_in;
        }

        /**
         * @brief Updates the historical map parameters to the next item in the deque.
         * @return bool true if we need to re-import the maps (i.e. the historical maps have changed between updates)
         */
        bool setHistorical(const double &generation)
        {
            parseHistorical();

            bool needs_update = false;
            if(generation > gen_since_historical)
            {
                if(all_historical_map_parameters.empty())
                {
                    historical_fine_map_file = "none";
                    historical_coarse_map_file = "none";
                    is_historical = true;
                }
                if(!all_historical_map_parameters.empty())
                {
                    needs_update = true;
                    HistoricalMapParameters nextHistoricalMapParameters = all_historical_map_parameters.front();
                    historical_fine_map_file = nextHistoricalMapParameters.fine_map_file;
                    historical_coarse_map_file = nextHistoricalMapParameters.coarse_map_file;
                    gen_since_historical = nextHistoricalMapParameters.generation;
                    habitat_change_rate = nextHistoricalMapParameters.habitat_change_rate;
                    all_historical_map_parameters.pop();
                }
            }
            return needs_update;
        }

        /**
         * @brief Checks if there will be another update to be performed on the map.
         * @return true if another map update exists
         */
        bool checkNeedsUpdate(const double &g)
        {
            parseHistorical();
            return g > gen_since_historical && !is_historical;

        }

        bool hasAnotherUpdate()
        {
            return historical_fine_map_file != "none" || historical_coarse_map_file != "none"
                   || !all_historical_map_parameters.empty();
        }

        /**
         * @brief Parses the historical map data and creates the queue of historical maps.
         */
        void parseHistorical()
        {
            if(!has_parsed_historical)
            {
                std::map<unsigned long, HistoricalMapParameters> tmp_map_parameters;
                for(unsigned long i = 0; i < configs.getSectionOptionsSize(); i++)
                {
                    if(configs[i].section.find("historical_fine") == 0)
                    {
                        // Then loop over each element to find the number, and check if it is equal to our input number.
                        unsigned long number = stol(configs[i].getOption("number"));
                        if(tmp_map_parameters.count(number) == 0)
                        {
                            tmp_map_parameters[number] = HistoricalMapParameters();
                            HistoricalMapParameters &mapParameters = tmp_map_parameters[number];
                            mapParameters.fine_map_file = configs[i].getOption("path");
                            mapParameters.habitat_change_rate = stod(configs[i].getOption("rate"));
                            mapParameters.generation = stod(configs[i].getOption("time"));
                        }
                        else
                        {
                            auto r = stod(configs[i].getOption("rate"));
                            auto g = stod(configs[i].getOption("time"));
                            HistoricalMapParameters &mapParameters = tmp_map_parameters[number];
                            if(!doubleCompare(mapParameters.habitat_change_rate, r, 0.00000001)
                               || !(doubleCompare(mapParameters.generation, g, 0.00000001)
                                    || doubleCompare(mapParameters.generation, g, g * 0.01)))
                            {
                                std::stringstream ss;
                                ss << "Rates and times differ between fine and coarse map for historical map number "
                                   << number << std::endl;
                                ss << "Files: " << mapParameters.fine_map_file << ", " << configs[i].getOption("path")
                                   << std::endl;
                                ss << "Original parameters: " << mapParameters.habitat_change_rate << ", "
                                   << mapParameters.generation << std::endl;
                                ss << "New parameters: " << r << ", " << g << std::endl;
                                throw FatalException(ss.str());
                            }
                            mapParameters.fine_map_file = configs[i].getOption("path");
                        }
                    }
                    else if(configs[i].section.find("historical_coarse") == 0)
                    {

                        unsigned long number = stol(configs[i].getOption("number"));
                        if(tmp_map_parameters.count(number) == 0)
                        {
                            tmp_map_parameters[number] = HistoricalMapParameters();
                            HistoricalMapParameters &mapParameters = tmp_map_parameters[number];
                            mapParameters.coarse_map_file = configs[i].getOption("path");
                            mapParameters.habitat_change_rate = stod(configs[i].getOption("rate"));
                            mapParameters.generation = stod(configs[i].getOption("time"));
                        }
                        else
                        {
                            auto r = stod(configs[i].getOption("rate"));
                            auto g = stod(configs[i].getOption("time"));
                            HistoricalMapParameters &mapParameters = tmp_map_parameters[number];
                            if(!doubleCompare(mapParameters.habitat_change_rate, r, 0.00000001)
                               || !(doubleCompare(mapParameters.generation, g, 0.00000001)
                                    || doubleCompare(mapParameters.generation, g, g * 0.01)))
                            {
                                std::stringstream ss;
                                ss << "Rates and times differ between fine and coarse map for historical map number "
                                   << number << std::endl;
                                ss << "Files: " << mapParameters.fine_map_file << ", " << configs[i].getOption("path")
                                   << std::endl;
                                ss << "Original parameters: " << mapParameters.habitat_change_rate << ", "
                                   << mapParameters.generation << std::endl;
                                ss << "New parameters: " << r << ", " << g << std::endl;
                                throw FatalException(ss.str());
                            }
                            mapParameters.coarse_map_file = configs[i].getOption("path");
                        }
                    }
                }
                vector<HistoricalMapParameters> tmp_sorted_historical_map_parameters;
                tmp_sorted_historical_map_parameters.reserve(tmp_map_parameters.size());
                // Make sure the queue is a sorted vector
                for(const auto &item:tmp_map_parameters)
                {
                    tmp_sorted_historical_map_parameters.push_back(item.second);
                }
                sort(tmp_sorted_historical_map_parameters.begin(), tmp_sorted_historical_map_parameters.end());
                for(const auto &item: tmp_map_parameters)
                {
                    all_historical_map_parameters.push(item.second);
                }
                setInitialHistoricalParameters();
                has_parsed_historical = true;
            }
        }

        void setInitialHistoricalParameters()
        {
            if(!all_historical_map_parameters.empty())
            {
                historical_fine_map_file = all_historical_map_parameters.front().fine_map_file;
                historical_coarse_map_file = all_historical_map_parameters.front().coarse_map_file;
                habitat_change_rate = all_historical_map_parameters.front().habitat_change_rate;
                gen_since_historical = all_historical_map_parameters.front().generation;
                all_historical_map_parameters.pop();
            }
        }

        /**
         * @brief Prints selected important variables to the terminal.
         */
        void printVars()
        {
            std::stringstream os;
            os << "Seed: " << seed << std::endl;
            os << "Speciation rate: " << spec << std::endl;
            if(is_protracted)
            {
                os << "Protracted variables: " << min_speciation_gen << ", " << max_speciation_gen << std::endl;
            }
            os << "Job Type: " << task << std::endl;
            os << "Max time: " << max_time << std::endl;
            printSpatialVars();
            os << "Output directory: " << output_directory << std::endl;
            os << "Disp Rel Cost: " << dispersal_relative_cost << std::endl;
            os << "Times: ";
            if(times_file == "set")
            {
                for(unsigned long i = 0; i < times.size(); i++)
                {
                    os << times[i];
                    if(i != times.size() - 1)
                    {
                        os << ", ";
                    }
                }
            }
            else
            {
                os << " 0.0";
            }
            os << std::endl;
            writeInfo(os.str());
        }


        /**
         * @brief Prints the spatial variables.
         */
        void printSpatialVars()
        {
            std::stringstream os;
            os << "Dispersal (tau, sigma): " << tau << ", " << sigma << std::endl;
            os << "Dispersal method: " << dispersal_method << std::endl;
            if(dispersal_method == "norm-uniform")
            {
                os << "Dispersal (m, cutoff): " << m_prob << ", " << cutoff << std::endl;
            }
            os << "Fine map\n-file: " << fine_map_file << std::endl;
            os << "-dimensions: (" << fine_map_x_size << ", " << fine_map_y_size << ")" << std::endl;
            os << "-offset: (" << fine_map_x_offset << ", " << fine_map_y_offset << ")" << std::endl;
            os << "Coarse map\n-file: " << coarse_map_file << std::endl;
            os << "-dimensions: (" << coarse_map_x_size << ", " << coarse_map_y_size << ")" << std::endl;
            os << "-offset: (" << coarse_map_x_offset << ", " << coarse_map_y_offset << ")" << std::endl;
            os << "-scale: " << coarse_map_scale << std::endl;
            os << "Sample grid" << std::endl;
            if(sample_mask_file != "none" && sample_mask_file != "null")
            {
                os << "-file: " << sample_mask_file << std::endl;
            }
            os << "-dimensions: (" << sample_x_size << ", " << sample_y_size << ")" << std::endl;
            os << "-optimised area: (" << grid_x_size << ", " << grid_y_size << ")" << std::endl;
            os << "-optimised offsets: (" << sample_x_offset << ", " << sample_y_offset << ")" << std::endl;
            os << "Deme: " << deme << std::endl;
            os << "Deme sample: " << deme_sample << std::endl;
            writeInfo(os.str());
        }

        /**
         * @brief Sets the metacommunity parameters.
         * @param metacommunity_size the number of individuals in the community
         * @param speciation_rate the speciation rate for the metacommunity
         * @param seed the seed for the simulation
         * @param task the job referencing number
         */
        void setMetacommunityParameters(const unsigned long &metacommunity_size,
                                        const long double &speciation_rate,
                                        const unsigned long &seed,
                                        const unsigned long &task)
        {
            output_directory = "Default";
            // randomise the seed slightly so that we get a different starting number to the initial simulation
            this->seed = static_cast<long long int>(elegantPairing(seed, task));
            this->task = (long long int) task;
            deme = metacommunity_size;
            deme_sample = 1.0;
            spec = speciation_rate;
            // Default to 1000 seconds - should be enough for most simulation sizes, but can be changed later if needed.
            max_time = 1000;
            times_file = "null";
            min_speciation_gen = 0.0;
            max_speciation_gen = 0.0;
        }

        /**
         * @brief Overloading the << operator for outputting to the output stream
         * @param os the output stream.
         * @param m the SimParameters object.
         * @return os the output stream.
         */
        friend std::ostream &operator<<(std::ostream &os, const SimParameters &m)
        {
            os << m.fine_map_file << "\n" << m.coarse_map_file << "\n" << m.historical_fine_map_file << "\n";
            os << m.historical_coarse_map_file << "\n" << m.sample_mask_file << "\n";
            os << m.seed << "\n" << m.task << "\n" << m.grid_x_size << "\n" << m.grid_y_size << "\n";
            os << m.sample_x_size << "\n" << m.sample_y_size << "\n" << m.sample_x_offset << "\n" << m.sample_y_offset
               << "\n";
            os << m.fine_map_x_size << "\n" << m.fine_map_y_size << "\n";
            os << m.fine_map_x_offset << "\n" << m.fine_map_y_offset << "\n" << m.coarse_map_x_size << "\n"
               << m.coarse_map_y_size << "\n" << m.coarse_map_x_offset << "\n";
            os << m.coarse_map_y_offset << "\n" << m.coarse_map_scale << "\n" << m.desired_specnum << "\n";
            os << m.dispersal_relative_cost << "\n" << m.deme << "\n" << m.deme_sample << "\n";
            os << m.spec << "\n" << m.sigma << "\n" << m.max_time << "\n" << m.gen_since_historical << "\n"
               << m.habitat_change_rate << "\n" << m.tau;
            os << "\n" << m.dispersal_method << "\n";
            os << m.m_prob << "\n" << m.cutoff << "\n" << m.restrict_self << "\n" << m.landscape_type << "\n"
               << m.times_file << "\n";
            os << m.dispersal_file << "\n" << m.uses_spatial_sampling << "\n";
            os << m.times.size() << "\n";
            for(const auto &each : m.times)
            {
                os << each << "\n";
            }
            os << m.configs;
            return os;
        }

        /**
         * @brief Overloading the >> operator for inputting from an input stream
         * @param is the input stream
         * @param m the mapvars object
         * @return is the input stream
         */
        friend std::istream &operator>>(std::istream &is, SimParameters &m)
        {
            getline(is, m.fine_map_file);
            getline(is, m.coarse_map_file);
            getline(is, m.historical_fine_map_file);
            getline(is, m.historical_coarse_map_file);
            getline(is, m.sample_mask_file);
            is >> m.seed >> m.task >> m.grid_x_size >> m.grid_y_size;
            is >> m.sample_x_size >> m.sample_y_size >> m.sample_x_offset >> m.sample_y_offset;
            is >> m.fine_map_x_size >> m.fine_map_y_size;
            is >> m.fine_map_x_offset >> m.fine_map_y_offset >> m.coarse_map_x_size >> m.coarse_map_y_size
               >> m.coarse_map_x_offset;
            is >> m.coarse_map_y_offset >> m.coarse_map_scale >> m.desired_specnum >> m.dispersal_relative_cost
               >> m.deme >> m.deme_sample;
            is >> m.spec >> m.sigma >> m.max_time >> m.gen_since_historical >> m.habitat_change_rate >> m.tau;
            is.ignore();
            getline(is, m.dispersal_method);
            is >> m.m_prob >> m.cutoff >> m.restrict_self >> m.landscape_type;
            is.ignore();
            getline(is, m.times_file);
            getline(is, m.dispersal_file);
            is >> m.uses_spatial_sampling;
            unsigned long tmp_size;
            double tmp_time;
            is >> tmp_size;
            for(unsigned long i = 0; i < tmp_size; i++)
            {
                is >> tmp_time;
                m.times.push_back(tmp_time);
            }
            is >> m.configs;
            return is;
        }

        SimParameters &operator=(const SimParameters &other) = default;
    };
}
#endif //SPECIATIONCOUNTER_SIMPARAMETERS_H
