//   GreKo chess engine
//   (c) 2002-2021 Vladimir Medvedev <vrm@bk.ru>
//   http://greko.su

#ifndef SEARCH_H
#define SEARCH_H

#ifndef SINGLE_THREAD
#include <thread>
#endif

#include "position.h"
#include "utils.h"

const int MAX_PLY = 256;

const int MAX_NUM_THREADS = 4;
extern int NUM_THREADS;

struct SearchParams
{
	SearchParams() :
		analysis(false),
		silent(false),
		limitDepth(false),
		limitNodes(false),
		limitTime(true),
		limitKnps(false),
		maxDepth(0),
		maxNodes(0),
		maxTimeHard(2000),
		maxTimeSoft(2000),
		maxKnps(0.0)
	{}

	bool  analysis;
	bool  silent;

	bool  limitDepth;
	bool  limitNodes;
	bool  limitTime;
	bool  limitKnps;

	int    maxDepth;
	NODES  maxNodes;
	U32    maxTimeHard;
	U32    maxTimeSoft;
	double maxKnps;
};
////////////////////////////////////////////////////////////////////////////////

struct SearchResults
{
	Move         bestMove;
	int          depth;
	NODES        nodes;
	vector<Move> pv;
	EVAL         score;
	U64          time;
};
////////////////////////////////////////////////////////////////////////////////

struct SearchContext
{
	int   currentIteration;
	bool  singleReply;
	U64   startTime;
	bool  terminated;
};
////////////////////////////////////////////////////////////////////////////////

class SearchThread
{
public:
	SearchThread() :
		m_id(-1),
		m_run(false)
	{}

	virtual ~SearchThread()
	{
#ifndef SINGLE_THREAD
		if (m_thread.joinable())
			m_thread.join();
#endif
	}

	EVAL AlphaBeta(const EVAL alpha, const EVAL beta, const int depth, const int ply);
	EVAL AlphaBetaQ(const EVAL alpha, const EVAL beta, const int ply, const int qply);

	void                CheckInput(bool force = false);
	void                CheckLimits();
	void                ClearNodes() { m_nodes = 0; }
	void                ClearHistory();
	void                ClearKillersAndRefutations();
	Move                GetNextBest(MoveList& mvlist, size_t i);
	NODES               GetNodes() const { return m_nodes; }
	const vector<Move>& GetPV() const { return m_pvs[0]; }
	bool                IsTerminated() const;
	void                Stop();
	void                ProcessInput(const string& s);
	void                SetId(int id) { m_id = id; }
	void                SetPosition(const Position& pos) { m_pos = pos; }
	int                 SuccessRate(Move mv);
	void                Start(int startDepth);
	void                UpdatePV(Move mv, int ply);
	void                UpdateSortScores(MoveList& mvlist, Move hashMove, int ply, Move lastMove);

private:
	void HelperProc(int depth);

	int          m_id;
	Move         m_killers[MAX_PLY + 1];
	int          m_histTry[64][14];
	int          m_histSuccess[64][14];
	Move         m_mateKillers[MAX_PLY + 1];
	MoveList     m_mvlists[MAX_PLY + 1];
	NODES        m_nodes;
	Position     m_pos;
	vector<Move> m_pvs[MAX_PLY + 1];
	Move         m_refutations[MAX_PLY + 1][64][14];
	bool         m_run;
#ifndef SINGLE_THREAD
	std::thread  m_thread;
#endif
};
////////////////////////////////////////////////////////////////////////////////

extern SearchParams g_searchParams;
extern SearchResults g_searchResults;

void ClearHash();
Move GetRandomMove(Position& pos);
bool IsGameOver(Position& pos, string& result, string& comment);
EVAL SEE(const Position& pos, Move mv);
void SetHashSize(double mb);
void SetNumThreads(int num);
void SetStrength(int level);
void StartPerft(Position& pos, int depth);
void StartSearch(const Position& startpos, int numThreads);

enum
{
	HASH_ALPHA = 0,
	HASH_BETA  = 1,
	HASH_EXACT = 2
};

class HashEntry
{
public:
	HashEntry()
	{
		memset(this, 0, sizeof(HashEntry));
	}

	Move GetMove() const { return Move(m_mv); }
	int GetDepth() const { return m_depth; }
	EVAL GetScore(int ply) const
	{
		EVAL score = m_score;
		if (score > CHECKMATE_SCORE - 50 && score <= CHECKMATE_SCORE)
			score -= ply;
		if (score < -CHECKMATE_SCORE + 50 && score >= -CHECKMATE_SCORE)
			score += ply;
		return score;
	}
	U8 GetType() const { return m_type; }
	U8 GetAge() const { return m_age; }

	bool Fits(U64 hash) const { return m_hashLock == (U32)(hash >> 32); }
	void LockHash(U64 hash) { m_hashLock = (U32)(hash >> 32); }

	void SetMove(Move mv) { m_mv = mv.ToInt(); }
	void SetDepth(int depth) { m_depth = (I16)depth; }
	void SetScore(EVAL score, int ply)
	{
		if (score > CHECKMATE_SCORE - 50 && score <= CHECKMATE_SCORE)
			score += ply;
		if (score < -CHECKMATE_SCORE + 50 && score >= CHECKMATE_SCORE)
			score -= ply;
		m_score = score;
	}
	void SetType(U8 type) { m_type = type; }
	void SetAge(U8 age) { m_age = age; }

private:

	U32 m_hashLock;   // 4
	U32 m_mv;         // 4
	I32 m_score;      // 4
	I16 m_depth;      // 2
	U8  m_type;       // 1
	U8  m_age;        // 1
};
////////////////////////////////////////////////////////////////////////////////

#endif
