/**
 * Tencent is pleased to support the open source community by making Tars available.
 *
 * Copyright (C) 2016THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License"); you may not use this file except 
 * in compliance with the License. You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed 
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 
 * specific language governing permissions and limitations under the License.
 */
#include "tars2python.h"
// begin modify
#include "util/tc_md5.h"
#include "util/tc_file.h"
#include "util/tc_common.h"
#include <string>
// end modify

#define TAB g_parse->getTab()
#define INC_TAB g_parse->incTab()
#define DEL_TAB g_parse->delTab()

const string MAP_PREFIX = "mapcls_";
const string VEC_PREFIX = "vctcls_";

string Tars2Python::generatePython(const EnumPtr &pPtr, const std::string &sNamespace)
{
	std::ostringstream s;
	s << TAB << "class " << pPtr->getId() << ":" << endl;
	INC_TAB;

	int nenum = -1;
	vector<TypeIdPtr>& member = pPtr->getAllMemberPtr();
	for (size_t i = 0; i < member.size(); i++)
	{
		if (member[i]->hasDefault())
		{
			nenum = tars::TC_Common::strto<int>(member[i]->def());
		}
		else
		{
			nenum++;
		}

		s << TAB << member[i]->getId()<< " = " << tars::TC_Common::tostr(nenum) << ";" << endl;
	}

	DEL_TAB;

	return s.str();
}

std::string Tars2Python::toFunctionName(const TypeIdPtr & pPtr, const string & sName)
{
	BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr->getTypePtr());
	if (bPtr)
	{
		switch (bPtr->kind())
		{
			case Builtin::KindBool	: 	return "tarscore.boolean";
			case Builtin::KindString:	return "tarscore.string";
			case Builtin::KindByte: 	return "tarscore.int8";
			case Builtin::KindShort:	return "tarscore." + string((bPtr->isUnsigned()?"uint8":"int16"));
			case Builtin::KindInt:		return "tarscore." + string((bPtr->isUnsigned()?"uint16":"int32"));
			case Builtin::KindLong: 	return "tarscore." + string((bPtr->isUnsigned()?"uint32":"int64"));
			case Builtin::KindFloat:	return "tarscore.float";
			case Builtin::KindDouble:	return "tarscore.double";
			default:					assert(false);
		}
	}

	StructPtr sPtr = StructPtr::dynamicCast(pPtr->getTypePtr());
	if (sPtr)
	{
		return sPtr->getId();
	}

	EnumPtr ePtr = EnumPtr::dynamicCast(pPtr->getTypePtr());
	if (ePtr)
	{
		return "tarscore.int32";
	}

	VectorPtr vPtr = VectorPtr::dynamicCast(pPtr->getTypePtr());
	if (vPtr)
	{
		BuiltinPtr vbPtr = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
		if (vbPtr && vbPtr->kind() == Builtin::KindByte)
		{
			return "tarscore.bytes";
		}

		return "value." + VEC_PREFIX + sName;
	}

	MapPtr mPtr = MapPtr::dynamicCast(pPtr->getTypePtr());
	if (mPtr)
	{
		return "value." + MAP_PREFIX + sName;
	}

	assert(false);
	return "";
}

string Tars2Python::getDefault(const TypeIdPtr & pPtr, const std::string &sDefault, const std::string & sCurStruct)
{
	BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr->getTypePtr());
	if (bPtr)
	{
		switch (bPtr->kind())
		{
			case Builtin::KindBool		: return sDefault.empty()?"True": (sDefault == "true"?"True":"False");
			case Builtin::KindString	: return "\"" + tars::TC_Common::replace(sDefault, "\"", "\\\"") +  "\"";
			case Builtin::KindByte		:
			case Builtin::KindShort		:
			case Builtin::KindInt		:
			case Builtin::KindLong		: return sDefault.empty()?"0":sDefault;
			case Builtin::KindFloat		:
			case Builtin::KindDouble	: return sDefault.empty()?"0.0":sDefault;
			default						: assert(false);
		}
	}

	VectorPtr vPtr = VectorPtr::dynamicCast(pPtr->getTypePtr());
	if (vPtr)
	{
		BuiltinPtr vbPtr = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
		if (vbPtr && vbPtr->kind() == Builtin::KindByte)
		{
			return "bytes()";
		}

		return sCurStruct + "." + VEC_PREFIX + pPtr->getId() + "()";
	}

	EnumPtr ePtr = EnumPtr::dynamicCast(pPtr->getTypePtr());
	if (ePtr)
	{
		string sid = ePtr->getSid();
		size_t pos = sid.find_last_of(":");
		return sDefault.empty()?"0":(sid.substr((pos == string::npos)?0:pos + 1) + "." + sDefault);
	}

	StructPtr sPtr = StructPtr::dynamicCast(pPtr->getTypePtr());
	if (sPtr)
	{
		return sPtr->getId() + "()";
	}

	MapPtr mPtr = MapPtr::dynamicCast(pPtr->getTypePtr());
	if (mPtr)
	{
		return sCurStruct + "." + MAP_PREFIX + pPtr->getId() + "()";
	}

	assert(false);
	return sDefault;
}

string Tars2Python::tostrEnum(const EnumPtr &pPtr)
{
	return "tarscore.int8";
}
string Tars2Python::makeDataType(const TypePtr & pPtr)
{
	BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr);
	if (bPtr)
	{
		switch (bPtr->kind())
		{
			case Builtin::KindBool	: 	return "tarscore.boolean";
			case Builtin::KindString:	return "tarscore.string";
			case Builtin::KindByte: 	return "tarscore.int8";
			case Builtin::KindShort:	return "tarscore." + string((bPtr->isUnsigned()?"uint8":"int16"));
			case Builtin::KindInt:		return "tarscore." + string((bPtr->isUnsigned()?"uint16":"int32"));
			case Builtin::KindLong: 	return "tarscore." + string((bPtr->isUnsigned()?"uint32":"int64"));
			case Builtin::KindFloat:	return "tarscore.float";
			case Builtin::KindDouble:	return "tarscore.double";
			default:					assert(false);
		}
	}

	VectorPtr vPtr = VectorPtr::dynamicCast(pPtr);
	if (vPtr)
	{
		BuiltinPtr vbPtr = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
		if (vbPtr && vbPtr->kind() == Builtin::KindByte)
		{
			return "tarscore.bytes";
		}

		return "tarscore.vctclass(" + makeDataType(vPtr->getTypePtr()) + ")";
	}

       EnumPtr ePtr = EnumPtr::dynamicCast(pPtr);
       if(ePtr) return tostrEnum(ePtr);
	
	StructPtr sPtr = StructPtr::dynamicCast(pPtr);
	if (sPtr)
	{
		return sPtr->getId();
	}

	MapPtr mPtr = MapPtr::dynamicCast(pPtr);
	if (mPtr)
	{
		return "tarscore.mapclass(" + makeDataType(mPtr->getLeftTypePtr()) + ", " + makeDataType(mPtr->getRightTypePtr()) + ")";
	}

	assert(false);
	return "";
}

string Tars2Python::generatePython(const StructPtr & pPtr, const NamespacePtr & nPtr)
{
	std::ostringstream s;
	vector<string> key = pPtr->getKey();
	vector<TypeIdPtr> & member = pPtr->getAllMemberPtr();

	s << TAB << "class " << pPtr->getId() << "(tarscore.struct):"<< endl;
	INC_TAB;

	s << TAB << "__tars_class__ = \"" << nPtr->getId() << "." << pPtr->getId() << "\";" << endl;
	for (size_t i = 0, len = member.size(); i < len; i++)
	{
		VectorPtr vPtr = VectorPtr::dynamicCast(member[i]->getTypePtr());
		if (vPtr)
		{
			BuiltinPtr vbPtr = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
			if (vbPtr && vbPtr->kind() == Builtin::KindByte) continue;

			string st = makeDataType(member[i]->getTypePtr());
			s << TAB << VEC_PREFIX << member[i]->getId() << " = " << st << ";" << endl;
		}

		MapPtr mPtr = MapPtr::dynamicCast(member[i]->getTypePtr());
		if (mPtr)
		{
			string st = makeDataType(member[i]->getTypePtr());
			s << TAB << MAP_PREFIX << member[i]->getId() << " = " << st << ";" << endl;
		}
	}

	s << TAB << endl;
	s << TAB << "def __init__(self):" << endl;
	INC_TAB;
	for (size_t i = 0; i < member.size(); i++)
	{
		s << TAB << "self." << member[i]->getId() << " = " << getDefault(member[i], member[i]->def(), pPtr->getId()) << ";" << endl;
	}
	DEL_TAB;

	s << TAB << endl;
	s << TAB << "@staticmethod" << endl;
	s << TAB << "def writeTo(oos, value):" << endl;
	INC_TAB;
	for (size_t i = 0; i < member.size(); i++)
	{
		std::string sFuncName = toFunctionName(member[i], member[i]->getId());
       	s << TAB << "oos.write(" << sFuncName << ", " << member[i]->getTag() << ", value." << member[i]->getId() << ");" << endl;
	}
	DEL_TAB;

	s << TAB << endl;
	s << TAB << "@staticmethod" << endl;
	s << TAB << "def readFrom(ios):" << endl;
	INC_TAB;
	s << TAB << "value = " << pPtr->getId() << "();" << endl;
	for (size_t i = 0; i < member.size(); i++)
	{
		std::string sFuncName = toFunctionName(member[i], member[i]->getId());
		s << TAB << "value." << member[i]->getId() << "= ios.read(" << sFuncName << ", " << member[i]->getTag()
            	<< ", " << (member[i]->isRequire()?"True":"False") << ", value." << member[i]->getId() << ");" << endl;
	}
	s << TAB << "return value;" << endl;
	DEL_TAB;

	DEL_TAB;
	return s.str();
}

string Tars2Python::makeParams(const OperationPtr &pPtr)
{
	 ostringstream str;
	 vector<ParamDeclPtr>& vParamDecl = pPtr->getAllParamDeclPtr();

	 for (size_t i = 0; i < vParamDecl.size(); i++)
	 {
		 if (vParamDecl[i]->isOut()) continue;

		 str << vParamDecl[i]->getTypeIdPtr()->getId() << ", ";
	 }

	 str << "context = ServantProxy.mapcls_context()";

	 return str.str();
}

#if 0
std::string Tars2Python::innerDataType(const TypeIdPtr & pPtr)
{
	BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr->getTypePtr());
	if (bPtr)
	{
		switch (bPtr->kind())
		{
			case Builtin::KindBool	: 	return "tarscore.boolean";
			case Builtin::KindString:	return "tarscore.string";
			case Builtin::KindByte: 	return "tarscore.int8";
			case Builtin::KindShort:	return "tarscore." + string((bPtr->isUnsigned()?"uint8":"int16"));
			case Builtin::KindInt:		return "tarscore." + string((bPtr->isUnsigned()?"uint16":"int32"));
			case Builtin::KindLong: 	return "tarscore." + string((bPtr->isUnsigned()?"uint32":"int64"));
			case Builtin::KindFloat:	return "tarscore.float";
			case Builtin::KindDouble:	return "tarscore.double";
			default:					assert(false);
		}
	}

	StructPtr sPtr = StructPtr::dynamicCast(pPtr->getTypePtr());
	if (sPtr)
	{
		return sPtr->getId();
	}

	EnumPtr ePtr = EnumPtr::dynamicCast(pPtr->getTypePtr());
	if (ePtr)
	{
		return "tarscore.int32";
	}

	VectorPtr vPtr = VectorPtr::dynamicCast(pPtr->getTypePtr());
	if (vPtr)
	{
		BuiltinPtr vbPtr = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
		if (vbPtr && vbPtr->kind() == Builtin::KindByte)
		{
			return "tarscore.bytes";
		}

		return "tarscore.vctclass(" + innerDataType(vPtr->getTypePtr()) + ")";
	}

	MapPtr mPtr = MapPtr::dynamicCast(pPtr->getTypePtr());
	if (mPtr)
	{
		return "tarscore.mapclass(" + innerDataType() + ")";
	}

	assert(false);
	return "";
}

#endif

string Tars2Python::makeOperations(const OperationPtr &pPtr)
{
	ostringstream str;
	vector<ParamDeclPtr>& vParamDecl = pPtr->getAllParamDeclPtr();

	str << TAB << "oos = tarscore.TarsOutputStream();" << endl;
    for(size_t i = 0; i < vParamDecl.size(); i++)
    {
        if(vParamDecl[i]->isOut()) continue;
        str << TAB << "oos.write(" << makeDataType(vParamDecl[i]->getTypeIdPtr()->getTypePtr()) << ", " << (i + 1) << ", " << vParamDecl[i]->getTypeIdPtr()->getId() << ");" << endl;
    }
    str << endl;

    str << TAB << "rsp = self.tars_invoke(ServantProxy.TARSNORMAL, \"" << pPtr->getId() << "\", oos.getBuffer(), context, None);" << endl << endl;

    if(vParamDecl.size() > 0 || pPtr->getReturnPtr()->getTypePtr())
    {
    	str << TAB << "ios = tarscore.TarsInputStream(rsp.sBuffer);" << endl;

    	string ret;
    	if (pPtr->getReturnPtr()->getTypePtr())
    	{
    		str << TAB << "ret = ios.read(" << makeDataType(pPtr->getReturnPtr()->getTypePtr()) << ", 0, True);" << endl;
    		ret += "ret, ";
    	}

        for(size_t i = 0; i < vParamDecl.size(); i++)
        {
            if(!vParamDecl[i]->isOut()) continue;
            str << TAB << vParamDecl[i]->getTypeIdPtr()->getId() << " = ios.read(" << makeDataType(vParamDecl[i]->getTypeIdPtr()->getTypePtr()) << ", " << (i + 1) << ", True);" << endl;
			ret += vParamDecl[i]->getTypeIdPtr()->getId() + ", ";
        }

        str << endl;

        if (!ret.empty())
        {
        	str << TAB << "return (" << ret.substr(0, ret.find_last_of(",")) << ");" << endl;
        }
    }

    return str.str();
}

string Tars2Python::generatePython(const InterfacePtr &pPtr)
{
	std::ostringstream str;
	str << "#proxy for client" << endl;

	str << TAB << "class " << pPtr->getId() << "Proxy(ServantProxy):" << endl;
	INC_TAB;

	vector<OperationPtr>& vOperation = pPtr->getAllOperationPtr();
	for(size_t i = 0; i < vOperation.size(); i++)
	{
		str << TAB << "def " << vOperation[i]->getId() << "(self, " << makeParams(vOperation[i]) << "):" << endl;
		INC_TAB;
		str << makeOperations(vOperation[i]) << endl;
		DEL_TAB;
	}

	DEL_TAB;
	return str.str();
}


string Tars2Python::generatePython(const NamespacePtr &pPtr)
{
	std::ostringstream str;

	/*
	vector<ConstPtr> &cs = pPtr->getAllConstPtr();
	for (size_t i = 0; i < cs.size(); i++)
	{
		str << generatePython(cs[i], pPtr->getId()) << endl;
	}
	str << endl;
	*/

	vector<EnumPtr> & es = pPtr->getAllEnumPtr();
	for (size_t i = 0; i < es.size(); i++)
	{
		str << generatePython(es[i], pPtr->getId()) << endl;
	}

	vector<StructPtr> & ss = pPtr->getAllStructPtr();
	for (size_t i = 0; i < ss.size(); i++)
	{
		str << generatePython(ss[i], pPtr) << endl;
	}

	vector<InterfacePtr> & is = pPtr->getAllInterfacePtr();
    for(size_t i = 0; i < is.size(); i++)
    {
        str << generatePython(is[i]) << endl << endl;
    }

	return str.str();
}

void Tars2Python::generatePython(const ContextPtr &pPtr)
{
	vector<NamespacePtr> namespaces = pPtr->getNamespaces();
	for(size_t i = 0; i < namespaces.size(); i++)
	{
		std::ostringstream str;
		str << "from tars.core import tarscore;" << endl;
		str << "from tars.__rpc import ServantProxy;" << endl;

		vector<string> include = pPtr->getIncludes();
	    for(size_t j = 0; j < include.size(); j++)
	    {
	    	set<string> vec;
 	    	getNamespace(tars::TC_File::excludeFileExt(include[j]) + ".tars", vec);

 	    	for (set<string>::iterator it = vec.begin(); it != vec.end(); it++)
 	    	{
 	    		str << "from " << _packagePrefix << *it << "." << tars::TC_File::excludeFileExt(include[j]) << " import *;" << endl;
 	    	}
	    }
	    str << endl << endl;

		str << generatePython(namespaces[i]) << endl;

		string sPath = getFilePath() + FILE_SEP + namespaces[i]->getId() + FILE_SEP;
		string sFileName = sPath + tars::TC_File::excludeFileExt(tars::TC_File::extractFileName(pPtr->getFileName())) + ".py";
		tars::TC_File::makeDirRecursive(sPath);
		tars::TC_File::save2file(sFileName, str.str());
	}

	makePackages();
}

void Tars2Python::getNamespace(const string & sFile, set<string> & vec)
{
	g_parse->parse(sFile);
	vector<ContextPtr> contexts = g_parse->getContexts();
	for (size_t i = 0; i < contexts.size(); i++)
	{
		if(sFile != contexts[i]->getFileName()) continue;

		vector<NamespacePtr> namespaces = contexts[i]->getNamespaces();
		for (size_t j = 0; j < namespaces.size(); j++)
			vec.insert(namespaces[j]->getId());
	}
}

void Tars2Python::createFile(const string &file)
{
	std::vector<ContextPtr> contexts = g_parse->getContexts();
	for(size_t i = 0; i < contexts.size(); i++)
	{
		if(file == contexts[i]->getFileName())
		{
			generatePython(contexts[i]);
		}
	}
}

void Tars2Python::makePackages()
{
	string root = _packagePrefix.substr(0, _packagePrefix.find("."));
	vector<string> files;
	tars::TC_File::listDirectory(_baseDir + "/" + root, files, true);
	files.push_back(_baseDir + "/" + root);

	for (size_t i = 0; i < files.size(); i++)
	{
		if (tars::TC_File::isFileExist(files[i], S_IFDIR) == true)
		{
			tars::TC_File::save2file(files[i] + FILE_SEP + "__init__.py", "");
		}
	}
}

string Tars2Python::getFilePath() const
{
	return _baseDir + FILE_SEP + tars::TC_Common::replace(_packagePrefix, ".", FILE_SEP);
}
