/**
 ** Shapeinf.cc: Info. about shapes read from various 'static' data files.
 **
 ** Written: 4/29/99 - JSF
 **/

/*
Copyright (C) 1998  Jeffrey S. Freedman
Copyright (C) 1999-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 "shapeinf.h"

#include "ammoinf.h"
#include "aniinf.h"
#include "armorinf.h"
#include "bodyinf.h"
#include "continf.h"
#include "data_utils.h"
#include "effhpinf.h"
#include "expinf.h"
#include "frflags.h"
#include "frnameinf.h"
#include "frusefun.h"
#include "ignore_unused_variable_warning.h"
#include "lightinf.h"
#include "monstinf.h"
#include "npcdollinf.h"
#include "objdollinf.h"
#include "ready.h"
#include "sfxinf.h"
#include "warminf.h"
#include "weaponinf.h"

#include <vector>

using std::cerr;
using std::endl;
using std::vector;

Shape_info::Shape_info() = default;

/*
 *  Not supported:
 */
Shape_info::Shape_info(const Shape_info& other) {
	copy(other);
}

Shape_info& Shape_info::operator=(const Shape_info& other) {
	if (this != &other) {
		copy(other);
	}
	return *this;
}

/*
 *  Clean up.
 */

Shape_info::~Shape_info() {
	delete weapon;
	delete ammo;
	delete armor;
	delete[] weapon_offsets;
	delete monstinf;
	delete npcpaperdoll;
	delete sfxinf;
	delete aniinf;
	delete explosion;
	delete body;
	clear_paperdoll_info();
	clear_effective_hp_info();
	clear_frame_name_info();
	clear_light_info();
	clear_warmth_info();
}

/*
 *  Copy.
 */

void Shape_info::copy(const Shape_info& inf2, bool skip_dolls) {
	for (int i = 0; i < 3; ++i) {
		tfa[i]  = inf2.tfa[i];
		dims[i] = inf2.dims[i];
	}
	weight        = inf2.weight;
	volume        = inf2.volume;
	shpdims[0]    = inf2.shpdims[0];
	shpdims[1]    = inf2.shpdims[1];
	ready_type    = inf2.ready_type;
	alt_ready1    = inf2.alt_ready1;
	alt_ready2    = inf2.alt_ready2;
	spell_flag    = inf2.spell_flag;
	occludes_flag = inf2.occludes_flag;
	if (!skip_dolls || gump_shape < 0) {
		gump_shape = inf2.gump_shape;
		gump_font  = inf2.gump_font;
	}
	monster_food      = inf2.monster_food;
	mountain_top      = inf2.mountain_top;
	barge_type        = inf2.barge_type;
	actor_flags       = inf2.actor_flags;
	shape_flags       = inf2.shape_flags;
	field_type        = inf2.field_type;
	modified_flags    = inf2.modified_flags;
	frompatch_flags   = inf2.frompatch_flags;
	have_static_flags = inf2.have_static_flags;
	// Allocated fields.
	delete[] weapon_offsets;
	if (inf2.weapon_offsets) {
		weapon_offsets = new unsigned char[64];
		memcpy(weapon_offsets, inf2.weapon_offsets, 64);
	} else {
		weapon_offsets = nullptr;
	}
	delete armor;
	armor = inf2.armor ? new Armor_info(*inf2.armor) : nullptr;
	delete ammo;
	ammo = inf2.ammo ? new Ammo_info(*inf2.ammo) : nullptr;
	delete weapon;
	weapon = inf2.weapon ? new Weapon_info(*inf2.weapon) : nullptr;
	delete monstinf;
	monstinf = inf2.monstinf ? new Monster_info(*inf2.monstinf) : nullptr;
	if (!skip_dolls || !npcpaperdoll) {
		delete npcpaperdoll;
		npcpaperdoll = inf2.npcpaperdoll ? new Paperdoll_npc(*inf2.npcpaperdoll)
										 : nullptr;
	}
	if (!skip_dolls || objpaperdoll.empty()) {
		copy_vector_info(inf2.objpaperdoll, objpaperdoll);
	}
	copy_vector_info(inf2.hpinf, hpinf);
	copy_vector_info(inf2.frflagsinf, frflagsinf);
	copy_vector_info(inf2.cntrules, cntrules);
	copy_vector_info(inf2.nameinf, nameinf);
	copy_vector_info(inf2.frucinf, frucinf);
	copy_vector_info(inf2.lightinf, lightinf);
	copy_vector_info(inf2.warminf, warminf);

	delete sfxinf;
	sfxinf = inf2.sfxinf ? new SFX_info(*inf2.sfxinf) : nullptr;
	delete explosion;
	explosion = inf2.explosion ? new Explosion_info(*inf2.explosion) : nullptr;
	delete aniinf;
	aniinf = inf2.aniinf ? new Animation_info(*inf2.aniinf) : nullptr;
	delete body;
	body = inf2.body ? new Body_info(*inf2.body) : nullptr;
}

/*
 *  Get (safely) a specified information set.
 */
// Get armor protection.
int Shape_info::get_armor() const {
	return armor ? armor->prot : 0;
}

// Get armor-granted immunities.
int Shape_info::get_armor_immunity() const {
	return armor ? armor->immune : 0;
}

// Get sprite of explosion.
int Shape_info::get_explosion_sprite() const {
	return explosion ? explosion->sprite : 5;
}

// Get sfx of explosion.
int Shape_info::get_explosion_sfx() const {
	return explosion ? explosion->sfxnum : -1;
}

int Shape_info::get_body_shape() const {
	return body ? body->bshape : 400;
}

int Shape_info::get_body_frame() const {
	return body ? body->bframe : 3;
}

const Weapon_info* Shape_info::get_weapon_info_safe() const {
	return weapon ? weapon : Weapon_info::get_default();
}

const Ammo_info* Shape_info::get_ammo_info_safe() const {
	return ammo ? ammo : Ammo_info::get_default();
}

const Monster_info* Shape_info::get_monster_info_safe() const {
	return monstinf ? monstinf : Monster_info::get_default();
}

const Animation_info* Shape_info::get_animation_info_safe(
		int shnum, int nframes) {
	ignore_unused_variable_warning(shnum);
	if (!aniinf) {
		aniinf = Animation_info::create_from_tfa(0, nframes);
	}
	return aniinf;
}

bool Shape_info::has_paperdoll_info() const {
	return !objpaperdoll.empty();
}

std::vector<Paperdoll_item>& Shape_info::set_paperdoll_info(bool tf) {
	return set_vector_info(tf, objpaperdoll);
}

void Shape_info::clean_invalid_paperdolls() {
	clean_vector(objpaperdoll);
}

void Shape_info::clear_paperdoll_info() {
	objpaperdoll.clear();
}

void Shape_info::add_paperdoll_info(Paperdoll_item& add) {
	add_vector_info(add, objpaperdoll);
}

bool Shape_info::has_content_rules() const {
	return !cntrules.empty();
}

std::vector<Content_rules>& Shape_info::set_content_rules(bool tf) {
	return set_vector_info(tf, cntrules);
}

void Shape_info::clean_invalid_content_rules() {
	clean_vector(cntrules);
}

void Shape_info::clear_content_rules() {
	cntrules.clear();
}

void Shape_info::add_content_rule(Content_rules& add) {
	add_vector_info(add, cntrules);
}

bool Shape_info::has_effective_hp_info() const {
	return !hpinf.empty();
}

std::vector<Effective_hp_info>& Shape_info::set_effective_hp_info(bool tf) {
	return set_vector_info(tf, hpinf);
}

void Shape_info::clean_invalid_hp_info() {
	clean_vector(hpinf);
}

void Shape_info::clear_effective_hp_info() {
	hpinf.clear();
}

void Shape_info::add_effective_hp_info(Effective_hp_info& add) {
	add_vector_info(add, hpinf);
}

bool Shape_info::has_frame_name_info() const {
	return !nameinf.empty();
}

std::vector<Frame_name_info>& Shape_info::set_frame_name_info(bool tf) {
	return set_vector_info(tf, nameinf);
}

void Shape_info::clean_invalid_name_info() {
	clean_vector(nameinf);
}

void Shape_info::clear_frame_name_info() {
	nameinf.clear();
}

void Shape_info::add_frame_name_info(Frame_name_info& add) {
	add_vector_info(add, nameinf);
}

bool Shape_info::has_frame_usecode_info() const {
	return !frucinf.empty();
}

std::vector<Frame_usecode_info>& Shape_info::set_frame_usecode_info(bool tf) {
	return set_vector_info(tf, frucinf);
}

void Shape_info::clean_invalid_usecode_info() {
	clean_vector(frucinf);
}

void Shape_info::clear_frame_usecode_info() {
	frucinf.clear();
}

void Shape_info::add_frame_usecode_info(Frame_usecode_info& add) {
	add_vector_info(add, frucinf);
}

bool Shape_info::has_frame_flags() const {
	return !frflagsinf.empty();
}

std::vector<Frame_flags_info>& Shape_info::set_frame_flags(bool tf) {
	return set_vector_info(tf, frflagsinf);
}

void Shape_info::clean_invalid_frame_flags() {
	clean_vector(frflagsinf);
}

void Shape_info::clear_frame_flags() {
	frflagsinf.clear();
}

void Shape_info::add_frame_flags(Frame_flags_info& add) {
	add_vector_info(add, frflagsinf);
}

bool Shape_info::has_light_info() const {
	return !lightinf.empty();
}

std::vector<Light_info>& Shape_info::set_light_info(bool tf) {
	return set_vector_info(tf, lightinf);
}

void Shape_info::clean_invalid_light_info() {
	clean_vector(lightinf);
}

void Shape_info::clear_light_info() {
	lightinf.clear();
}

void Shape_info::add_light_info(Light_info& add) {
	add_vector_info(add, lightinf);
}

bool Shape_info::has_warmth_info() const {
	return !warminf.empty();
}

std::vector<Warmth_info>& Shape_info::set_warmth_info(bool tf) {
	return set_vector_info(tf, warminf);
}

void Shape_info::clean_invalid_warmth_info() {
	clean_vector(warminf);
}

void Shape_info::clear_warmth_info() {
	warminf.clear();
}

void Shape_info::add_warmth_info(Warmth_info& add) {
	add_vector_info(add, warminf);
}

const Frame_name_info* Shape_info::get_frame_name(
		int frame, int quality) const {
	return Search_vector_data_double_wildcards(
			nameinf, frame, quality, &Frame_name_info::frame,
			&Frame_name_info::quality);
}

const Frame_usecode_info* Shape_info::get_frame_usecode(
		int frame, int quality) const {
	return Search_vector_data_double_wildcards(
			frucinf, frame, quality, &Frame_usecode_info::frame,
			&Frame_usecode_info::quality);
}

int Shape_info::get_effective_hps(int frame, int quality) const {
	const Effective_hp_info* inf = Search_vector_data_double_wildcards(
			hpinf, frame, quality, &Effective_hp_info::frame,
			&Effective_hp_info::quality);
	return inf ? inf->hps : 0;    // Default to indestructible.
}

int Shape_info::get_object_flags(int frame, int qual) const {
	const Frame_flags_info* inf = Search_vector_data_double_wildcards(
			frflagsinf, frame, qual, &Frame_flags_info::frame,
			&Frame_flags_info::quality);
	return inf ? inf->m_flags : 0;    // Default to no flagss.
}

const Paperdoll_item* Shape_info::get_item_paperdoll(
		int frame, int spot) const {
	if (objpaperdoll.empty()) {
		return nullptr;    // No paperdoll.
	}
	Paperdoll_item inf;
	inf.world_frame = frame;
	if (spot == both_hands) {
		spot = lhand;
	} else if (spot == lrgloves) {
		spot = lfinger;
	} else if (spot == neck) {
		spot = amulet;
	} else if (spot == scabbard) {
		spot = belt;
	}
	inf.spot = spot;
	// Try finding exact match first.
	auto it = std::lower_bound(objpaperdoll.begin(), objpaperdoll.end(), inf);
	if (it == objpaperdoll.end()) {    // Nowhere to be found.
		return nullptr;
	} else if (*it == inf && !it->is_invalid()) {    // Have it already.
		return &*it;
	}
	// Time for wildcard world frame.
	inf.world_frame = -1;
	it              = std::lower_bound(it, objpaperdoll.end(), inf);
	if (it == objpaperdoll.end() || *it != inf    // It just isn't there...
		|| it->is_invalid()) {                    // ... or it is invalid.
		return nullptr;
	} else {    // At last!
		return &*it;
	}
}

bool Shape_info::is_shape_accepted(int shape) const {
	const Content_rules* inf = Search_vector_data_single_wildcard(
			cntrules, shape, &Content_rules::shape);
#ifdef DEBUG
	if (inf && !inf->accept) {
		cerr << "Shape '" << shape << "' was REJECTED" << endl;
	}
#endif
	return inf ? inf->accept : true;    // Default to true.
}

int Shape_info::get_object_light(int frame) const {
	if (!is_light_source()) {
		// Don't bother checking if not a light source.
		return 0;
	}
	const Light_info* inf = Search_vector_data_single_wildcard(
			lightinf, (frame & 31), &Light_info::frame);
	return inf ? inf->light : 1;    // Default to candle-strength.
}

int Shape_info::get_object_warmth(int frame) const {
	const Warmth_info* inf = Search_vector_data_single_wildcard(
			warminf, (frame & 31), &Warmth_info::frame);
	return inf ? inf->warmth : 0;    // Default to no warmth.
}

/*
 *  Set 3D dimensions.
 */

void Shape_info::set_3d(
		int xt, int yt, int zt    // In tiles.
) {
	xt = (xt - 1) & 7;    // Force legal values.
	yt = (yt - 1) & 7;
	zt &= 7;
	tfa[2]  = (tfa[2] & ~63) | xt | (yt << 3);
	tfa[0]  = (tfa[0] & ~(7 << 5)) | (zt << 5);
	dims[0] = xt + 1;
	dims[1] = yt + 1;
	dims[2] = zt;
}

/*
 *  Set weapon offsets for given frame.
 */

void Shape_info::set_weapon_offset(
		int           frame,                // 0-31.
		unsigned char x, unsigned char y    // 255 means "dont' draw".
) {
	if (frame < 0 || frame > 31) {
		return;
	}
	if (x == 255 && y == 255) {
		if (weapon_offsets) {    // +++Could delete if all 255's now.
			weapon_offsets[frame * 2] = weapon_offsets[frame * 2 + 1] = 255;
		}
		return;
	}
	if (!weapon_offsets) {
		weapon_offsets = new unsigned char[64];
		std::memset(weapon_offsets, 255, 64);
	}
	weapon_offsets[frame * 2]     = x;
	weapon_offsets[frame * 2 + 1] = y;
}

/*
 *  Get rotated frame (algorithmically).
 */

int Shape_info::get_rotated_frame(
		int curframe,
		int quads    // 1=90, 2=180, 3=270.
) const {
	// Seat is a special case.
	if (barge_type == barge_seat) {
		const int dir = curframe % 4;    // Current dir (0-3).
		return (curframe - dir) + (dir + quads) % 4;
	} else if (is_barge_part()) {    // Piece of a barge?
		switch (quads) {
		case 1:
			return (curframe ^ 32) ^ ((curframe & 32) ? 3 : 1);
		case 2:
			return curframe ^ 2;
		case 3:
			return (curframe ^ 32) ^ ((curframe & 32) ? 1 : 3);
		default:
			return curframe;
		}
	} else {
		// Reflect.  Bit 32==horizontal.
		return curframe ^ ((quads % 2) << 5);
	}
}
