/**
 * 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 "tars2cs.h"
#include "util/tc_md5.h"
#include "util/tc_file.h"
#include "util/tc_common.h"
#include <string>
#include <string.h>

#define TARS_PACKAGE     "Wup.Tars"

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

//////////////////////////////////////////////////////////////////////////////////
string Tars2Cs::toTypeInit(const TypePtr &pPtr) const
{
    BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr);
    if (bPtr)
    {
        switch (bPtr->kind())
        {
            case Builtin::KindBool:     return "false;";
            case Builtin::KindByte:     return "0;";
            case Builtin::KindShort:    return "0;";
            case Builtin::KindInt:      return "0;";
            case Builtin::KindLong:     return "0L;";
            case Builtin::KindFloat:    return "0.0f;";
            case Builtin::KindDouble:   return "0.0;";
            case Builtin::KindString:   return "\"\";";
            default:                    return "";
        }
    }

    VectorPtr vPtr = VectorPtr::dynamicCast(pPtr);
    if (vPtr)
    {
        string sType;
        size_t iPosBegin, iPosEnd;
        sType = tostr(vPtr->getTypePtr());
        if ((iPosBegin = sType.find("<")) != string::npos && (iPosEnd = sType.rfind(">")) != string::npos)
        {
            sType = sType.substr(0, iPosBegin) +  sType.substr(iPosEnd+1);
        }
        sType = tars::TC_Common::replace(sType, "[]" , "[1]");
        return "(" + tostr(vPtr->getTypePtr()) + "[]) new " + sType + "[1];";;
    }

    MapPtr mPtr = MapPtr::dynamicCast(pPtr);
    if (mPtr) return "new " + tostrMap(mPtr, true) + "();";

    StructPtr sPtr = StructPtr::dynamicCast(pPtr);
    if (sPtr) return "new " + tostrStruct(sPtr) + "();";

    EnumPtr ePtr = EnumPtr::dynamicCast(pPtr);
    if (ePtr) return "0;";

    return "";
}

string Tars2Cs::toObjStr(const TypePtr &pPtr) const
{
    string sType = tostr(pPtr);

    if (sType == "bool") return "bool";
    if (sType == "byte")    return "byte";
    if (sType == "short" )  return "short";
    if (sType == "int" )    return "int";
    if (sType == "long" )   return "long";
    if (sType == "float" )  return "float";
    if (sType == "double" ) return "double";

    return sType;
}

string Tars2Cs::tostr(const TypePtr &pPtr) const
{
    BuiltinPtr bPtr = BuiltinPtr::dynamicCast(pPtr);
    if (bPtr) return tostrBuiltin(bPtr);

    VectorPtr vPtr = VectorPtr::dynamicCast(pPtr);
    if (vPtr) return tostrVector(vPtr);

    MapPtr mPtr = MapPtr::dynamicCast(pPtr);
    if (mPtr) return tostrMap(mPtr);

    StructPtr sPtr = StructPtr::dynamicCast(pPtr);
    if (sPtr) return tostrStruct(sPtr);

    EnumPtr ePtr = EnumPtr::dynamicCast(pPtr);
    if (ePtr) return tostrEnum(ePtr);

    if (!pPtr) return "void";

    assert(false);
    return "";
}

/*******************************BuiltinPtr********************************/
string Tars2Cs::tostrBuiltin(const BuiltinPtr &pPtr) const
{
    string s;

    switch (pPtr->kind())
    {
        case Builtin::KindBool:     s = "bool";  break;
        case Builtin::KindByte:     s = "byte";     break;
        case Builtin::KindShort:    s = "short";    break;
        case Builtin::KindInt:      s = "int";      break;
        case Builtin::KindLong:     s = "long";     break;
        case Builtin::KindFloat:    s = "float";    break;
        case Builtin::KindDouble:   s = "double";   break;
        case Builtin::KindString:   s = "string";   break;
        case Builtin::KindVector:   s = "System.Collections.Generic.List";  break;
        case Builtin::KindMap:      s = "System.Collections.Generic.Dictionary";    break;
        default:                    assert(false);  break;
    }

    return s;
}

string Tars2Cs::tostrVector(const VectorPtr &pPtr) const
{
    string s = "System.Collections.Generic.List" + string("<") + toObjStr(pPtr->getTypePtr()) + ">";
    return s;
}

string Tars2Cs::tostrMap(const MapPtr &pPtr, bool bNew) const
{
    string s;
    if (!bNew)
    {
        s = "System.Collections.Generic.Dictionary";
    }
    else
    {
        s = "System.Collections.Generic.Dictionary";
    }
    s += string("<") + toObjStr(pPtr->getLeftTypePtr()) + ", " + toObjStr(pPtr->getRightTypePtr()) + ">";

    return s;
}

string Tars2Cs::tostrStruct(const StructPtr &pPtr) const
{
    return tars::TC_Common::replace(pPtr->getSid(), "::", ".").substr(tars::TC_Common::replace(pPtr->getSid(), "::", ".").find(".")+1);
}

string Tars2Cs::tostrEnum(const EnumPtr &pPtr) const
{
    return "int";
}

/******************************StructPtr***************************************/
string Tars2Cs::generateCs(const StructPtr &pPtr, const NamespacePtr &nPtr) const
{
    ostringstream s;
    s << g_parse->printHeaderRemark();

    vector<string> key = pPtr->getKey();
    vector<TypeIdPtr>& member = pPtr->getAllMemberPtr();

    s << TAB << "using System;"<<endl;
    s << TAB << "namespace "<< nPtr->getId()<< endl;
    s << "{"<<endl;
    INC_TAB;
    s << endl;

    s << TAB << "public sealed class " << pPtr->getId() << " : " TARS_PACKAGE ".TarsStruct"<< endl;
    s << TAB << "{" << endl;
    INC_TAB;

    for (size_t i = 0; i < member.size(); i++)
    {
        string sDefault;
        if (member[i]->hasDefault())
        {
            BuiltinPtr bPtr  = BuiltinPtr::dynamicCast(member[i]->getTypePtr());

            if (bPtr && bPtr->kind() == Builtin::KindString)
            {
                sDefault = tars::TC_Common::replace(member[i]->def(), "\"", "\\\"");
                sDefault = " = \"" + sDefault + "\"";
            }
            else
            {
                sDefault = " = " + member[i]->def();
            }
            
            s << TAB << tostr(member[i]->getTypePtr()) << " _" << member[i]->getId() << sDefault << ";" << endl;
            s << TAB << "public " << tostr(member[i]->getTypePtr()) << " "  << member[i]->getId() << endl;
            s << TAB << "{" << endl;

            INC_TAB;
            s << TAB << "get" << endl;
            s << TAB << "{" << endl;
            INC_TAB;
            s << TAB << " return _" << member[i]->getId() << ";" << endl;
            DEL_TAB;
            s << TAB << "}" << endl;
            s << TAB << "set" << endl;
            s << TAB << "{" << endl;
            INC_TAB;
            s << TAB << "_" << member[i]->getId() << " = value; " << endl;
            DEL_TAB;
            s << TAB << "}" << endl;
            DEL_TAB;
            
            s << TAB << "}" << endl;
        }
        else
        {
            sDefault = " {get; set;} ";
            s << TAB << "public " << tostr(member[i]->getTypePtr()) << " " << member[i]->getId() << sDefault << endl;
        }

        s << endl;
    }

    //writeTo()
    s << TAB << "public override void WriteTo(" TARS_PACKAGE ".TarsOutputStream _os)" << endl;
    s << TAB << "{" << endl;
    INC_TAB;
    for (size_t i = 0; i < member.size(); i++)
    {
        s << TAB << "_os.Write(" << member[i]->getId()
        << ", " << member[i]->getTag() << ");" << endl;
    }
    DEL_TAB;
    s << TAB << "}" << endl;
    s << endl;

    //readFrom()
    s << TAB << "public override void ReadFrom(" TARS_PACKAGE ".TarsInputStream _is)" << endl;
    s << TAB << "{" << endl;
    INC_TAB;
    for (size_t i = 0; i < member.size(); i++)
    {
        s << TAB << member[i]->getId() << " = (" + tostr(member[i]->getTypePtr()) + ")"
            << " _is.Read(" << member[i]->getId() << ", " << member[i]->getTag() << ", " 
            << (member[i]->isRequire() ? "true" : "false") << ");" << endl << endl;
    }
    DEL_TAB;
    s << TAB << "}" << endl;
    s << endl;

    //display()
    s << TAB << "public override void Display(System.Text.StringBuilder _os, int _level)" << endl;
    s << TAB << "{" << endl;
    INC_TAB;
    s << TAB << TARS_PACKAGE ".TarsDisplayer _ds = new " TARS_PACKAGE ".TarsDisplayer(_os, _level);" << endl;
    for (size_t i = 0; i < member.size(); i++)
    {
        s << TAB << "_ds.Display(" << member[i]->getId() << ", \"" << member[i]->getId() << "\");" << endl;
    }
    DEL_TAB;
    s << TAB << "}" << endl;
    s << endl;

    DEL_TAB;
    s << TAB << "}" << endl;
    DEL_TAB; 
    s << TAB << "}" << endl;
    s << endl;

    string fileCs  = getFilePath(nPtr->getId()) + pPtr->getId() + ".cs";
    tars::TC_File::makeDirRecursive(getFilePath(nPtr->getId()));
    tars::TC_File::save2file(fileCs, s.str());

    return s.str();
}

/******************************ConstPtr***************************************/
void Tars2Cs::generateCs(const vector<EnumPtr> &es,const vector<ConstPtr> &cs,const NamespacePtr &nPtr) const
{
    if (es.size()==0 && cs.size()==0)
    {
        return;
    }
    ostringstream s;
    s << g_parse->printHeaderRemark();

    s << TAB << "using System;"<<endl;
    s << TAB << "namespace " << nPtr->getId()<< endl;
    s << "{"<<endl;
    INC_TAB;

    if (cs.size()>0)
    {
        s << TAB << "class Const " << endl;
        s << TAB << "{" << endl;
        INC_TAB;
        for (size_t i = 0; i < cs.size(); i++)
        {
            if (cs[i]->getConstGrammarPtr()->t == ConstGrammar::STRING)
            {
                string tmp = tars::TC_Common::replace(cs[i]->getConstGrammarPtr()->v, "\"", "\\\"");
                s  << TAB << "public const "<< tostr(cs[i]->getTypeIdPtr()->getTypePtr()) << " "<<cs[i]->getTypeIdPtr()->getId()<< " = \""
                << tmp << "\";"<< endl;
            }
            else if (tostr(cs[i]->getTypeIdPtr()->getTypePtr()) == "long")
            {
                s  << TAB << "public const "<< tostr(cs[i]->getTypeIdPtr()->getTypePtr()) << " "<<cs[i]->getTypeIdPtr()->getId()<< " = "
                << cs[i]->getConstGrammarPtr()->v << "L;" << endl;
            }
            else
            {
                s  << TAB << "public const "<< tostr(cs[i]->getTypeIdPtr()->getTypePtr()) <<" "<<cs[i]->getTypeIdPtr()->getId()<< " = "
                << cs[i]->getConstGrammarPtr()->v << ";" << endl;
            }
        }
        DEL_TAB;
        s << TAB << "}" << endl;
    }
    if (es.size()>0)
    {
        for (size_t i = 0; i < es.size(); i++)
        {
            s << TAB << "public enum "<<es[i]->getId()<<endl;
            s<< TAB <<"{"<<endl;
            INC_TAB;
            vector<TypeIdPtr>& member = es[i]->getAllMemberPtr();
            for (size_t i = 0; i < member.size(); i++)
            {
                s << TAB << member[i]->getId();
				if(member[i]->hasDefault())
				{
					s << " = " << member[i]->def();
				}

				s <<","<<endl;
            }
            DEL_TAB;
            s<< TAB <<"}"<<endl;
        }
    }
    DEL_TAB;
    s << TAB << "}" << endl;

    string fileCs  = getFilePath(nPtr->getId()) + nPtr->getId()+"_const.cs";
    tars::TC_File::makeDirRecursive(getFilePath(nPtr->getId()));
    tars::TC_File::save2file(fileCs, s.str());

    return;
}

/******************************NamespacePtr***************************************/
void Tars2Cs::generateCs(const NamespacePtr &pPtr) const
{
    vector<StructPtr>       &ss    = pPtr->getAllStructPtr();
    vector<EnumPtr>         &es    = pPtr->getAllEnumPtr();
    vector<ConstPtr>        &cs    = pPtr->getAllConstPtr();

    for (size_t i = 0; i < ss.size(); i++)
    {
        generateCs(ss[i], pPtr);
    }

    generateCs(es,cs,pPtr);

    return;
}

void Tars2Cs::generateCs(const ContextPtr &pPtr) const
{
    ostringstream s;

    vector<NamespacePtr> namespaces = pPtr->getNamespaces();

    for (size_t i = 0; i < namespaces.size(); i++)
    {
        generateCs(namespaces[i]);
    }

}

void Tars2Cs::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())
        {
            generateCs(contexts[i]);
        }
    }
}

void Tars2Cs::setBaseDir(const string &dir)
{
    _baseDir = dir;
}

void Tars2Cs::setBasePackage(const string &prefix)
{
    _packagePrefix = prefix;
    if (_packagePrefix.length() != 0 && _packagePrefix.substr(_packagePrefix.length()-1, 1) != ".")
    {
        _packagePrefix += ".";
    }
}

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

