/*
    PIP - Platform Independent Primitives
    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 "ts_file.h"

#include "pifile.h"
#include "piiostream.h"
#include "pitranslator.h"


bool TSFile::Context::confirm(const PIString & msg, const PIString & file, int line) {
	if (msg.isEmpty()) return false;
	bool is_new = !messages.contains(msg);
	auto & m(messages[msg]);
	m.source   = msg;
	m.filename = file;
	m.line     = line >= 0 ? PIString::fromNumber(line) : PIString();
	m.type.setFlag(Message::Missing, false);
	if (is_new) m.type.setFlag(Message::Unfinished);
	return is_new;
}


PIString TSFile::mask(const PIString & in) {
	static const PIVector<PIPair<PIString, PIString>> map = {
		{"&",  "&amp;" },
		{"<",  "&lt;"  },
		{">",  "&gt;"  },
		{"'",  "&apos;"},
		{"\"", "&quot;"},
	};
	PIString ret = in;
	for (const auto & i: map)
		ret.replaceAll(i.first, i.second);
	return ret;
}


PIString TSFile::unmask(const PIString & in) {
	static const PIVector<PIPair<PIString, PIString>> map = {
		{"<",  "&lt;"  },
		{">",  "&gt;"  },
		{"'",  "&apos;"},
		{"\"", "&quot;"},
		{"&",  "&amp;" },
	};
	PIString ret = in;
	for (const auto & i: map)
		ret.replaceAll(i.second, i.first);
	return ret;
}


TSFile::Content TSFile::read(const PIString & path) {
	enum Phase {
		pHeader,
		pContext,
		pContextName,
		pMessage,
		pMessageBody,
	};
	Content ret;
	PIFile f(path, PIIODevice::ReadOnly);
	if (!f.isOpened()) return ret;
	PIIOTextStream ts(&f);
	Context * cc = nullptr;
	Message msg;
	int phase         = pHeader;
	bool multi_source = false, multi_translation = false, multi_comment = false;
	while (!ts.isEnd()) {
		auto line = ts.readLine().trim();
		if (multi_source) {
			if (line.endsWith("</source>")) {
				line.cutRight(9);
				multi_source = false;
			}
			msg.source += "\n" + unmask(line);
			continue;
		}
		if (multi_translation) {
			if (line.endsWith("</translation>")) {
				line.cutRight(14);
				multi_translation = false;
			}
			msg.translation += "\n" + unmask(line);
			continue;
		}
		if (multi_comment) {
			if (line.endsWith("</translatorcomment>")) {
				line.cutRight(20);
				multi_comment = false;
			}
			msg.comment += "\n" + unmask(line);
			continue;
		}
		switch (phase) {
		case pHeader:
			if (line.startsWith("<TS")) {
				int ind = line.find("language");
				line.remove(0, ind + 8);
				line.takeSymbol();
				ret.lang = line.takeRange('"', '"');
				// piCout << "lang" << ret.lang;
				phase    = pContext;
			}
			break;
		case pContext:
			if (line == "<context>") phase = pContextName;
			break;
		case pContextName:
			if (line.startsWith("<name>")) {
				line.cutLeft(6).cutRight(7);
				cc    = &(ret.contexts[line]);
				phase = pMessage;
			}
			break;
		case pMessage:
			if (line == "<message>") {
				msg   = {};
				phase = pMessageBody;
			}
			break;
		case pMessageBody:
			if (line == "</message>") {
				if (cc) cc->messages[msg.source] = msg;
				phase = pMessage;
			} else if (line.startsWith("<source>")) {
				line.cutLeft(8);
				if (line.endsWith("</source>"))
					line.cutRight(9);
				else
					multi_source = true;
				msg.source = unmask(line);
			} else if (line.startsWith("<location")) {
				PIString trs = line.takeRange('<', '>').cutLeft(8);
				while (trs.isNotEmpty()) {
					PIString t = trs.takeCWord();
					trs.cutLeft(1);
					PIString v = trs.takeRange('\"', '\"');
					if (t == "filename") msg.filename = v;
					if (t == "line") msg.line = v;
					if (trs.size_s() <= 2) break;
				}
			} else if (line.startsWith("<translation")) {
				PIString trs = line.takeRange('<', '>').cutLeft(11);
				while (trs.isNotEmpty()) {
					PIString t = trs.takeCWord();
					trs.cutLeft(1);
					PIString v = trs.takeRange('\"', '\"');
					if (t == "type") {
						if (v == "unfinished")
							msg.type.setFlag(Message::Unfinished);
						else if (v == "vanished")
							msg.type.setFlag(Message::Missing);
					}
				}
				if (line.endsWith("</translation>"))
					line.cutRight(14);
				else
					multi_translation = true;
				msg.translation = unmask(line);
			} else if (line.startsWith("<translatorcomment>")) {
				line.cutLeft(19);
				if (line.endsWith("</translatorcomment>"))
					line.cutRight(20);
				else
					multi_comment = true;
				msg.comment = unmask(line);
			}
			break;
		}
		if (line == "</context>") {
			cc    = nullptr;
			phase = pContext;
		}
	}
	return ret;
}


bool TSFile::write(const PIString & path, const TSFile::Content & content, bool no_obsolete) {
	PIFile outf(path, PIIODevice::ReadWrite);
	if (!outf.isOpened()) {
		piCerr << "Can`t open file \"%1\"!"_tr("TSFile").arg(outf.path());
		return false;
	}
	outf.clear();
	PIIOTextStream ts(&outf);
	auto writeMessage = [&ts](const Message & m) {
		if (m.source.isEmpty()) return;
		ts << "    <message>\n";
		if (m.filename.isNotEmpty()) {
			ts << "        <location filename=\"" << m.filename << "\"";
			if (m.line.isNotEmpty()) ts << " line=\"" << m.line << "\"";
			ts << "/>\n";
		}
		ts << "        <source>" << mask(m.source) << "</source>\n";
		if (m.comment.isNotEmpty()) {
			ts << "        <translatorcomment>" << mask(m.comment) << "</translatorcomment>\n";
		}
		ts << "        <translation";
		if (m.type[Message::Missing])
			ts << " type=\"vanished\"";
		else if (m.type[Message::Unfinished])
			ts << " type=\"unfinished\"";
		ts << ">" << mask(m.translation) << "</translation>\n";
		ts << "    </message>\n";
	};

	ts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
	ts << "<!DOCTYPE TS>\n";
	ts << "<TS version=\"2.1\" language=\"" << content.lang << "\">\n";
	auto cit = content.contexts.makeIterator();
	while (cit.next()) {
		if (cit.value().messages.isEmpty()) continue;
		ts << "<context>\n";
		ts << "    <name>" << cit.key() << "</name>\n";
		auto mit = cit.value().messages.makeIterator();
		while (mit.next()) {
			if (mit.value().type[Message::Missing] && no_obsolete) continue;
			writeMessage(mit.value());
		}
		ts << "</context>\n";
	};
	ts << "</TS>\n";

	return true;
}


void TSFile::Content::markAllMissing() {
	auto cit = contexts.makeIterator();
	while (cit.next()) {
		auto mit = cit.value().messages.makeIterator();
		while (mit.next()) {
			mit.value().type.setFlag(Message::Missing);
		}
	}
}
