#include <iostream>
#include <vector>
#include <memory>

using std::shared_ptr;
using std::make_shared;

int const income_interval = 6;
int const start_money = 650;
int const start_income = 50;
//just under 07:00 minutes is usually when the game ends
int const game_time = 400;

// multiple of time required to be spent
int const require_spent_multiple = 10;
// initial delay before spending is required
int const require_spent_delay = 30;

struct plan {
	//plan acquisition cost
	int cost;
	//resulting increase in revenue
	int increase;
	//timeout before repeat purchase
	int regen;
};
std::vector<plan> plans({
	{ 50, 2, 4 },
	{ 300, 15, 13 },
	{ 1000, 70, 60 },
});

struct game_state {
	// how much money we have now
	int money;
	//income on each income interval
	int income;
	//total money we had/have
	int total;
	//money spent on upgrades
	int spent;
	//money invested for income
	int invested;
	//block purchase of the plan for regen time
	std::vector<int> plan_timeout;
	//total elapsed time
	int time;
	
	game_state() {
		money = start_money;
		income = start_income;
		total = money;
		plan_timeout.resize( plans.size(), 0 );
		time = 0;
		spent = 0;
		invested = 0;
	}
	
	void step() {
		time++;
		
		//step plan regen time
		for( auto & pt : plan_timeout ) {
			if( pt > 0 ) {
				pt--;
			}
		}
		
		//add new money each interval
		if( time % income_interval == 0 ) {
			money += income;
			total += income;
		}
	}
	
	bool can_buy_plan(size_t i) {
		return money >= plans[i].cost 
			&& plan_timeout[i] == 0;
	}
	
	void buy_plan(int i) {
		plan_timeout[i] += plans[i].regen;
		invested += plans[i].cost;
		money -= plans[i].cost;
		income += plans[i].increase;
	}
};

struct strategy {
	shared_ptr<game_state> state;
	//name of this strategy
	std::string name;
	//stop investing at this time, if -1 then never stop
	int stop_invest_when;
	
	strategy( std::string const & name ) :
		state( new game_state ),
		name(name) {
		stop_invest_when = -1;
	}
	strategy( std::string const & name, shared_ptr<game_state> state ) :
		state( state ),
		name( name ) {
		stop_invest_when = -1;
	}
	
	void step(int spend) {
		//ensure we're spending enough
		if( state->spent < spend ) {
			int a = std::min( spend, state->money );
			state->money -= a;
			state->spent += a;
		}
		
		state->step();
		step_impl();
	}
	
	virtual void step_impl() = 0;
	
	virtual bool should_buy_plan( int i ) {
		if( stop_invest_when >= 0 && state->time >= stop_invest_when ) {
			return false;
		}
		return state->can_buy_plan(i);
	}
};

struct buy_all : public strategy {
	buy_all() : strategy( "buy_all" ) { }
	
	void step_impl() {
		for( int i=plans.size()-1; i >=0; --i ) {
			if( should_buy_plan(i) ) {
				state->buy_plan(i);
			}
		}
	}
};

struct buy_one_plan : public strategy {
	static std::string plan_name( int i ) {
		char buf[32];
		sprintf( buf, "Plan%d", i );
		return buf;
	}
	
	int plan;
	
	buy_one_plan(int i) : 
		strategy( plan_name(i) ),
		plan(i) {
	}
	
	void step_impl() {
		if( should_buy_plan(plan) ) {
			state->buy_plan(plan);
		}
	}
};

struct buy_none : public strategy {
	buy_none() :
		strategy( "none" ) {
	}
	
	void step_impl() {
	}
};

typedef std::vector<shared_ptr<strategy>> strategies;

/**
	The main simulation driver. It steps through the strategies and outputs
	the results at regular intervals.
*/
void run( std::string const & name, strategies const & all ) {
	std::cout << std::endl;
	std::cout << " - - " << name << " - - " << std::endl;
	
	for( int i=0; i < game_time; ++i ) {
		//must have spent at least this much by this time
		int spend = i * require_spent_multiple;
		if( i < require_spent_delay ) {
			spend = 0;
		}
		
		for( auto & s : all ) {
			s->step(spend);
		}
		
		//write opdate regularly
		if( i % 30 == 0 ) {
			for( auto & s : all ) {
				std::cout << s->name << ": " << s->state->money << "/" << s->state->total << "\t";
			}
			std::cout << std::endl;
		}
	}
	
	std::cout << "Spent/Invested" << std::endl;
	for( auto & s : all ) {
		std::cout << s->name << ": " << (s->state->spent+s->state->money) 
			<< "/" << s->state->invested <<  "\t";
	}
	std::cout << std::endl;
}

//build a stopping strategy
shared_ptr<strategy> stop_at( int time, shared_ptr<strategy> st ) {
	st->stop_invest_when = time;
	
	char buf[32];
	sprintf( buf, "Stop@%d", time );
	st->name += buf;
	
	return st;
}

int main() {
	run( "Variety", {
		make_shared<buy_none>(),
		make_shared<buy_all>(),
		make_shared<buy_one_plan>(0),
		make_shared<buy_one_plan>(1),
		make_shared<buy_one_plan>(2),
	});
	
	//buy_all appears best, so check when to stop
	run( "Stopping", {
		stop_at( 180, make_shared<buy_all>() ),
		stop_at( 240, make_shared<buy_all>() ),
		stop_at( 300, make_shared<buy_all>() ),
		stop_at( 360, make_shared<buy_all>() ),
	});
	
	//with spending the Plan0/1 appears good
	run( "Stopping Plan0", {
		stop_at( 180, make_shared<buy_one_plan>(1) ),
		stop_at( 240, make_shared<buy_one_plan>(1) ),
		stop_at( 300, make_shared<buy_one_plan>(1) ),
		stop_at( 360, make_shared<buy_one_plan>(1) ),
	});
}
