/*******************************************************************************
 *	GenBData.cc	BEAM Data output
 *			T.Barnaby,	BEAM Ltd,	12/9/03
 *******************************************************************************
 */
#define	DEBUG	1

#include <stdio.h>
#include <stdarg.h>
#include <strings.h>
#include <GenBData.h>
#include <bidl.h>

void GenBData::produceInt(Node* n){
	BIter		i;
	BString		s;
	BString		s1;
	BString		c;
	BList<Node*>*	nl;
	BString		typeName;
	BString		baseClass;

	switch(n->nodeType()){
	case Node::TMODULE:
		omodule = n->name();
		ofileInt.writeLine(BString("namespace ") + omodule + " {\n");
		ofileInt.indentMore();
		for(n->nodes().start(i); !n->nodes().isEnd(i); n->nodes().next(i)){
			produceInt(n->nodes()[i]);
		}
		ofileInt.indentLess();
		ofileInt.writeLine("}\n");
		break;
	case Node::TLIST:
		for(n->nodes().start(i); !n->nodes().isEnd(i); n->nodes().next(i)){
			produceInt(n->nodes()[i]);
		}
		break;
	case Node::TSTRUCT:
		// Ignore internal objects
		if((n->name() == "BObjData") || (n->name() == "BObject") || (n->name() == "BObj"))
			return;
		typeName = BString("") + n->name();
		
		baseClass = getBaseClass(n);
		if((baseClass ==" BObjData") || (baseClass == "BObject")){
			oobjType = ObjTypeBObject;
			ofileInt.writeLine(BString("class ") + typeName + " : public " +  getTypeName(n->node(1)->node(0), 1) + " {\n");
		}
		else if(baseClass == "BObj"){
			oobjType = ObjTypeBObj;
			ofileInt.writeLine(BString("class ") + typeName + " : public " +  getTypeName(n->node(1)->node(0), 1) + " {\n");
		}
		else {
			ofileInt.writeLine(BString("class ") + typeName + " {\n");
			oobjType = ObjTypeStd;
		}
		ofileInt.writeLine("public:\n");
		ofileInt.indentMore();
		
		if(oobjType == ObjTypeBObject){
			ofileInt.writeLine(typeName + "();\n");
			ofileInt.writeLine("static BObject*	createObj();\n");
			ofileInt.writeLine("static BType	otype;\n");
			ofileInt.writeLine("BType& getType();\n");
			ofileInt.writeLine("BMemberList	getMemberList();\n");
		}
		else {
			// Add default constructor
//			ofileInt.writeLine(typeName + "();\n");
			
			// Add constructer to initialise parameters
			nl = &n->node(0)->nodes();
			s = "";
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				if(i != nl->begin())
					s = s + ", ";

				if(nl->get(i)->nodeType() == Node::TSTRUCTITEM_ARRAYFIXED){
					s = s + getTypeName(nl->get(i)->node(0), 0) + "* p" + nl->get(i)->node(1)->name();
					s = s + " =  0";
				}
				else {
					s = s + getTypeName(nl->get(i)->node(0), 0) + " p" + nl->get(i)->node(1)->name();
					s = s + " = " + getTypeName(nl->get(i)->node(0), 0) + "()";
				}
			}
			ofileInt.writeLine(typeName + "(" + s + ");\n");
			
			if(oobjType == ObjTypeBObj){
				ofileInt.writeLine("BString getType();\n");
				ofileInt.writeLine("BError setMembers(BDictString& members);\n");
				ofileInt.writeLine("BError setMember(BString name, BString value);\n");
				ofileInt.writeLine("BError getMembers(BDictString& members);\n");
				ofileInt.writeLine("BError getMember(BString name, BString& value);\n");
			}
		}
		
		ofileInt.indentLess();
		ofileInt.writeLine("public:\n");
		ofileInt.indentMore();

		nl = &n->node(0)->nodes();
		for(nl->start(i); !nl->isEnd(i); nl->next(i)){
			s = getTypeName(nl->get(i)->node(0), oobjType == ObjTypeBObject) + "\t";
			s = s + "" + nl->get(i)->node(1)->name();

			if(nl->get(i)->nodeType() == Node::TSTRUCTITEM_ARRAYFIXED){
				s = s + "[" + nl->get(i)->node(2)->name() + "]";
			}

			if(nl->get(i)->node(2) && nl->get(i)->node(2)->nodeType() == Node::TCOMMENT)
				c = BString("\t//") + nl->get(i)->node(2)->name();
			else
				c = "";
			ofileInt.writeLine(s + ";" + c + "\n");
		}

		ofileInt.indentLess();
		ofileInt.writeLine("};\n\n");
		
		if(ooptions.hasKey("qt4")){
			opostNamespace += BString("Q_DECLARE_METATYPE(") + omodule + "::" + typeName + ");\n";
		}
		break;

	case Node::TCOMMENT:
		s = BString("//") + n->name() + "\n";
		ofileInt.writeLine(s);
		break;
	case Node::TENUM:
		s = "enum ";

		if(n->node(0)){
			s = s + n->node(0)->name() + " ";
		}
		else {
			s = s + "\t";
		}
		
		s = s + "\t{ ";
		
		s1 = "";
		nl = &n->node(1)->nodes();
		for(nl->start(i); !nl->isEnd(i); nl->next(i)){
			if(s1.len())
				s1 += ", ";
			if(nl->get(i)->nodes().number() == 2){
				s1 += nl->get(i)->node(0)->name();
				s1 += " = ";
				s1 += nl->get(i)->node(1)->name();
			}
			else {
				s1 += nl->get(i)->name();
			}
		}
		ofileInt.writeLine(s + s1 + "};\n\n");
		break;
	default:
		break;
	}
}

void GenBData::produceImp(Node* n){
	BString		s;
	BIter		i;
	BList<Node*>*	nl;
	BString		typeName;
	BString		parentTypeName;
	BString		baseClass;

	switch(n->nodeType()){
	case Node::TMODULE:
		omodule = n->name();
		ofileImp.writeLine(BString("namespace ") + omodule + " {\n");
		ofileImp.indentMore();
		for(n->nodes().start(i); !n->nodes().isEnd(i); n->nodes().next(i)){
			produceImp(n->nodes()[i]);
		}
		ofileImp.indentLess();
		ofileImp.writeLine("}\n");
		break;
	case Node::TLIST:
		for(n->nodes().start(i); !n->nodes().isEnd(i); n->nodes().next(i)){
			produceImp(n->nodes()[i]);
		}
		break;
	case Node::TTYPEDOMAIN:
		otypeDomain = atoi(n->name());
		break;
	case Node::TSTRUCT:
		if((n->name() == "BObjData") || (n->name() == "BObject") || (n->name() == "BObj"))
			return;
		typeName = BString("") + n->name();

		baseClass = getBaseClass(n);
		if((baseClass ==" BObjData") || (baseClass == "BObject")){
			oobjType = ObjTypeBObject;
			parentTypeName = getTypeName(n->node(1)->node(0), 1);
		}
		else if(baseClass == "BObj"){
			oobjType = ObjTypeBObj;
			parentTypeName = getTypeName(n->node(1)->node(0), 1);
		}
		else {
			oobjType = ObjTypeStd;
		}

		ofileImp.writeLine("\n");
		ofileImp.writeLine(BString("// ") + typeName + " Object Implementation\n");
		
		if(oobjType == ObjTypeBObject){
			ofileImp.writeLine(BString("BType ") + typeName + "::otype = btypesList.appendType(BType(\"" + typeName + "\", " + BString(otypeDomain) + ", " + BString(otypeNum) + ", createObj));\n");
			ofileImp.writeLine(BString("BType& ") + typeName + "::getType(){\n");
			ofileImp.indentMore();
			ofileImp.writeLine(BString("return otype;\n"));
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");

			ofileImp.writeLine(BString("BObject* ") + typeName + "::createObj(){\n");
			ofileImp.indentMore();
			ofileImp.writeLine(BString("return new ") + typeName + ";\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");
		}
		
		// Add default constructor
//			ofileImp.writeLine(typeName + "::" + typeName + "(){\n");
//			ofileImp.writeLine("}\n");

		// Add constructer to initialise parameters
		nl = &n->node(0)->nodes();
		s = "";
		for(nl->start(i); !nl->isEnd(i); nl->next(i)){
			if(i != nl->begin())
				s = s + ", ";
			if(nl->get(i)->nodeType() == Node::TSTRUCTITEM_ARRAYFIXED){
				s = s + getTypeName(nl->get(i)->node(0), 0) + "* p" + nl->get(i)->node(1)->name();
			}
			else {
				s = s + getTypeName(nl->get(i)->node(0), 0) + " p" + nl->get(i)->node(1)->name();
			}
		}
		ofileImp.writeLine(typeName + "::" + typeName + "(" + s + "){\n");
		ofileImp.indentMore();
		nl = &n->node(0)->nodes();
		for(nl->start(i); !nl->isEnd(i); nl->next(i)){
			s = "";
			if(nl->get(i)->nodeType() == Node::TSTRUCTITEM_ARRAYFIXED){
				s = s + "if(p" + nl->get(i)->node(1)->name() + "){\n";
				ofileImp.writeLine(s);
				ofileImp.indentMore();
				s = "";
				s = s + "memcpy(" + nl->get(i)->node(1)->name();
				s = s + ", p" + nl->get(i)->node(1)->name();
				s = s + ", sizezof(" + nl->get(i)->node(1)->name() +"));";
				ofileImp.writeLine(s + "\n");
				ofileImp.indentLess();
				ofileImp.writeLine("}\n");
			}
			else {
				s = s + "" + nl->get(i)->node(1)->name();
				s = s + " = p" + nl->get(i)->node(1)->name();
				ofileImp.writeLine(s + ";\n");
			}
		}
		ofileImp.indentLess();
		ofileImp.writeLine("}\n");
		
		if(oobjType == ObjTypeBObject){
			ofileImp.writeLine(BString("BMemberList ") + typeName + "::getMemberList(){\n");
			ofileImp.indentMore();
			ofileImp.writeLine("BMemberList\tl;\n\n");
			ofileImp.writeLine(BString("l = ") + parentTypeName + "::getMemberList();\n");

			nl = &n->node(0)->nodes();
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				s = nl->get(i)->node(1)->name();
				ofileImp.writeLine(BString("l.append(BMember(\"") + s + "\", &" + s + "));\n");
			}
			ofileImp.writeLine("return l;\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");
		}
		
		if(oobjType == ObjTypeBObj){
			int	first;
			
			ofileImp.writeLine(BString("BString ") + typeName + "::getType(){ return \"" + typeName + "\"; }\n");

			ofileImp.writeLine(BString("BError ") + typeName + "::getMembers(BDictString& members){\n");
			ofileImp.indentMore();
			ofileImp.writeLine("BError\terr;\n\n");
			nl = &n->node(0)->nodes();
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				s = nl->get(i)->node(1)->name();
				if(s == "timePeriod")
					ofileImp.writeLine(s + ".getMembers(members);\n");
				else
					ofileImp.writeLine(BString("toBString(") + s + ", members[\"" + s + "\"]);\n");
			}
			ofileImp.writeLine("return err;\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");

			ofileImp.writeLine(BString("BError ") + typeName + "::getMember(BString name, BString& value){\n");
			ofileImp.indentMore();
			ofileImp.writeLine("BError\terr;\n\n");
			nl = &n->node(0)->nodes();
			first = 1;
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				s = nl->get(i)->node(1)->name();
				if(s != "timePeriod"){
					if(first){
						ofileImp.writeLine(BString("if(name == \"") + s + "\") toBString(" + s + ", value);\n");
						first = 0;
					}
					else {
						ofileImp.writeLine(BString("else if(name == \"") + s + "\") toBString(" + s + ", value);\n");
					}
				}
			}
			ofileImp.writeLine("return err;\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");

			ofileImp.writeLine(BString("BError ") + typeName + "::setMembers(BDictString& members){\n");
			ofileImp.indentMore();
			ofileImp.writeLine("BError\terr;\n\n");
			nl = &n->node(0)->nodes();
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				s = nl->get(i)->node(1)->name();
				if(s == "timePeriod")
					ofileImp.writeLine(s + ".setMembers(members);\n");
				else
					ofileImp.writeLine(BString("fromBString(members[\"") + s + "\"], " + s + ");\n");
			}
			ofileImp.writeLine("return err;\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");

			ofileImp.writeLine(BString("BError ") + typeName + "::setMember(BString name, BString value){\n");
			ofileImp.indentMore();
			ofileImp.writeLine("BError\terr;\n\n");
			nl = &n->node(0)->nodes();
			first = 1;
			for(nl->start(i); !nl->isEnd(i); nl->next(i)){
				s = nl->get(i)->node(1)->name();
				if(s != "timePeriod"){
					if(first){
						ofileImp.writeLine(BString("if(name == \"") + s + "\") fromBString(value, " + s + ");\n");
						first = 0;
					}
					else {
						ofileImp.writeLine(BString("else if(name == \"") + s + "\") fromBString(value, " + s + ");\n");
					}
				}
			}
			ofileImp.writeLine("return err;\n");
			ofileImp.indentLess();
			ofileImp.writeLine("}\n");
		}

		otypeNum++;
		break;
	default:
		break;
	}
}

BError GenBData::produceHeaderInt(){
	BError	err;
	BString	fileName = ofileName + "D.h";
	BString	fileNameUpper = ofileName + "D_H";

	fileNameUpper.toUpper();

	ofileInt.printf("/*******************************************************************************\n");
	ofileInt.printf(" *\t%s\tProduced by Bidl\n", fileName.retStr());
	ofileInt.printf(" *******************************************************************************\n");
	ofileInt.printf(" */\n");
	ofileInt.printf("\n");
	ofileInt.printf("#ifndef %s\n", fileNameUpper.retStr());
	ofileInt.printf("#define %s 1\n\n", fileNameUpper.retStr());

	ofileInt.printf("#include <Boap.h>\n");
	ofileInt.printf("#include <BObj.h>\n");
	ofileInt.printf("#include <BDate.h>\n");
	ofileInt.printf("#include <BTimeStamp.h>\n");
	ofileInt.printf("#include <BComplex.h>\n");
	ofileInt.printf("#include <BList.h>\n");
	ofileInt.printf("#include <BArray.h>\n");

	if(getUseBObjects()){
		ofileInt.printf("#include <BObjData.h>\n");
		ofileInt.printf("#include <BObjInt.h>\n");
		ofileInt.printf("#include <BObjString.h>\n");
		ofileInt.printf("#include <BObjDate.h>\n");
		ofileInt.printf("#include <BObjDateTime.h>\n");
		ofileInt.printf("#include <BObjListVar.h>\n");
	}

	if(ooptions.hasKey("qt4")){
		ofileInt.printf("#include <QtCore/qmetatype.h>\n");
	}
	
	ofileInt.printf("\n");
	return err;
}
BError GenBData::produceTrailerInt(){
	BError	err;

	ofileInt.printf("%s\n", opostNamespace.retStr());
	ofileInt.printf("#endif\n");
	return err;
}

BError GenBData::produceHeaderImp(){
	BError	err;

	ofileImp.printf("/*******************************************************************************\n");
	ofileImp.printf(" *\t%s\tProduced by Bidl\n", (ofileName + ".cc").retStr());
	ofileImp.printf(" *******************************************************************************\n");
	ofileImp.printf(" */\n");
	ofileImp.printf("\n");
	ofileImp.printf("#include <%s>\n", (ofileName + "D.h").retStr());
#ifdef ZAP1
	ofileImp.printf("#include <BType.h>\n");
#endif
	ofileImp.printf("\n");
	return err;
}

BError GenBData::produceTrailerImp(){
	BError	err;

#ifdef ZAP1	
	// Produce Python Init
	ofileImp.writeLine("\n// Python module init\n");
	ofileImp.writeLine("#include <Python.h>\n");
	ofileImp.writeLine("\n");
	ofileImp.writeLine("static PyMethodDef methods[] = {\n");
	ofileImp.indentMore();
	ofileImp.writeLine("{NULL, NULL}\n");
	ofileImp.indentLess();
	ofileImp.writeLine("};\n");
	ofileImp.writeLine(BString("extern \"C\" void initlib") + omodule + "(){\n");
	ofileImp.indentMore();
	ofileImp.writeLine(BString("Py_InitModule(\"lib") + omodule + "\", methods);\n");
	ofileImp.indentLess();
	ofileImp.writeLine("}\n");
#endif
	return err;
}

GenBData::GenBData(){
	otypeDomain = 0;
	otypeNum = 1;
	oobjType = ObjTypeStd;
}

GenBData::~GenBData(){
}

BError GenBData::produce(Node* n, BString fileName){
	BError	err;
	
//	dprintf("GenBData::produce: %p\n", n);

	ofileName = fileName;
	ofileInt.open(ofileName + "D.h", "w");
	ofileImp.open(ofileName + "D.cc", "w");
	
	// File Headers
	produceHeaderInt();
	produceHeaderImp();
	
	// Generate code
	produceInt(n);
	produceImp(n);

	// Generate trailers
	produceTrailerInt();
	produceTrailerImp();

	ofileImp.close();
	ofileInt.close();

	return err;
}
