/**
 * 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 "parse.h"
#include "tars.tab.hpp"
#include <errno.h>
#include <fstream>
#include <string.h>
#include "util/tc_common.h"
#include "util/tc_file.h"

TarsParsePtr g_parse = new TarsParse();

void yyerror(char const *msg)
{
    g_parse->error(msg);
}

TarsParse::TarsParse()
{
    _bWithTars = false;
    _bUseCurrentPath = false;
    _itab = 0;
	_bUseCurrentPathFirst = false;
    initScanner();
}

void TarsParse::setTars(bool bWithTars)
{
    _bWithTars = bWithTars;
}

void TarsParse::setHeader(const string &sHeader)
{
    _sHeader = sHeader;
}

void TarsParse::setCurrentPriority(bool bFlag)
{
	_bUseCurrentPathFirst = bFlag;
}

string TarsParse::getHeader()
{
    if(_sHeader.empty())
        return _sHeader;
    return _sHeader + "/";
}

void TarsParse::clear()
{
    while(!_contexts.empty()) _contexts.pop();
    while(!_contains.empty()) _contains.pop();
    _vcontexts.clear();
    _structs.clear();
    _enums.clear();
    _namespaces.clear();
}

void TarsParse::parse(const string& sFileName)
{
    if (_bUseCurrentPath)
    {
        std::string sTemp = sFileName;
        bool isExist = tars::TC_File::isFileExist(sFileName);

        if (!isExist)
        {
            sTemp = tars::TC_File::extractFileName(sFileName);
        }

        clear();

        _contains.push(new Container(""));
        if(!(yyin = fopen(sTemp.c_str(), "r")))
        {
            error("open file '" + sFileName + "(" + sTemp + ")" + "' error :" + string(strerror(errno)));
        }

        pushFile(sFileName);

        yyparse();

        return ;
    }

	if (_bUseCurrentPathFirst)
    {
        std::string sTemp = tars::TC_File::extractFileName(sFileName);
        bool isExist = tars::TC_File::isFileExist(sTemp);

        if (!isExist)
        {
            sTemp = sFileName;
        }

        clear();

        _contains.push(new Container(""));
        if(!(yyin = fopen(sTemp.c_str(), "r")))
        {
            error("open file '" + sFileName + "(" + sTemp + ")" + "' error :" + string(strerror(errno)));
        }

        pushFile(sFileName);

        yyparse();

        return ;
    }

    {
        clear();

        _contains.push(new Container(""));
        if(!(yyin = fopen(sFileName.c_str(), "r")))
        {
            error("open file '" + sFileName + "' error :" + string(strerror(errno)));
        }

        pushFile(sFileName);

        yyparse();
    }
}

void TarsParse::pushFile(const string &file)
{
    ContextPtr c = new Context(file);
    _contexts.push(c);
    _vcontexts.push_back(c);
}

ContextPtr TarsParse::popFile()
{
    ContextPtr c = _contexts.top();
    _contexts.pop();
    return c;
}

bool TarsParse::getFilePath(const string &s, string &file)
{
    if (_bUseCurrentPath)
    {
        if(s.length() < 2 || s[0] != '\"' || s[s.length()-1] != '\"')
        {
            error("#include need \"FILENAME\"");
        }

        file = s.substr(1, s.length() - 2);

        std::string sTemp = file;
        bool isExist = tars::TC_File::isFileExist(file);

        if (!isExist)
        {
            file = tars::TC_File::extractFileName(file);
        }

        for(size_t i = 0; i < _vcontexts.size(); i++)
        {
            if(_vcontexts[i]->getFileName() == file)
            {
                return false;
            }
        }

        return true;
    }

	if(_bUseCurrentPathFirst)
    {
        if(s.length() < 2 || s[0] != '\"' || s[s.length()-1] != '\"')
        {
            error("#include need \"FILENAME\"");
        }

        file = s.substr(1, s.length() - 2);

        std::string sTemp = tars::TC_File::extractFileName(file);
        bool isExist = tars::TC_File::isFileExist(sTemp);

        if (isExist)
        {
            file = sTemp;
        }

        for(size_t i = 0; i < _vcontexts.size(); i++)
        {
            if(_vcontexts[i]->getFileName() == file)
            {
                return false;
            }
        }

        return true;
    }

    if(s.length() < 2 || s[0] != '\"' || s[s.length()-1] != '\"')
    {
        error("#include need \"FILENAME\"");
    }

    file = s.substr(1, s.length() - 2);

    if (!tars::TC_File::isFileExist(file))
    {
        for (size_t i = 0; i < _vIncludePath.size(); i++) 
        {
            if (tars::TC_File::isFileExist(_vIncludePath[i] + "/" + file))
            {
                file = _vIncludePath[i] + "/" + file;
                break;
            }
        }
    }

    for(size_t i = 0; i < _vcontexts.size(); i++)
    {
        if(_vcontexts[i]->getFileName() == file)
        {
            return false;
        }
    }

    return true;
}

string TarsParse::getCurrFileName()
{
    return _contexts.top()->getFileName();
}

void TarsParse::nextLine()
{
   _contexts.top()->nextLine();
}

ContextPtr TarsParse::currentContextPtr()
{
    return _contexts.top();
}

void TarsParse::error(const string &msg)
{
    cerr <<  _contexts.top()->getFileName() << ": " << _contexts.top()->getCurrLine() << ": error: " << msg << endl;
    exit(-1);
}

int TarsParse::checkKeyword(const string& s)
{
    std::map<std::string, int>::const_iterator it = _keywordMap.find(s);
    if(it != _keywordMap.end())
    {
        return it->second;
    }

    if(!_bWithTars)
    {
        string sPrefix = "tars";
        //不能以tars开头
        if((s.length() >= sPrefix.length()) && (s.substr(0, sPrefix.length()) == sPrefix))
        {
            error("identifier can't start with 'tars'");
        }
    }

    return TARS_IDENTIFIER;
}

void TarsParse::initScanner()
{
    _keywordMap["void"]     = TARS_VOID;
    _keywordMap["struct"]   = TARS_STRUCT;
    _keywordMap["bool"]     = TARS_BOOL;
    _keywordMap["byte"]     = TARS_BYTE;
    _keywordMap["short"]    = TARS_SHORT;
    _keywordMap["int"]      = TARS_INT;
    _keywordMap["double"]   = TARS_DOUBLE;
    _keywordMap["float"]    = TARS_FLOAT;
    _keywordMap["long"]     = TARS_LONG;
    _keywordMap["string"]   = TARS_STRING;
    _keywordMap["vector"]   = TARS_VECTOR;
    _keywordMap["map"]      = TARS_MAP;
    _keywordMap["key"]      = TARS_KEY;
    _keywordMap["routekey"] = TARS_ROUTE_KEY;
    _keywordMap["module"]   = TARS_NAMESPACE;
    _keywordMap["interface"]= TARS_INTERFACE;
    _keywordMap["out"]      = TARS_OUT;
    _keywordMap["require"]  = TARS_REQUIRE;
    _keywordMap["optional"] = TARS_OPTIONAL;
    _keywordMap["false"]    = TARS_FALSE;
    _keywordMap["true"]     = TARS_TRUE;
    _keywordMap["enum"]     = TARS_ENUM;
    _keywordMap["const"]    = TARS_CONST;
    _keywordMap["unsigned"] = TARS_UNSIGNED;
}

string TarsParse::getTab()
{
    ostringstream s;
    for(int i = 0; i < _itab; i++)
    {
        s << "    ";
    }

    return s.str();
}

BuiltinPtr TarsParse::createBuiltin(Builtin::Kind kind,bool isUnsigned)
{
    return new Builtin(kind,isUnsigned);
}

VectorPtr TarsParse::createVector(const TypePtr &ptr)
{
    return new Vector(ptr);
}

MapPtr TarsParse::createMap(const TypePtr &pleft, const TypePtr &pright)
{
    return new Map(pleft, pright);
}

void TarsParse::addNamespacePtr(const NamespacePtr &nPtr)
{
    _namespaces.push_back(nPtr);
}

NamespacePtr TarsParse::findNamespace(const string &id)
{
    for(size_t i = 0; i < _namespaces.size(); i++)
    {
        if(_namespaces[i]->getId() == id)
        {
            return _namespaces[i];
        }
    }

    return NULL;
}

NamespacePtr TarsParse::currentNamespace()
{
    return _namespaces.back();
}

void TarsParse::addStructPtr(const StructPtr &sPtr)
{
    _structs.push_back(sPtr);
}

void TarsParse::addEnumPtr(const EnumPtr &ePtr)
{
    _enums.push_back(ePtr);
}

StructPtr TarsParse::findStruct(const string &sid)
{
    string ssid = sid;

    //在当前namespace中查找
    NamespacePtr np = currentNamespace();
    if(ssid.find("::") == string::npos)
    {
        ssid = np->getId() + "::" + ssid;
    }

    for(size_t i = 0; i < _structs.size(); i++)
    {
        if(_structs[i]->getSid() == ssid)
        {
            return _structs[i];
        }
    }

    return NULL;
}

EnumPtr TarsParse::findEnum(const string &sid)
{
    string ssid = sid;

    //在当前namespace中查找
    NamespacePtr np = currentNamespace();
    if(ssid.find("::") == string::npos)
    {
        ssid = np->getId() + "::" + sid;
    }

    for(size_t i = 0; i < _enums.size(); i++)
    {
        if(_enums[i]->getSid() == ssid)
        {
            return _enums[i];
        }
    }

    return NULL;
}

bool TarsParse::checkEnum(const string &idName)
{
    for(size_t i = 0; i < _enums.size(); i++)
    {
        vector<TypeIdPtr> & list = _enums[i]->getAllMemberPtr();
	
        for (size_t j = 0; j < list.size(); j++)
        {
            if (list[j]->getId() == idName)
            {
                return true;
            }
        }
    }

    return false;
}
void TarsParse::checkConflict(const string &sid)
{
    //是否和枚举重名
    if(findEnum(sid))
    {
        error("conflicts with enum '" + sid + "'");
    }

    //是否和结构重名
    if(findStruct(sid))
    {
        error("conflicts with struct '" + sid + "'");
    }
}

TypePtr TarsParse::findUserType(const string &sid)
{
    StructPtr sPtr = findStruct(sid);
    if(sPtr) return sPtr;

    EnumPtr ePtr = findEnum(sid);
    if(ePtr) return ePtr;

    return NULL;
}

ContainerPtr TarsParse::currentContainer()
{
    return _contains.top();
}

void TarsParse::pushContainer(const ContainerPtr &c)
{
    _contains.push(c);
    NamespacePtr np = NamespacePtr::dynamicCast(c);
    if(np)
    {
        addNamespacePtr(np);
    }
}

ContainerPtr TarsParse::popContainer()
{
    ContainerPtr c = _contains.top();
    _contains.pop();

    return c;
}

void TarsParse::checkTag(int i)
{
    if(i >= 256)
    {
        error("struct memeber tag can't beyond 256");
    }
    if(i < 0)
    {
        error("struct memeber tag can't less then 0");
    }
}

void TarsParse::checkSize(int i)
{
    if(i >= 1024*1024)
    {
        error("struct memeber size can't beyond 1M");
    }
    if(i < 1)
    {
        error("struct memeber size can't less than 1");
    }
}

void TarsParse::checkArrayVaid(TypePtr &tPtr,int size)
{
    checkSize(size);
    //只有string/vector可以为array类型
    //vector不可以嵌套array类型 例如不允许vector<string:8>:2; 

    VectorPtr vPtr = VectorPtr::dynamicCast(tPtr);
    if(vPtr )
    {
        if(vPtr->getTypePtr()->isArray())
        {
            error("fixed array type can not be nested");
        }
        return; 
    }
    BuiltinPtr bPtr  = BuiltinPtr::dynamicCast(tPtr);
    if(bPtr && bPtr->kind()== Builtin::KindString)
    {
       return;
    }
    error("only string or vector can use fix array");
}

void TarsParse::checkPointerVaid(TypePtr &tPtr)
{
    //必须为vector<Byte>类型

    VectorPtr vPtr = VectorPtr::dynamicCast(tPtr);
    if(vPtr)
    {
        BuiltinPtr bPtr  = BuiltinPtr::dynamicCast(vPtr->getTypePtr());
        if( bPtr && bPtr->kind()== Builtin::KindByte) 
        return; 
    }  
    error("only 'byte *' can  be pointer type");
}

void TarsParse::checkConstValue(TypeIdPtr &tPtr, int c)
{
    //只有内建类型才能有缺省值
    BuiltinPtr bPtr  = BuiltinPtr::dynamicCast(tPtr->getTypePtr());
	EnumPtr ePtr 	 = EnumPtr::dynamicCast(tPtr->getTypePtr()); 
    if(!bPtr && !ePtr)
    {
        error("only base/enum type can have default value");
    }

    if (ePtr)
    {
        if (c != ConstGrammar::VALUE && c != ConstGrammar::ENUM)
        {
            error("default value of enum only be int or enum_type");
        }

        return ;
	}

    int b = bPtr->kind();

    if(c == ConstGrammar::VALUE)
    {
        if(b == Builtin::KindBool)
        {
            error("default value of bool can only be true or false");
        }
        if(b == Builtin::KindString)
        {
            error("default value of string can only be \"string\"");
        }
    }
    else if(c == ConstGrammar::BOOL)
    {
        if(b != Builtin::KindBool)
        {
            error("only bool type can be true or false");
        }
    }
    else if(c == ConstGrammar::STRING)
    {
        if(b != Builtin::KindString)
        {
            error("only string type can be \"string\"");
        }
    }
}

string TarsParse::printHeaderRemark()
{
    ostringstream s;
    s << "// **********************************************************************" << endl;
    s << "// This file was generated by a TARS parser!" << endl;
    s << "// TARS version " << TARS_VERSION << "." << endl;
    s << "// **********************************************************************" << endl;
    s << endl;

    return s.str();
}

string TarsParse::getFileName(const string &fileName)
{
	string tmpFileName = fileName;
	string::size_type pos = tmpFileName.rfind('/');
	if(pos != string::npos)
	{
		tmpFileName = tmpFileName.substr(pos + 1);
	}
	else
	{
		pos = tmpFileName.rfind('\\');
		if(pos != string::npos)
		{
			tmpFileName = tmpFileName.substr(pos + 1);
		}
	}

	return tars::TC_File::excludeFileExt(tmpFileName);
}

string TarsParse::replaceFileName(const string &fileName, const string &ext)
{
	return tars::TC_File::excludeFileExt(getFileName(fileName)) + "." + ext;
}

string TarsParse::getAbsoluteFileName(const string &baseDir, const string &fileName)
{
	return baseDir + FILE_SEP + fileName;
}
