#include "TabuSearch.imp.hh"
#include <iostream.h>
#include <assert.h>
#include <math.h>
#include <mallba/iopacket.hh>
#include <stdio.h>
#include <pvm3/pvm3.h>

#ifndef INIT_TAG
#define INIT_TAG 0
#endif

#ifndef STATE_TAG
#define STATE_TAG 1
#endif

#ifndef END_TAG
#define END_TAG 2
#endif

#ifndef INFO_TAG
#define INFO_TAG 4
#endif


namespace TabuSearch {


	// State -----------------------------------------------------------------


	State::State( const Problem& pbm )
	: direction(pbm.direction()),
	  current_trial(0),
	  current_iteration(0),
	  current_init_cost((-1) * pbm.direction() * TS_INFINITY()),

	  current_solution(pbm),
	  current_cost(current_init_cost),
	  current_best_solution(pbm),
	  current_best_cost(current_cost),
	  current_worst_cost((-1) * current_cost),

	  global_best_solution(pbm),
	  global_best_cost(current_best_cost),
	  global_worst_cost(current_worst_cost),
	  time_spent(0.0)
	{
	}


	State::~State()
	{
	}


	void State::update( const int trial, const int iteration, const double cicost,
						const Solution csolution, const double ccost,
						const float time )
	{  	
		bool betterL=false, betterG=false;
		bool worseL=false , worseG=false;

		current_trial     = trial;
		current_iteration = iteration;
		current_solution  = csolution;
		current_cost      = ccost;
		time_spent        = time;

		switch (direction)
		{
			case minimize: betterL = (ccost < current_best_cost);
						   betterG = (ccost < global_best_cost);
						   worseL  = (ccost > current_worst_cost);
						   worseG  = (ccost > global_worst_cost);
						   break;

			case maximize: betterL = (ccost > current_best_cost);
						   betterG = (ccost > global_best_cost);
						   worseL  = (ccost < current_worst_cost);
						   worseG  = (ccost < global_worst_cost);
						   break;
		}

		if ((iteration == 0) || betterL)
		{
			current_best_solution = csolution;
			current_best_cost     = ccost;

			if (betterG)
			{
				global_best_solution = csolution;
				global_best_cost     = ccost;
			}
		}

		if ((iteration == 0) || worseL)
		{
			current_worst_cost = ccost;

			if (worseG) global_worst_cost = ccost;
		}

		current_init_cost = cicost;
	}


	State& State::operator= (const State& state)
	{
		current_trial     = state.current_trial;
		current_iteration = state.current_iteration;
		current_init_cost = state.current_init_cost;

		current_solution      = state.current_solution;
		current_cost          = state.current_cost;
		current_best_solution = state.current_best_solution;
		current_best_cost     = state.current_best_cost;
		current_worst_cost    = state.current_worst_cost;

		global_best_solution = state.global_best_solution;
		global_best_cost     = state.global_best_cost;
		global_worst_cost    = state.global_worst_cost;

		time_spent = state.time_spent;

		return *this;
	}
	
	
	ostream& operator<< (ostream& os, const State& state)
	{
		os << "\n\n\n----- STATE REPORT -------------\n" << endl;

		os << " TRIAL "              << state.current_trial       << endl;
		os << "  . iteration:     " << state.current_iteration   << endl;
		os << "  · Initial cost:  " << state.current_init_cost   << endl;
		os << "  · solution:      " << state.current_solution;
		os << "  · cost:          " << state.current_cost        << endl;
		os << "  · best solution: " << state.current_best_solution;
		os << "  · best cost:     " << state.current_best_cost   << endl;
		os << "  · worst cost:    " << state.current_worst_cost  << endl << endl;

		os << " GLOBAL " << endl;
		os << "  · best solution: "  << state.global_best_solution;
		os << "  · best bost:     "  << state.global_best_cost  << endl;
		os << "  · worst cost:    "  << state.global_worst_cost << endl << endl;

		os << " TOTAL EXECUTION TIME (so far): " << state.time_spent       << endl;

		return os;
	}
	
	
	istream& operator>> (istream& is, State& state)
	{
		return is;
	}
	
	
	opacket& operator<< (opacket& op, const State& state)
	{
		return op;
	}
	
	
	ipacket& operator>> (ipacket& ip, State& state)
	{
		return ip;
	}



	// Statistics ------------------------------------------------------


	Statistics::Statistics( const Problem& pbm )
	: total_nb_trials(0),
	  last_state(pbm),
	  fraction_of_tabu_moves(TS_UNDEFINED()),
	  improve_wrt_init_sol(TS_UNDEFINED()),
	  improve_wrt_subopt(TS_UNDEFINED()),
	  average_best_cost(TS_UNDEFINED())
	{
	}
	

	Statistics::~Statistics()
	{
	}


	void Statistics::reset()
	{
		double undefined = TS_UNDEFINED();

		total_nb_trials = 0;
		fraction_of_tabu_moves = undefined;
		improve_wrt_init_sol   = undefined;
		improve_wrt_subopt     = undefined;
		average_best_cost      = undefined;
	}


	void Statistics::update( const int ntrials, const State& state,
							 const double fraction, const double improve_init,
							 const double improve_subopt, const double avgbcost )
	{
		total_nb_trials = ntrials;
		last_state      = state;

		fraction_of_tabu_moves = fraction;
		improve_wrt_init_sol   = improve_init;
		improve_wrt_subopt     = improve_subopt;
		average_best_cost      = avgbcost;
	}


	ostream& operator<< (ostream& os, const Statistics& stats)
	{
		os << "\n---------------------------------------------------------------" << endl;
		os << "                           STATISTICS                            " << endl;
		os << stats.last_state;
		os << " · Fraction of tabu moves: " << stats.fraction_of_tabu_moves     << endl;
		os << " · Improve wrt initial solution: " << stats.improve_wrt_init_sol << endl;
		os << " · Improve wrt known suboptimum: " << stats.improve_wrt_subopt   << endl;
		os << " · Average best cost: " << stats.average_best_cost               << endl;

		return os;		
	}


	opacket& operator<< (opacket& op, const Statistics& stats)
	{
		return op;
	}
 	

	// Solver (superclasse)---------------------------------------------------


    Solver::Solver (const Problem& pbm, const SetUpParams& setup)
	: problem(pbm), params(setup),
	  state(pbm), statistics(pbm), userstat(), structure( setup,pbm ),
	  tid(0)
	{
	}


	const State& Solver::get_state () const
	{
		return state;
	}


	const Statistics& Solver::get_statistics () const
	{
		return statistics;
	}


	const Solution& Solver::best_solution () const
	{
		return state.global_best_solution;
	}


	double Solver::best_cost () const
	{
		return state.global_best_cost;
	}


	double Solver::worst_cost () const
	{
		return state.global_worst_cost;
	}


	Solver::~Solver ()
	{
	}


	// Solver sequencial -----------------------------------------------------


	Solver_Seq::Solver_Seq (const Problem& pbm, const SetUpParams& setup)
	: Solver(pbm,setup)
	{
/*		int pid = pvm_mytid();
		assert(pid>=0);

		FILE* f=fopen("PVM_ERRORS.readme","w");
		pvm_catchout(f);
		int cod = pvm_spawn( "TabuSearchObserver.e", 0, 0, 0 ,1, &tid );
		assert(cod==1);
*/	}


	Solver_Seq::~Solver_Seq ()
	{
	}
	

	void Solver_Seq::run ()
	{
		int       iter, trial=1, repetitions=0;
		bool      found=true, improve=true;
		double    delta;
		Movement  mov;

		Solution  sol(problem);
		Solution  previous(problem);
		double    cost, prevcost, initcost;

		float time = used_time();
		float time_spent = time;

		// A starting signal is emitted to the observer
		// (information about the setup parameters is sent)

/*		opacket op;
		op << params.independent_runs << params.nb_iterations;
		op.send( tid,INIT_TAG );
*/

		while (trial <= params.independent_runs)
		{
		    // A starting solution is created
		
			sol = Solution( problem );
			sol.initial_solution();
			cost = initcost = sol.fitness();

			// Initialization of the main variables

			iter = 0;
			state.update( trial,iter,cost,sol,cost,time_spent );

			while ( !terminateQ( problem,sol,params,iter ) && found )
			{
				previous = sol;
				prevcost = cost;

				// INTENSIFICATION, rewarding the current solution
			
				if (repetitions >= params.max_repetitions)
				{
					double cost_before_intens = cost;
	
					for(int i=0; i<params.nb_intensifications; i++)
					{		
						sol.reward();

						if (params.max_neighbors < 0)
							found = choose_best_all( problem, sol, params, structure, state, mov );
						else
							found = choose_best_move( problem, sol, params, structure, state, mov );
							
						sol.unreward();

		
						if (found)
						{
							delta = sol.delta( mov );
							sol.apply( mov );

							structure.make_tabu( mov, sol, state );
							structure.update();
				        	
							if (params.use_delta_function)  cost += delta;
							else  cost = sol.fitness();
	
							// Update the state

							iter++;
							time_spent += used_time(time);
							state.update( trial, iter, initcost, sol, cost, time_spent );
//                            cout << state << endl;
//cout << cost << endl;
						}
					}					
										

					switch (problem.direction())
					{
						case minimize: improve=(cost < cost_before_intens); break;
						case maximize: improve=(cost > cost_before_intens); break;
						default:       improve=false;
					}
											

					// DIVERSIFICATION SLOWLY, penalizing the current solution
							
					if (found && (!improve))
					{
						for(int i=0; i<params.nb_diversifications; i++)
						{
							sol.penalize();

	 						if (params.max_neighbors < 0)
								found = choose_best_all( problem,sol,params,structure,state,mov );
							else
								found = choose_best_move( problem,sol,params,structure,state,mov );
								
							sol.unpenalize();

		
							if (found)
							{
								delta = sol.delta( mov );
								sol.apply( mov );

								structure.make_tabu( mov, sol, state );
								structure.update();
				        	
								if (params.use_delta_function)  cost += delta;
								else  cost = sol.fitness();

								// Update the state

								iter++;
								time_spent += used_time(time);
								state.update( trial, iter, initcost, sol, cost, time_spent );
//                                cout << state << endl;
//cout << cost << endl;
							}
						}
					

						switch (problem.direction())
						{
							case minimize: improve=(cost < cost_before_intens); break;
							case maximize: improve=(cost > cost_before_intens); break;
							default:       improve=false;
						}
											

						// ESCAPE (DIVERSIFICATION STRONGLY)

		 				if (found && (!improve))
						{
							sol.escape();
							cost = sol.fitness();

							// Update the state

							iter++;
							time_spent += used_time(time);
							state.update( trial, iter, initcost, sol, cost, time_spent );
//                            cout << state << endl;
//cout << cost << endl;
						}
					}
				}


				// NORMAL & ESCAPED EXPLORATION
	
				if (params.max_neighbors < 0)
					found = choose_best_all( problem, sol, params, structure, state, mov );
				else
					found = choose_best_move( problem, sol, params, structure, state, mov );						
		

				if (found)
				{
					delta = sol.delta( mov );
					sol.apply( mov );
					
					structure.make_tabu( mov, sol, state );
					structure.update();
				        	
					if (params.use_delta_function) cost += delta; 
					else cost = sol.fitness();

					// Update the state

					iter++;
					time_spent += used_time(time);
					state.update( trial, iter, initcost, sol, cost, time_spent );	
//					cout << state << endl;
//cout << cost << endl;
				}

			
				// Information about the current state of the algorithm
				// is sent to the observer

/*				opacket op2;
				op2 << bcost_in_trial << iter << cost << bcost << trial;
				op2.send( tid,STATE_TAG );
*/
				// Check if the solution obtained is a repetition of the previous one.

				repetitions = ((previous==sol) ? (repetitions+1) : 0);
			}

			// Statistics related to the trial done

/*			improve = ( (params.known_suboptimum < 0) ? (TS_UNDEFINED()) :
						((bcost_in_trial * 100 / params.known_suboptimum)-100) );
			cout << "% " << improve << endl;

			statistics.update( params.independent_runs, state,
				double(userstat.total_nb_tabu_moves/userstat.total_nb_explored_moves),
				(100 - (bcost_in_trial * 100 / icost_in_trial)), improve, 0 );
*/
//			cout << statistics << endl;

	
			trial++;
		}

		// An ending signal is emitted to the observer

/*		opacket op3;
		op3.send( tid, END_TAG );
*/
	}




	// Solver LAN ------------------------------------------------------------

	Solver_Lan::Solver_Lan (const Problem& pbm, const SetUpParams& setup):
	            Solver(pbm,setup) {}

	Solver_Lan::~Solver_Lan () {}

	void Solver_Lan::run () {}



	// Solver WAN ------------------------------------------------------------

	Solver_Wan::Solver_Wan (const Problem& pbm, const SetUpParams& setup):
		    Solver(pbm,setup) {}

	Solver_Wan::~Solver_Wan () {}

	void Solver_Wan::run (){}


}


