/**
 * This file is part of Hercules.
 * http://herc.ws - http://github.com/HerculesWS/Hercules
 *
 * Copyright (C) 2012-2018  Hercules Dev Team
 * Copyright (C)  Athena Dev Teams
 *
 * Hercules 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 3 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, see <http://www.gnu.org/licenses/>.
 */
#define HERCULES_CORE

#include "inter.h"

#include "char/char.h"
#include "char/geoip.h"
#include "char/int_auction.h"
#include "char/int_clan.h"
#include "char/int_elemental.h"
#include "char/int_guild.h"
#include "char/int_homun.h"
#include "char/int_mail.h"
#include "char/int_mercenary.h"
#include "char/int_party.h"
#include "char/int_pet.h"
#include "char/int_quest.h"
#include "char/int_rodex.h"
#include "char/int_storage.h"
#include "char/int_achievement.h"
#include "char/mapif.h"
#include "common/cbasetypes.h"
#include "common/conf.h"
#include "common/db.h"
#include "common/memmgr.h"
#include "common/mmo.h"
#include "common/nullpo.h"
#include "common/showmsg.h"
#include "common/socket.h"
#include "common/sql.h"
#include "common/strlib.h"
#include "common/timer.h"

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

#define WISDATA_TTL (60*1000) // Wis data Time To Live (60 seconds)
#define WISDELLIST_MAX 256    // Number of elements in the list Delete data Wis

static struct inter_interface inter_s;
struct inter_interface *inter;

static int char_server_port = 3306;
static char char_server_ip[32] = "127.0.0.1";
static char char_server_id[32] = "ragnarok";
static char char_server_pw[100] = "ragnarok";
static char char_server_db[32] = "ragnarok";
static char default_codepage[32] = ""; //Feature by irmin.

int party_share_level = 10;

// recv. packet list
static int inter_recv_packet_length[] = {
	-1,-1, 7,-1, -1,13,36, (2 + 4 + 4 + 4 + NAME_LENGTH),  0, 0, 0, 0,  0, 0,  0, 0, // 3000-
	 6,-1, 6,-1,  0, 0, 0, 0, 10,-1, 0, 0,  0, 0,  0, 0,    // 3010- Account Storage, Achievements [Smokexyz]
	-1,10,-1,14, 14,19, 6,-1, 14,14, 0, 0,  0, 0,  0, 0,    // 3020- Party
	-1, 6,-1,-1, 55,19, 6,-1, 14,-1,-1,-1, 18,19,186,-1,    // 3030-
	-1, 9, 0, 0, 10,10, 0, 0,  7, 6,10,10, 10,-1,  0, 0,    // 3040- Clan System(3044-3045)
	-1,-1,10,10,  0,-1,12, 0,  0, 0, 0, 0,  0, 0,  0, 0,    // 3050-  Auction System [Zephyrus], Item Bound [Mhalicot]
	 6,-1, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,    // 3060-  Quest system [Kevin] [Inkfish]
	-1,10, 6,-1,  0, 0, 0, 0,  0, 0, 0, 0, -1,10,  6,-1,    // 3070-  Mercenary packets [Zephyrus], Elemental packets [pakpil]
	52,14,-1, 6,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0,  0, 0,    // 3080-
	-1,10,-1, 6,  0, 20,10,11, -1,6 + NAME_LENGTH, 0, 0,  0, 0,  0, 0,    // 3090-  Homunculus packets [albator], RoDEX packets
};

static struct DBMap *wis_db = NULL; // int wis_id -> struct WisData*
static int wis_dellist[WISDELLIST_MAX], wis_delnum;

#define MAX_JOB_NAMES 150
static char *msg_table[MAX_JOB_NAMES]; //  messages 550 ~ 699 are job names

static const char *inter_msg_txt(int msg_number)
{
	msg_number -= 550;
	if (msg_number >= 0 && msg_number < MAX_JOB_NAMES &&
	    msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0')
		return msg_table[msg_number];

	return "Unknown";
}

/**
 * Reads Message Data.
 *
 * This is a modified version of the mapserver's inter->msg_config_read to
 * only read messages with IDs between 550 and 550+MAX_JOB_NAMES.
 *
 * @param[in] cfg_name       configuration filename to read.
 * @param[in] allow_override whether to allow duplicate message IDs to override the original value.
 * @return success state.
 */
static bool inter_msg_config_read(const char *cfg_name, bool allow_override)
{
	int msg_number;
	char line[1024], w1[1024], w2[1024];
	FILE *fp;
	static int called = 1;

	nullpo_ret(cfg_name);
	if ((fp = fopen(cfg_name, "r")) == NULL) {
		ShowError("Messages file not found: %s\n", cfg_name);
		return 1;
	}

	if ((--called) == 0)
		memset(msg_table, 0, sizeof(msg_table[0]) * MAX_JOB_NAMES);

	while(fgets(line, sizeof(line), fp) ) {
		if (line[0] == '/' && line[1] == '/')
			continue;
		if (sscanf(line, "%1023[^:]: %1023[^\r\n]", w1, w2) != 2)
			continue;

		if (strcmpi(w1, "import") == 0)
			inter->msg_config_read(w2, true);
		else {
			msg_number = atoi(w1);
			if( msg_number < 550 || msg_number > (550+MAX_JOB_NAMES) )
				continue;
			msg_number -= 550;
			if (msg_number >= 0 && msg_number < MAX_JOB_NAMES) {
				if (msg_table[msg_number] != NULL) {
					if (!allow_override) {
						ShowError("Duplicate message: ID '%d' was already used for '%s'. Message '%s' will be ignored.\n",
						          msg_number, w2, msg_table[msg_number]);
						continue;
					}
					aFree(msg_table[msg_number]);
				}
				msg_table[msg_number] = (char *)aMalloc((strlen(w2) + 1)*sizeof (char));
				strcpy(msg_table[msg_number],w2);
			}
		}
	}

	fclose(fp);

	return 0;
}

/*==========================================
 * Cleanup Message Data
 *------------------------------------------*/
static void inter_do_final_msg(void)
{
	int i;
	for (i = 0; i < MAX_JOB_NAMES; i++)
		aFree(msg_table[i]);
}
/* from pc.c due to @accinfo. any ideas to replace this crap are more than welcome. */
static const char *inter_job_name(int class)
{
	switch (class) {
		case JOB_NOVICE:   // 550
		case JOB_SWORDMAN: // 551
		case JOB_MAGE:     // 552
		case JOB_ARCHER:   // 553
		case JOB_ACOLYTE:  // 554
		case JOB_MERCHANT: // 555
		case JOB_THIEF:    // 556
			return inter->msg_txt(550 - JOB_NOVICE + class);

		case JOB_KNIGHT:     // 557
		case JOB_PRIEST:     // 558
		case JOB_WIZARD:     // 559
		case JOB_BLACKSMITH: // 560
		case JOB_HUNTER:     // 561
		case JOB_ASSASSIN:   // 562
			return inter->msg_txt(557 - JOB_KNIGHT + class);

		case JOB_KNIGHT2:
			return inter->msg_txt(557);

		case JOB_CRUSADER:  // 563
		case JOB_MONK:      // 564
		case JOB_SAGE:      // 565
		case JOB_ROGUE:     // 566
		case JOB_ALCHEMIST: // 567
		case JOB_BARD:      // 568
		case JOB_DANCER:    // 569
			return inter->msg_txt(563 - JOB_CRUSADER + class);

		case JOB_CRUSADER2:
			return inter->msg_txt(563);

		case JOB_WEDDING:      // 570
		case JOB_SUPER_NOVICE: // 571
		case JOB_GUNSLINGER:   // 572
		case JOB_NINJA:        // 573
		case JOB_XMAS:         // 574
			return inter->msg_txt(570 - JOB_WEDDING + class);

		case JOB_SUMMER:
			return inter->msg_txt(621);

		case JOB_NOVICE_HIGH:   // 575
		case JOB_SWORDMAN_HIGH: // 576
		case JOB_MAGE_HIGH:     // 577
		case JOB_ARCHER_HIGH:   // 578
		case JOB_ACOLYTE_HIGH:  // 579
		case JOB_MERCHANT_HIGH: // 580
		case JOB_THIEF_HIGH:    // 581
			return inter->msg_txt(575 - JOB_NOVICE_HIGH + class);

		case JOB_LORD_KNIGHT:    // 582
		case JOB_HIGH_PRIEST:    // 583
		case JOB_HIGH_WIZARD:    // 584
		case JOB_WHITESMITH:     // 585
		case JOB_SNIPER:         // 586
		case JOB_ASSASSIN_CROSS: // 587
			return inter->msg_txt(582 - JOB_LORD_KNIGHT + class);

		case JOB_LORD_KNIGHT2:
			return inter->msg_txt(582);

		case JOB_PALADIN:   // 588
		case JOB_CHAMPION:  // 589
		case JOB_PROFESSOR: // 590
		case JOB_STALKER:   // 591
		case JOB_CREATOR:   // 592
		case JOB_CLOWN:     // 593
		case JOB_GYPSY:     // 594
			return inter->msg_txt(588 - JOB_PALADIN + class);

		case JOB_PALADIN2:
			return inter->msg_txt(588);

		case JOB_BABY:          // 595
		case JOB_BABY_SWORDMAN: // 596
		case JOB_BABY_MAGE:     // 597
		case JOB_BABY_ARCHER:   // 598
		case JOB_BABY_ACOLYTE:  // 599
		case JOB_BABY_MERCHANT: // 600
		case JOB_BABY_THIEF:    // 601
			return inter->msg_txt(595 - JOB_BABY + class);

		case JOB_BABY_KNIGHT:     // 602
		case JOB_BABY_PRIEST:     // 603
		case JOB_BABY_WIZARD:     // 604
		case JOB_BABY_BLACKSMITH: // 605
		case JOB_BABY_HUNTER:     // 606
		case JOB_BABY_ASSASSIN:   // 607
			return inter->msg_txt(602 - JOB_BABY_KNIGHT + class);

		case JOB_BABY_KNIGHT2:
			return inter->msg_txt(602);

		case JOB_BABY_CRUSADER:  // 608
		case JOB_BABY_MONK:      // 609
		case JOB_BABY_SAGE:      // 610
		case JOB_BABY_ROGUE:     // 611
		case JOB_BABY_ALCHEMIST: // 612
		case JOB_BABY_BARD:      // 613
		case JOB_BABY_DANCER:    // 614
			return inter->msg_txt(608 - JOB_BABY_CRUSADER + class);

		case JOB_BABY_CRUSADER2:
			return inter->msg_txt(608);

		case JOB_SUPER_BABY:
			return inter->msg_txt(615);

		case JOB_TAEKWON:
			return inter->msg_txt(616);
		case JOB_STAR_GLADIATOR:
		case JOB_STAR_GLADIATOR2:
			return inter->msg_txt(617);
		case JOB_SOUL_LINKER:
			return inter->msg_txt(618);

		case JOB_GANGSI:         // 622
		case JOB_DEATH_KNIGHT:   // 623
		case JOB_DARK_COLLECTOR: // 624
			return inter->msg_txt(622 - JOB_GANGSI + class);

		case JOB_RUNE_KNIGHT:      // 625
		case JOB_WARLOCK:          // 626
		case JOB_RANGER:           // 627
		case JOB_ARCH_BISHOP:      // 628
		case JOB_MECHANIC:         // 629
		case JOB_GUILLOTINE_CROSS: // 630
			return inter->msg_txt(625 - JOB_RUNE_KNIGHT + class);

		case JOB_RUNE_KNIGHT_T:      // 656
		case JOB_WARLOCK_T:          // 657
		case JOB_RANGER_T:           // 658
		case JOB_ARCH_BISHOP_T:      // 659
		case JOB_MECHANIC_T:         // 660
		case JOB_GUILLOTINE_CROSS_T: // 661
			return inter->msg_txt(656 - JOB_RUNE_KNIGHT_T + class);

		case JOB_ROYAL_GUARD:   // 631
		case JOB_SORCERER:      // 632
		case JOB_MINSTREL:      // 633
		case JOB_WANDERER:      // 634
		case JOB_SURA:          // 635
		case JOB_GENETIC:       // 636
		case JOB_SHADOW_CHASER: // 637
			return inter->msg_txt(631 - JOB_ROYAL_GUARD + class);

		case JOB_ROYAL_GUARD_T:   // 662
		case JOB_SORCERER_T:      // 663
		case JOB_MINSTREL_T:      // 664
		case JOB_WANDERER_T:      // 665
		case JOB_SURA_T:          // 666
		case JOB_GENETIC_T:       // 667
		case JOB_SHADOW_CHASER_T: // 668
			return inter->msg_txt(662 - JOB_ROYAL_GUARD_T + class);

		case JOB_RUNE_KNIGHT2:
			return inter->msg_txt(625);

		case JOB_RUNE_KNIGHT_T2:
			return inter->msg_txt(656);

		case JOB_ROYAL_GUARD2:
			return inter->msg_txt(631);

		case JOB_ROYAL_GUARD_T2:
			return inter->msg_txt(662);

		case JOB_RANGER2:
			return inter->msg_txt(627);

		case JOB_RANGER_T2:
			return inter->msg_txt(658);

		case JOB_MECHANIC2:
			return inter->msg_txt(629);

		case JOB_MECHANIC_T2:
			return inter->msg_txt(660);

		case JOB_BABY_RUNE:     // 638
		case JOB_BABY_WARLOCK:  // 639
		case JOB_BABY_RANGER:   // 640
		case JOB_BABY_BISHOP:   // 641
		case JOB_BABY_MECHANIC: // 642
		case JOB_BABY_CROSS:    // 643
		case JOB_BABY_GUARD:    // 644
		case JOB_BABY_SORCERER: // 645
		case JOB_BABY_MINSTREL: // 646
		case JOB_BABY_WANDERER: // 647
		case JOB_BABY_SURA:     // 648
		case JOB_BABY_GENETIC:  // 649
		case JOB_BABY_CHASER:   // 650
			return inter->msg_txt(638 - JOB_BABY_RUNE + class);

		case JOB_BABY_RUNE2:
			return inter->msg_txt(638);

		case JOB_BABY_GUARD2:
			return inter->msg_txt(644);

		case JOB_BABY_RANGER2:
			return inter->msg_txt(640);

		case JOB_BABY_MECHANIC2:
			return inter->msg_txt(642);

		case JOB_SUPER_NOVICE_E: // 651
		case JOB_SUPER_BABY_E:   // 652
			return inter->msg_txt(651 - JOB_SUPER_NOVICE_E + class);

		case JOB_KAGEROU: // 653
		case JOB_OBORO:   // 654
			return inter->msg_txt(653 - JOB_KAGEROU + class);

		case JOB_REBELLION:
			return inter->msg_txt(655);

		case JOB_SUMMONER:
			return inter->msg_txt(669);

		default:
			return inter->msg_txt(620); // "Unknown Job"
	}
}

/**
 * Argument-list version of inter_msg_to_fd
 * @see inter_msg_to_fd
 */
static void inter_vmsg_to_fd(int fd, int u_fd, int aid, char *msg, va_list ap)
{
	char msg_out[512];
	va_list apcopy;
	int len = 1;/* yes we start at 1 */

	nullpo_retv(msg);
	va_copy(apcopy, ap);
	len += vsnprintf(msg_out, 512, msg, apcopy);
	va_end(apcopy);

	WFIFOHEAD(fd,12 + len);

	WFIFOW(fd,0) = 0x3807;
	WFIFOW(fd,2) = 12 + (unsigned short)len;
	WFIFOL(fd,4) = u_fd;
	WFIFOL(fd,8) = aid;
	safestrncpy(WFIFOP(fd,12), msg_out, len);

	WFIFOSET(fd,12 + len);

	return;
}

/**
 * Sends a message to map server (fd) to a user (u_fd) although we use fd we
 * keep aid for safe-check.
 * @param fd   Mapserver's fd
 * @param u_fd Recipient's fd
 * @param aid  Recipient's expected for sanity checks on the mapserver
 * @param msg  Message format string
 * @param ...  Additional parameters for (v)sprinf
 */
static void inter_msg_to_fd(int fd, int u_fd, int aid, char *msg, ...) __attribute__((format(printf, 4, 5)));
static void inter_msg_to_fd(int fd, int u_fd, int aid, char *msg, ...)
{
	va_list ap;
	va_start(ap,msg);
	inter->vmsg_to_fd(fd, u_fd, aid, msg, ap);
	va_end(ap);
}

/* [Dekamaster/Nightroad] */
static void inter_accinfo(int u_fd, int aid, int castergroup, const char *query, int map_fd)
{
	char query_esq[NAME_LENGTH*2+1];
	int account_id;
	char *data;

	SQL->EscapeString(inter->sql_handle, query_esq, query);

	account_id = atoi(query);

	if (account_id < START_ACCOUNT_NUM) {
		// is string
		if ( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `account_id`,`name`,`class`,`base_level`,`job_level`,`online` FROM `%s` WHERE `name` LIKE '%s' LIMIT 10", char_db, query_esq)
				|| SQL->NumRows(inter->sql_handle) == 0 ) {
			if( SQL->NumRows(inter->sql_handle) == 0 ) {
				inter->msg_to_fd(map_fd, u_fd, aid, "No matches were found for your criteria, '%s'",query);
			} else {
				Sql_ShowDebug(inter->sql_handle);
				inter->msg_to_fd(map_fd, u_fd, aid, "An error occurred, bother your admin about it.");
			}
			SQL->FreeResult(inter->sql_handle);
			return;
		} else {
			if( SQL->NumRows(inter->sql_handle) == 1 ) {//we found a perfect match
				SQL->NextRow(inter->sql_handle);
				SQL->GetData(inter->sql_handle, 0, &data, NULL); account_id = atoi(data);
				SQL->FreeResult(inter->sql_handle);
			} else {// more than one, listing... [Dekamaster/Nightroad]
				inter->msg_to_fd(map_fd, u_fd, aid, "Your query returned the following %d results, please be more specific...",(int)SQL->NumRows(inter->sql_handle));
				while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
					int class;
					int base_level, job_level, online;
					char name[NAME_LENGTH];

					SQL->GetData(inter->sql_handle, 0, &data, NULL); account_id = atoi(data);
					SQL->GetData(inter->sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name));
					SQL->GetData(inter->sql_handle, 2, &data, NULL); class = atoi(data);
					SQL->GetData(inter->sql_handle, 3, &data, NULL); base_level = atoi(data);
					SQL->GetData(inter->sql_handle, 4, &data, NULL); job_level = atoi(data);
					SQL->GetData(inter->sql_handle, 5, &data, NULL); online = atoi(data);

					inter->msg_to_fd(map_fd, u_fd, aid, "[AID: %d] %s | %s | Level: %d/%d | %s", account_id, name, inter->job_name(class), base_level, job_level, online?"Online":"Offline");
				}
				SQL->FreeResult(inter->sql_handle);
				return;
			}
		}
	}

	/* it will only get here if we have a single match */
	/* and we will send packet with account id to login server asking for account info */
	if( account_id ) {
		mapif->on_parse_accinfo(account_id, u_fd, aid, castergroup, map_fd);
	}

	return;
}

static void inter_accinfo2(bool success, int map_fd, int u_fd, int u_aid, int account_id, const char *userid, const char *user_pass,
		const char *email, const char *last_ip, const char *lastlogin, const char *pin_code, const char *birthdate,
		int group_id, int logincount, int state)
{
	nullpo_retv(userid);
	nullpo_retv(user_pass);
	nullpo_retv(email);
	nullpo_retv(last_ip);
	nullpo_retv(lastlogin);
	nullpo_retv(birthdate);
	if (map_fd <= 0 || !sockt->session_is_active(map_fd))
		return; // check if we have a valid fd

	if (!success) {
		inter->msg_to_fd(map_fd, u_fd, u_aid, "No account with ID '%d' was found.", account_id);
		return;
	}

	inter->msg_to_fd(map_fd, u_fd, u_aid, "-- Account %d --", account_id);
	inter->msg_to_fd(map_fd, u_fd, u_aid, "User: %s | GM Group: %d | State: %d", userid, group_id, state);

// enable this if you really know what you doing.
#if 0
	if (*user_pass != '\0') { /* password is only received if your gm level is greater than the one you're searching for */
		if (pin_code && *pin_code != '\0')
			inter->msg_to_fd(map_fd, u_fd, u_aid, "Password: %s (PIN:%s)", user_pass, pin_code);
		else
			inter->msg_to_fd(map_fd, u_fd, u_aid, "Password: %s", user_pass );
	}
#endif

	inter->msg_to_fd(map_fd, u_fd, u_aid, "Account e-mail: %s | Birthdate: %s", email, birthdate);
	inter->msg_to_fd(map_fd, u_fd, u_aid, "Last IP: %s (%s)", last_ip, geoip->getcountry(sockt->str2ip(last_ip)));
	inter->msg_to_fd(map_fd, u_fd, u_aid, "This user has logged %d times, the last time were at %s", logincount, lastlogin);
	inter->msg_to_fd(map_fd, u_fd, u_aid, "-- Character Details --");

	if ( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `char_id`, `name`, `char_num`, `class`, `base_level`, `job_level`, `online` "
	                                         "FROM `%s` WHERE `account_id` = '%d' ORDER BY `char_num` LIMIT %d", char_db, account_id, MAX_CHARS)
	  || SQL->NumRows(inter->sql_handle) == 0 ) {
		if (SQL->NumRows(inter->sql_handle) == 0) {
			inter->msg_to_fd(map_fd, u_fd, u_aid, "This account doesn't have characters.");
		} else {
			inter->msg_to_fd(map_fd, u_fd, u_aid, "An error occurred, bother your admin about it.");
			Sql_ShowDebug(inter->sql_handle);
		}
	} else {
		while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
			char *data;
			int char_id, class;
			int char_num, base_level, job_level, online;
			char name[NAME_LENGTH];

			SQL->GetData(inter->sql_handle, 0, &data, NULL); char_id = atoi(data);
			SQL->GetData(inter->sql_handle, 1, &data, NULL); safestrncpy(name, data, sizeof(name));
			SQL->GetData(inter->sql_handle, 2, &data, NULL); char_num = atoi(data);
			SQL->GetData(inter->sql_handle, 3, &data, NULL); class = atoi(data);
			SQL->GetData(inter->sql_handle, 4, &data, NULL); base_level = atoi(data);
			SQL->GetData(inter->sql_handle, 5, &data, NULL); job_level = atoi(data);
			SQL->GetData(inter->sql_handle, 6, &data, NULL); online = atoi(data);

			inter->msg_to_fd(map_fd, u_fd, u_aid, "[Slot/CID: %d/%d] %s | %s | Level: %d/%d | %s", char_num, char_id, name, inter->job_name(class), base_level, job_level, online?"On":"Off");
		}
	}
	SQL->FreeResult(inter->sql_handle);

	return;
}
/**
 * Handles save reg data from map server and distributes accordingly.
 *
 * @param val either str or int, depending on type
 * @param type false when int, true otherwise
 **/
static void inter_savereg(int account_id, int char_id, const char *key, unsigned int index, intptr_t val, bool is_string)
{
	char val_esq[1000];
	nullpo_retv(key);
	/* to login server we go! */
	if( key[0] == '#' && key[1] == '#' ) {/* global account reg */
		if (sockt->session_is_valid(chr->login_fd))
			chr->global_accreg_to_login_add(key,index,val,is_string);
		else {
			ShowError("Login server unavailable, cant perform update on '%s' variable for AID:%d CID:%d\n",key,account_id,char_id);
		}
	} else if ( key[0] == '#' ) {/* local account reg */
		if( is_string ) {
			if( val ) {
				SQL->EscapeString(inter->sql_handle, val_esq, (char*)val);
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", acc_reg_str_db, account_id, key, index, val_esq) )
					Sql_ShowDebug(inter->sql_handle);
			} else {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", acc_reg_str_db, account_id, key, index) )
					Sql_ShowDebug(inter->sql_handle);
			}
		} else {
			if( val ) {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`account_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", acc_reg_num_db, account_id, key, index, (int)val) )
					Sql_ShowDebug(inter->sql_handle);
			} else {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `account_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", acc_reg_num_db, account_id, key, index) )
					Sql_ShowDebug(inter->sql_handle);
			}
		}
	} else { /* char reg */
		if( is_string ) {
			if( val ) {
				SQL->EscapeString(inter->sql_handle, val_esq, (char*)val);
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%s')", char_reg_str_db, char_id, key, index, val_esq) )
					Sql_ShowDebug(inter->sql_handle);
			} else {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", char_reg_str_db, char_id, key, index) )
					Sql_ShowDebug(inter->sql_handle);
			}
		} else {
			if( val ) {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "REPLACE INTO `%s` (`char_id`,`key`,`index`,`value`) VALUES ('%d','%s','%u','%d')", char_reg_num_db, char_id, key, index, (int)val) )
					Sql_ShowDebug(inter->sql_handle);
			} else {
				if( SQL_ERROR == SQL->Query(inter->sql_handle, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `key` = '%s' AND `index` = '%u' LIMIT 1", char_reg_num_db, char_id, key, index) )
					Sql_ShowDebug(inter->sql_handle);
			}
		}
	}
}

// Load account_reg from sql (type=2)
static int inter_accreg_fromsql(int account_id, int char_id, int fd, int type)
{
	char* data;
	size_t len;
	unsigned int plen = 0;

	switch( type ) {
		case 3: //char reg
			if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", char_reg_str_db, char_id) )
				Sql_ShowDebug(inter->sql_handle);
			break;
		case 2: //account reg
			if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", acc_reg_str_db, account_id) )
				Sql_ShowDebug(inter->sql_handle);
			break;
		case 1: //account2 reg
			ShowError("inter->accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
			return 0;
		default:
			ShowError("inter->accreg_fromsql: Invalid type %d\n", type);
			return 0;
	}

	WFIFOHEAD(fd, 60000 + 300);
	WFIFOW(fd, 0) = 0x3804;
	/* 0x2 = length, set prior to being sent */
	WFIFOL(fd, 4) = account_id;
	WFIFOL(fd, 8) = char_id;
	WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
	WFIFOB(fd, 13) = 1;/* is string type */
	WFIFOW(fd, 14) = 0;/* count */
	plen = 16;

	/**
	 * Vessel!
	 *
	 * str type
	 * { keyLength(B), key(<keyLength>), index(L), valLength(B), val(<valLength>) }
	 **/
	while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
		SQL->GetData(inter->sql_handle, 0, &data, NULL);
		len = strlen(data)+1;

		WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */
		plen += 1;

		safestrncpy(WFIFOP(fd,plen), data, len);
		plen += len;

		SQL->GetData(inter->sql_handle, 1, &data, NULL);

		WFIFOL(fd, plen) = (unsigned int)atol(data);
		plen += 4;

		SQL->GetData(inter->sql_handle, 2, &data, NULL);
		len = strlen(data)+1;

		WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 254 */
		plen += 1;

		safestrncpy(WFIFOP(fd,plen), data, len);
		plen += len;

		WFIFOW(fd, 14) += 1;

		if( plen > 60000 ) {
			WFIFOW(fd, 2) = plen;
			WFIFOSET(fd, plen);

			/* prepare follow up */
			WFIFOHEAD(fd, 60000 + 300);
			WFIFOW(fd, 0) = 0x3804;
			/* 0x2 = length, set prior to being sent */
			WFIFOL(fd, 4) = account_id;
			WFIFOL(fd, 8) = char_id;
			WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
			WFIFOB(fd, 13) = 1;/* is string type */
			WFIFOW(fd, 14) = 0;/* count */
			plen = 16;
		}
	}

	/* mark & go. */
	WFIFOW(fd, 2) = plen;
	WFIFOSET(fd, plen);

	SQL->FreeResult(inter->sql_handle);

	switch( type ) {
		case 3: //char reg
			if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `char_id`='%d'", char_reg_num_db, char_id) )
				Sql_ShowDebug(inter->sql_handle);
			break;
		case 2: //account reg
			if( SQL_ERROR == SQL->Query(inter->sql_handle, "SELECT `key`, `index`, `value` FROM `%s` WHERE `account_id`='%d'", acc_reg_num_db, account_id) )
				Sql_ShowDebug(inter->sql_handle);
			break;
#if 0 // This is already checked above.
		case 1: //account2 reg
			ShowError("inter->accreg_fromsql: Char server shouldn't handle type 1 registry values (##). That is the login server's work!\n");
			return 0;
#endif // 0
	}

	WFIFOHEAD(fd, 60000 + 300);
	WFIFOW(fd, 0) = 0x3804;
	/* 0x2 = length, set prior to being sent */
	WFIFOL(fd, 4) = account_id;
	WFIFOL(fd, 8) = char_id;
	WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
	WFIFOB(fd, 13) = 0;/* is int type */
	WFIFOW(fd, 14) = 0;/* count */
	plen = 16;

	/**
	 * Vessel!
	 *
	 * int type
	 * { keyLength(B), key(<keyLength>), index(L), value(L) }
	 **/
	while ( SQL_SUCCESS == SQL->NextRow(inter->sql_handle) ) {
		SQL->GetData(inter->sql_handle, 0, &data, NULL);
		len = strlen(data)+1;

		WFIFOB(fd, plen) = (unsigned char)len;/* won't be higher; the column size is 32 */
		plen += 1;

		safestrncpy(WFIFOP(fd,plen), data, len);
		plen += len;

		SQL->GetData(inter->sql_handle, 1, &data, NULL);

		WFIFOL(fd, plen) = (unsigned int)atol(data);
		plen += 4;

		SQL->GetData(inter->sql_handle, 2, &data, NULL);

		WFIFOL(fd, plen) = atoi(data);
		plen += 4;

		WFIFOW(fd, 14) += 1;

		if( plen > 60000 ) {
			WFIFOW(fd, 2) = plen;
			WFIFOSET(fd, plen);

			/* prepare follow up */
			WFIFOHEAD(fd, 60000 + 300);
			WFIFOW(fd, 0) = 0x3804;
			/* 0x2 = length, set prior to being sent */
			WFIFOL(fd, 4) = account_id;
			WFIFOL(fd, 8) = char_id;
			WFIFOB(fd, 12) = 0;/* var type (only set when all vars have been sent, regardless of type) */
			WFIFOB(fd, 13) = 0;/* is int type */
			WFIFOW(fd, 14) = 0;/* count */
			plen = 16;
		}
	}

	/* mark as complete & go. */
	WFIFOB(fd, 12) = type;
	WFIFOW(fd, 2) = plen;
	WFIFOSET(fd, plen);

	SQL->FreeResult(inter->sql_handle);
	return 1;
}

/**
 * Reads the 'inter_configuration/log' config entry and initializes required variables.
 *
 * @param filename Path to configuration file (used in error and warning messages).
 * @param config   The current config being parsed.
 * @param imported Whether the current config is imported from another file.
 *
 * @retval false in case of error.
 */
static bool inter_config_read_log(const char *filename, const struct config_t *config, bool imported)
{
	const struct config_setting_t *setting = NULL;

	nullpo_retr(false, filename);
	nullpo_retr(false, config);

	if ((setting = libconfig->lookup(config, "inter_configuration/log")) == NULL) {
		if (imported)
			return true;
		ShowError("sql_config_read: inter_configuration/log was not found in %s!\n", filename);
		return false;
	}

	libconfig->setting_lookup_bool_real(setting, "log_inter", &inter->enable_logs);

	return true;
}

/**
 * Reads the 'char_configuration/sql_connection' config entry and initializes required variables.
 *
 * @param filename Path to configuration file (used in error and warning messages).
 * @param config   The current config being parsed.
 * @param imported Whether the current config is imported from another file.
 *
 * @retval false in case of error.
 */
static bool inter_config_read_connection(const char *filename, const struct config_t *config, bool imported)
{
	const struct config_setting_t *setting = NULL;

	nullpo_retr(false, filename);
	nullpo_retr(false, config);

	if ((setting = libconfig->lookup(config, "char_configuration/sql_connection")) == NULL) {
		if (imported)
			return true;
		ShowError("char_config_read: char_configuration/sql_connection was not found in %s!\n", filename);
		ShowWarning("inter_config_read_connection: Defaulting sql_connection...\n");
		return false;
	}

	libconfig->setting_lookup_int(setting, "db_port", &char_server_port);
	libconfig->setting_lookup_mutable_string(setting, "db_hostname", char_server_ip, sizeof(char_server_ip));
	libconfig->setting_lookup_mutable_string(setting, "db_username", char_server_id, sizeof(char_server_id));
	libconfig->setting_lookup_mutable_string(setting, "db_password", char_server_pw, sizeof(char_server_pw));
	libconfig->setting_lookup_mutable_string(setting, "db_database", char_server_db, sizeof(char_server_db));
	libconfig->setting_lookup_mutable_string(setting, "default_codepage", default_codepage, sizeof(default_codepage));

	return true;
}

/**
 * Reads the 'inter_configuration' config file and initializes required variables.
 *
 * @param filename Path to configuration file
 * @param imported Whether the current config is from an imported file.
 *
 * @retval false in case of error.
 */
static bool inter_config_read(const char *filename, bool imported)
{
	struct config_t config;
	const struct config_setting_t *setting = NULL;
	const char *import = NULL;
	bool retval = true;

	nullpo_retr(false, filename);

	if (!libconfig->load_file(&config, filename))
		return false;

	if ((setting = libconfig->lookup(&config, "inter_configuration")) == NULL) {
		libconfig->destroy(&config);
		if (imported)
			return true;
		ShowError("inter_config_read: inter_configuration was not found in %s!\n", filename);
		return false;
	}
	libconfig->setting_lookup_int(setting, "party_share_level", &party_share_level);

	if (!inter->config_read_log(filename, &config, imported))
		retval = false;

	ShowInfo("Done reading %s.\n", filename);

	// import should overwrite any previous configuration, so it should be called last
	if (libconfig->lookup_string(&config, "import", &import) == CONFIG_TRUE) {
		if (strcmp(import, filename) == 0 || strcmp(import, chr->INTER_CONF_NAME) == 0) {
			ShowWarning("inter_config_read: Loop detected in %s! Skipping 'import'...\n", filename);
		} else {
			if (!inter->config_read(import, true))
				retval = false;
		}
	}

	libconfig->destroy(&config);
	return retval;
}

/**
 * Save interlog into sql (arglist version)
 * @see inter_log
 */
static int inter_vlog(char *fmt, va_list ap)
{
	char str[255];
	char esc_str[sizeof(str)*2+1];// escaped str
	va_list apcopy;

	va_copy(apcopy, ap);
	vsnprintf(str, sizeof(str), fmt, apcopy);
	va_end(apcopy);

	SQL->EscapeStringLen(inter->sql_handle, esc_str, str, strnlen(str, sizeof(str)));
	if( SQL_ERROR == SQL->Query(inter->sql_handle, "INSERT INTO `%s` (`time`, `log`) VALUES (NOW(),  '%s')", interlog_db, esc_str) )
		Sql_ShowDebug(inter->sql_handle);

	return 0;
}

/**
 * Save interlog into sql
 * @param fmt Message's format string
 * @param ... Additional (printf-like) arguments
 * @return Always 0 // FIXME
 */
static int inter_log(char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start(ap,fmt);
	ret = inter->vlog(fmt, ap);
	va_end(ap);

	return ret;
}

// initialize
static int inter_init_sql(const char *file)
{
	inter->config_read(file, false);

	//DB connection initialized
	inter->sql_handle = SQL->Malloc();
	ShowInfo("Connect Character DB server.... (Character Server)\n");
	if( SQL_ERROR == SQL->Connect(inter->sql_handle, char_server_id, char_server_pw, char_server_ip, (uint16)char_server_port, char_server_db) )
	{
		Sql_ShowDebug(inter->sql_handle);
		SQL->Free(inter->sql_handle);
		exit(EXIT_FAILURE);
	}

	if( *default_codepage ) {
		if( SQL_ERROR == SQL->SetEncoding(inter->sql_handle, default_codepage) )
			Sql_ShowDebug(inter->sql_handle);
	}

	wis_db = idb_alloc(DB_OPT_RELEASE_DATA);
	inter_guild->sql_init();
	inter_storage->sql_init();
	inter_party->sql_init();
	inter_pet->sql_init();
	inter_homunculus->sql_init();
	inter_mercenary->sql_init();
	inter_elemental->sql_init();
	inter_mail->sql_init();
	inter_auction->sql_init();
	inter_rodex->sql_init();
	inter_achievement->sql_init();

	geoip->init();
	inter->msg_config_read("conf/messages.conf", false);
	return 0;
}

// finalize
static void inter_final(void)
{
	wis_db->destroy(wis_db, NULL);

	inter_guild->sql_final();
	inter_storage->sql_final();
	inter_party->sql_final();
	inter_pet->sql_final();
	inter_homunculus->sql_final();
	inter_mercenary->sql_final();
	inter_elemental->sql_final();
	inter_mail->sql_final();
	inter_auction->sql_final();
	inter_rodex->sql_final();
	inter_achievement->sql_final();

	geoip->final(true);
	inter->do_final_msg();
	return;
}

static int inter_mapif_init(int fd)
{
	return 0;
}

//--------------------------------------------------------

/**
 * Existence check of WISP data
 * @see DBApply
 */
static int inter_check_ttl_wisdata_sub(union DBKey key, struct DBData *data, va_list ap)
{
	int64 tick;
	struct WisData *wd = DB->data2ptr(data);
	nullpo_ret(wd);
	tick = va_arg(ap, int64);

	if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX)
		wis_dellist[wis_delnum++] = wd->id;

	return 0;
}

static int inter_check_ttl_wisdata(void)
{
	int64 tick = timer->gettick();
	int i;

	do {
		wis_delnum = 0;
		wis_db->foreach(wis_db, inter->check_ttl_wisdata_sub, tick);
		for(i = 0; i < wis_delnum; i++) {
			struct WisData *wd = (struct WisData*)idb_get(wis_db, wis_dellist[i]);
			ShowWarning("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst);
			// removed. not send information after a timeout. Just no answer for the player
			//mapif->wis_end(wd, 1); // flag: 0: success to send whisper, 1: target character is not logged in?, 2: ignored by target
			idb_remove(wis_db, wd->id);
		}
	} while(wis_delnum >= WISDELLIST_MAX);

	return 0;
}

static struct WisData *inter_add_wisdata(int fd, const unsigned char *src, const unsigned char *dst, const unsigned char *msg, int msg_len)
{
	static int wisid = 0;
	struct WisData *wd;

	CREATE(wd, struct WisData, 1);

	// Whether the failure of previous wisp/page transmission (timeout)
	inter->check_ttl_wisdata();

	wd->id = ++wisid;
	wd->fd = fd;
	wd->len = msg_len;
	memcpy(wd->src, src, NAME_LENGTH);
	memcpy(wd->dst, dst, NAME_LENGTH);
	memcpy(wd->msg, msg, wd->len);
	wd->tick = timer->gettick();
	idb_put(wis_db, wd->id, wd);

	return wd;
}

static struct WisData *inter_get_wisdata(int id)
{
	return idb_get(wis_db, id);
}

static void inter_remove_wisdata(int id)
{
	idb_remove(wis_db, id);
}

//--------------------------------------------------------

/// Returns the length of the next complete packet to process,
/// or 0 if no complete packet exists in the queue.
///
/// @param length The minimum allowed length, or -1 for dynamic lookup
static int inter_check_length(int fd, int length)
{
	if( length == -1 )
	{// variable-length packet
		if( RFIFOREST(fd) < 4 )
			return 0;
		length = RFIFOW(fd,2);
	}

	if( (int)RFIFOREST(fd) < length )
		return 0;

	return length;
}

static int inter_parse_frommap(int fd)
{
	int cmd;
	int len = 0;
	cmd = RFIFOW(fd,0);
	// Check is valid packet entry
	if(cmd < 0x3000 || cmd >= 0x3000 + ARRAYLENGTH(inter_recv_packet_length) || inter_recv_packet_length[cmd - 0x3000] == 0)
		return 0;

	// Check packet length
	if((len = inter->check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0)
		return 2;

	switch(cmd) {
	case 0x3000: mapif->parse_broadcast(fd); break;
	case 0x3001: mapif->parse_WisRequest(fd); break;
	case 0x3002: mapif->parse_WisReply(fd); break;
	case 0x3003: mapif->parse_WisToGM(fd); break;
	case 0x3004: mapif->parse_Registry(fd); break;
	case 0x3005: mapif->parse_RegistryRequest(fd); break;
	case 0x3006: mapif->parse_NameChangeRequest(fd); break;
	case 0x3007: mapif->parse_accinfo(fd); break;
	default:
		if(  inter_party->parse_frommap(fd)
		  || inter_guild->parse_frommap(fd)
		  || inter_storage->parse_frommap(fd)
		  || inter_pet->parse_frommap(fd)
		  || inter_homunculus->parse_frommap(fd)
		  || inter_mercenary->parse_frommap(fd)
		  || inter_elemental->parse_frommap(fd)
		  || inter_mail->parse_frommap(fd)
		  || inter_auction->parse_frommap(fd)
		  || inter_quest->parse_frommap(fd)
		  || inter_rodex->parse_frommap(fd)
		  || inter_clan->parse_frommap(fd)
		  || inter_achievement->parse_frommap(fd)
		   )
			break;
		else
			return 0;
	}

	RFIFOSKIP(fd, len);
	return 1;
}

void inter_defaults(void)
{
	inter = &inter_s;

	inter->enable_logs = true;
	inter->sql_handle = NULL;

	inter->msg_txt = inter_msg_txt;
	inter->msg_config_read = inter_msg_config_read;
	inter->do_final_msg = inter_do_final_msg;
	inter->job_name = inter_job_name;
	inter->vmsg_to_fd = inter_vmsg_to_fd;
	inter->msg_to_fd = inter_msg_to_fd;
	inter->savereg = inter_savereg;
	inter->accreg_fromsql = inter_accreg_fromsql;
	inter->config_read = inter_config_read;
	inter->vlog = inter_vlog;
	inter->log = inter_log;
	inter->init_sql = inter_init_sql;
	inter->mapif_init = inter_mapif_init;
	inter->check_ttl_wisdata_sub = inter_check_ttl_wisdata_sub;
	inter->check_ttl_wisdata = inter_check_ttl_wisdata;
	inter->check_length = inter_check_length;
	inter->parse_frommap = inter_parse_frommap;
	inter->final = inter_final;
	inter->config_read_log = inter_config_read_log;
	inter->config_read_connection = inter_config_read_connection;
	inter->accinfo = inter_accinfo;
	inter->accinfo2 = inter_accinfo2;
	inter->add_wisdata = inter_add_wisdata;
	inter->get_wisdata = inter_get_wisdata;
	inter->remove_wisdata = inter_remove_wisdata;
}
