//===== Hercules Plugin ======================================
//= Charms
//===== By: ==================================================
//= AnnieRuru
//= original by digitalhamster
//===== Current Version: =====================================
//= 1.2
//===== Compatible With: ===================================== 
//= Hercules 2018-05-25
//===== Description: =========================================
//= Give item bonus like the game DiabloII
//===== Topic ================================================
//= http://herc.ws/board/topic/11575-charms/
//===== Additional Comments: =================================  
//= if you set Charm_Stack: true, it still run the status_calc_pc even if
//= - you already having the charm, but just add it up
//= - you have 10 charms in stack (give only 1 time bonus), but drop 5 charms
//= it still run status_calc_pc everytime, and I don't know a better way to do this ...
//============================================================

#include "common/hercules.h"
#include "map/pc.h"
#include "map/itemdb.h"
#include "map/status.h"
#include "common/conf.h"
#include "common/memmgr.h"
#include "common/nullpo.h"
#include "plugins/HPMHooking.h"
#include "common/HPMDataCheck.h"

HPExport struct hplugin_info pinfo = {
	"charms",
	SERVER_TYPE_MAP,
	"1.2",
	HPM_VERSION,
};

struct charm_item_data {
	bool charm;
	bool charm_stack;
};

struct player_data { // this stupid player variable is needed to prevent item dup
	bool recalculate;
};

void itemdb_readdb_additional_fields_pre( int *itemid, struct config_setting_t **it, int *n, const char **source ) {
	struct item_data *idata = itemdb->load( *itemid );
	struct charm_item_data *cidata = getFromITEMDATA( idata, 0 );
	struct config_setting_t *tt;
	if ( idata->type != IT_ETC )
		return;
	if ( !cidata ) {
		CREATE( cidata, struct charm_item_data, 1 );
		addToITEMDATA( idata, cidata, 0, true );
	}
	if (( tt = libconfig->setting_get_member( *it, "Charm" ) ))
		if ( libconfig->setting_get_bool(tt) )
			cidata->charm = true;
	if (( tt = libconfig->setting_get_member( *it, "Charm_Stack" ) ))
		if ( libconfig->setting_get_bool(tt) )
			cidata->charm_stack = true;
	return;
}

int itemdb_isstackable_pre( int *nameid ) {
	struct item_data *idata = itemdb->search( *nameid );
	struct charm_item_data *cidata;
	nullpo_ret(idata);
	if ( idata->type != IT_ETC )
		return 1;
	cidata = getFromITEMDATA( idata, 0 );
	if ( !cidata )
		return 1;
	if ( cidata->charm_stack == true ) {
		hookStop();
		return 1;
	}
	if ( cidata->charm == true ) {
		hookStop();
		return 0;
	}
	return 1;
}

int itemdb_isstackable2_pre( struct item_data **data ) {
	struct charm_item_data *cidata = NULL;
	nullpo_ret(data);
	if ( (*data)->type != IT_ETC )
		return 1;
	cidata = getFromITEMDATA( *data, 0 );
	if( !cidata )
		return 1;
	if ( cidata->charm_stack == true ) {
		hookStop();
		return 1;
	}
	if ( cidata->charm == true ) {
		hookStop();
		return 0;
	}
	return 1;
}

// TODO: Maybe should add those job/level/upper flag restriction like I did on eathena ? hmm ... but hercules omit those fields ... using default
void status_calc_pc_additional_pre( struct map_session_data **sd, enum e_status_calc_opt *opt ) {
	int i = 0;
	struct charm_item_data *cidata = NULL;
	for ( i = 0; i < MAX_INVENTORY; ++i ) {
		if ( !(*sd)->inventory_data[i] )
			continue;
		if ( (*sd)->inventory_data[i]->type != IT_ETC )
			continue;
		cidata = getFromITEMDATA( (*sd)->inventory_data[i], 0 );
		if ( !cidata )
			continue;
		if ( cidata->charm == false )
			continue;
		if ( (*sd)->inventory_data[i]->script ) {
			script->run( (*sd)->inventory_data[i]->script, 0, (*sd)->bl.id, 0 );
		}
	}
	return;
}

int pc_additem_post( int retVal, struct map_session_data *sd, struct item *item_data ,int amount, e_log_pick_type log_type ) {
	struct item_data *idata = itemdb->search( item_data->nameid );
	struct charm_item_data *cidata = NULL;
	if ( retVal != 0 )
		return retVal;
	if ( idata->type != IT_ETC )
		return retVal;
	cidata = getFromITEMDATA( idata, 0 );
	if ( !cidata )
		return retVal;
	if ( cidata->charm == true ) {
//		ShowDebug( "run recalculation" );
		status_calc_pc( sd, SCO_NONE );
	}
	return retVal;
}

int pc_delitem_pre( struct map_session_data **sd, int *n, int *amount, int *type, short *reason, e_log_pick_type *log_type ) {
	struct charm_item_data *cidata = NULL;
	struct player_data *ssd = NULL;
	nullpo_retr( 1, *sd );
	if ( (*sd)->status.inventory[*n].nameid == 0 || *amount <= 0 || (*sd)->status.inventory[*n].amount < *amount || (*sd)->inventory_data[*n] == NULL ) {
		hookStop();
		return 1;
	}
	if ( (*sd)->inventory_data[*n]->type != IT_ETC )
		return 0;
	cidata = getFromITEMDATA( (*sd)->inventory_data[*n], 0 );
	if ( !cidata )
		return 0;
	if ( cidata->charm == true ) {
		ssd = getFromMSD( *sd, 0 );
		if ( !ssd ) {
			CREATE( ssd, struct player_data, 1 );
			ssd->recalculate = 1;
			addToMSD( *sd, ssd, 0, true );
		}
		else
			ssd->recalculate = 1;
	}
	return 0;
}
// maybe I should've just overload this function ...
int pc_delitem_post( int retVal, struct map_session_data *sd, int n, int amount, int type, short reason, e_log_pick_type log_type ) {
	struct player_data *ssd = getFromMSD( sd, 0 );
	if ( ssd && ssd->recalculate == 1 ) {
//		ShowDebug( "run recalculation" );
		status_calc_pc( sd, SCO_NONE );
		ssd->recalculate = 0;
	}
	return retVal;
}

HPExport void plugin_init (void) {
	addHookPre( itemdb, readdb_additional_fields, itemdb_readdb_additional_fields_pre );
	addHookPre( itemdb, isstackable, itemdb_isstackable_pre );
	addHookPre( itemdb, isstackable2, itemdb_isstackable2_pre );
	addHookPre( status, calc_pc_additional, status_calc_pc_additional_pre );
	addHookPost( pc, additem, pc_additem_post );
	addHookPre( pc, delitem, pc_delitem_pre );
	addHookPost( pc, delitem, pc_delitem_post );
}