#pragma once

#include <map>
#include <vector>
#include <string>
#include <set>
#include "GOAPAction.h"
#include "GOAPState.h"
#include "GOAPResources.h"

namespace Game
{
	/*
		Comparision function for states
		based on the resources of each state
	*/
	struct StateComp
	{
		bool operator()(GOAPState* a, GOAPState* b)
		{
			return a->Comp(*b);
		}
	};

	/*
		Used to sort GOAP states in the state list
		based on the HValues
	*/
	struct GOAPComp
	{
		bool operator()(GOAPState* a, GOAPState* b)
		{
			if(a->GetScore() == b->GetScore())
			{
				if(a->GetHVal() == b->GetHVal())
					return a < b;
				else
					return a->GetHVal() < b->GetHVal();
			}
			else
				return a->GetScore() < b->GetScore();
		}
	};


	class GOAPSolver
	{
	public:
		GOAPSolver(GOAPState& end, std::map<int, std::string>& idnames,
					std::vector<GOAPAction>& actions);
		~GOAPSolver();

		/*
			Sets the starting state for the solver, and the resources
			owned by the character
		*/
		void SetStart(GOAPState& startState, GOAPResources& presources);
		
		/*
			Searches all possibilities to find the best course of action
			(Limited to 10,000 attempts)
		*/
		void PerformAStar(std::vector<GOAPAction>& actionList);

	private:
		std::map<int, std::string> idtoname;//used for printing plans
		std::vector<GOAPAction> actionSet;
		std::set<GOAPState*, StateComp> generatedStates;
		std::set<GOAPState*, GOAPComp> open;

		GOAPState start;
		GOAPState end;
		GOAPState current;
		bool foundEnd;

		/*
			Checks the "neighboring" states to find the possible actions
			that can be performed (with their outcomes)
			and computes the HVal and GVal for those actions
		*/
		void PerformOneStep(std::vector<GOAPAction>& actionList);
		
				/*
			Returns the cheapest option from the open list
		*/
		GOAPState* GetCheapest();
		
		/*
			Removes the cheapest option from the open list
		*/
		void RemoveOpen();
		
		/*
			Adds the state to the open list
		*/
		void Open(GOAPState* state);
		
		/*
			Removes the state from the open list and adds it to the closed list
		*/
		void Close(GOAPState* state);

		/*
			Checks to see if the state satisfies all of the end conditions
		*/
		bool ReachedEnd(GOAPState* state);
		
		/*
			Creates the list of actions needed to get from the start state
			to the end state
		*/
		void GeneratePath(GOAPState* state, std::vector<GOAPAction>& list);

		/*
			Uses the number of differences between the current state and the given
			state as the heuristic 
			(more differences are worse)
		*/
		float ComputeHeuristic(GOAPState* state);

		/*
			Checks to see if the given state can already be reached through
			another method (that is, already exists in the list)
		*/
		bool isInGenerated(GOAPState* s);
		
		/*
			Returns the state from the list of computed states
		*/
		GOAPState* GetGenerated(GOAPState* s);
		
		/*
			Adds the state to the list of computed states
		*/
		void AddToGenerated(GOAPState* s);
		
		/*
			Clear all computed states
		*/
		void ClearStates();
	};
}
#include "GOAPSolver.h"
#include "CMacros.h"
#include "CWindow.h"
#include "GOAPAction.h"

using namespace std;
using namespace Combustion;

namespace Game
{
	GOAPSolver::GOAPSolver(GOAPState& e, map<int, string>& idnames, vector<GOAPAction>& actions)
		: end(e), idtoname(idnames), actionSet(actions), foundEnd(false)
	{}

	GOAPSolver::~GOAPSolver() 
	{
		ClearStates();
	}

	void GOAPSolver::ClearStates()
	{
		for(auto it = generatedStates.begin(); it != generatedStates.end(); ++it)
			delete (*it);
		generatedStates.clear();
	}

	void GOAPSolver::SetStart(GOAPState& startState, GOAPResources& resources)
	{ 
		foundEnd = false;
		
		start = GOAPState(startState);
		start.SetWorldState(resources);
		current = start;
		open.clear();
		
		ClearStates();
		Open(¤t);
	}

	GOAPState* GOAPSolver::GetCheapest()
	{
		GOAPState* state = *(open.begin());
		return state;
	}

	void GOAPSolver::Open(GOAPState* state)
	{
		state->SetIsClosed(false);
		state->SetIsOpen(true);
		open.insert(state);
	}

	void GOAPSolver::Close(GOAPState* state)
	{
		state->SetIsClosed(true);
		state->SetIsOpen(false);
	}

	void GOAPSolver::RemoveOpen()
	{
		open.erase( open.begin() );
	}

	bool GOAPSolver::ReachedEnd(GOAPState* state)
	{
		return state->IsSatisfied();
	}

	void GOAPSolver::GeneratePath(GOAPState* state, std::vector<GOAPAction>& list)
	{
		if(state->GetAction() != 0)
		{
			list.push_back(*(state->GetAction()));
		}

		if(state->GetParent() != 0)
			GeneratePath(state->GetParent(), list);
	}

	void GOAPSolver::PerformOneStep(std::vector<GOAPAction>& actionList)
	{
		if(open.size() == 0)
			return;

		GOAPState* currstate = GetCheapest();

		if( ReachedEnd(currstate) )
		{
			foundEnd = true;
			GeneratePath(currstate, actionList);
			return;
		}

		//move current to closed set
		RemoveOpen();
		Close(currstate);

		//find all actions that can be performed on the current state
		for(unsigned int i = 0; i < actionSet.size(); ++i)
		{
			if( actionSet[i].MeetsPrecondition(*currstate) )
			{
				//Get state if this action is performed
				GOAPState* neighbor = actionSet[i].ApplyToState(*currstate);
				neighbor->SetAction(&(actionSet[i]));

				if(!isInGenerated(neighbor))
				{
					neighbor->SetIsOpen(true);
					AddToGenerated(neighbor);
				}
				else
				{
					GOAPState* temp = GetGenerated(neighbor);
					delete neighbor;
					if(neighbor->GetIsClosed())
						continue;
					neighbor = temp;
				}

				//if neighbor isOpen
				if(neighbor->GetIsOpen())
				{
					neighbor->SetGVal( currstate->GetGVal() + actionSet[i].GetGCost() );
					neighbor->SetHVal( ComputeHeuristic(neighbor) );
					neighbor->SetParent(currstate);

					//check to see if the path to this one is better....
					if(currstate->GetGVal() < neighbor->GetGVal())
					{
						neighbor->SetParent(currstate);
						neighbor->SetGVal(currstate->GetGVal() + actionSet[i].GetGCost());
					}

					Open(neighbor);
				}
			}
		}
	}

	void GOAPSolver::PerformAStar(std::vector<GOAPAction>& actionList)
	{
		int maxAttempts = 10000;

		while(!foundEnd && maxAttempts > 0)
		{
			PerformOneStep(actionList);
			maxAttempts--;
		}
	}

	float GOAPSolver::ComputeHeuristic(GOAPState* state)
	{
		return end.ComputeNumDifferences(*state);
	}

	bool GOAPSolver::isInGenerated(GOAPState* s)
	{
		return (generatedStates.find(s) != generatedStates.end());
	}

	GOAPState* GOAPSolver::GetGenerated(GOAPState* s)
	{
		return *(generatedStates.find(s));
	}

	void GOAPSolver::AddToGenerated(GOAPState* s)
	{
		generatedStates.insert(s);
	}
}