/*
Copyright (C) 2007, 2010 - Bit-Blot

This file is part of Aquaria.

Aquaria 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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "DSQ.h"
#include "Game.h"
#include "Avatar.h"
#include "StatsAndAchievements.h"

#ifndef ARRAYSIZE
#define ARRAYSIZE(x) (sizeof (x) / sizeof ((x)[0]))
#endif

#define _ACH_ID( id, name ) { id, #id, name, "", 0, 0 }

static Achievement g_rgAchievements[] =
{
	_ACH_ID( ACH_DISCOVER_ALL_RECIPES,	"Experienced Chef" ),		// verified
	_ACH_ID( ACH_MASS_TRANSIT,			"Mass Transit" ),			// verified
	_ACH_ID( ACH_EXPLORER,				"Explorer" ),				// verified
	_ACH_ID( ACH_HEALTHY,				"Healthy" ),				// verified (really make sure)

	_ACH_ID( ACH_AQUIRE_ALL_SONGS,		"Songstress" ),				// verified
	_ACH_ID( ACH_DEFEAT_PRIESTS,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_DEFEAT_OCTOMUN,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_DEFEAT_ROCKCRAB,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_DEFEAT_MANTISSHRIMP,	"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_DEFEAT_KINGJELLY,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_REACHED_OPEN_WATERS,	"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_REACHED_SPRITE_CAVE,	"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_REACHED_THE_VEIL,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_ROMANCE,				"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_BELLY_OF_THE_WHALE,	"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_ALEK_AND_DAREC,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_THE_FROZEN_VEIL,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_MOMMY_AND_DADDY,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_RESCUED_ALL_SOULS,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_BUCKING_BRONCO,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_COMBO_EATER,			"xxxxxxxxx" ),				// verified (adjust # of devours?)
	_ACH_ID( ACH_ATE_A_PARROT,			"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_FLUNG_A_MONKEY,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_KILLED_THE_COWARD,		"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_HIGH_DIVE,				"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_SPEED_RACER,			"xxxxxxxxx" ),				// verified
	_ACH_ID( ACH_DEFEAT_MERGOG,			"xxxxxxxxx" ),				// verified
};


// temp record
bool	killedParrotWithBite		= false;
bool	flungMonkey		= false;
bool	killedCoward				= false;

bool	highDiveIsHigh				= false;
bool	highDiveIsDone				= false;

float	ridingEkkritTime			= 0.0f;
float	ridingEkkritTimeMax			= 60.0f;
bool	rodeEkkritToTheStars		= false;

int		biteDeathComboNum			= 0;
float	biteDeathComboTime			= 0.5f;
float	biteDeathComboCounter		= 0.0f;
const int biteDeathComboMax			= 6;

const int seahorseRaceAchievementTimeMin = 59;


const int SUNKENCITY_BOSSDONE				= 16;

const int FLAG_SUNKENCITY_PUZZLE			= 113;

const int FLAG_SPIRIT_ERULIAN				= 124;
const int FLAG_SPIRIT_KROTITE				= 125;
const int FLAG_SPIRIT_DRASK					= 126;
const int FLAG_SPIRIT_DRUNIAD				= 127;

const int FLAG_TRANSTURTLE_VEIL01			= 130;
const int FLAG_TRANSTURTLE_OPENWATER06		= 131;
const int FLAG_TRANSTURTLE_FOREST04			= 132;
const int FLAG_TRANSTURTLE_OPENWATER03		= 133;
const int FLAG_TRANSTURTLE_FOREST05			= 134;
const int FLAG_TRANSTURTLE_MAINAREA			= 135;
const int FLAG_TRANSTURTLE_SEAHORSE			= 136;
const int FLAG_TRANSTURTLE_VEIL02			= 137;
const int FLAG_TRANSTURTLE_ABYSS03			= 138;
const int FLAG_TRANSTURTLE_FINALBOSS		= 139;

const int FLAG_SEAHORSEBESTTIME				= 247;

const int FLAG_MINIBOSS_START				= 700;
const int FLAG_MINIBOSS_NAUTILUSPRIME		= 700;
const int FLAG_MINIBOSS_KINGJELLY			= 701;
const int FLAG_MINIBOSS_MERGOG				= 702;
const int FLAG_MINIBOSS_CRAB				= 703;
const int FLAG_MINIBOSS_OCTOMUN				= 704;
const int FLAG_MINIBOSS_MANTISSHRIMP		= 705;
const int FLAG_MINIBOSS_PRIESTS				= 706;
const int FLAG_MINIBOSS_END					= 720;


//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
#ifdef _MSC_VER
#pragma warning( push )
//  warning C4355: 'this' : used in base member initializer list
//  This is OK because it's warning on setting up the Steam callbacks, they won't use this until after construction is done
#pragma warning( disable : 4355 ) 
#endif
StatsAndAchievements::StatsAndAchievements()
{
	/*
	std::ostringstream os;
	os << "app_id: " << SteamUtils()->GetAppID();
	debugLog(os.str());
	*/

#ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL
	unlockedDisplayTimestamp = -1.0f;
#endif

	requestedStats = false;
	statsValid = false;
	storeStats = false;

	/*
	m_flGameFeetTraveled = 0;

	m_nTotalGamesPlayed = 0;
	m_nTotalNumWins = 0;
	m_nTotalNumLosses = 0;
	m_flTotalFeetTraveled = 0;
	m_flMaxFeetTraveled = 0;

	m_flAverageSpeed = 0;

	m_hDisplayFont = pGameEngine->HCreateFont( ACHDISP_FONT_HEIGHT, FW_MEDIUM, false, "Arial" );
	if ( !m_hDisplayFont )
		OutputDebugString( "Stats font was not created properly, text won't draw\n" );
	*/
}
#ifdef _MSC_VER
#pragma warning( pop )
#endif

//-----------------------------------------------------------------------------
// Purpose: Run a frame for the CStatsAndAchievements
//-----------------------------------------------------------------------------
void StatsAndAchievements::RunFrame()
{
#ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL
	if ( !requestedStats )
	{
		requestedStats = true;

		const size_t max_achievements = ARRAYSIZE(g_rgAchievements);
		VFILE *io = NULL;

		// Get generic achievement data...
		std::string fname = localisePath("data/achievements.txt");
		io = vfopen(fname.c_str(), "r");
		char line[1024];
		for (size_t i = 0; i < max_achievements; i++)
		{
			if (!io || (vfgets(line, sizeof (line), io) == NULL))
				snprintf(line, sizeof (line), "Achievement #%d", (int) i);
			else
			{
				for (char *ptr = (line + strlen(line)) - 1; (ptr >= line) && ((*ptr == '\r') || (*ptr == '\n')); ptr--)
					*ptr = '\0';
			}
			line[sizeof (g_rgAchievements[i].name) - 1] = '\0';  // just in case.
			strcpy(g_rgAchievements[i].name, line);

			if (!io || (vfgets(line, sizeof (line), io) == NULL))
				snprintf(line, sizeof (line), "[Description of Achievement #%d is missing!]", (int) i);
			else
			{
				for (char *ptr = (line + strlen(line)) - 1; (ptr >= line) && ((*ptr == '\r') || (*ptr == '\n')); ptr--)
					*ptr = '\0';
			}
			line[sizeof (g_rgAchievements[i].desc) - 1] = '\0';  // just in case.
			strcpy(g_rgAchievements[i].desc, line);

			// unsupported at the moment.
			g_rgAchievements[i].iconImage = 0;
		}

		if (io != NULL)
			vfclose(io);

		// See what this specific player has achieved...

		unsigned char *buf = new unsigned char[max_achievements];
		size_t br = 0;
		fname = (core->getUserDataFolder() + "/achievements.bin");
		FILE *u = fopen(fname.c_str(), "rb");
		if (u == NULL)
			statsValid = true;  // nothing to report.
		else
		{
			br = fread(buf, sizeof (buf[0]), max_achievements, u);
			fclose(u);
		}

		if (br == max_achievements)
		{
			statsValid = true;  // but we'll reset if there's a problem.
			for (size_t i = 0; statsValid && (i < max_achievements); i++)
			{
				const int val = ((int) (buf[i] ^ 0xFF)) - ((int)i);
				statsValid = ((val == 0) || (val == 1));
				g_rgAchievements[i].achieved = (val == 1);
			}
		}
		delete[] buf;
	}
#endif

	if ( !statsValid ) {
		debugLog("stats not valid");
		return;
	}

	// Get info from sources

	// Evaluate achievements

	// but only if we're not in a mod
	if (!dsq->mod.isActive())
	{
		for ( int iAch = 0; iAch < ARRAYSIZE( g_rgAchievements ); ++iAch )
		{
			EvaluateAchievement( g_rgAchievements[iAch] );
		}
	}


	// Store stats
	StoreStatsIfNecessary();
}

void StatsAndAchievements::appendStringData(std::string &data)
{
	if (!statsValid)
	{
		data += "(Sorry, achievement data is apparently invalid.)\n\n";
		return;
	}

	int count;

	count = 0;
	data += "Unlocked:\n\n";
	for ( int iAch = 0; iAch < ARRAYSIZE( g_rgAchievements ); ++iAch )
	{
		const Achievement &ach = g_rgAchievements[iAch];
		if (!ach.achieved)
			continue;
		count++;
		data += "  ";
		data += ach.name;
		data += ": ";
		data += ach.desc;
		data += "\n";
	}

	if (count == 0)
		data += "  (none!)\n";
	data += "\n";

	count = 0;
	data += "Locked:\n\n";
	for ( int iAch = 0; iAch < ARRAYSIZE( g_rgAchievements ); ++iAch )
	{
		const Achievement &ach = g_rgAchievements[iAch];
		if (ach.achieved)
			continue;
		count++;
		data += "  ";
		data += ach.name;
		data += ": ";
		data += ach.desc;
		data += "\n";
	}

	if (count == 0)
		data += "  (none!)\n";
	data += "\n";
}

//-----------------------------------------------------------------------------
// Purpose: Accumulate distance traveled
//-----------------------------------------------------------------------------
/*
void StatsAndAchievements::AddDistanceTraveled( float flDistance )
{
	m_flGameFeetTraveled += SpaceWarClient()->PixelsToFeet( flDistance );
}
*/

//-----------------------------------------------------------------------------
// Purpose: Game state has changed
//-----------------------------------------------------------------------------
/*
void StatsAndAchievements::OnGameStateChange( EClientGameState eNewState )
{
	if ( !m_bStatsValid )
		return;

	switch ( eNewState )
	{
	case k_EClientStatsAchievements:
	case k_EClientGameStartServer:
	case k_EClientGameMenu:
	case k_EClientGameQuitMenu:
	case k_EClientGameExiting:
	case k_EClientGameInstructions:
	case k_EClientGameConnecting:
	case k_EClientGameConnectionFailure:
	default:
		break;
	case k_EClientGameActive:
		// Reset per-game stats
		m_flGameFeetTraveled = 0;
		m_ulTickCountGameStart = m_pGameEngine->GetGameTickCount();
		break;
	case k_EClientFindInternetServers:
		break;	
	case k_EClientGameWinner:
		if ( SpaceWarClient()->BLocalPlayerWonLastGame() )
			m_nTotalNumWins++;
		else
			m_nTotalNumLosses++;
		// fall through
	case k_EClientGameDraw:

		// Tally games
		m_nTotalGamesPlayed++;

		// Accumulate distances
		m_flTotalFeetTraveled += m_flGameFeetTraveled;

		// New max?
		if ( m_flGameFeetTraveled > m_flMaxFeetTraveled )
			m_flMaxFeetTraveled = m_flGameFeetTraveled;

		// Calc game duration
		m_flGameDurationSeconds = ( m_pGameEngine->GetGameTickCount() - m_ulTickCountGameStart ) / 1000.0;

		// We want to update stats the next frame.
		storeStats = true;

		break;
	}
}
*/


//-----------------------------------------------------------------------------
// Purpose: see if we should unlock this achievement
//-----------------------------------------------------------------------------
void StatsAndAchievements::EvaluateAchievement( Achievement &achievement )
{
	//debugLog("Eval...");

	// Already have it?
	if ( achievement.achieved ) {
		//debugLog(std::string(achievement.chAchievementID) + " was already achieved");
		return;
	}

	switch ( achievement.achievementID )
	{
	// real evals:
	case ACH_DISCOVER_ALL_RECIPES:
		{
			bool knowAll=true;

			// this code is part of what avoids duplicate recipes being required
			// in the case of veggie soup there are two ways to make it
			// i figure that finding one way is enough for the achievement
			bool didLeafPoultice = false;
			bool didPoisonLoaf = false;
			bool didVeggieSoup = false;

			for (int i = 0; i < dsq->continuity.recipes.size(); i++ )
			{
				if (dsq->continuity.recipes[i].isKnown())
				{
					if (dsq->continuity.recipes[i].result == "LeafPoultice") {
						didLeafPoultice = true;
					} else if (dsq->continuity.recipes[i].result == "PoisonLoaf") {
						didPoisonLoaf = true;
					} else if (dsq->continuity.recipes[i].result == "VeggieSoup") {
						didVeggieSoup = true;
					}
				}
			}
			for (int i = 0; i < dsq->continuity.recipes.size(); i++ )
			{
				if (!dsq->continuity.recipes[i].isKnown())
				{
					if ((dsq->continuity.recipes[i].result == "LeafPoultice" && didLeafPoultice)
						|| (dsq->continuity.recipes[i].result == "PoisonLoaf" && didPoisonLoaf)
						|| (dsq->continuity.recipes[i].result == "VeggieSoup" && didVeggieSoup))
					{}
					else {
						/*
						std::ostringstream os;
						os << "doesn't know recipe: " << dsq->continuity.recipes[i].result;
						debugLog(os.str());
						*/
						knowAll = false;
					}
				}
			}

			if (knowAll)
			{
				UnlockAchievement(achievement);
			}
		}
		break;

	case ACH_MASS_TRANSIT:
		{
			/*
			debugLog("eval ACH_MASS_TRANSIT");
			std::ostringstream os;
			os << "FLAG_TRANSTURTLE_VEIL01: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_VEIL01) << "\n";
			os << "FLAG_TRANSTURTLE_VEIL02: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_VEIL02) << "\n";
			os << "FLAG_TRANSTURTLE_OPENWATER03: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_OPENWATER03) << "\n";
			os << "FLAG_TRANSTURTLE_FOREST04: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_FOREST04) << "\n";
			os << "FLAG_TRANSTURTLE_FOREST05: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_FOREST05) << "\n";
			os << "FLAG_TRANSTURTLE_MAINAREA: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_MAINAREA) << "\n";
			os << "FLAG_TRANSTURTLE_SEAHORSE: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_SEAHORSE) << "\n";
			os << "FLAG_TRANSTURTLE_ABYSS03: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_ABYSS03) << "\n";
			os << "FLAG_TRANSTURTLE_FINALBOSS: " << dsq->continuity.getFlag(FLAG_TRANSTURTLE_FINALBOSS) << "\n";
			debugLog(os.str());
			*/

			if (dsq->continuity.getFlag(FLAG_TRANSTURTLE_VEIL01) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_VEIL02) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_OPENWATER03) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_FOREST04) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_FOREST05) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_MAINAREA) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_SEAHORSE) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_ABYSS03) > 0
				&& dsq->continuity.getFlag(FLAG_TRANSTURTLE_FINALBOSS) > 0)

			{
				UnlockAchievement(achievement);
			}
		}
		break;

	case ACH_EXPLORER:
		{
			// check world map data somehow
			bool hasAllMap = true;
			for (int i = 0; i < dsq->continuity.worldMap.getNumWorldMapTiles(); i++)
			{
				WorldMapTile *tile = dsq->continuity.worldMap.getWorldMapTile(i);
				if (!tile->revealed && (nocasecmp(tile->name, "thirteenlair") != 0)) {
					/*
					std::ostringstream os;
					os << "does not have: " << tile->name;
					debugLog(os.str());
					*/
					hasAllMap = false;
					break;
				}
			}
			if (hasAllMap)
			{
				UnlockAchievement(achievement);
			}
		}
		break;

	case ACH_HEALTHY:
		// is it really 10??
		if ( dsq->continuity.maxHealth >= 10 )
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_AQUIRE_ALL_SONGS:
		//debugLog("eval ACH_AQUIRE_ALL_SONGS");
		if (dsq->continuity.hasSong(SONG_BIND)
			&& dsq->continuity.hasSong(SONG_SHIELDAURA)
			&& dsq->continuity.hasSong(SONG_LI)
			&& dsq->continuity.hasSong(SONG_ENERGYFORM)
			&& dsq->continuity.hasSong(SONG_BEASTFORM)
			&& dsq->continuity.hasSong(SONG_NATUREFORM)
			&& dsq->continuity.hasSong(SONG_SUNFORM)
			&& dsq->continuity.hasSong(SONG_DUALFORM)
			&& dsq->continuity.hasSong(SONG_FISHFORM)
			&& dsq->continuity.hasSong(SONG_SPIRITFORM))
		{
			//errorLog("ACH_AQUIRE_ALL_SONGS!");
			UnlockAchievement(achievement);
		}
		break;

	// gameplay
	case ACH_DEFEAT_PRIESTS:
		//if (dsq->continuity.getFlag(FLAG_MINIBOSS_PRIESTS) > 0)
		if (dsq->continuity.hasSong(SONG_SPIRITFORM))
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_DEFEAT_OCTOMUN:
		if (dsq->continuity.getFlag(FLAG_MINIBOSS_OCTOMUN) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_DEFEAT_ROCKCRAB:
		if (dsq->continuity.getFlag(FLAG_MINIBOSS_CRAB) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_DEFEAT_MERGOG:
		if (dsq->continuity.getFlag(FLAG_MINIBOSS_MERGOG) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_DEFEAT_MANTISSHRIMP:
		if (dsq->continuity.getFlag(FLAG_MINIBOSS_MANTISSHRIMP) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_DEFEAT_KINGJELLY:
		if (dsq->continuity.getFlag(FLAG_MINIBOSS_KINGJELLY) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_REACHED_OPEN_WATERS:
		if (dsq->game->sceneName == "openwater02")
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_REACHED_SPRITE_CAVE:
		if (dsq->game->sceneName == "forestspritecave")
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_REACHED_THE_VEIL:
		// when Naija jumps through the veil
		if (dsq->continuity.getFlag("leftWater")!=0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_ROMANCE:
		if (dsq->continuity.getFlag(FLAG_LI) >= 100)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_BELLY_OF_THE_WHALE:
		if (dsq->game->sceneName == "whale")
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_ALEK_AND_DAREC:
		if (dsq->game->sceneName == "weirdcave")
		{
			UnlockAchievement(achievement);
		}
		break;
	
	case ACH_THE_FROZEN_VEIL:
		if (dsq->game->sceneName == "frozenveil")
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_MOMMY_AND_DADDY:
		//if (dsq->continuity.getFlag(SUNKENCITY_BOSSDONE) > 0)
		//setFlag(FLAG_SUNKENCITY_PUZZLE, SUNKENCITY_BOSSDONE)
		if (dsq->continuity.getFlag(FLAG_SUNKENCITY_PUZZLE) >= SUNKENCITY_BOSSDONE)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_RESCUED_ALL_SOULS:
		if (dsq->continuity.getFlag(FLAG_SPIRIT_ERULIAN) > 0
			&& dsq->continuity.getFlag(FLAG_SPIRIT_KROTITE) > 0
			&& dsq->continuity.getFlag(FLAG_SPIRIT_DRASK) > 0
			&& dsq->continuity.getFlag(FLAG_SPIRIT_DRUNIAD) > 0)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_BUCKING_BRONCO:
		// ride ekkrit for a minute
		if (rodeEkkritToTheStars)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_COMBO_EATER:
		// eat n=12 things in a row
		if (biteDeathComboNum >= biteDeathComboMax)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_ATE_A_PARROT:
		// eat one parrot
		if (killedParrotWithBite)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_FLUNG_A_MONKEY:
		// monkey gets hit with nature form
		if (flungMonkey)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_KILLED_THE_COWARD:
		// coward dies
		if (killedCoward)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_HIGH_DIVE:
		// fall from bird nest to water
		if (highDiveIsDone)
		{
			UnlockAchievement(achievement);
		}
		break;

	case ACH_SPEED_RACER:
		// get really low time in seahorse race
		int bestTime = dsq->continuity.getFlag(FLAG_SEAHORSEBESTTIME);
		if (bestTime > 0 && bestTime <= seahorseRaceAchievementTimeMin)
		{
			UnlockAchievement(achievement);
		}
		break;
	}
}

void StatsAndAchievements::flingMonkey(Entity *e)
{
	flungMonkey = true;
}

void StatsAndAchievements::entityDied(Entity *eDead)
{
	if (eDead->name == "parrot" && eDead->lastDamage.damageType == DT_AVATAR_BITE) {
		killedParrotWithBite = true;
	}

	if (eDead->name == "coward") {
		killedCoward = true;
	}

	if (eDead->lastDamage.damageType == DT_AVATAR_BITE) {
		// an enemy got bit to death
		// how much time from the last death?
		if (biteDeathComboCounter > biteDeathComboTime) {
			// then we missed out on a combo, suck a nut
			biteDeathComboNum = 0;
		} else {
			// we made it!!
			biteDeathComboNum ++;
		}
		biteDeathComboCounter = 0;
	}

	/*
	eDead->getState() == 
	if (eDead->name == "monkey") {
		// we killed a monkey, but how?
		if (eDead->lastDamage.damageType == DT_AVATAR_VINE) {
			// with a nature form vine
			flungMonkey = true;
		}
	}
	*/
}

void StatsAndAchievements::update(float dt)
{
	//debugLog("update stats and achievements");
	Avatar *avatar = 0;
	if (dsq->game && dsq->game->avatar) {
		avatar = dsq->game->avatar;
	}

	if (avatar) {
		BoneLock *b = avatar->getBoneLock();

		if (!rodeEkkritToTheStars) {
			if (!dsq->game->isPaused() && b->on) {
				//debugLog("boneLock->entity->name: " + b->entity->name);
				if (b->entity->name == "ekkrit") {
					ridingEkkritTime += dt;

					/*
					std::ostringstream os;
					os << "ridingEkkritTime: " << ridingEkkritTime;
					debugLog(os.str());
					*/

					if (ridingEkkritTime >= ridingEkkritTimeMax) {
						rodeEkkritToTheStars = true;
					}
				}
			}
		}

		// TODO: verify sceneName is correct
		if (dsq->game->sceneName == "veil02") {
			if (avatar->position.x > 18056 && avatar->position.x < 21238 && avatar->position.y < 4185) {
				debugLog("highDiveIsHigh!");
				highDiveIsHigh = true;
			} else if (highDiveIsHigh && avatar->isUnderWater()) {
				// fell into water from right place, must have been the right jump (lol, we hope)
				highDiveIsDone = true;
				debugLog("highDiveIsDone!");
			}
		} else {
			highDiveIsHigh = false;
			highDiveIsDone = false;
		}
	}

#ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL
	// change no state if we're still fading in/out.
	if (!dsq->achievement_box->alpha.isInterpolating())
	{
		const float maxUnlockDisplayTime = 5.0f;
		// still displaying an unlock notification?
		if (unlockedDisplayTimestamp > 0.0f)
		{
			unlockedDisplayTimestamp -= dt;
			if (unlockedDisplayTimestamp <= 0.0f)
			{
				unlockedDisplayTimestamp = -1.0f;
				dsq->achievement_text->alpha.interpolateTo(0, 1);
				dsq->achievement_box->alpha.interpolateTo(0, 1.2);
			}
		}

		// more achievements to display?
		else if (unlockedToBeDisplayed.size() > 0)
		{
			const std::string &name = unlockedToBeDisplayed.front();
			unlockedDisplayTimestamp = maxUnlockDisplayTime;
			std::string text("Achievement Unlocked:\n");
			text += name;
			unlockedToBeDisplayed.pop();
			dsq->achievement_text->setText(text);
			dsq->achievement_text->alpha.interpolateTo(1, 1);
			dsq->achievement_box->alpha.interpolateTo(1, 0.1);
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Unlock this achievement
//-----------------------------------------------------------------------------
void StatsAndAchievements::UnlockAchievement( Achievement &achievement )
{
	if ((achievement.achieved) || (!statsValid))
		return;

	achievement.achieved = true;

	// the icon may change once it's unlocked
	achievement.iconImage = 0;

#ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL
    unlockedToBeDisplayed.push( std::string(achievement.name) );
#endif

	// Store stats end of frame
	storeStats = true;
}

//-----------------------------------------------------------------------------
// Purpose: Store stats in the Steam database
//-----------------------------------------------------------------------------
void StatsAndAchievements::StoreStatsIfNecessary()
{
	if ( storeStats )
	{
		// already set any achievements in UnlockAchievement

#ifdef BBGE_BUILD_ACHIEVEMENTS_INTERNAL
		storeStats = false;  // only ever try once.

		// FIXME: We should use a temporary file to ensure that data
		// isn't lost if the filesystem gets full.  The canonical
		// method is to write to a new file, then call
		// rename("new.file", "existing.file") after the new file has
		// been successfully written; POSIX specifies that such a call
		// atomically replaces "existing.file" with "new.file".
		// However, I've heard that Windows doesn't allow this sort of
		// file replacement.  Will this work on Windows and MacOS?
		// Please advise.  --achurch

		const std::string fname(core->getUserDataFolder() + "/achievements.bin");
		FILE *io = fopen(fname.c_str(), "wb");
		if (io == NULL)
			return;

		const size_t max_achievements = ARRAYSIZE(g_rgAchievements);
		unsigned char *buf = new unsigned char[max_achievements];
        
		for (size_t i = 0; i < max_achievements; i++)
		{
			int val = g_rgAchievements[i].achieved ? 1 : 0;
			buf[i] = ((unsigned char) (val + ((int)i))) ^ 0xFF;
		}

		if (fwrite(buf, sizeof (buf[0]), max_achievements, io) != max_achievements)
			debugLog("Failed to write achievements 1");
		delete[] buf;

		char cruft[101];
		for (size_t i = 0; i < sizeof (cruft); i++)
			cruft[i] = (char) rand();
		if (fwrite(cruft, sizeof (cruft[0]), ARRAYSIZE(cruft), io) != ARRAYSIZE(cruft))
			debugLog("Failed to write achievements 2");

		fclose(io);
#endif
	}
}


