/**
 ** Spellbook.cc - Spellbook object.
 **
 ** Written: 10/1/98 - JSF
 **/

/*
Copyright (C) 2000-2022 The Exult Team.

This program 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.
*/

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "spellbook.h"

#include "Gump_manager.h"
#include "actors.h"
#include "cheat.h"
#include "databuf.h"
#include "frflags.h"
#include "game.h"
#include "gamemap.h"
#include "gamewin.h"
#include "ignore_unused_variable_warning.h"
#include "ucmachine.h"
#include "ucsched.h"
#include "usefuns.h"

using std::ostream;

// TODO: De-hard-code reagent shape and costs.
const int REAGENTS = 842;    // Shape #.

/*
 *  Flags for required reagents.  Bits match frame #.
 */
const int bp = 1;       // Black pearl.
const int bm = 2;       // Blood moss.
const int ns = 4;       // Nightshade.
const int mr = 8;       // Mandrake root.
const int gr = 16;      // Garlic.
const int gn = 32;      // Ginseng.
const int ss = 64;      // Spider silk.
const int sa = 128;     // Sulphuras ash.
const int bs = 256;     // Blood spawn.
const int sc = 512;     // Serpent scales.
const int wh = 1024;    // Worm's hart.

// Black Gate:
unsigned short Spellbook_object::bg_reagents[9 * 8] = {
		0, 0, 0, 0, 0, 0, 0, 0,    // Linear spells require no reagents.
		// Circle 1:
		gr | gn | mr, gr | gn, ns | ss, gr | ss, sa | ss, sa, ns, gr | gn,
		// Circle 2:
		bm | sa, bp | mr, bp | sa, mr | sa, gr | gn | mr, gr | gn | sa,
		bp | bm | mr, bm | ns | mr | sa | bp | ss,
		// Circle 3:
		gr | ns | sa, gr | gn | ss, ns | mr | bm, sa | gn | gr | mr, ns | ss,
		ns | mr, bp | ns | bm, ns | ss | bp,
		// Circle 4:
		ss | mr, bp | sa | mr, mr | bp | bm, gr | mr | ns | sa, mr | bp | bm,
		bm | sa, bm | mr | ns | ss | sa, bm | sa,
		// Circle 5:
		bp | ns | ss, mr | gr | bm, gr | bp | sa | ss, bm | bp | mr | sa,
		gn | ss | mr | gr, ns | bm, bp | sa | ss, gn | ns | ss,
		// Circle 6:
		gr | mr | ns, sa | ss | bm | gn | ns | mr, bp | mr | ss | sa,
		sa | bp | bm, mr | ns | sa | bm, ns | ss | bp, gn | ss | bp,
		bm | sa | mr,
		// Circle 7:
		mr | ss, bp | ns | sa, bm | bp | mr | ss | sa, bp | mr | ss | sa,
		bm | mr | ns | sa, bp | ns | ss | mr, bp | gn | mr, gr | gn | mr | ss,
		// Circle 8:
		bp | bm | gr | gn | mr | ns | ss | sa, bm | mr | ns | sa,
		gr | gn | mr | ns | bm, mr | ns | bm | bp, gr | gn | ss | sa,
		bm | gr | mr, bp | mr | ns, bm | gr | mr};
// Serpent Isle:
unsigned short Spellbook_object::si_reagents[9 * 8] = {
		// Circle 1:
		gr | gn | mr, gr | gn, ns | ss, gr | ss, sa | ss, sa, ns, bp | bm | mr,
		// Circle 2:
		gr | gn, bm | sa, ns | sa, bp | sa | wh, mr | sa, gr | gn | ss,
		gr | gn | mr, gr | gn | sa,
		// Circle 3:
		gr | gn | wh, gr | ns | sa, bp | mr, bp | gr, gr | gn | mr | sa,
		ns | ss, bp | ns | ss, bp | mr | sa,
		// Circle 4:
		bm | mr, gr | ss, mr | sa, sa | bm | gr | mr | ss | sc,
		gr | mr | ns | sa, bm | sa, bp | ss, bm | sa,
		// Circle 5:
		mr | ss, bp | gr | ss | sa, bm | bp | mr | sa, gr | gn | mr | ss,
		bm | ns, gn | ns | ss, sa | bm | mr | ns | ss, bp | gr | mr | sa,
		// Circle 6:
		bp | ns | ss, gr | mr | ns, gr | mr | ns, bp | wh | ss | sa,
		bp | wh | mr | ss | sa, bm | bp | wh | sa, bm | gn | sa,
		mr | sa | ss | sc,
		// Circle 7:
		bp | mr | ss | sa, bm | mr | ns | sa, gr | gn, bp | gn | mr,
		bm | ns | sa, gr | gn | mr | ss, bp | bm | mr | ss, bp | mr | sa,
		// Circle 8:
		wh | ss, bs | bp | ns | sa, bm | bp | mr | ss | sa, bm | bp | mr,
		bm | gr | ss | wh | sc, bm | bp | gr | ss | wh | sc, gr | mr | sa,
		bp | bs | mr | ns,
		// Circle 9:
		bm | mr | ns | sa, bm | bs | gr | gn | mr | ns, bp | bm | mr | ns,
		bm | bs | bp | ns | sa, bp | gr | mr | ss | sa, bm | gr | mr | ss,
		bm | gr | mr, ns | sa | wh | sc | bs};

/*
 *  Get usecode function for a given spell:
 */
int Get_usecode(int spell) {
	return BaseSpellsUsecode + spell;
}

/*
 *  Test for "Ring" of Reagants.
 */

bool Spellbook_object::has_ring(Actor* act) {
	return act->check_gear_powers(Frame_flags::infinite_reagents);
}

/*
 *  Create a spellbook from Ireg data.
 */

Spellbook_object::Spellbook_object(
		int shapenum, int framenum, unsigned int shapex, unsigned int shapey,
		unsigned int   lft,
		unsigned char* c,       // Circle spell flags.
		unsigned char  bmark    // Spell bookmark.
		)
		: Ireg_game_object(shapenum, framenum, shapex, shapey, lft),
		  bookmark(bmark == 255 ? -1 : bmark) {
	memcpy(circles, c, sizeof(circles));
	// Point to reagent table.
	reagents = GAME_SI ? si_reagents : bg_reagents;
}

/*
 *  Add a spell.
 *
 *  Output: 0 if already have it, 1 if successful.
 */

int Spellbook_object::add_spell(int spell    // 0-71
) {
	const int circle = spell / 8;
	const int num    = spell % 8;    // # within circle.
	if (circles[circle] & (1 << num)) {
		return 0;    // Already have it.
	}
	circles[circle] |= (1 << num);
	return 1;
}

/*
 *  Remove all spells.
 */

void Spellbook_object::clear_spells() {
	memset(circles, 0, sizeof(circles));
}

/*
 *  Remove a spell.
 *
 *  Output: 1 if had spell, 0 if not.
 */

int Spellbook_object::remove_spell(int spell    // 0-71
) {
	const int circle = spell / 8;
	const int num    = spell % 8;    // # within circle.
	if ((circles[circle] & (1 << num)) == 0) {
		return 0;    // Already does not have it.
	}
	circles[circle] ^= (1 << num);
	return 1;
}

/*
 *  Can we do a given spell?
 */

bool Spellbook_object::can_do_spell(Actor* act, int spell) {
	if (cheat.in_wizard_mode()) {
		return true;    // Cheating.
	}
	const int           circle = spell / 8;    // Circle spell is in.
	const unsigned char cflags = circles[circle];
	if ((cflags & (1 << (spell % 8))) == 0) {
		return false;    // We don't have that spell.
	}
	const int mana = act->get_property(Actor::mana);
	// TODO: Need to de-hard-code cost.
	const int cost  = circle + (GAME_SI ? 1 : 0);
	const int level = act->get_level();
	if ((mana < cost) || (level < circle)) {
		// Not enough mana or not yet at required level?
		return false;
	}
	if (has_ring(act)) {    // Ring of reagents (SI)?
		return true;
	}
	// Figure what we used.
	unsigned short flags = reagents[spell];
	// Go through bits.
	for (int r = 0; flags; r++, flags = flags >> 1) {
		// Need 1 of each required reagent.
		if ((flags & 1) && act->count_objects(REAGENTS, c_any_qual, r) == 0) {
			return false;    // Missing.
		}
	}
	return true;
}

/*
 *  Perform a spell.
 *
 *  Output: False if unsuccessful.
 */

bool Spellbook_object::do_spell(
		Actor* act, int spell,
		bool can_do,      // Already checked.
		bool in_combat    // Being used in combat.
) {
	if (can_do || can_do_spell(act, spell)) {
		int circle = spell / 8;    // Figure/subtract mana.
		if (cheat.in_wizard_mode()) {
			circle = 0;
		}
		const int mana = act->get_property(Actor::mana);
		// TODO: Need to de-hard-code cost.
		act->set_property(Actor::mana, mana - circle - (GAME_SI ? 1 : 0));
		// Figure what we used.
		unsigned short flags = reagents[spell];

		if (!cheat.in_wizard_mode() && !has_ring(act)) {
			// Go through bits.
			for (int r = 0; flags; r++, flags = flags >> 1) {
				// Remove 1 of each required reagent.
				if (flags & 1) {
					act->remove_quantity(1, REAGENTS, c_any_qual, r);
				}
			}
		}
		execute_spell(act, spell, in_combat);
		return true;
	}
	return false;
}

/*
 *  Perform the usecode for a spell.
 */

void Spellbook_object::execute_spell(
		Actor* act, int spell,
		bool in_combat    // Being used in combat.
) {
	act->begin_casting(859);    // TODO: Need to de-hard-code.

	// We use intercept_item for spells cast from readied spellbook
	// while in combat.
	// First, save current.
	Game_object* oldtarg;
	Tile_coord*  oldtile;
	ucmachine->save_intercept(oldtarg, oldtile);
	if (in_combat) {    // Use caster's target if for combat.
		ucmachine->intercept_click_on_item(act->get_target());
	} else {    // Otherwise, disable intercept for gump casting.
		ucmachine->intercept_click_on_item(nullptr);
	}

	ucmachine->call_usecode(
			Get_usecode(spell), act,
			in_combat ? Usecode_machine::weapon
					  : Usecode_machine::double_click);

	// Restore previous intercept_item.
	ucmachine->restore_intercept(oldtarg, oldtile);
}

/*
 *  Show book when double-clicked.
 */

void Spellbook_object::activate(int event) {
	ignore_unused_variable_warning(event);
	gumpman->add_gump(this, get_info().get_gump_shape());
}

/*
 *  Write out.
 */

void Spellbook_object::write_ireg(ODataSource* out) {
	unsigned char buf[24];    // 18-byte entry.
	uint8*        ptr = write_common_ireg(18, buf);
	memcpy(ptr, &circles[0], 5);    // Store the way U7 does it.
	ptr += 5;
	Write1(ptr, nibble_swap(get_lift()));
	memcpy(ptr, &circles[5], 4);    // Rest of spell circles.
	ptr += 4;
	Write1(ptr, 0);    // 3 unknowns.
	Write1(ptr, 0);
	Write1(ptr, 0);
	Write1(ptr, bookmark >= 0 ? bookmark : 255);
	out->write(reinterpret_cast<char*>(buf), ptr - buf);
	// Write scheduled usecode.
	Game_map::write_scheduled(out, this);
}

// Get size of IREG. Returns -1 if can't write to buffer
int Spellbook_object::get_ireg_size() {
	// These shouldn't ever happen, but you never know
	if (gumpman->find_gump(this) || Usecode_script::find(this)) {
		return -1;
	}
	return 14 + get_common_ireg_size();
}
