/*
 * GillesPy2 is a modeling toolkit for biochemical simulation.
 * Copyright (C) 2019-2021 GillesPy2 developers.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <cmath>
#include <random>
#include <csignal>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#undef max
#endif

#include "SSASolver.h"

namespace Gillespy
{
	volatile bool interrupted = false;

#ifdef _WIN32
	BOOL WINAPI eventHandler(DWORD CtrlType)
	{
		interrupted = true;
		return TRUE;
	}
#endif

	void signalHandler(int signum)
	{
		interrupted = true;
	}

	void ssa_direct(Simulation<unsigned int> *simulation)
	{
#ifdef _WIN32
		SetConsoleCtrlHandler(eventHandler, TRUE);
#else
		signal(SIGINT, signalHandler);
#endif

		if (simulation)
		{
			std::mt19937_64 rng(simulation->random_seed);

			//Number of bytes for copying states
			unsigned int state_size = sizeof(int) * ((simulation->model)->number_species);

			//Calculated propensity values for current state
			double *propensity_values = new double[(simulation->model)->number_reactions];

			//Simulate for each trajectory
			for (unsigned int trajectory_number = 0; trajectory_number < simulation->number_trajectories; trajectory_number++)
			{
				if (interrupted)
				{
					break;
				}

				//Set up current state from initial state
				unsigned int entry_count = 0;
				simulation->current_time = 0;
				simulation->reset_output_buffer(trajectory_number);
				simulation->output_buffer_range(std::cout);

				//calculate initial propensities
				for (unsigned int reaction_number = 0; reaction_number < ((simulation->model)->number_reactions); reaction_number++)
				{
					propensity_values[reaction_number] = (simulation->propensity_function)->evaluate(reaction_number, simulation->current_state);
				}

				double propensity_sum;
				while (simulation->current_time < (simulation->end_time))
				{
					if (interrupted)
					{
						break;
					}

					//Sum propensities
					propensity_sum = 0;
					for (unsigned int reaction_number = 0; reaction_number < ((simulation->model)->number_reactions); reaction_number++)
					{
						propensity_sum += propensity_values[reaction_number];
					}

					//No more reactions
					if (propensity_sum <= 0)
					{
						//Quit simulating this trajectory
						break;
					}

					//Reaction will fire, determine which one
					double cumulative_sum = rng() * propensity_sum / rng.max();
					simulation->current_time += -log(rng() * 1.0 / rng.max()) / propensity_sum;

					for (unsigned int potential_reaction = 0; potential_reaction < ((simulation->model)->number_reactions); potential_reaction++)
					{
						cumulative_sum -= propensity_values[potential_reaction];
						//This reaction fired

						if (cumulative_sum <= 0 && propensity_values[potential_reaction] > 0)
						{
							//Update current state
							Reaction &reaction = ((simulation->model)->reactions[potential_reaction]);
							for (unsigned int species_number = 0; species_number < ((simulation->model)->number_species); species_number++)
							{
								simulation->current_state[species_number] += reaction.species_change[species_number];
							}

							//Recalculate needed propensities
							for (unsigned int &affected_reaction : reaction.affected_reactions)
							{
								propensity_values[affected_reaction] = (simulation->propensity_function)->evaluate(affected_reaction, simulation->current_state);
							}

							break;
						}
					}

					// Output timestep results
					while (entry_count < simulation->number_timesteps && simulation->timeline[entry_count] <= simulation->current_time)
					{
						if (interrupted)
						{
							break;
						}

						simulation->output_buffer_range(std::cout, entry_count++);
					}
				}

				
				// Copy final state for rest of entries
				if (entry_count < simulation->number_timesteps)
				{
					simulation->current_time = simulation->timeline[simulation->number_timesteps - 1];
					simulation->output_buffer_range(std::cout, simulation->number_timesteps - 1);
				}
			}

			delete propensity_values;
		}
	}
}
