/*
    This file is part of the Boson game
    Copyright (C) 2004 Andreas Beckermann (b_mann@gmx.de)

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <qdom.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qstringlist.h>

static bool openFile(const QString& file, QDomDocument* doc);
static bool saveTo(const QString& fileBase, QDomDocument& doc);
static bool writeDeclaration(QTextStream& header, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase);
static bool writeImplementation(QTextStream& cpp, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase);
static bool writeConstructorImplementation(QTextStream& cpp, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase);
static bool writeCreateChildren(QTextStream& cpp, const QDomElement& e, const QString& parent);
static bool writePropertiesList(QTextStream& cpp, const QDomElement& root);
static bool writeConnections(QTextStream& cpp, const QDomElement& root);

const char* g_headline = "/* TODO: write some clever stuff into the header. e.g. \"Do not edit this file by hand \" */ \n\n";

 // FIXME: atm the "name" is the variable name. the "name" of the Widgets tag should become the classname of the resulting file
QString g_className;
QStringList g_addIncludes;

int main(int argc, char** argv)
{
 QString fileName;
 QString outFileNameBase;
 g_addIncludes.clear();
 for (int i = 1; i < argc; i++) {
	if (strcmp(argv[i], "--input") == 0) {
		if (argc <= i + 1) {
			fprintf(stderr, "Expected input filename after --input\n");
			return 1;
		}
		i++;
		fileName = argv[i];
	} else if (strcmp(argv[i], "--output") == 0) {
		if (argc <= i + 1) {
			fprintf(stderr, "Expected output basename after --output\n");
			return 1;
		}
		i++;
		outFileNameBase = argv[i];
	} else if (strcmp(argv[i], "--addinclude") == 0) {
		if (argc <= i + 1) {
			fprintf(stderr, "Expected header file --addinclude\n");
			return 1;
		}
		i++;
		g_addIncludes.append(argv[i]);
	}
 }
 if (fileName.isEmpty()) {
	fprintf(stderr, "no --input filename given\n");
	return 1;
 }
 if (outFileNameBase.isEmpty()) {
	fprintf(stderr, "no --output basename given\n");
	return 1;
 }
 QDomDocument doc("BoDesigner");
 if (!openFile(fileName, &doc)) {
	fprintf(stderr, "Error reading %s\n", fileName.latin1());
	return 1;
 }

 if (!saveTo(outFileNameBase, doc)) {
	fprintf(stderr, "could not output .cpp and .h files\n");
	return 1;
 }
 return 0;
}

bool openFile(const QString& fileName, QDomDocument* doc)
{
 if (fileName.isEmpty()) {
	fprintf(stderr, "No filename\n");
	return false;
 }
 QFile file(fileName);
 if (!file.open(IO_ReadOnly)) {
	fprintf(stderr, "Could not open file %s\n", fileName.latin1());
	return false;
 }
 if (!doc->setContent(&file)) {
	fprintf(stderr, "Could not parse file %s\n", fileName.latin1());
	return false;
 }
 file.close();
 QDomElement root = doc->documentElement();
 if (root.isNull()) {
	fprintf(stderr, "NULL root element\n");
	return false;
 }
 QDomElement widgets = root.namedItem("Widgets").toElement();
 if (widgets.isNull()) {
	fprintf(stderr, "NULL Widgets element\n");
	return false;
 }
 return true;
}

bool saveTo(const QString& fileBase, QDomDocument& doc)
{
 QFile fileCpp(fileBase + ".cpp");
 QFile fileH(fileBase + ".h");
 if (!fileCpp.open(IO_WriteOnly)) {
	fprintf(stderr, "Could not open %s.cpp for writing\n", fileBase.latin1());
	return false;
 }
 if (!fileH.open(IO_WriteOnly)) {
	fprintf(stderr, "Could not open %s.h for writing\n", fileBase.latin1());
	return false;
 }
 QTextStream cpp(&fileCpp);
 QTextStream header(&fileH);
 QDomElement root = doc.documentElement();
 QDomElement widgets = root.namedItem("Widgets").toElement();
 QDomElement methods = root.namedItem("Methods").toElement();

 g_className = widgets.namedItem("Properties").namedItem("name").toElement().text();
 if (!writeDeclaration(header, widgets, methods, fileBase)) {
	fprintf(stderr, "Unable to write declaration\n");
	return false;
 }
 if (!writeImplementation(cpp, widgets, methods, fileBase)) {
	fprintf(stderr, "Unable to write implementation\n");
	return false;
 }
 return true;
}

bool writeDeclaration(QTextStream& header, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase)
{
 header << g_headline;

 header << "#ifndef " << fileNameBase.upper() << "_H\n";
 header << "#define " << fileNameBase.upper() << "_H\n";
 header << "\n";

 // AB: this is hardcoded and will probably stay like this
 header << "#include \"boufo.h\"\n";
 header << "\n";

 header << "class " << g_className << " : public " << widgets.namedItem("ClassName").toElement().text() << "\n{\n";

 // AB: since we support BoUfoWidget derived classes only, our widgets are
 // always QObjects.
 header << "\tQ_OBJECT\n";

 header << "public:\n";

 // we always provide one c'tor and a d'tor.
 header << "\t" << g_className << "();\n";
 header << "\t~" << g_className << "();\n";
 header << "\n";

 // AB: all member widgets are public. we do this because if we'd use protected,
 // then we would always have to derive this widget to use it - or we'd have to
 // add set/get methods using the designer for every single property that we'd
 // like to change at any time. that would be very inconvenient.
 header << "public: // member variables\n";
 QDomNodeList list = widgets.elementsByTagName("Widget");
 for (unsigned int i = 0; i < list.count(); i++) {
	QDomElement e = list.item(i).toElement();
	header << "\t" << e.namedItem("ClassName").toElement().text() << "* "
			<< e.namedItem("Properties").namedItem("name").toElement().text()
			<< ";\n";
 }

 if (!methods.isNull()) {
	QDomNodeList list = methods.elementsByTagName("Method");
	bool started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "signal") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "signals:\n";
			started = true;
		}
		header << "\t" << e.attribute("return") << " " << e.attribute("name") << ";\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "method") {
			continue;
		}
		if (e.attribute("visibility") != "public") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "public: // public methods\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "slot") {
			continue;
		}
		if (e.attribute("visibility") != "public") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "public slots:\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "method") {
			continue;
		}
		if (e.attribute("visibility") != "protected") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "protected: // protected methods\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "slot") {
			continue;
		}
		if (e.attribute("visibility") != "protected") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "protected slots:\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "method") {
			continue;
		}
		if (e.attribute("visibility") != "private") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "private: // private methods\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
	for (unsigned int i = 0; i < list.count(); i++) {
		QDomElement e = list.item(i).toElement();
		if (e.attribute("type") != "slot") {
			continue;
		}
		if (e.attribute("visibility") != "private") {
			continue;
		}
		if (!started) {
			header << "\n";
			header << "private slots:\n";
			started = true;
		}
		header << "\tvirtual " << e.attribute("return") << " " << e.attribute("name") << " = 0;\n";
	}
	started = false;
 }

 header << "};\n\n";
 header << "#endif\n";

 return true;
}

// TODO: use i18n() whenever a string that is to be translated occurs.
// especially on all label->setText() calls or so.
bool writeImplementation(QTextStream& cpp, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase)
{
 cpp << g_headline;

 cpp << "#include \"" << fileNameBase << ".h\"\n";
 cpp << "#include \"" << fileNameBase << ".moc\"\n";
 cpp << "\n";
 cpp << "#include <qmap.h>\n"; // for loading the properties
 cpp << "\n";
 for (unsigned int i = 0; i < g_addIncludes.count(); i++) {
	cpp << "#include <" << g_addIncludes[i] << ">\n";
 }
 cpp << "\n";

 cpp << g_className << "::" << g_className << "()\n";
 cpp << "\t: " << widgets.namedItem("ClassName").toElement().text() << "()\n";
 cpp << "{\n";
 if (!writeConstructorImplementation(cpp, widgets, methods, fileNameBase)) {
	fprintf(stderr, "Could not write c'tor implementation\n");
	return false;
 }
 cpp << "}\n\n";

 cpp << g_className << "::~" << g_className << "()\n";
 cpp << "{\n";
 // not much to do, as libufo handles deletions
 cpp << "}\n\n";


 return true;
}

bool writeConstructorImplementation(QTextStream& cpp, const QDomElement& widgets, const QDomElement& methods, const QString& fileNameBase)
{
 // TODO: this->loadPropertiesFromXML(widgets.namedItem("Properties").toElement())

 cpp << " QMap<QString, QString> properties;\n";
 if (!writePropertiesList(cpp, widgets)) {
	return false;
 }
 cpp << " loadProperties(properties);\n";

 if (!writeCreateChildren(cpp, widgets, QString::null)) {
	return false;
 }
 if (!writeConnections(cpp, methods)) {
	return false;
 }

 return true;
}

bool writeCreateChildren(QTextStream& cpp, const QDomElement& root, const QString& parent)
{
 QDomNode n = root.firstChild();
 while (!n.isNull()) {
	QDomElement e = n.toElement();
	n = n.nextSibling();
	if (e.isNull()) {
		continue;
	}
	if (e.tagName() != "Widget") {
		continue;
	}
	QString className = e.namedItem("ClassName").toElement().text();
	QString name = e.namedItem("Properties").namedItem("name").toElement().text();
	if (className.isEmpty()) {
		fprintf(stderr, "empty ClassName\n");
		return false;
	}
	if (name.isEmpty()) {
		fprintf(stderr, "empty name\n");
		return false;
	}
	cpp << " " << name << " = new " << className << "();\n";

	if (!writePropertiesList(cpp, e)) {
		fprintf(stderr, "could not write properties list\n");
		return false;
	}
	cpp << " " << name << "->loadProperties(properties);\n";

	cpp << " ";
	if (!parent.isEmpty()) {
		cpp << parent << "->";
	}
	cpp << "addWidget(" << name << ");\n";
	cpp << "\n";

	if (!writeCreateChildren(cpp, e, name)) {
		return false;
	}
 }
 return true;
}

bool writePropertiesList(QTextStream& cpp, const QDomElement& root)
{
 cpp << " properties.clear();\n";
 QDomNamedNodeMap attributes = root.attributes();
 QDomElement properties = root.namedItem("Properties").toElement();
 for (QDomNode n = properties.firstChild(); !n.isNull(); n = n.nextSibling()) {
	QDomElement e = n.toElement();
	if (e.isNull()) {
		continue;
	}
	if (e.text().isEmpty()) {
		continue;
	}
	cpp << " properties.insert(\"" << e.tagName() << "\", \"" << e.text() << "\");\n";
 }
 return true;
}

bool writeConnections(QTextStream& cpp, const QDomElement& root)
{
 if (root.isNull()) {
	// no methods => no connections
	return true;
 }
 bool started = false;
 for (QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) {
	QDomElement e = n.toElement();
	if (e.isNull() || e.tagName() != "Method") {
		continue;
	}
	QDomElement c = e.namedItem("Connections").toElement();
	if (c.isNull()) {
		continue;
	}
	QString methodName = e.attribute("name");
	QString methodType = e.attribute("type");
	for (QDomNode n2 = c.firstChild(); !n2.isNull(); n2 = n2.nextSibling()) {
		QDomElement e2 = n2.toElement();
		if (e2.isNull() || e2.tagName() != "Connection") {
			continue;
		}
		QString type = e2.attribute("type");
		if (type != "sender" && type != "receiver") {
			continue;
		}
		QString sender = e2.attribute("sender");
		QString signal = e2.attribute("signal");
		QString receiver = e2.attribute("receiver");
		QString slot = e2.attribute("slot");
		if (type == "sender") {
			sender = "this";
			signal = methodName;
		} else if (type == "receiver") {
			receiver = "this";
			slot = methodName;
		}
		if (!started) {
			cpp << "\n\n";
			cpp << " // Connections\n";
			started = true;
		}
		cpp << " connect("
				<< sender
				<< ", SIGNAL(" << signal << "),\n"
				<< "\t\t" << receiver;
		if (methodType == "signal" && type == "receiver") {
			// usually the receiver is a SLOT, but there are two
			// cases when it is a signal:
			// 1. methodType == signal && type == receiver
			//    -> this signal is called by some other signal
			// 2. type == sender && ???
			//    -> this signal calls another signal
			//       (methodType == signal is implicated, as a slot
			//        can never be sender)
			// the first case is easy to recognize, as we know that
			// we are a signal. the second case is not, as we have
			// no way to find out whether the receiver is a signal
			// as well. we could add a GUI element in the editor,
			// but that would make things complex to the user.
			// however i think (hope?) this case should be very
			// rare, as it makes little sense to add a signal that
			// just calls another signal.
			cpp << ", SIGNAL(" << slot << "));\n";
		} else {
			cpp << ", SLOT(" << slot << "));\n";
		}
	}
 }
 return true;
}

