#include "common/hercules.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include "common/HPMi.h"
#include "common/nullpo.h"
#include "common/memmgr.h"
#include "common/mmo.h"
#include "common/socket.h"
#include "common/sql.h"
#include "common/showmsg.h"

#include "char/char.h"
#include "char/inter.h"
#include "char/mapif.h"

#undef DEFAULT_AUTOSAVE_INTERVAL	//Its defined in map.h too..

#include "map/atcommand.h"
#include "map/clif.h"
#include "map/chrif.h"
#include "map/map.h"
#include "map/party.h"
#include "map/pc.h"

#include "HPMHooking.h"
#include "common/HPMDataCheck.h"


HPExport struct hplugin_info pinfo = {
	"Pad",		// Plugin name
	SERVER_TYPE_MAP|SERVER_TYPE_CHAR,// Which server types this plugin works with?
	"1.3",			// Plugin version
	HPM_VERSION,	// HPM Version (don't change, macro is automatically updated)
};

int padded_users = 0;
bool update_pad(int count);
bool trigger_char();

ACMD(pad)
{
	if (!message || !*message) {
		clif->message(fd, "Please Specify how much users to increase.");
		return false;
	}
	
	if (update_pad(atoi(message)) == true) {
		clif->message(fd, "Padding Set.");
	} else {
		clif->message(fd, "Failed to set the padding.");
	}
	
	return true;
}


void map_final_pad(void) {
	update_pad(0);
}

#if 0
void user_count_who(struct map_session_data **sd, int *count)
{
	int fd;

	nullpo_retv(sd);
	fd = (*sd)->fd;

	WFIFOHEAD(fd,(clif->packet(0xc2))->len);
	WFIFOW(fd,0) = 0xc2;
	WFIFOL(fd,2) = (*count)+padded_users;
	WFIFOSET(fd,(clif->packet(0xc2))->len);
	hookStop();
}
#endif

/*==========================================
 * Display list of online characters with
 * various info.
 *------------------------------------------*/
ACMD(who) {
	struct map_session_data *pl_sd = NULL;
	struct s_mapiterator *iter = NULL;
	char player_name[NAME_LENGTH] = "";
	int count = 0;
	int level = 0;
	StringBuf buf;
	/**
	 * 1 = @who  : Player name, [Title], [Party name], [Guild name]
	 * 2 = @who2 : Player name, [Title], BLvl, JLvl, Job
	 * 3 = @who3 : [CID/AID] Player name [Title], Map, X, Y
	 */
	int display_type = 1;
	int map_id = -1;

	if (stristr(info->command, "map") != NULL) {
		char map_name[MAP_NAME_LENGTH_EXT] = "";
		if (sscanf(message, "%15s %23s", map_name, player_name) < 1 || (map_id = map->mapname2mapid(map_name)) < 0)
			map_id = sd->bl.m;
	} else {
		sscanf(message, "%23s", player_name);
	}

	if (stristr(info->command, "2") != NULL)
		display_type = 2;
	else if (stristr(info->command, "3") != NULL)
		display_type = 3;

	level = pc_get_group_level(sd);
	StrBuf->Init(&buf);

	iter = mapit_getallusers();
	for (pl_sd = (TBL_PC*)mapit->first(iter); mapit->exists(iter); pl_sd = (TBL_PC*)mapit->next(iter)) {
		if (!((pc_has_permission(pl_sd, PC_PERM_HIDE_SESSION) || pc_isinvisible(pl_sd)) && pc_get_group_level(pl_sd) > level)) { // you can look only lower or same level
			if (stristr(pl_sd->status.name, player_name) == NULL // search with no case sensitive
				|| (map_id >= 0 && pl_sd->bl.m != map_id))
				continue;
			switch (display_type) {
				case 2: {
					StrBuf->Printf(&buf, msg_fd(fd,343), pl_sd->status.name); // "Name: %s "
					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
						StrBuf->Printf(&buf, msg_fd(fd,344), pcg->get_name(pl_sd->group)); // "(%s) "
					StrBuf->Printf(&buf, msg_fd(fd,347), pl_sd->status.base_level, pl_sd->status.job_level,
									 pc->job_name(pl_sd->status.class)); // "| Lv:%d/%d | Job: %s"
					break;
				}
				case 3: {
					if (pc_has_permission(sd, PC_PERM_WHO_DISPLAY_AID))
						StrBuf->Printf(&buf, msg_fd(fd,912), pl_sd->status.char_id, pl_sd->status.account_id); // "(CID:%d/AID:%d) "
					StrBuf->Printf(&buf, msg_fd(fd,343), pl_sd->status.name); // "Name: %s "
					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
						StrBuf->Printf(&buf, msg_fd(fd,344), pcg->get_name(pl_sd->group)); // "(%s) "
					StrBuf->Printf(&buf, msg_fd(fd,348), mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); // "| Location: %s %d %d"
					break;
				}
				default: {
					struct party_data *p = party->search(pl_sd->status.party_id);
					struct guild *g = pl_sd->guild;

					StrBuf->Printf(&buf, msg_fd(fd,343), pl_sd->status.name); // "Name: %s "
					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
						StrBuf->Printf(&buf, msg_fd(fd,344), pcg->get_name(pl_sd->group)); // "(%s) "
					if (p != NULL)
						StrBuf->Printf(&buf, msg_fd(fd,345), p->party.name); // " | Party: '%s'"
					if (g != NULL)
						StrBuf->Printf(&buf, msg_fd(fd,346), g->name); // " | Guild: '%s'"
					break;
				}
			}
			clif->messagecolor_self(fd, COLOR_DEFAULT, StrBuf->Value(&buf));/** for whatever reason clif->message crashes with some patterns, see bugreport:8186 **/
			StrBuf->Clear(&buf);
			count++;
		}
	}
	mapit->free(iter);

	if (map_id < 0) {
			count = count+padded_users;
		if (count == 0)
			StrBuf->AppendStr(&buf, msg_fd(fd,28)); // No player found.
		else if (count == 1)
			StrBuf->AppendStr(&buf, msg_fd(fd,29)); // 1 player found.
		else
			StrBuf->Printf(&buf, msg_fd(fd,30), count); // %d players found.
	} else {
		if (count == 0)
			StrBuf->Printf(&buf, msg_fd(fd,54), map->list[map_id].name); // No player found in map '%s'.
		else if (count == 1)
			StrBuf->Printf(&buf, msg_fd(fd,55), map->list[map_id].name); // 1 player found in map '%s'.
		else
			StrBuf->Printf(&buf, msg_fd(fd,56), count, map->list[map_id].name); // %d players found in map '%s'.
	}
	clif->message(fd, StrBuf->Value(&buf));
	StrBuf->Destroy(&buf);
	return true;
}

/// @users - displays the number of players present on each map (and percentage)
/// #users displays on the target user instead of self
ACMD(users)
{
	char buf[CHAT_SIZE_MAX];
	int users[MAX_MAPINDEX];
	int users_all;
	struct s_mapiterator* iter;

	memset(users, 0, sizeof(users));
	users_all = 0;

	// count users on each map
	iter = mapit_getallusers();
	for(;;)
	{
		struct map_session_data* sd2 = (struct map_session_data*)mapit->next(iter);
		if( sd2 == NULL )
			break;// no more users

		if( sd2->mapindex >= MAX_MAPINDEX )
			continue;// invalid mapindex

		if( users[sd2->mapindex] < INT_MAX ) ++users[sd2->mapindex];
		if( users_all < INT_MAX ) ++users_all;
	}
	mapit->free(iter);

	if (users_all) {
		int i;
		// display results for each map
		for (i = 0; i < MAX_MAPINDEX; ++i) {
			if (users[i] == 0)
				continue; // empty

			safesnprintf(buf, sizeof(buf), "%s: %d (%.2f%%)", mapindex_id2name(i), users[i], (float)(100.0f*users[i]/users_all));
			clif->message(sd->fd, buf);
		}
	}

	// display overall count
	safesnprintf(buf, sizeof(buf), "all: %d", users_all+padded_users);
	clif->message(sd->fd, buf);

	return true;
}

void create_pad(void)
{
	/* Create Table */
	if( SQL_ERROR == SQL->Query(map->mysql_handle,
			"CREATE TABLE IF NOT EXISTS `pad_main` ( `padding` int(11) NOT NULL DEFAULT '0', PRIMARY KEY  (`padding`) )" ) )
		Sql_ShowDebug(map->mysql_handle);
}

void select_pad(enum server_types server_type)
{
	char* data;
	struct Sql* mysql_handle;
	
	if (server_type == SERVER_TYPE_MAP) {
		mysql_handle = map->mysql_handle;
	} else if (server_type == SERVER_TYPE_CHAR) {
		mysql_handle = inter->sql_handle;
	} else {
		return;
	}
	/* Select Padding if Any */
	if (SQL_ERROR == SQL->Query(mysql_handle, "SELECT `padding` FROM `pad_main`") )
		Sql_ShowDebug(mysql_handle);
	else if( SQL_SUCCESS == SQL->NextRow(mysql_handle) )
	{
		SQL->GetData(mysql_handle, 0, &data, NULL);
		padded_users = atoi(data);
	}
	else
	{
		padded_users = 0;
	}
	SQL->FreeResult(mysql_handle);
}

bool update_pad(int count)
{
	struct Sql* sql_handle = NULL;
	if (SERVER_TYPE == SERVER_TYPE_CHAR)
		sql_handle = inter->sql_handle;
	else
		sql_handle = map->mysql_handle;
	// Delete Existing
	if( SQL_ERROR == SQL->Query(sql_handle,
			"TRUNCATE TABLE `pad_main`" )){
				Sql_ShowDebug(sql_handle);
				return false;
			}
	// Update on SQL
	if( SQL_ERROR == SQL->Query(sql_handle,
			"INSERT INTO `pad_main` VALUES ('%d') ON DUPLICATE KEY UPDATE `padding`='%d'",
			count, count) ){	//Double due to On Duplicate Key.
				Sql_ShowDebug(sql_handle);
				return false;
			}

	padded_users = count;
	if (SERVER_TYPE == SERVER_TYPE_MAP)
		trigger_char();
	return true;
}

#if 0
void login_send_count(int *users)
{
	Assert_retv(chr->login_fd != -1);
	WFIFOHEAD(chr->login_fd,6);
	WFIFOW(chr->login_fd,0) = 0x2714;
	WFIFOL(chr->login_fd,2) = (*users)+padded_users;
	WFIFOSET(chr->login_fd,6);
	hookStop();
}
#endif

int char_count_users(int retVal)
{
	retVal += padded_users;
	return retVal;
}

// Synchronizing MapChar Server
#define chrif_check(a) do { if(!chrif->isconnected()) return a; } while(0)
	
bool trigger_char(void) {
	chrif_check(false);

	WFIFOHEAD(chrif->fd,6);
	WFIFOW(chrif->fd,0) = 0x2c00;
	WFIFOL(chrif->fd,2) = padded_users;
	WFIFOSET(chrif->fd,6);

	return true;
}

void char_change_pad(int fd) {
	int padding = RFIFOL(fd, 2);
	padded_users = padding;
	return;
}

void map_crash_pad(int *id) {
	update_pad(0);
}

/* Server Startup */
HPExport void plugin_init (void)
{
	select_pad(SERVER_TYPE);
	switch(SERVER_TYPE){
		case SERVER_TYPE_MAP:
			addAtcommand("pad",pad);
			addAtcommand("who",who);
			addAtcommand("who2",who);
			addAtcommand("who3",who);
			addAtcommand("users",users);
#if 0			
			addHookPre(clif, user_count, user_count_who);
#endif
			addHookPre(map, list_final, map_final_pad);
			create_pad();
			break;
		case SERVER_TYPE_CHAR:
#if 0
			addHookPre(loginif, send_users_count, login_send_count);
#endif
			addHookPost(chr, count_users, char_count_users);
			addHookPre(mapif, on_disconnect, map_crash_pad);
			addPacket(0x2c00, 6, char_change_pad, hpParse_FromMap);	
			
			break;
	}
}

HPExport void server_online (void) {
	ShowInfo ("'%s' Plugin by Dastgir/Hercules. Version '%s'\n",pinfo.name,pinfo.version);
}
