/**
 ** data_utils.h: Generic data template functions.
 **/

/*
Copyright (C) 2009-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.
*/

#ifndef INCL_DATA_UTILS
#define INCL_DATA_UTILS 1

#include "U7obj.h"
#include "databuf.h"
#include "exceptions.h"
#include "exult_constants.h"
#include "fnames.h"
#include "ignore_unused_variable_warning.h"
#include "msgfile.h"

#include <algorithm>
#include <iterator>
#include <map>
#include <sstream>
#include <string>
#include <vector>

/*
 *  Generic vector data handler routines.
 *  They all assume that the template class has the following
 *  operators defined:
 *      (1) operator< (which must be a strict weak order)
 *      (2) operator==
 *      (3) operator!=
 *      (4) operator= that sets modified flag if needed.
 *  They also assume that the vector is totally ordered
 *  with the operator< -- using these functions will ensure
 *  that this is the case.
 */
template <typename T>
void add_vector_info(const T& inf, std::vector<T>& vec) {
	// Find using operator<.
	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
	if (it == vec.end() || *it != inf) {    // Not found.
		vec.insert(it, inf);                // Add new.
	} else {                                // Already exists.
		bool st  = it->have_static();
		bool inv = it->is_invalid();
		it->set(inf);    // Replace information.
		if (st && inv && !inf.is_invalid() && !inf.from_patch()
			&& !inf.was_modified()) {
			it->set_modified(false);
		}
	}
}

template <typename T>
void copy_vector_info(const std::vector<T>& from, std::vector<T>& to) {
	if (from.size()) {
		to.resize(from.size());
		std::copy(from.begin(), from.end(), to.begin());
	} else {
		to.clear();
	}
}

template <typename T>
std::vector<T>& set_vector_info(bool tf, std::vector<T>& vec) {
	invalidate_vector(vec);
	if (!tf) {
		clean_vector(vec);
	}
	return vec;
}

template <typename T>
void invalidate_vector(std::vector<T>& vec) {
	for (auto& elem : vec) {
		elem.set_invalid(true);
	}
}

template <typename T>
void clean_vector(std::vector<T>& vec) {
	vec.erase(
			std::remove_if(
					vec.begin(), vec.end(),
					[](T& elem) {
						return elem.is_invalid() && !elem.have_static();
					}),
			vec.end());
}

template <class T, typename U>
static const T* Search_vector_data_single_wildcard(
		const std::vector<T>& vec, int src, U T::*dat) {
	if (vec.empty()) {    // Not found.
		return nullptr;
	}
	T inf;
	inf.*dat = src;
	// Try finding exact match first.
	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
	if (it == vec.end()) {    // Nowhere to be found.
		return nullptr;
	} else if (*it == inf && !it->is_invalid()) {    // Have it already.
		return &*it;
	}
	// Try wildcard shape.
	inf.*dat = -1;
	it       = std::lower_bound(it, vec.end(), inf);
	if (it == vec.end() || *it != inf    // It just isn't there...
		|| it->is_invalid()) {           // ... or it is invalid.
		return nullptr;
	} else {    // At last!
		return &*it;
	}
}

template <class T>
static const T* Search_vector_data_double_wildcards(
		const std::vector<T>& vec, int frame, int quality, short T::*fr,
		short T::*qual) {
	if (vec.empty()) {
		return nullptr;    // No name.
	}
	T inf;
	inf.*fr   = frame;
	inf.*qual = quality;
	// Try finding exact match first.
	auto it = std::lower_bound(vec.begin(), vec.end(), inf);
	if (it == vec.end()) {    // Nowhere to be found.
		return nullptr;
	}
	auto& new_info = *it;
	if (new_info == inf && !it->is_invalid()) {    // Have it already.
		return &new_info;
	}
	// We only have to search forward for a match.
	if (quality != -1) {
		if (new_info.*fr == frame) {
			// Maybe quality is to blame. Try wildcard quality.
			inf.*qual = -1;
			it        = std::lower_bound(it, vec.end(), inf);
			if (it == vec.end()) {    // Nowhere to be found.
				return nullptr;
			} else if (*it == inf && !it->is_invalid()) {    // We got it!
				return &*it;
			}
		}
		// Maybe frame is to blame? Try search for specific
		// quality with wildcard frame.
		inf.*qual = quality;
		inf.*fr   = -1;
		it        = std::lower_bound(it, vec.end(), inf);
		if (it == vec.end()) {    // Nowhere to be found.
			return nullptr;
		} else if (*it == inf && !it->is_invalid()) {    // We got it!
			return &*it;
		}
		inf.*qual = -1;
	}
	// *Still* haven't found it. Last try: wildcard frame *and* quality.
	inf.*fr = -1;
	it      = std::lower_bound(it, vec.end(), inf);
	if (it == vec.end() || *it != inf || *it != inf    // It just isn't there...
		|| it->is_invalid()) {                         // ... or it is invalid.
		return nullptr;
	} else {    // At last!
		return &*it;
	}
}

/*
 *  Generic data handler routine.
 */
template <typename T>
T* set_info(bool tf, T*& pt) {
	if (!tf) {
		delete pt;
		pt = nullptr;
	} else if (!pt) {
		pt = new T();
	}
	return pt;
}

/*
 *  Get # entries of binary data file (with Exult extension).
 */
inline int Read_count(std::istream& in) {
	int cnt = Read1(in);    // How the originals did it.
	if (cnt == 255) {
		cnt = little_endian::Read2(in);    // Exult extension.
	}
	return cnt;
}

/*
 *  Write # entries of binary data file (with Exult extension).
 */
inline void Write_count(std::ostream& out, int cnt) {
	if (cnt >= 255) {
		// Exult extension.
		out.put(static_cast<char>(255));
		little_endian::Write2(out, cnt);
	} else {
		out.put(static_cast<char>(cnt));
	}
}

/*
 *  Generic base data-agnostic reader class.
 */
class Base_reader {
protected:
	bool haveversion;

	virtual void read_data(
			std::istream& in, size_t index, int version, bool patch,
			Exult_Game game, bool binary) {
		ignore_unused_variable_warning(in, index, version, patch, game, binary);
	}

	// Binary data file.
	void read_binary_internal(std::istream& in, bool patch, Exult_Game game) {
		int vers = 0;
		if (haveversion) {
			vers = Read1(in);
		}
		const size_t cnt = Read_count(in);
		for (size_t j = 0; j < cnt; j++) {
			read_data(in, j, vers, patch, game, true);
		}
	}

public:
	Base_reader(bool h) : haveversion(h) {}

	virtual ~Base_reader() = default;

	// Text data file.
	void parse(
			std::vector<std::string>& strings, int version, bool patch,
			Exult_Game game) {
		for (size_t j = 0; j < strings.size(); j++) {
			if (!strings[j].empty()) {
				std::istringstream strin(strings[j], std::ios::in);
				read_data(strin, j, version, patch, game, false);
			}
		}
		strings.clear();
	}

	// Binary data file.
	void read(const char* fname, bool patch, Exult_Game game) {
		if (!U7exists(fname)) {
			return;
		}
		auto pFin = U7open_in(fname);
		if (!pFin) {
			return;
		}
		auto& fin = *pFin;
		read_binary_internal(fin, patch, game);
	}

	// Binary resource file.
	void read(Exult_Game game, int resource) {
		// Only for BG and SI.
		if (game != BLACK_GATE && game != SERPENT_ISLE) {
			return;
		}
		/*  ++++ Not because of ES.
		snprintf(buf, sizeof(buf), "config/%s", fname);
		str_int_pair resource = game->get_resource(buf);
		U7object txtobj(resource.str, resource.num);
		*/
		const bool  bg = game == BLACK_GATE;
		const char* flexfile
				= bg ? BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX)
					 : BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX);
		const U7object     txtobj(flexfile, resource);
		std::size_t        len;
		auto               txt = txtobj.retrieve(len);
		const std::string  databuf(reinterpret_cast<char*>(txt.get()), len);
		std::istringstream strin(databuf, std::ios::in | std::ios::binary);
		read_binary_internal(strin, false, game);
	}
};

class ID_reader_functor {
public:
	int operator()(std::istream& in, int index, int version, bool binary) {
		ignore_unused_variable_warning(index, version);
		return binary ? little_endian::Read2(in) : ReadInt(in);
	}
};

/*
 *  Post-read data transformation functors.
 */
template <class Info>
class Null_functor {
public:
	void operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(in, info, version, patch, game);
	}
};

template <int flag, class Info>
class Patch_flags_functor {
public:
	void operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(in, version, game);
		if (patch) {
			info.frompatch_flags |= flag;
		}
	}
};

/*
 *  Generic functor-based reader class for maps.
 */
template <
		class Info, class Functor, class Transform = Null_functor<Info>,
		class ReadID = ID_reader_functor>
class Functor_multidata_reader : public Base_reader {
protected:
	std::map<int, Info>& info;
	Functor              reader;
	Transform            postread;
	ReadID               idread;

	void read_data(
			std::istream& in, size_t index, int version, bool patch,
			Exult_Game game, bool binary) override {
		int id = idread(in, index, version, binary);
		if (id >= 0) {
			Info& inf = info[id];
			reader(in, version, patch, game, inf);
			postread(in, version, patch, game, inf);
		}
	}

public:
	Functor_multidata_reader(std::map<int, Info>& nfo, bool h = false)
			: Base_reader(h), info(nfo) {}
};

/*
 *  Generic functor-based reader class.
 */
template <class Info, class Functor, class Transform = Null_functor<Info>>
class Functor_data_reader : public Base_reader {
protected:
	Info&     info;
	Functor   reader;
	Transform postread;

	void read_data(
			std::istream& in, size_t index, int version, bool patch,
			Exult_Game game, bool binary) override {
		ignore_unused_variable_warning(index, binary);
		reader(in, version, patch, game, info);
		postread(in, version, patch, game, info);
	}

public:
	Functor_data_reader(Info& nfo, bool h = false)
			: Base_reader(h), info(nfo) {}
};

/*
 *  Data reader functors.
 */
template <typename T, class Info, T Info::*data>
class Text_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		info.*data = ReadInt(in);
		return true;
	}
};

template <typename T, class Info, T Info::*data1, T Info::*data2>
class Text_pair_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		info.*data1 = ReadInt(in, -1);
		info.*data2 = ReadInt(in, -1);
		return true;
	}
};

template <typename T, class Info, T Info::*data, int bit>
class Bit_text_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		// For backwards compatibility.
		const bool biton = ReadInt(in, 1) != 0;
		if (biton) {
			info.*data |= (static_cast<T>(1) << bit);
		} else {
			info.*data &= ~(static_cast<T>(1) << bit);
		}
		return true;
	}
};

template <typename T, class Info, T Info::*data>
class Bit_field_text_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		int size  = 8 * sizeof(T);    // Bit count.
		int bit   = 0;
		T   flags = 0;
		while (in.good() && bit < size) {
			if (ReadInt(in) != 0) {
				flags |= (static_cast<T>(1) << bit);
			} else {
				flags &= ~(static_cast<T>(1) << bit);
			}
			bit++;
		}
		info.*data = flags;
		return true;
	}
};

template <typename T, class Info, T Info::*data, unsigned pad>
class Binary_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		in.read(reinterpret_cast<char*>(&(info.*data)), sizeof(T));
		if (pad) {    // Skip some bytes.
			in.ignore(pad);
		}
		return true;
	}
};

template <
		typename T1, typename T2, class Info, T1 Info::*data1, T2 Info::*data2,
		unsigned pad>
class Binary_pair_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		ignore_unused_variable_warning(version, patch, game);
		in.read(reinterpret_cast<char*>(&(info.*data1)), sizeof(T1));
		in.read(reinterpret_cast<char*>(&(info.*data2)), sizeof(T2));
		if (pad) {    // Skip some bytes.
			in.ignore(pad);
		}
		return true;
	}
};

template <typename T, class Info, T* Info::*data>
class Class_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		T* cls = new T();
		cls->set_patch(patch);    // Set patch flag.
		if (!cls->read(in, version, game)) {
			delete cls;
			return false;
		}
		T*& pt = info.*data;
		if (cls->is_invalid() && pt) {
			// 'Delete old' flag.
			delete pt;
			pt = nullptr;
			delete cls;
			return false;
		}
		if (!patch) {    // This is a static data file.
			info.have_static_flags |= T::get_info_flag();
		}
		// Delete old.
		delete pt;
		pt = cls;
		return true;
	}
};

template <typename T, class Info, std::vector<T> Info::*data>
class Vector_reader_functor {
public:
	bool operator()(
			std::istream& in, int version, bool patch, Exult_Game game,
			Info& info) {
		T cls;
		if (!patch) {
			cls.set_static(true);
		}
		cls.set_patch(patch);
		cls.read(in, version, game);
		std::vector<T>& vec = info.*data;
		add_vector_info(cls, vec);
		return true;
	}
};

/*
 *  Reads text data file and parses it according to passed
 *  parser functions.
 */
template <class Parser>
static void Read_text_data_file(
		const char*           fname,    // Name of file to read, sans extension
		std::vector<Parser*>& parsers,         // What to use to parse data.
		std::vector<const char*>& sections,    // The names of the sections
		bool editing, Exult_Game game, int resource) {
	std::vector<std::vector<std::string>> static_strings;
	std::vector<std::vector<std::string>> patch_strings;

	int  static_version = 1;
	int  patch_version  = 1;
	char buf[50];
	if (game == BLACK_GATE || game == SERPENT_ISLE) {
		/*  ++++ Not because of ES.
		snprintf(buf, sizeof(buf), "config/%s", fname);
		str_int_pair resource = game->get_resource(buf);
		U7object txtobj(resource.str, resource.num);
		*/
		const bool  bg = game == BLACK_GATE;
		const char* flexfile
				= bg ? BUNDLE_CHECK(BUNDLE_EXULT_BG_FLX, EXULT_BG_FLX)
					 : BUNDLE_CHECK(BUNDLE_EXULT_SI_FLX, EXULT_SI_FLX);
		IExultDataSource ds(flexfile, resource);
		static_version = Read_text_msg_file_sections(
				&ds, static_strings, sections.data(), sections.size());
	} else {
		try {
			snprintf(buf, sizeof(buf), "<STATIC>/%s.txt", fname);
			auto pIn = U7open_in(buf, false);
			if (!pIn) {
				throw file_open_exception(buf);
			}
			auto& in       = *pIn;
			static_version = Read_text_msg_file_sections(
					in, static_strings, sections.data(), sections.size());
		} catch (std::exception&) {
			if (!editing) {
				for (auto* parser : parsers) {
					delete parser;
				}
				throw;
			}
			static_strings.resize(sections.size());
		}
	}
	patch_strings.resize(sections.size());
	snprintf(buf, sizeof(buf), "<PATCH>/%s.txt", fname);
	if (U7exists(buf)) {
		auto pIn = U7open_in(buf, false);
		if (!pIn) {
			throw file_open_exception(buf);
		}
		auto& in      = *pIn;
		patch_version = Read_text_msg_file_sections(
				in, patch_strings, sections.data(), sections.size());
	}
	for (size_t i = 0; i < sections.size(); i++) {
		parsers[i]->parse(static_strings[i], static_version, false, game);
		parsers[i]->parse(patch_strings[i], patch_version, true, game);
	}
}

/*
 *  Generic base data-agnostic writer class.
 */
class Base_writer {
protected:
	const char* name;
	const int   version;
	int         cnt;

	virtual int check_write() {
		return 0;
	}

	virtual void write_data(std::ostream& out, Exult_Game game) {
		ignore_unused_variable_warning(out, game);
	}

public:
	Base_writer(const char* s, int v = -1) : name(s), version(v), cnt(-1) {}

	virtual ~Base_writer() = default;

	int check() {
		// Return cached value, if any.
		return cnt > -1 ? cnt : cnt = check_write();
	}

	void write_text(std::ostream& out, Exult_Game game) {
		if (cnt <= 0) {    // Nothing to do.
			return;
		}
		// Section is not empty.
		out << "%%section " << name << std::endl;
		write_data(out, game);
		out << "%%endsection" << std::endl;
	}

	void write_binary(Exult_Game game) {
		if (cnt <= 0) {    // Nothing to do.
			return;
		}
		auto pFout = U7open_out(name);
		if (!pFout) {
			return;
		}
		auto& fout = *pFout;
		if (version >= 0) {    // container.dat has version #.
			fout.put(version);
		}
		Write_count(fout, cnt);    // Object count, with Exult extension.
		write_data(fout, game);
	}
};

/*
 *  Generic functor-based writer class for maps.
 */
template <class Info, class Functor>
class Functor_multidata_writer : public Base_writer {
protected:
	std::map<int, Info>& info;
	const int            numshapes;
	Functor              writer;

	int check_write() override {
		int num = 0;
		for (auto& kvpair : info) {
			if (writer(kvpair.second)) {
				num++;
			}
		}
		return num;
	}

	void write_data(std::ostream& out, Exult_Game game) override {
		for (auto& kvpair : info) {
			if (writer(kvpair.second)) {
				writer(out, kvpair.first, game, kvpair.second);
			}
		}
	}

public:
	Functor_multidata_writer(
			const char* s, std::map<int, Info>& nfo, int n, int v = -1)
			: Base_writer(s, v), info(nfo), numshapes(n) {
		check();
	}
};

/*
 *  Generic functor-based writer class.
 */
template <class Info, class Functor>
class Functor_data_writer : public Base_writer {
protected:
	Info&   info;
	Functor writer;

	int check_write() override {
		return writer(info) ? 1 : 0;
	}

	void write_data(std::ostream& out, Exult_Game game) override {
		if (writer(info)) {
			writer(out, -1, game, info);
		}
	}

public:
	Functor_data_writer(const char* s, Info& nfo, int v = -1)
			: Base_writer(s, v), info(nfo) {
		check();
	}
};

/*
 *  Base flag checker functor.
 */
template <int flag, class Info>
class Flag_check_functor {
public:
	bool operator()(Info& info) {
		return (info.modified_flags | info.frompatch_flags) & flag;
	}
};

inline void WriteIndex(std::ostream& out, int index) {
	if (index >= 0) {
		WriteInt(out, index);
	}
}

/*
 *  Data checker and writer functors.
 */
template <int flag, typename T, class Info, T Info::*data>
class Text_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		out << ":";
		WriteIndex(out, index);
		WriteInt(out, info.*data, true);
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <int flag, typename T, class Info, T Info::*data1, T Info::*data2>
class Text_pair_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		out << ":";
		WriteIndex(out, index);
		WriteInt(out, info.*data1);
		WriteInt(out, info.*data2, true);
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <int flag, typename T, class Info, T Info::*data, int bit>
class Bit_text_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		bool val = ((info.*data) & (static_cast<T>(1) << bit));
		out << ":";
		WriteIndex(out, index);
		WriteInt(out, val, true);
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <int flag, typename T, class Info, T Info::*data>
class Bit_field_text_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		out << ":";
		WriteIndex(out, index);
		int size  = 8 * sizeof(T) - 1;    // Bit count.
		int bit   = 0;
		T   flags = info.*data;
		while (bit < size) {
			out << static_cast<bool>((flags & (static_cast<T>(1) << bit)) != 0)
				<< '/';
			bit++;
		}
		out << static_cast<bool>((flags & (static_cast<T>(1) << size)) != 0)
			<< std::endl;
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <int flag, typename T, class Info, T Info::*data, int pad>
class Binary_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		little_endian::Write2(out, index);
		out.write(reinterpret_cast<char*>(&(info.*data)), sizeof(T));
		for (int i = 0; i < pad; i++) {
			out.put(0);
		}
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <
		int flag, typename T1, typename T2, class Info, T1 Info::*data1,
		T2 Info::*data2, int pad>
class Binary_pair_writer_functor {
	Flag_check_functor<flag, Info> check;

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		ignore_unused_variable_warning(game);
		little_endian::Write2(out, index);
		out.write(reinterpret_cast<char*>(&(info.*data1)), sizeof(T1));
		out.write(reinterpret_cast<char*>(&(info.*data2)), sizeof(T2));
		std::fill_n(std::ostream_iterator<char>(out), pad, 0);
	}

	bool operator()(Info& info) {
		return check(info);
	}
};

template <typename T, class Info, T* Info::*data>
class Class_writer_functor {
public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		T* cls = info.*data;
		if (!cls) {                 // Write 'remove' block.
			if (!T::is_binary) {    // Text entry.
				out << ':' << index << "/-255" << std::endl;
			} else if (T::entry_size >= 3) {
				// T::entry_size should be >= 3!
				// For stupid compilers...
				const size_t   nelems = T::entry_size >= 3 ? T::entry_size : 1;
				auto*          buf    = new unsigned char[nelems];
				unsigned char* ptr    = buf;
				little_endian::Write2(ptr, index);
				if (T::entry_size >= 4) {
					memset(ptr, 0, T::entry_size - 3);
				}
				buf[T::entry_size - 1] = 0xff;
				out.write(reinterpret_cast<const char*>(buf), T::entry_size);
				delete[] buf;
			}
		} else {
			cls->write(out, index, game);
		}
	}

	bool operator()(Info& info) {
		T* cls = info.*data;
		if (!cls) {
			return (info.have_static_flags & T::get_info_flag()) != 0;
		}
		return cls->need_write();
	}
};

template <typename T, class Info, std::vector<T> Info::*data>
class Vector_writer_functor {
	bool need_write(T& inf) const {
		return inf.need_write() || (inf.is_invalid() && inf.have_static());
	}

public:
	void operator()(std::ostream& out, int index, Exult_Game game, Info& info) {
		std::vector<T>& vec = info.*data;
		for (auto& elem : vec) {
			if (need_write(elem)) {
				elem.write(out, index, game);
			}
		}
	}

	bool operator()(Info& info) {
		std::vector<T>& vec = info.*data;
		if (vec.empty()) {    // Nothing to do.
			return false;
		}
		for (auto& elem : vec) {
			if (need_write(elem)) {
				return true;
			}
		}
		return false;
	}
};

/*
 *  Writes text data file according to passed writer functions.
 */
template <class Writer>
static void Write_text_data_file(
		const char*           fname,    // Name of file to read, sans extension
		std::vector<Writer*>& writers,    // What to use to write data.
		int version, Exult_Game game) {
	size_t cnt = 0;
	for (auto* writer : writers) {
		cnt += writer->check();
	}
	if (!cnt) {
		// Nothing to do.
		return;
	}
	char buf[50];
	snprintf(buf, sizeof(buf), "<PATCH>/%s.txt", fname);
	auto pOut = U7open_out(buf, true);    // (It's a text file.)
	if (!pOut) {
		return;
	}
	auto& out = *pOut;
	out << "#\tExult " << VERSION << " text message file."
		<< "\tWritten by ExultStudio." << std::endl;
	out << "%%section version" << std::endl
		<< ":" << version << std::endl
		<< "%%endsection" << std::endl;
	for (auto* writer : writers) {
		writer->write_text(out, game);
	}
}

#endif
