/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "nodedef.h"

#include "itemdef.h"
#ifndef SERVER
#include "client/tile.h"
#include "mesh.h"
#include <IMeshManipulator.h>
#endif
#include "log.h"
#include "settings.h"
#include "nameidmapping.h"
#include "util/numeric.h"
#include "util/serialize.h"
#include "exceptions.h"
#include "debug.h"
#include "gamedef.h"
#include <fstream> // Used in applyTextureOverrides()

/*
	NodeBox
*/

void NodeBox::reset()
{
	type = NODEBOX_REGULAR;
	// default is empty
	fixed.clear();
	// default is sign/ladder-like
	wall_top = aabb3f(-BS/2, BS/2-BS/16., -BS/2, BS/2, BS/2, BS/2);
	wall_bottom = aabb3f(-BS/2, -BS/2, -BS/2, BS/2, -BS/2+BS/16., BS/2);
	wall_side = aabb3f(-BS/2, -BS/2, -BS/2, -BS/2+BS/16., BS/2, BS/2);
}

void NodeBox::serialize(std::ostream &os, u16 protocol_version) const
{
	int version = protocol_version >= 21 ? 2 : 1;
	writeU8(os, version);

	if (version == 1 && type == NODEBOX_LEVELED)
		writeU8(os, NODEBOX_FIXED);
	else
		writeU8(os, type);

	if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED)
	{
		writeU16(os, fixed.size());
		for(std::vector<aabb3f>::const_iterator
				i = fixed.begin();
				i != fixed.end(); ++i)
		{
			writeV3F1000(os, i->MinEdge);
			writeV3F1000(os, i->MaxEdge);
		}
	}
	else if(type == NODEBOX_WALLMOUNTED)
	{
		writeV3F1000(os, wall_top.MinEdge);
		writeV3F1000(os, wall_top.MaxEdge);
		writeV3F1000(os, wall_bottom.MinEdge);
		writeV3F1000(os, wall_bottom.MaxEdge);
		writeV3F1000(os, wall_side.MinEdge);
		writeV3F1000(os, wall_side.MaxEdge);
	}
}

void NodeBox::deSerialize(std::istream &is)
{
	int version = readU8(is);
	if(version < 1 || version > 2)
		throw SerializationError("unsupported NodeBox version");

	reset();

	type = (enum NodeBoxType)readU8(is);

	if(type == NODEBOX_FIXED || type == NODEBOX_LEVELED)
	{
		u16 fixed_count = readU16(is);
		while(fixed_count--)
		{
			aabb3f box;
			box.MinEdge = readV3F1000(is);
			box.MaxEdge = readV3F1000(is);
			fixed.push_back(box);
		}
	}
	else if(type == NODEBOX_WALLMOUNTED)
	{
		wall_top.MinEdge = readV3F1000(is);
		wall_top.MaxEdge = readV3F1000(is);
		wall_bottom.MinEdge = readV3F1000(is);
		wall_bottom.MaxEdge = readV3F1000(is);
		wall_side.MinEdge = readV3F1000(is);
		wall_side.MaxEdge = readV3F1000(is);
	}
}

/*
	TileDef
*/

void TileDef::serialize(std::ostream &os, u16 protocol_version) const
{
	if (protocol_version >= 26)
		writeU8(os, 2);
	else if (protocol_version >= 17)
		writeU8(os, 1);
	else
		writeU8(os, 0);
	os<<serializeString(name);
	writeU8(os, animation.type);
	writeU16(os, animation.aspect_w);
	writeU16(os, animation.aspect_h);
	writeF1000(os, animation.length);
	if (protocol_version >= 17)
		writeU8(os, backface_culling);
	if (protocol_version >= 26) {
		writeU8(os, tileable_horizontal);
		writeU8(os, tileable_vertical);
	}
}

void TileDef::deSerialize(std::istream &is, bool culling_ignore)
{
	int version = readU8(is);
	name = deSerializeString(is);
	animation.type = (TileAnimationType)readU8(is);
	animation.aspect_w = readU16(is);
	animation.aspect_h = readU16(is);
	animation.length = readF1000(is);
	if (version >= 1)
		backface_culling = readU8(is);
	if (version >= 2) {
		tileable_horizontal = readU8(is);
		tileable_vertical = readU8(is);
	}
	// when connecting to old servers - do not use
	// provided values here since culling needs to be
	// disabled by default for these drawtypes
	if (culling_ignore)
		backface_culling = false;
}


/*
	SimpleSoundSpec serialization
*/

static void serializeSimpleSoundSpec(const SimpleSoundSpec &ss,
		std::ostream &os)
{
	os<<serializeString(ss.name);
	writeF1000(os, ss.gain);
}
static void deSerializeSimpleSoundSpec(SimpleSoundSpec &ss, std::istream &is)
{
	ss.name = deSerializeString(is);
	ss.gain = readF1000(is);
}

/*
	ContentFeatures
*/

ContentFeatures::ContentFeatures()
{
	reset();
}

ContentFeatures::~ContentFeatures()
{
}

void ContentFeatures::reset()
{
	/*
		Cached stuff
	*/
#ifndef SERVER
	solidness = 2;
	visual_solidness = 0;
	backface_culling = true;

#endif
	has_on_construct = false;
	has_on_destruct = false;
	has_after_destruct = false;
	/*
		Actual data

		NOTE: Most of this is always overridden by the default values given
		      in builtin.lua
	*/
	name = "";
	groups.clear();
	// Unknown nodes can be dug
	groups["dig_immediate"] = 2;
	drawtype = NDT_NORMAL;
	mesh = "";
#ifndef SERVER
	for(u32 i = 0; i < 24; i++)
		mesh_ptr[i] = NULL;
	minimap_color = video::SColor(0, 0, 0, 0);
#endif
	visual_scale = 1.0;
	for(u32 i = 0; i < 6; i++)
		tiledef[i] = TileDef();
	for(u16 j = 0; j < CF_SPECIAL_COUNT; j++)
		tiledef_special[j] = TileDef();
	alpha = 255;
	post_effect_color = video::SColor(0, 0, 0, 0);
	param_type = CPT_NONE;
	param_type_2 = CPT2_NONE;
	is_ground_content = false;
	light_propagates = false;
	sunlight_propagates = false;
	walkable = true;
	pointable = true;
	diggable = true;
	climbable = false;
	buildable_to = false;
	floodable = false;
	rightclickable = true;
	leveled = 0;
	liquid_type = LIQUID_NONE;
	liquid_alternative_flowing = "";
	liquid_alternative_source = "";
	liquid_viscosity = 0;
	liquid_renewable = true;
	liquid_range = LIQUID_LEVEL_MAX+1;
	drowning = 0;
	light_source = 0;
	damage_per_second = 0;
	node_box = NodeBox();
	selection_box = NodeBox();
	collision_box = NodeBox();
	waving = 0;
	legacy_facedir_simple = false;
	legacy_wallmounted = false;
	sound_footstep = SimpleSoundSpec();
	sound_dig = SimpleSoundSpec("__group");
	sound_dug = SimpleSoundSpec();
}

void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
{
	if(protocol_version < 24){
		serializeOld(os, protocol_version);
		return;
	}

	writeU8(os, 7); // version
	os<<serializeString(name);
	writeU16(os, groups.size());
	for(ItemGroupList::const_iterator
			i = groups.begin(); i != groups.end(); ++i){
		os<<serializeString(i->first);
		writeS16(os, i->second);
	}
	writeU8(os, drawtype);
	writeF1000(os, visual_scale);
	writeU8(os, 6);
	for(u32 i = 0; i < 6; i++)
		tiledef[i].serialize(os, protocol_version);
	writeU8(os, CF_SPECIAL_COUNT);
	for(u32 i = 0; i < CF_SPECIAL_COUNT; i++){
		tiledef_special[i].serialize(os, protocol_version);
	}
	writeU8(os, alpha);
	writeU8(os, post_effect_color.getAlpha());
	writeU8(os, post_effect_color.getRed());
	writeU8(os, post_effect_color.getGreen());
	writeU8(os, post_effect_color.getBlue());
	writeU8(os, param_type);
	writeU8(os, param_type_2);
	writeU8(os, is_ground_content);
	writeU8(os, light_propagates);
	writeU8(os, sunlight_propagates);
	writeU8(os, walkable);
	writeU8(os, pointable);
	writeU8(os, diggable);
	writeU8(os, climbable);
	writeU8(os, buildable_to);
	os<<serializeString(""); // legacy: used to be metadata_name
	writeU8(os, liquid_type);
	os<<serializeString(liquid_alternative_flowing);
	os<<serializeString(liquid_alternative_source);
	writeU8(os, liquid_viscosity);
	writeU8(os, liquid_renewable);
	writeU8(os, light_source);
	writeU32(os, damage_per_second);
	node_box.serialize(os, protocol_version);
	selection_box.serialize(os, protocol_version);
	writeU8(os, legacy_facedir_simple);
	writeU8(os, legacy_wallmounted);
	serializeSimpleSoundSpec(sound_footstep, os);
	serializeSimpleSoundSpec(sound_dig, os);
	serializeSimpleSoundSpec(sound_dug, os);
	writeU8(os, rightclickable);
	writeU8(os, drowning);
	writeU8(os, leveled);
	writeU8(os, liquid_range);
	writeU8(os, waving);
	// Stuff below should be moved to correct place in a version that otherwise changes
	// the protocol version
	os<<serializeString(mesh);
	collision_box.serialize(os, protocol_version);
	writeU8(os, floodable);
}

void ContentFeatures::deSerialize(std::istream &is)
{
	int version = readU8(is);
	if(version != 7){
		deSerializeOld(is, version);
		return;
	}

	name = deSerializeString(is);
	groups.clear();
	u32 groups_size = readU16(is);
	for(u32 i = 0; i < groups_size; i++){
		std::string name = deSerializeString(is);
		int value = readS16(is);
		groups[name] = value;
	}
	drawtype = (enum NodeDrawType)readU8(is);

	bool ignore_culling = ((version <= 26) &&
			((drawtype == NDT_MESH) ||
			 (drawtype == NDT_PLANTLIKE) ||
			 (drawtype == NDT_FIRELIKE) ||
			 (drawtype == NDT_LIQUID)));

	visual_scale = readF1000(is);
	if(readU8(is) != 6)
		throw SerializationError("unsupported tile count");
	for(u32 i = 0; i < 6; i++)
		tiledef[i].deSerialize(is, ignore_culling);
	if(readU8(is) != CF_SPECIAL_COUNT)
		throw SerializationError("unsupported CF_SPECIAL_COUNT");
	for(u32 i = 0; i < CF_SPECIAL_COUNT; i++)
		tiledef_special[i].deSerialize(is, ignore_culling);
	alpha = readU8(is);
	post_effect_color.setAlpha(readU8(is));
	post_effect_color.setRed(readU8(is));
	post_effect_color.setGreen(readU8(is));
	post_effect_color.setBlue(readU8(is));
	param_type = (enum ContentParamType)readU8(is);
	param_type_2 = (enum ContentParamType2)readU8(is);
	is_ground_content = readU8(is);
	light_propagates = readU8(is);
	sunlight_propagates = readU8(is);
	walkable = readU8(is);
	pointable = readU8(is);
	diggable = readU8(is);
	climbable = readU8(is);
	buildable_to = readU8(is);
	deSerializeString(is); // legacy: used to be metadata_name
	liquid_type = (enum LiquidType)readU8(is);
	liquid_alternative_flowing = deSerializeString(is);
	liquid_alternative_source = deSerializeString(is);
	liquid_viscosity = readU8(is);
	liquid_renewable = readU8(is);
	light_source = readU8(is);
	damage_per_second = readU32(is);
	node_box.deSerialize(is);
	selection_box.deSerialize(is);
	legacy_facedir_simple = readU8(is);
	legacy_wallmounted = readU8(is);
	deSerializeSimpleSoundSpec(sound_footstep, is);
	deSerializeSimpleSoundSpec(sound_dig, is);
	deSerializeSimpleSoundSpec(sound_dug, is);
	rightclickable = readU8(is);
	drowning = readU8(is);
	leveled = readU8(is);
	liquid_range = readU8(is);
	waving = readU8(is);
	// If you add anything here, insert it primarily inside the try-catch
	// block to not need to increase the version.
	try{
		// Stuff below should be moved to correct place in a version that
		// otherwise changes the protocol version
	mesh = deSerializeString(is);
	collision_box.deSerialize(is);
	floodable = readU8(is);
	}catch(SerializationError &e) {};
}

/*
	CNodeDefManager
*/

class CNodeDefManager: public IWritableNodeDefManager {
public:
	CNodeDefManager();
	virtual ~CNodeDefManager();
	void clear();
	virtual IWritableNodeDefManager *clone();
	inline virtual const ContentFeatures& get(content_t c) const;
	inline virtual const ContentFeatures& get(const MapNode &n) const;
	virtual bool getId(const std::string &name, content_t &result) const;
	virtual content_t getId(const std::string &name) const;
	virtual void getIds(const std::string &name, std::set<content_t> &result) const;
	virtual const ContentFeatures& get(const std::string &name) const;
	content_t allocateId();
	virtual content_t set(const std::string &name, const ContentFeatures &def);
	virtual content_t allocateDummy(const std::string &name);
	virtual void updateAliases(IItemDefManager *idef);
	virtual void applyTextureOverrides(const std::string &override_filepath);
	virtual void updateTextures(IGameDef *gamedef,
		void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress),
		void *progress_cbk_args);
	void serialize(std::ostream &os, u16 protocol_version) const;
	void deSerialize(std::istream &is);

	inline virtual bool getNodeRegistrationStatus() const;
	inline virtual void setNodeRegistrationStatus(bool completed);

	virtual void pendNodeResolve(NodeResolver *nr);
	virtual bool cancelNodeResolveCallback(NodeResolver *nr);
	virtual void runNodeResolveCallbacks();
	virtual void resetNodeResolveState();

private:
	void addNameIdMapping(content_t i, std::string name);
#ifndef SERVER
	void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef,
		u32 shader_id, bool use_normal_texture, bool backface_culling,
		u8 alpha, u8 material_type);
#endif

	// Features indexed by id
	std::vector<ContentFeatures> m_content_features;

	// A mapping for fast converting back and forth between names and ids
	NameIdMapping m_name_id_mapping;

	// Like m_name_id_mapping, but only from names to ids, and includes
	// item aliases too. Updated by updateAliases()
	// Note: Not serialized.

	std::map<std::string, content_t> m_name_id_mapping_with_aliases;

	// A mapping from groups to a list of content_ts (and their levels)
	// that belong to it.  Necessary for a direct lookup in getIds().
	// Note: Not serialized.
	std::map<std::string, GroupItems> m_group_to_items;

	// Next possibly free id
	content_t m_next_id;

	// NodeResolvers to callback once node registration has ended
	std::vector<NodeResolver *> m_pending_resolve_callbacks;

	// True when all nodes have been registered
	bool m_node_registration_complete;
};


CNodeDefManager::CNodeDefManager()
{
	clear();
}


CNodeDefManager::~CNodeDefManager()
{
#ifndef SERVER
	for (u32 i = 0; i < m_content_features.size(); i++) {
		ContentFeatures *f = &m_content_features[i];
		for (u32 j = 0; j < 24; j++) {
			if (f->mesh_ptr[j])
				f->mesh_ptr[j]->drop();
		}
	}
#endif
}


void CNodeDefManager::clear()
{
	m_content_features.clear();
	m_name_id_mapping.clear();
	m_name_id_mapping_with_aliases.clear();
	m_group_to_items.clear();
	m_next_id = 0;

	resetNodeResolveState();

	u32 initial_length = 0;
	initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1);
	initial_length = MYMAX(initial_length, CONTENT_AIR + 1);
	initial_length = MYMAX(initial_length, CONTENT_IGNORE + 1);
	m_content_features.resize(initial_length);

	// Set CONTENT_UNKNOWN
	{
		ContentFeatures f;
		f.name = "unknown";
		// Insert directly into containers
		content_t c = CONTENT_UNKNOWN;
		m_content_features[c] = f;
		addNameIdMapping(c, f.name);
	}

	// Set CONTENT_AIR
	{
		ContentFeatures f;
		f.name                = "air";
		f.drawtype            = NDT_AIRLIKE;
		f.param_type          = CPT_LIGHT;
		f.light_propagates    = true;
		f.sunlight_propagates = true;
		f.walkable            = false;
		f.pointable           = false;
		f.diggable            = false;
		f.buildable_to        = true;
		f.floodable           = true;
		f.is_ground_content   = true;
		// Insert directly into containers
		content_t c = CONTENT_AIR;
		m_content_features[c] = f;
		addNameIdMapping(c, f.name);
	}

	// Set CONTENT_IGNORE
	{
		ContentFeatures f;
		f.name                = "ignore";
		f.drawtype            = NDT_AIRLIKE;
		f.param_type          = CPT_NONE;
		f.light_propagates    = false;
		f.sunlight_propagates = false;
		f.walkable            = false;
		f.pointable           = false;
		f.diggable            = false;
		f.buildable_to        = true; // A way to remove accidental CONTENT_IGNOREs
		f.is_ground_content   = true;
		// Insert directly into containers
		content_t c = CONTENT_IGNORE;
		m_content_features[c] = f;
		addNameIdMapping(c, f.name);
	}
}


IWritableNodeDefManager *CNodeDefManager::clone()
{
	CNodeDefManager *mgr = new CNodeDefManager();
	*mgr = *this;
	return mgr;
}


inline const ContentFeatures& CNodeDefManager::get(content_t c) const
{
	return c < m_content_features.size()
			? m_content_features[c] : m_content_features[CONTENT_UNKNOWN];
}


inline const ContentFeatures& CNodeDefManager::get(const MapNode &n) const
{
	return get(n.getContent());
}


bool CNodeDefManager::getId(const std::string &name, content_t &result) const
{
	std::map<std::string, content_t>::const_iterator
		i = m_name_id_mapping_with_aliases.find(name);
	if(i == m_name_id_mapping_with_aliases.end())
		return false;
	result = i->second;
	return true;
}


content_t CNodeDefManager::getId(const std::string &name) const
{
	content_t id = CONTENT_IGNORE;
	getId(name, id);
	return id;
}


void CNodeDefManager::getIds(const std::string &name,
		std::set<content_t> &result) const
{
	//TimeTaker t("getIds", NULL, PRECISION_MICRO);
	if (name.substr(0,6) != "group:") {
		content_t id = CONTENT_IGNORE;
		if(getId(name, id))
			result.insert(id);
		return;
	}
	std::string group = name.substr(6);

	std::map<std::string, GroupItems>::const_iterator
		i = m_group_to_items.find(group);
	if (i == m_group_to_items.end())
		return;

	const GroupItems &items = i->second;
	for (GroupItems::const_iterator j = items.begin();
		j != items.end(); ++j) {
		if ((*j).second != 0)
			result.insert((*j).first);
	}
	//printf("getIds: %dus\n", t.stop());
}


const ContentFeatures& CNodeDefManager::get(const std::string &name) const
{
	content_t id = CONTENT_UNKNOWN;
	getId(name, id);
	return get(id);
}


// returns CONTENT_IGNORE if no free ID found
content_t CNodeDefManager::allocateId()
{
	for (content_t id = m_next_id;
			id >= m_next_id; // overflow?
			++id) {
		while (id >= m_content_features.size()) {
			m_content_features.push_back(ContentFeatures());
		}
		const ContentFeatures &f = m_content_features[id];
		if (f.name == "") {
			m_next_id = id + 1;
			return id;
		}
	}
	// If we arrive here, an overflow occurred in id.
	// That means no ID was found
	return CONTENT_IGNORE;
}


// IWritableNodeDefManager
content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &def)
{
	// Pre-conditions
	assert(name != "");
	assert(name == def.name);

	// Don't allow redefining ignore (but allow air and unknown)
	if (name == "ignore") {
		warningstream << "NodeDefManager: Ignoring "
			"CONTENT_IGNORE redefinition"<<std::endl;
		return CONTENT_IGNORE;
	}

	content_t id = CONTENT_IGNORE;
	if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
		// Get new id
		id = allocateId();
		if (id == CONTENT_IGNORE) {
			warningstream << "NodeDefManager: Absolute "
				"limit reached" << std::endl;
			return CONTENT_IGNORE;
		}
		assert(id != CONTENT_IGNORE);
		addNameIdMapping(id, name);
	}
	m_content_features[id] = def;
	verbosestream << "NodeDefManager: registering content id \"" << id
		<< "\": name=\"" << def.name << "\""<<std::endl;

	// Add this content to the list of all groups it belongs to
	// FIXME: This should remove a node from groups it no longer
	// belongs to when a node is re-registered
	for (ItemGroupList::const_iterator i = def.groups.begin();
		i != def.groups.end(); ++i) {
		std::string group_name = i->first;

		std::map<std::string, GroupItems>::iterator
			j = m_group_to_items.find(group_name);
		if (j == m_group_to_items.end()) {
			m_group_to_items[group_name].push_back(
				std::make_pair(id, i->second));
		} else {
			GroupItems &items = j->second;
			items.push_back(std::make_pair(id, i->second));
		}
	}
	return id;
}


content_t CNodeDefManager::allocateDummy(const std::string &name)
{
	assert(name != "");	// Pre-condition
	ContentFeatures f;
	f.name = name;
	return set(name, f);
}


void CNodeDefManager::updateAliases(IItemDefManager *idef)
{
	std::set<std::string> all = idef->getAll();
	m_name_id_mapping_with_aliases.clear();
	for (std::set<std::string>::iterator
			i = all.begin(); i != all.end(); ++i) {
		std::string name = *i;
		std::string convert_to = idef->getAlias(name);
		content_t id;
		if (m_name_id_mapping.getId(convert_to, id)) {
			m_name_id_mapping_with_aliases.insert(
				std::make_pair(name, id));
		}
	}
}

void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath)
{
	infostream << "CNodeDefManager::applyTextureOverrides(): Applying "
		"overrides to textures from " << override_filepath << std::endl;

	std::ifstream infile(override_filepath.c_str());
	std::string line;
	int line_c = 0;
	while (std::getline(infile, line)) {
		line_c++;
		if (trim(line) == "")
			continue;
		std::vector<std::string> splitted = str_split(line, ' ');
		if (splitted.size() != 3) {
			errorstream << override_filepath
				<< ":" << line_c << " Could not apply texture override \""
				<< line << "\": Syntax error" << std::endl;
			continue;
		}

		content_t id;
		if (!getId(splitted[0], id)) {
			errorstream << override_filepath
				<< ":" << line_c << " Could not apply texture override \""
				<< line << "\": Unknown node \""
				<< splitted[0] << "\"" << std::endl;
			continue;
		}

		ContentFeatures &nodedef = m_content_features[id];

		if (splitted[1] == "top")
			nodedef.tiledef[0].name = splitted[2];
		else if (splitted[1] == "bottom")
			nodedef.tiledef[1].name = splitted[2];
		else if (splitted[1] == "right")
			nodedef.tiledef[2].name = splitted[2];
		else if (splitted[1] == "left")
			nodedef.tiledef[3].name = splitted[2];
		else if (splitted[1] == "back")
			nodedef.tiledef[4].name = splitted[2];
		else if (splitted[1] == "front")
			nodedef.tiledef[5].name = splitted[2];
		else if (splitted[1] == "all" || splitted[1] == "*")
			for (int i = 0; i < 6; i++)
				nodedef.tiledef[i].name = splitted[2];
		else if (splitted[1] == "sides")
			for (int i = 2; i < 6; i++)
				nodedef.tiledef[i].name = splitted[2];
		else {
			errorstream << override_filepath
				<< ":" << line_c << " Could not apply texture override \""
				<< line << "\": Unknown node side \""
				<< splitted[1] << "\"" << std::endl;
			continue;
		}
	}
}

void CNodeDefManager::updateTextures(IGameDef *gamedef,
	void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress),
	void *progress_callback_args)
{
#ifndef SERVER
	infostream << "CNodeDefManager::updateTextures(): Updating "
		"textures in node definitions" << std::endl;
	ITextureSource *tsrc = gamedef->tsrc();
	IShaderSource *shdsrc = gamedef->getShaderSource();
	scene::ISceneManager* smgr = gamedef->getSceneManager();
	scene::IMeshManipulator* meshmanip = smgr->getMeshManipulator();

	bool new_style_water           = g_settings->getBool("new_style_water");
	bool connected_glass           = g_settings->getBool("connected_glass");
	bool opaque_water              = g_settings->getBool("opaque_water");
	bool enable_shaders            = g_settings->getBool("enable_shaders");
	bool enable_bumpmapping        = g_settings->getBool("enable_bumpmapping");
	bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");
	bool enable_mesh_cache         = g_settings->getBool("enable_mesh_cache");
	bool enable_minimap            = g_settings->getBool("enable_minimap");
	std::string leaves_style       = g_settings->get("leaves_style");

	bool use_normal_texture = enable_shaders &&
		(enable_bumpmapping || enable_parallax_occlusion);

	u32 size = m_content_features.size();

	for (u32 i = 0; i < size; i++) {
		ContentFeatures *f = &m_content_features[i];

		// minimap pixel color - the average color of a texture
		if (enable_minimap && f->tiledef[0].name != "")
			f->minimap_color = tsrc->getTextureAverageColor(f->tiledef[0].name);

		// Figure out the actual tiles to use
		TileDef tiledef[6];
		for (u32 j = 0; j < 6; j++) {
			tiledef[j] = f->tiledef[j];
			if (tiledef[j].name == "")
				tiledef[j].name = "unknown_node.png";
		}

		bool is_liquid = false;
		bool is_water_surface = false;

		u8 material_type = (f->alpha == 255) ?
			TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA;

		switch (f->drawtype) {
		default:
		case NDT_NORMAL:
			f->solidness = 2;
			break;
		case NDT_AIRLIKE:
			f->solidness = 0;
			break;
		case NDT_LIQUID:
			assert(f->liquid_type == LIQUID_SOURCE);
			if (opaque_water)
				f->alpha = 255;
			f->solidness = new_style_water ? 0 : 1;
			is_liquid = true;
			break;
		case NDT_FLOWINGLIQUID:
			assert(f->liquid_type == LIQUID_FLOWING);
			f->solidness = 0;
			if (opaque_water)
				f->alpha = 255;
			is_liquid = true;
			break;
		case NDT_GLASSLIKE:
			f->solidness = 0;
			f->visual_solidness = 1;
			break;
		case NDT_GLASSLIKE_FRAMED:
			f->solidness = 0;
			f->visual_solidness = 1;
			break;
		case NDT_GLASSLIKE_FRAMED_OPTIONAL:
			f->solidness = 0;
			f->visual_solidness = 1;
			f->drawtype = connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE;
			break;
		case NDT_ALLFACES:
			f->solidness = 0;
			f->visual_solidness = 1;
			break;
		case NDT_ALLFACES_OPTIONAL:
			if (leaves_style == "fancy") {
				f->drawtype = NDT_ALLFACES;
				f->solidness = 0;
				f->visual_solidness = 1;
			} else if (leaves_style == "simple") {
				for (u32 j = 0; j < 6; j++) {
					if (f->tiledef_special[j].name != "")
						tiledef[j].name = f->tiledef_special[j].name;
				}
				f->drawtype = NDT_GLASSLIKE;
				f->solidness = 0;
				f->visual_solidness = 1;
			} else {
				f->drawtype = NDT_NORMAL;
				f->solidness = 2;
				for (u32 i = 0; i < 6; i++)
					tiledef[i].name += std::string("^[noalpha");
			}
			if (f->waving == 1)
				material_type = TILE_MATERIAL_WAVING_LEAVES;
			break;
		case NDT_PLANTLIKE:
			f->solidness = 0;
			if (f->waving == 1)
				material_type = TILE_MATERIAL_WAVING_PLANTS;
			break;
		case NDT_FIRELIKE:
			f->solidness = 0;
			break;
		case NDT_MESH:
			f->solidness = 0;
			break;
		case NDT_TORCHLIKE:
		case NDT_SIGNLIKE:
		case NDT_FENCELIKE:
		case NDT_RAILLIKE:
		case NDT_NODEBOX:
			f->solidness = 0;
			break;
		}

		if (is_liquid) {
			material_type = (f->alpha == 255) ?
				TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT;
			if (f->name == "default:water_source")
				is_water_surface = true;
		}

		u32 tile_shader[6];
		for (u16 j = 0; j < 6; j++) {
			tile_shader[j] = shdsrc->getShader("nodes_shader",
				material_type, f->drawtype);
		}

		if (is_water_surface) {
			tile_shader[0] = shdsrc->getShader("water_surface_shader",
				material_type, f->drawtype);
		}

		// Tiles (fill in f->tiles[])
		for (u16 j = 0; j < 6; j++) {
			fillTileAttribs(tsrc, &f->tiles[j], &tiledef[j], tile_shader[j],
				use_normal_texture, f->tiledef[j].backface_culling, f->alpha, material_type);
		}

		// Special tiles (fill in f->special_tiles[])
		for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
			fillTileAttribs(tsrc, &f->special_tiles[j], &f->tiledef_special[j],
				tile_shader[j], use_normal_texture,
				f->tiledef_special[j].backface_culling, f->alpha, material_type);
		}

		if ((f->drawtype == NDT_MESH) && (f->mesh != "")) {
			// Meshnode drawtype
			// Read the mesh and apply scale
			f->mesh_ptr[0] = gamedef->getMesh(f->mesh);
			if (f->mesh_ptr[0]){
				v3f scale = v3f(1.0, 1.0, 1.0) * BS * f->visual_scale;
				scaleMesh(f->mesh_ptr[0], scale);
				recalculateBoundingBox(f->mesh_ptr[0]);
				meshmanip->recalculateNormals(f->mesh_ptr[0], true, false);
			}
		} else if ((f->drawtype == NDT_NODEBOX) &&
				((f->node_box.type == NODEBOX_REGULAR) ||
				(f->node_box.type == NODEBOX_FIXED)) &&
				(!f->node_box.fixed.empty())) {
			//Convert regular nodebox nodes to meshnodes
			//Change the drawtype and apply scale
			f->drawtype = NDT_MESH;
			f->mesh_ptr[0] = convertNodeboxesToMesh(f->node_box.fixed);
			v3f scale = v3f(1.0, 1.0, 1.0) * f->visual_scale;
			scaleMesh(f->mesh_ptr[0], scale);
			recalculateBoundingBox(f->mesh_ptr[0]);
			meshmanip->recalculateNormals(f->mesh_ptr[0], true, false);
		}

		//Cache 6dfacedir and wallmounted rotated clones of meshes
		if (enable_mesh_cache && f->mesh_ptr[0] && (f->param_type_2 == CPT2_FACEDIR)) {
			for (u16 j = 1; j < 24; j++) {
				f->mesh_ptr[j] = cloneMesh(f->mesh_ptr[0]);
				rotateMeshBy6dFacedir(f->mesh_ptr[j], j);
				recalculateBoundingBox(f->mesh_ptr[j]);
				meshmanip->recalculateNormals(f->mesh_ptr[j], true, false);
			}
		} else if (enable_mesh_cache && f->mesh_ptr[0] && (f->param_type_2 == CPT2_WALLMOUNTED)) {
			static const u8 wm_to_6d[6] = {20, 0, 16+1, 12+3, 8, 4+2};
			for (u16 j = 1; j < 6; j++) {
				f->mesh_ptr[j] = cloneMesh(f->mesh_ptr[0]);
				rotateMeshBy6dFacedir(f->mesh_ptr[j], wm_to_6d[j]);
				recalculateBoundingBox(f->mesh_ptr[j]);
				meshmanip->recalculateNormals(f->mesh_ptr[j], true, false);
			}
			rotateMeshBy6dFacedir(f->mesh_ptr[0], wm_to_6d[0]);
			recalculateBoundingBox(f->mesh_ptr[0]);
			meshmanip->recalculateNormals(f->mesh_ptr[0], true, false);
		}

		progress_callback(progress_callback_args, i, size);
	}
#endif
}


#ifndef SERVER
void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile,
		TileDef *tiledef, u32 shader_id, bool use_normal_texture,
		bool backface_culling, u8 alpha, u8 material_type)
{
	tile->shader_id     = shader_id;
	tile->texture       = tsrc->getTextureForMesh(tiledef->name, &tile->texture_id);
	tile->alpha         = alpha;
	tile->material_type = material_type;

	// Normal texture and shader flags texture
	if (use_normal_texture) {
		tile->normal_texture = tsrc->getNormalTexture(tiledef->name);
	}
	tile->flags_texture = tsrc->getShaderFlagsTexture(tile->normal_texture ? true : false);

	// Material flags
	tile->material_flags = 0;
	if (backface_culling)
		tile->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
	if (tiledef->animation.type == TAT_VERTICAL_FRAMES)
		tile->material_flags |= MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
	if (tiledef->tileable_horizontal)
		tile->material_flags |= MATERIAL_FLAG_TILEABLE_HORIZONTAL;
	if (tiledef->tileable_vertical)
		tile->material_flags |= MATERIAL_FLAG_TILEABLE_VERTICAL;

	// Animation parameters
	int frame_count = 1;
	if (tile->material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) {
		// Get texture size to determine frame count by aspect ratio
		v2u32 size = tile->texture->getOriginalSize();
		int frame_height = (float)size.X /
				(float)tiledef->animation.aspect_w *
				(float)tiledef->animation.aspect_h;
		frame_count = size.Y / frame_height;
		int frame_length_ms = 1000.0 * tiledef->animation.length / frame_count;
		tile->animation_frame_count = frame_count;
		tile->animation_frame_length_ms = frame_length_ms;
	}

	if (frame_count == 1) {
		tile->material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
	} else {
		std::ostringstream os(std::ios::binary);
		tile->frames.resize(frame_count);

		for (int i = 0; i < frame_count; i++) {

			FrameSpec frame;

			os.str("");
			os << tiledef->name << "^[verticalframe:"
				<< frame_count << ":" << i;

			frame.texture = tsrc->getTextureForMesh(os.str(), &frame.texture_id);
			if (tile->normal_texture)
				frame.normal_texture = tsrc->getNormalTexture(os.str());
			frame.flags_texture = tile->flags_texture;
			tile->frames[i] = frame;
		}
	}
}
#endif


void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version) const
{
	writeU8(os, 1); // version
	u16 count = 0;
	std::ostringstream os2(std::ios::binary);
	for (u32 i = 0; i < m_content_features.size(); i++) {
		if (i == CONTENT_IGNORE || i == CONTENT_AIR
				|| i == CONTENT_UNKNOWN)
			continue;
		const ContentFeatures *f = &m_content_features[i];
		if (f->name == "")
			continue;
		writeU16(os2, i);
		// Wrap it in a string to allow different lengths without
		// strict version incompatibilities
		std::ostringstream wrapper_os(std::ios::binary);
		f->serialize(wrapper_os, protocol_version);
		os2<<serializeString(wrapper_os.str());

		// must not overflow
		u16 next = count + 1;
		FATAL_ERROR_IF(next < count, "Overflow");
		count++;
	}
	writeU16(os, count);
	os << serializeLongString(os2.str());
}


void CNodeDefManager::deSerialize(std::istream &is)
{
	clear();
	int version = readU8(is);
	if (version != 1)
		throw SerializationError("unsupported NodeDefinitionManager version");
	u16 count = readU16(is);
	std::istringstream is2(deSerializeLongString(is), std::ios::binary);
	ContentFeatures f;
	for (u16 n = 0; n < count; n++) {
		u16 i = readU16(is2);

		// Read it from the string wrapper
		std::string wrapper = deSerializeString(is2);
		std::istringstream wrapper_is(wrapper, std::ios::binary);
		f.deSerialize(wrapper_is);

		// Check error conditions
		if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) {
			warningstream << "NodeDefManager::deSerialize(): "
				"not changing builtin node " << i << std::endl;
			continue;
		}
		if (f.name == "") {
			warningstream << "NodeDefManager::deSerialize(): "
				"received empty name" << std::endl;
			continue;
		}

		// Ignore aliases
		u16 existing_id;
		if (m_name_id_mapping.getId(f.name, existing_id) && i != existing_id) {
			warningstream << "NodeDefManager::deSerialize(): "
				"already defined with different ID: " << f.name << std::endl;
			continue;
		}

		// All is ok, add node definition with the requested ID
		if (i >= m_content_features.size())
			m_content_features.resize((u32)(i) + 1);
		m_content_features[i] = f;
		addNameIdMapping(i, f.name);
		verbosestream << "deserialized " << f.name << std::endl;
	}
}


void CNodeDefManager::addNameIdMapping(content_t i, std::string name)
{
	m_name_id_mapping.set(i, name);
	m_name_id_mapping_with_aliases.insert(std::make_pair(name, i));
}


IWritableNodeDefManager *createNodeDefManager()
{
	return new CNodeDefManager();
}


//// Serialization of old ContentFeatures formats
void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version) const
{
	if (protocol_version == 13)
	{
		writeU8(os, 5); // version
		os<<serializeString(name);
		writeU16(os, groups.size());
		for (ItemGroupList::const_iterator
				i = groups.begin(); i != groups.end(); ++i) {
			os<<serializeString(i->first);
			writeS16(os, i->second);
		}
		writeU8(os, drawtype);
		writeF1000(os, visual_scale);
		writeU8(os, 6);
		for (u32 i = 0; i < 6; i++)
			tiledef[i].serialize(os, protocol_version);
		//CF_SPECIAL_COUNT = 2 before cf ver. 7 and protocol ver. 24
		writeU8(os, 2);
		for (u32 i = 0; i < 2; i++)
			tiledef_special[i].serialize(os, protocol_version);
		writeU8(os, alpha);
		writeU8(os, post_effect_color.getAlpha());
		writeU8(os, post_effect_color.getRed());
		writeU8(os, post_effect_color.getGreen());
		writeU8(os, post_effect_color.getBlue());
		writeU8(os, param_type);
		writeU8(os, param_type_2);
		writeU8(os, is_ground_content);
		writeU8(os, light_propagates);
		writeU8(os, sunlight_propagates);
		writeU8(os, walkable);
		writeU8(os, pointable);
		writeU8(os, diggable);
		writeU8(os, climbable);
		writeU8(os, buildable_to);
		os<<serializeString(""); // legacy: used to be metadata_name
		writeU8(os, liquid_type);
		os<<serializeString(liquid_alternative_flowing);
		os<<serializeString(liquid_alternative_source);
		writeU8(os, liquid_viscosity);
		writeU8(os, light_source);
		writeU32(os, damage_per_second);
		node_box.serialize(os, protocol_version);
		selection_box.serialize(os, protocol_version);
		writeU8(os, legacy_facedir_simple);
		writeU8(os, legacy_wallmounted);
		serializeSimpleSoundSpec(sound_footstep, os);
		serializeSimpleSoundSpec(sound_dig, os);
		serializeSimpleSoundSpec(sound_dug, os);
	}
	else if (protocol_version > 13 && protocol_version < 24) {
		writeU8(os, 6); // version
		os<<serializeString(name);
		writeU16(os, groups.size());
		for (ItemGroupList::const_iterator
			i = groups.begin(); i != groups.end(); ++i) {
				os<<serializeString(i->first);
				writeS16(os, i->second);
		}
		writeU8(os, drawtype);
		writeF1000(os, visual_scale);
		writeU8(os, 6);
		for (u32 i = 0; i < 6; i++)
			tiledef[i].serialize(os, protocol_version);
		//CF_SPECIAL_COUNT = 2 before cf ver. 7 and protocol ver. 24
		writeU8(os, 2);
		for (u32 i = 0; i < 2; i++)
			tiledef_special[i].serialize(os, protocol_version);
		writeU8(os, alpha);
		writeU8(os, post_effect_color.getAlpha());
		writeU8(os, post_effect_color.getRed());
		writeU8(os, post_effect_color.getGreen());
		writeU8(os, post_effect_color.getBlue());
		writeU8(os, param_type);
		writeU8(os, param_type_2);
		writeU8(os, is_ground_content);
		writeU8(os, light_propagates);
		writeU8(os, sunlight_propagates);
		writeU8(os, walkable);
		writeU8(os, pointable);
		writeU8(os, diggable);
		writeU8(os, climbable);
		writeU8(os, buildable_to);
		os<<serializeString(""); // legacy: used to be metadata_name
		writeU8(os, liquid_type);
		os<<serializeString(liquid_alternative_flowing);
		os<<serializeString(liquid_alternative_source);
		writeU8(os, liquid_viscosity);
		writeU8(os, liquid_renewable);
		writeU8(os, light_source);
		writeU32(os, damage_per_second);
		node_box.serialize(os, protocol_version);
		selection_box.serialize(os, protocol_version);
		writeU8(os, legacy_facedir_simple);
		writeU8(os, legacy_wallmounted);
		serializeSimpleSoundSpec(sound_footstep, os);
		serializeSimpleSoundSpec(sound_dig, os);
		serializeSimpleSoundSpec(sound_dug, os);
		writeU8(os, rightclickable);
		writeU8(os, drowning);
		writeU8(os, leveled);
		writeU8(os, liquid_range);
	} else
		throw SerializationError("ContentFeatures::serialize(): "
			"Unsupported version requested");
}

void ContentFeatures::deSerializeOld(std::istream &is, int version)
{
	if (version == 5) // In PROTOCOL_VERSION 13
	{
		name = deSerializeString(is);
		groups.clear();
		u32 groups_size = readU16(is);
		for(u32 i=0; i<groups_size; i++){
			std::string name = deSerializeString(is);
			int value = readS16(is);
			groups[name] = value;
		}
		drawtype = (enum NodeDrawType)readU8(is);

		bool ignore_culling = ((version <= 26) &&
				((drawtype == NDT_MESH) ||
				(drawtype == NDT_PLANTLIKE) ||
				(drawtype == NDT_FIRELIKE) ||
				(drawtype == NDT_LIQUID)));

		visual_scale = readF1000(is);
		if (readU8(is) != 6)
			throw SerializationError("unsupported tile count");
		for (u32 i = 0; i < 6; i++)
			tiledef[i].deSerialize(is, ignore_culling);
		if (readU8(is) != CF_SPECIAL_COUNT)
			throw SerializationError("unsupported CF_SPECIAL_COUNT");
		for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
			tiledef_special[i].deSerialize(is, ignore_culling);
		alpha = readU8(is);
		post_effect_color.setAlpha(readU8(is));
		post_effect_color.setRed(readU8(is));
		post_effect_color.setGreen(readU8(is));
		post_effect_color.setBlue(readU8(is));
		param_type = (enum ContentParamType)readU8(is);
		param_type_2 = (enum ContentParamType2)readU8(is);
		is_ground_content = readU8(is);
		light_propagates = readU8(is);
		sunlight_propagates = readU8(is);
		walkable = readU8(is);
		pointable = readU8(is);
		diggable = readU8(is);
		climbable = readU8(is);
		buildable_to = readU8(is);
		deSerializeString(is); // legacy: used to be metadata_name
		liquid_type = (enum LiquidType)readU8(is);
		liquid_alternative_flowing = deSerializeString(is);
		liquid_alternative_source = deSerializeString(is);
		liquid_viscosity = readU8(is);
		light_source = readU8(is);
		damage_per_second = readU32(is);
		node_box.deSerialize(is);
		selection_box.deSerialize(is);
		legacy_facedir_simple = readU8(is);
		legacy_wallmounted = readU8(is);
		deSerializeSimpleSoundSpec(sound_footstep, is);
		deSerializeSimpleSoundSpec(sound_dig, is);
		deSerializeSimpleSoundSpec(sound_dug, is);
	} else if (version == 6) {
		name = deSerializeString(is);
		groups.clear();
		u32 groups_size = readU16(is);
		for (u32 i = 0; i < groups_size; i++) {
			std::string name = deSerializeString(is);
			int	value = readS16(is);
			groups[name] = value;
		}
		drawtype = (enum NodeDrawType)readU8(is);
		visual_scale = readF1000(is);
		if (readU8(is) != 6)
			throw SerializationError("unsupported tile count");
		for (u32 i = 0; i < 6; i++)
			tiledef[i].deSerialize(is, drawtype);
		// CF_SPECIAL_COUNT in version 6 = 2
		if (readU8(is) != 2)
			throw SerializationError("unsupported CF_SPECIAL_COUNT");
		for (u32 i = 0; i < 2; i++)
			tiledef_special[i].deSerialize(is, drawtype);
		alpha = readU8(is);
		post_effect_color.setAlpha(readU8(is));
		post_effect_color.setRed(readU8(is));
		post_effect_color.setGreen(readU8(is));
		post_effect_color.setBlue(readU8(is));
		param_type = (enum ContentParamType)readU8(is);
		param_type_2 = (enum ContentParamType2)readU8(is);
		is_ground_content = readU8(is);
		light_propagates = readU8(is);
		sunlight_propagates = readU8(is);
		walkable = readU8(is);
		pointable = readU8(is);
		diggable = readU8(is);
		climbable = readU8(is);
		buildable_to = readU8(is);
		deSerializeString(is); // legacy: used to be metadata_name
		liquid_type = (enum LiquidType)readU8(is);
		liquid_alternative_flowing = deSerializeString(is);
		liquid_alternative_source = deSerializeString(is);
		liquid_viscosity = readU8(is);
		liquid_renewable = readU8(is);
		light_source = readU8(is);
		damage_per_second = readU32(is);
		node_box.deSerialize(is);
		selection_box.deSerialize(is);
		legacy_facedir_simple = readU8(is);
		legacy_wallmounted = readU8(is);
		deSerializeSimpleSoundSpec(sound_footstep, is);
		deSerializeSimpleSoundSpec(sound_dig, is);
		deSerializeSimpleSoundSpec(sound_dug, is);
		rightclickable = readU8(is);
		drowning = readU8(is);
		leveled = readU8(is);
		liquid_range = readU8(is);
	} else {
		throw SerializationError("unsupported ContentFeatures version");
	}
}


inline bool CNodeDefManager::getNodeRegistrationStatus() const
{
	return m_node_registration_complete;
}


inline void CNodeDefManager::setNodeRegistrationStatus(bool completed)
{
	m_node_registration_complete = completed;
}


void CNodeDefManager::pendNodeResolve(NodeResolver *nr)
{
	nr->m_ndef = this;
	if (m_node_registration_complete)
		nr->nodeResolveInternal();
	else
		m_pending_resolve_callbacks.push_back(nr);
}


bool CNodeDefManager::cancelNodeResolveCallback(NodeResolver *nr)
{
	size_t len = m_pending_resolve_callbacks.size();
	for (size_t i = 0; i != len; i++) {
		if (nr != m_pending_resolve_callbacks[i])
			continue;

		len--;
		m_pending_resolve_callbacks[i] = m_pending_resolve_callbacks[len];
		m_pending_resolve_callbacks.resize(len);
		return true;
	}

	return false;
}


void CNodeDefManager::runNodeResolveCallbacks()
{
	for (size_t i = 0; i != m_pending_resolve_callbacks.size(); i++) {
		NodeResolver *nr = m_pending_resolve_callbacks[i];
		nr->nodeResolveInternal();
	}

	m_pending_resolve_callbacks.clear();
}


void CNodeDefManager::resetNodeResolveState()
{
	m_node_registration_complete = false;
	m_pending_resolve_callbacks.clear();
}


////
//// NodeResolver
////

NodeResolver::NodeResolver()
{
	m_ndef            = NULL;
	m_nodenames_idx   = 0;
	m_nnlistsizes_idx = 0;
	m_resolve_done    = false;

	m_nodenames.reserve(16);
	m_nnlistsizes.reserve(4);
}


NodeResolver::~NodeResolver()
{
	if (!m_resolve_done && m_ndef)
		m_ndef->cancelNodeResolveCallback(this);
}


void NodeResolver::nodeResolveInternal()
{
	m_nodenames_idx   = 0;
	m_nnlistsizes_idx = 0;

	resolveNodeNames();
	m_resolve_done = true;

	m_nodenames.clear();
	m_nnlistsizes.clear();
}


bool NodeResolver::getIdFromNrBacklog(content_t *result_out,
	const std::string &node_alt, content_t c_fallback)
{
	if (m_nodenames_idx == m_nodenames.size()) {
		*result_out = c_fallback;
		errorstream << "NodeResolver: no more nodes in list" << std::endl;
		return false;
	}

	content_t c;
	std::string name = m_nodenames[m_nodenames_idx++];

	bool success = m_ndef->getId(name, c);
	if (!success && node_alt != "") {
		name = node_alt;
		success = m_ndef->getId(name, c);
	}

	if (!success) {
		errorstream << "NodeResolver: failed to resolve node name '" << name
			<< "'." << std::endl;
		c = c_fallback;
	}

	*result_out = c;
	return success;
}


bool NodeResolver::getIdsFromNrBacklog(std::vector<content_t> *result_out,
	bool all_required, content_t c_fallback)
{
	bool success = true;

	if (m_nnlistsizes_idx == m_nnlistsizes.size()) {
		errorstream << "NodeResolver: no more node lists" << std::endl;
		return false;
	}

	size_t length = m_nnlistsizes[m_nnlistsizes_idx++];

	while (length--) {
		if (m_nodenames_idx == m_nodenames.size()) {
			errorstream << "NodeResolver: no more nodes in list" << std::endl;
			return false;
		}

		content_t c;
		std::string &name = m_nodenames[m_nodenames_idx++];

		if (name.substr(0,6) != "group:") {
			if (m_ndef->getId(name, c)) {
				result_out->push_back(c);
			} else if (all_required) {
				errorstream << "NodeResolver: failed to resolve node name '"
					<< name << "'." << std::endl;
				result_out->push_back(c_fallback);
				success = false;
			}
		} else {
			std::set<content_t> cids;
			std::set<content_t>::iterator it;
			m_ndef->getIds(name, cids);
			for (it = cids.begin(); it != cids.end(); ++it)
				result_out->push_back(*it);
		}
	}

	return success;
}