/*	Lemons JSON DB module service script
	This is loaded automatically by the JSON service, and runs in the
	background. */

// Loop forever
js.branch_limit = 0;
js.time_limit = 0;

// The JSON service passes the module's working directory as argv[0]
var root = argv[0];

load("sbbsdefs.js");
load("json-client.js");
load("event-timer.js");
load(root + "defs.js");

// We'll need a JSON client handle in various functions within this script
var jsonClient,
	timer,
	levelTime = 0;

// This will be called when we receive an update to a valid location
var updateScores = function(data) {
	
	// Make sure that the data provided is sane
	if(	typeof data.player != "string"
		||
		typeof data.system != "string"
		||
		typeof data.level != "number"
		||
		typeof data.score != "number"
	) {
		return;
	}
	
	// Make a unique ID / valid property name for the player
	data.uid = base64_encode(data.player + "@" + data.system, true);
	data.date = time();
	
	// Grab the current high scores list
	var hs = jsonClient.read(DBNAME, "SCORES.HIGH", 1);
	var changed = false; // We'll toggle this if we need to overwrite the list

	/*	If this player's score is higher than any score in the list, shove
		it into the list before that score. */
	for(var s = 0; s < hs.length; s++) {
		if(data.score < hs[s].score)
			continue;
		hs.splice(s, 0, data);
		changed = true;
		break;
	}

	/*	If this player's score is the lowest in the list, but the list isn't
		fully populated, tack this record onto the end of the list. */
	if(!changed && hs.length < 20) {
		hs.push(data);
		changed = true;
	}

	//	If we flagged a DB update, then update the DB.
	if(changed) {
		while(hs.length > 20)
			hs.pop();
		jsonClient.write(DBNAME, "SCORES.HIGH", hs, 2);
	}

	// In any event, we want to update this player's record
	updatePlayer(data);

}

// This will be called via updateScores
var updatePlayer = function(data) {

	// Read the player's record from the DB
	var player = jsonClient.read(DBNAME, "PLAYERS." + data.uid, 1);

	// Or populate said record if it doesn't exist
	if(!player) {
		jsonClient.write(
			DBNAME,
			"PLAYERS." + data.uid,
			{ 'LEVEL' : data.level, 'SCORES' : [] },
			2
		);
	/*	If the player has reached a new level, update their level number so
		they can start there next time. */
	} else if(player.LEVEL < data.level) {
		jsonClient.write(
			DBNAME,
			"PLAYERS." + data.uid + ".LEVEL",
			data.level,
			2
		);
	}

	/*	Tack this score onto the player's 'scores' array.  We're not doing
		anything with this data at the moment, but we could. */
	jsonClient.push(
		DBNAME,
		"PLAYERS." + data.uid + ".SCORES",
		{ 'level' : data.level, 'score' : data.score, 'date' : data.date },
		2
	);

}

/*	We'll set this as the JSON client's callback function, and it will be
	called when an update to a subscribed location is received. */
var processUpdate = function(update) {

	// We're not interested in any updates that aren't writes
	if(update.oper != "WRITE")
		return;

	// Additionally, the update must be to a *.LATEST location
	var location = update.location.split(".");
	if(location.length != "2" || location[1] != "LATEST")
		return;

	switch(location[0].toUpperCase()) {

		// We're only subscribing to SCORES.LATEST right now
		case "SCORES":
			updateScores(update.data);
			break;
		
		// But maybe we'll want to watch this in the future
		case "PLAYERS":
			updatePlayer(update.data);
			break;
		
		default:
			break;

	}

}

/*	On startup, or if the local levels.json file has been updated recently,
	overwrite level data in the DB from the local file. */
var pushLevels = function() {

	if(levelTime == file_date(root + "levels.json"))
		return;
	
	levelTime = file_date(root + "levels.json");

	var f = new File(root + "levels.json");
	f.open("r");
	var levels = JSON.parse(f.read());
	f.close();

	jsonClient.write(DBNAME, "LEVELS", levels, 2);

}

// Set things up
var init = function() {

	while(system.lastuser < 1)
		mswait(15000);

	// Load the server config if it exists, or fake it if not
	if(file_exists(root + "server.ini")) {
		var f = new File(root + "server.ini");
		f.open("r");
		var ini = f.iniGetObject();
		f.close();
		ini.port = parseInt(ini.port);
	} else {
		var ini = { 'host' : "127.0.0.1", 'port' : 10088 };
	}

	// Create our JSON client handle (declared outside this function)
	jsonClient = new JSONClient(ini.host, ini.port);

	// Authenticate to the service as this board's sysop
	var usr = new User(1);
	jsonClient.ident("ADMIN", usr.alias, usr.security.password);

	// Subscribe to updates to SCORES.LATEST
	jsonClient.subscribe(DBNAME, "SCORES.LATEST");

	// If SCORES doesn't exist, create it with dummy data
	if(!jsonClient.read(DBNAME, "SCORES", 1)) {
		jsonClient.write(
			DBNAME,
			"SCORES",
			{ 'LATEST' : {}, 'HIGH' : [] },
			2
		);
	}

	// If PLAYERS doesn't exist, create it with dummy data
	if(!jsonClient.read(DBNAME, "PLAYERS", 1)) {
		jsonClient.write(
			DBNAME,
			"PLAYERS",
			{ 'LATEST' : {} },
			2
		);
	}

	// Overwrite levels in the DB with local data
	pushLevels();
	// And do so again if the local data is updated
	timer = new Timer();
	timer.addEvent(300000, true, pushLevels);

	// Call processUpdate when an update is received
	jsonClient.callback = processUpdate;

}

// Keep things rolling until we're told to stop
var main = function() {

	while(!js.terminated) {
		mswait(5);
		timer.cycle();
		jsonClient.cycle();
	}

}

// Clean things up
var cleanUp = function() {
	jsonClient.disconnect();
}

// Try to do things, log an error if necessary
try {
	init();
	main();
	cleanUp();
} catch(err) {
	log(LOG_ERR, err);
}

// This is implied, but hey, why not?
exit();
