/*
    PIP - Platform Independent Primitives
    Code model generator
    Ivan Pelipenko peri4ko@yandex.ru

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

#include "stream.h"

#include "pitranslator.h"


bool writeClassStreamMembersOut(Runtime & rt, const PICodeParser::Entity * e, int & cnt, bool simple, PIString var_prefix) {
	if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += ".";
	for (const PICodeParser::Entity * p: e->parents) {
		if (p->is_anonymous) continue;
		if (!writeClassStreamMembersOut(rt, p, cnt, simple, var_prefix)) return false;
	}
	PIVector<PICodeParser::Member> ml;
	for (const PICodeParser::Member & m: e->members) {
		if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue;
		ml << m;
	}
	bool is_union = e->type == "union";
	PISet<int> used_id;
	for (const PICodeParser::Member & m: ml) {
		if (m.isBitfield()) continue;
		if (m.attributes[PICodeParser::Static]) continue;
		if (m.meta.value("id") == "-") continue;
		auto type = findEntity(rt, m.type);
		if (type) {
			if (type->is_anonymous) {
				if (!writeClassStreamMembersOut(rt, type, cnt, simple, var_prefix + m.name)) return false;
				continue;
			}
		}
		++cnt;
		if (m.meta.contains("id")) cnt = m.meta.value("id").toInt();
		if (used_id[cnt]) {
			piCerr << "Error with \"%1\" stream operator: ID %2 already used"_tr("pip_cmg").arg(e->name).arg(cnt);
			return false;
		}
		if (m.dims.isEmpty()) {
			if (simple) {
				rt.ts << "\ts << ";
				if (rt.parser.isEnum(m.type)) rt.ts << "(int)";
				rt.ts << "v." << var_prefix << m.name << ";\n";
			} else {
				rt.ts << "\tcs.add(" << cnt << ", ";
				if (rt.parser.isEnum(m.type)) rt.ts << "(int)";
				rt.ts << "v." << var_prefix << m.name << ");\n";
			}
		} else {
			PIString ptype = m.type.left(m.type.find('[')).trim();
			PIString size  = m.dims[0];
			for (int i = 1; i < m.dims.size_s(); ++i) {
				size += " * ";
				size += m.dims[i];
			}
			if (simple) {
				rt.ts << "\tfor (int i = 0; i < " << size << "; ++i)\n";
				rt.ts << "\t\ts << ((const " << ptype << " *)(" << "v." << var_prefix << m.name << "))[i];\n";
			} else {
				rt.ts << "\tcs << cs.chunk(" << cnt << ", PIVector<" << ptype << " >((const " << ptype << " *)(" << "v." << var_prefix
					  << m.name << "), ";
				rt.ts << size << "));\n";
			}
		}
		if (is_union) break;
	}
	if (is_union) return true;
	return true;
}


bool writeClassStreamMembersIn(Runtime & rt, const PICodeParser::Entity * e, int & cnt, bool simple, PIString var_prefix) {
	if (var_prefix.isNotEmpty() && !var_prefix.endsWith('.')) var_prefix += ".";
	for (const PICodeParser::Entity * p: e->parents) {
		if (p->is_anonymous) continue;
		if (!writeClassStreamMembersIn(rt, p, cnt, simple, var_prefix)) return false;
	}
	PIVector<PICodeParser::Member> ml;
	for (const PICodeParser::Member & m: e->members) {
		if (m.is_type_ptr || (m.visibility != PICodeParser::Public)) continue;
		ml << m;
	}
	bool is_union = e->type == "union";
	PISet<int> used_id;
	for (const PICodeParser::Member & m: ml) {
		if (m.isBitfield()) continue;
		if (m.attributes[PICodeParser::Static]) continue;
		if (m.meta.value("id") == "-") continue;
		auto type = findEntity(rt, m.type);
		if (type) {
			if (type->is_anonymous) {
				if (!writeClassStreamMembersIn(rt, type, cnt, simple, var_prefix + m.name)) return false;
				continue;
			}
		}
		++cnt;
		if (m.meta.contains("id")) cnt = m.meta.value("id").toInt();
		if (used_id[cnt]) {
			piCerr << "Error with \"%1\" stream operator: ID %2 already used"_tr("pip_cmg").arg(e->name).arg(cnt);
			return false;
		}
		used_id << cnt;
		if (m.dims.isEmpty()) {
			bool is_enum = rt.parser.isEnum(m.type);
			if (simple) {
				rt.ts << "\t";
				if (is_enum) rt.ts << "{int i; ";
				rt.ts << "s >> ";
				if (is_enum)
					rt.ts << "i;";
				else
					rt.ts << "v." << var_prefix << m.name << ";";
				if (is_enum) rt.ts << " v. << var_prefix" << m.name << " = (" << m.type << ")i;}";
				rt.ts << "\n";
			} else {
				rt.ts << "\t\tcase " << cnt << ":";
				if (is_enum) rt.ts << " {int i;";
				rt.ts << " cs.get(";
				if (is_enum)
					rt.ts << "i";
				else
					rt.ts << "v." << var_prefix << m.name;
				rt.ts << ");";
				if (is_enum) rt.ts << " v." << var_prefix << m.name << " = (" << m.type << ")i;}";
				rt.ts << " break;\n";
			}
		} else {
			PIString ptype = m.type.left(m.type.find('[')).trim();
			PIString size  = m.dims[0];
			for (int i = 1; i < m.dims.size_s(); ++i) {
				size += " * ";
				size += m.dims[i];
			}
			if (simple) {
				rt.ts << "\tfor (int i = 0; i < " << size << "; ++i)\n";
				rt.ts << "\t\ts >> ((" << ptype << " *)(" << "v." << var_prefix << m.name << "))[i];\n";
			} else {
				rt.ts << "\t\tcase " << cnt << ": {\n\t\t\tPIVector<" << ptype << " > d; cs.get(d);\n";
				rt.ts << "\t\t\tint cnt = piMini(d.size_s(), " << size << ");\n";
				rt.ts << "\t\t\tfor (int i = 0; i < cnt; ++i)\n";
				rt.ts << "\t\t\t\t((" << ptype << " *)(" << "v." << var_prefix << m.name << "))[i] = d[i];\n";
				rt.ts << "\t\t\t}\n";
				rt.ts << "\t\t\tbreak;\n";
			}
		}
		if (is_union) break;
	}
	if (is_union) return true;
	return true;
}


bool needClassStream(const PICodeParser::Entity * e) {
	if (e->meta.contains("no-stream")) return false;
	for (const PICodeParser::Member & m: e->members) {
		if (m.is_type_ptr || m.isBitfield() || !m.dims.isEmpty() || (m.visibility != PICodeParser::Public)) continue;
		if (m.attributes[PICodeParser::Static]) continue;
		if (m.meta.value("id") == "-") continue;
		return true;
	}
	for (const PICodeParser::Entity * p: e->parents) {
		if (p->is_anonymous) continue;
		if (needClassStream(p)) return true;
	}
	return false;
}


bool makeClassStream(Runtime & rt, const PICodeParser::Entity * e) {
	if (!needClassStream(e)) return true;
	bool simple = e->meta.contains("simple-stream");
	rt.ts << "\nBINARY_STREAM_WRITE(" << e->name << ") {\n";
	if (!simple) rt.ts << "\tPIChunkStream cs;\n";
	int cnt = 0;
	if (!writeClassStreamMembersOut(rt, e, cnt, simple)) return false;
	if (!simple) rt.ts << "\ts << cs.data();\n";
	rt.ts << "\treturn s;\n}\n";
	rt.ts << "BINARY_STREAM_READ (" << e->name << ") {\n";
	if (!simple) {
		// ts << "\tif (s.size_s() < 4) return s;\n";
		rt.ts << "\tPIChunkStream cs;\n";
		rt.ts << "\tcs.extract(s);\n";
		rt.ts << "\twhile (!cs.atEnd()) {\n";
		rt.ts << "\t\tswitch (cs.read()) {\n";
	}
	cnt = 0;
	if (!writeClassStreamMembersIn(rt, e, cnt, simple)) return false;
	if (!simple) rt.ts << "\t\t}\n\t}\n";
	rt.ts << "\treturn s;\n}\n";
	return true;
}
