/**
 * 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.
 */

#ifndef __TC_CONFIG_H_
#define __TC_CONFIG_H_

#include <map>
#include <list>
#include <stack>
#include <vector>
#include "util/tc_ex.h"

namespace tars
{
/////////////////////////////////////////////////
/** 
 * @file  tc_config.h 
 * @brief  配置文件读取类. 
 */
/////////////////////////////////////////////////

/**
* 域分隔符
*/
const char TC_CONFIG_DOMAIN_SEP = '/';

/**
* 参数开始符
*/
const char TC_CONFIG_PARAM_BEGIN = '<';

/**
* 参数结束符
*/
const char TC_CONFIG_PARAM_END = '>';

/**
* @brief 配置文件异常类.
*/
struct TC_Config_Exception : public TC_Exception
{
    TC_Config_Exception(const string &buffer) : TC_Exception(buffer){};
    TC_Config_Exception(const string &buffer, int err) : TC_Exception(buffer, err){};
    ~TC_Config_Exception() throw(){};
};

/**
 * @brief 配置没有文件错误
 */
struct TC_ConfigNoParam_Exception : public TC_Exception
{
    TC_ConfigNoParam_Exception(const string &buffer) : TC_Exception(buffer){};
    ~TC_ConfigNoParam_Exception() throw(){};
};


/** 
* @brief 定义配置文件中的域的类. 
*/
class TC_ConfigDomain
{
public:
    friend class TC_Config;

    /**
    * @brief 构造函数. 
    *  
    * @param sLine  指配置文件中的一行，按行读取
    */
    TC_ConfigDomain(const string &sLine);

    /**
    *  @brief 析够函数.
    */
    ~TC_ConfigDomain();

    /**
     * @brief 拷贝复制.
     *  
     * @param tcd 一个TC_ConfigDomain对象，指配置文件的一个域
     */
    TC_ConfigDomain(const TC_ConfigDomain &tcd);

    /**
     * @brief 赋值. 
     * 
     * @param tcd 一个TC_ConfigDomain对象，指配置文件的一个域
     */
    TC_ConfigDomain& operator=(const TC_ConfigDomain &tcd);


    struct DomainPath
    {
        vector<string>  _domains;
        string          _param;
    };

    /**
     * @brief 解析一个domain 
     * 对形如"/Main/Domain<Name>"的path进行解析，解析的结果为一个 
     * DomainPath 类型，包括路径_domains和路径中对应的配置项_param.
     *  
     * @param path       一个经过处理的字符串，必须符合一定的要求
     * @param bWithParam "/Main/Domain<Name>"时bWithParam为ture 
     *                   "/Main/Domain"时bWithParam为false
     * @return DomainPath 一个DomainPath对象，解析出域中的域名和参数
     */
    static DomainPath parseDomainName(const string& path, bool bWithParam);

    /**
    * @brief 增加子域名称
    * 增加一个子域，name是子域的名字，比如"/Main/Domain" 
    * 再增加一个name为subDomain的子域,变成"/Main/Domain/subDomain" 
    *  
    * @param name               子域的名称 
    * @return TC_ConfigDomain*  指向子域的指针 
    * @throws TC_Config_Exception 
    */
    TC_ConfigDomain* addSubDomain(const string& name);

    /**
    * @brief 递归搜索子域 Sub Domain. 
    *  
    * @param itBegin 迭代器 ，指向_domains的顶部
    * @param itEnd  迭代器 ，指向_domains的底部
    * @return TC_ConfigDomain* 指向目标子域的指针
    */
    TC_ConfigDomain *getSubTcConfigDomain(vector<string>::const_iterator itBegin, vector<string>::const_iterator itEnd);
    const TC_ConfigDomain *getSubTcConfigDomain(vector<string>::const_iterator itBegin, vector<string>::const_iterator itEnd) const;

    /**
    * @brief Get Param Value 获取参数/值对. 
    *  
    * @param sName 配置项名称
    * @return      配置项对应的值
    */
    string getParamValue(const string &sName) const;

    /**
    * @brief Get Param Map 获取map 
    * map是参数/值对，是配置项和配置项的值对应的键值对, 
    * eg. SyncThreadNum = 2
    * @return 一个域中的参数和其对应的值的map 
    */
    const map<string, string>& getParamMap() const { return _param; }

    /**
    * @brief Get Domain Map 获取map. 
    *  
    * @return map
    */
    const map<string, TC_ConfigDomain*>& getDomainMap() const { return _subdomain; }

    /**
    * @brief Set Param Value 设置参数/值对. 
    * 
    * @param sLine 行
    * @return 
    */
    void setParamValue(const string &sLine);

    /**
     * @brief 插入参数，把key里没有的配置项添加到最后 
     *  
     * @param 存配置项和对应配置项值的map
     */
    void insertParamValue(const map<string, string> &m);

    /**
    * @brief Destroy 释放. 
    *  
    * @return 无
    */
    void destroy();

    /**
    * @brief Get Name 获取域名称
    * @return 域名称
    */
    string getName() const;

    /**
     * @brief 设置域名称 
     * 
     * @param 域名称
     */
    void setName(const string& name);

    /**
     * @brief 按照文件中的顺序获取key
     * 
     * @return vector<string>存放key的vector
     */
    vector<string> getKey() const;

    /**
     * @brief 按照文件中的顺序获取line
     * 
     * @return vector<string>存放key的vector
     */
    vector<string> getLine() const;

    /**
     * @brief 按照文件中的顺序获取子Domain
     * 
     * @return vector<string>存放域名字的vector
     */
    vector<string> getSubDomain() const;

    /**
    * @brief 转换成配置文件的字符串格式. 
    *  
    * @param i  tab的层数
    * @return   string类型配置字符串
    */
    string tostr(int i) const;

    /**
     * @brief 克隆.
     * 
     * @return TC_ConfigDomain*
     */
    TC_ConfigDomain* clone() const 
    { 
        return new TC_ConfigDomain(*this); 
    }

    /**
     * @brief 转义. 
     *  
     * @param name 
     * @return string
     */
    static string parse(const string& s);

    /**
     * @brief 方向转义. 
     *  
     * @param s 
     * @return string
     */
    static string reverse_parse(const string &s);

    /**
    * @brief 设置参数/值对 
    *  
    * @param sName  配置项名称
    * @param sValue 配置项的值
    * @return 无
    */
    void setParamValue(const string &sName, const string &sValue);

protected:

    /**
    * 域名称
    */
    string                  _name;

    /**
    * name/value对，配置项的名称和配置项的值
    */
    map<string, string>     _param;

    /**
     * key也就是配置项的插入顺序
     */
    vector<string>          _key;

    /**
    * 子域
    */
    map<string, TC_ConfigDomain*>        _subdomain;

    /**
     * 域的插入顺序
     */
    vector<string>          _domain;
 
    /**
     * 整行的配置列表
     */
    vector<string>          _line;
};

/**
 * @brief 定义配置文件的类（兼容wbl模式）.
 
 * 支持从string中解析配置文件；
 
 * 支持生成配置文件；
 
 * 解析出错抛出异常；
 
 * 采用[]获取配置，如果无配置则抛出异常；
 
 * 采用get获取配置，不存在则返回空；
 
 * 读取配置文件是线程安全的，insert域等函数非线程安全；
 
 * 例如：
 
 *  <Main>
 *  <Domain>
 *      Name = Value
 *  </Domain>
 *   <Domain1>
 *       Name = Value
 *   </Domain1>
 *  </Main>
 
 *  获取参数:conf["/Main/Domain<Name>"] 获取域Map:
 *   getDomainMap("/Main/Domain", m); m得到Name/Value对
 * 获取域Vector: getDomainVector("/Main", v); v得到Domain列表 可以增加域或域下面的值对
 
 */
class TC_Config
{
public:

    /**
    * @brief 构造函数
    */
    TC_Config();

    /**
     * @brief 拷贝复制.
     *  
     * @param tc为TC_Config类型
     */
    TC_Config(const TC_Config &tc);

    /**
     * @brief 赋值. 
     *  
     * @param tcd 
     * @return TC_Config&
     */
    TC_Config& operator=(const TC_Config &tc);

    /**
    * @brief 解析文件. 
    *  
    * 把fileName对应的文件转化为输入流后调用parse对其进行解析 
    * @param sFileName : 文件名称 
    * @return 无 
    * @throws TC_Config_Exception 
    */
    void parseFile(const string& sFileName);

    /**
    * @brief 解析字符串. 
    *  
    * 把string 类型先转化为输入流后调用parse对其进行解析 
    * @return void 
    * @throws TC_Config_Exception 
    */
    void parseString(const string& buffer);

    /**
    * @brief 获取值, 如果没有则抛出异常， 
    * 对于型如/Main/Domain<Param>的字符串，获取域/Main/Domain下的配置项名字为Param的值
    * @param sName 参数名称,例如: /Main/Domain<Param>
    * @return      配置项对应的值 
    *  @throws     TC_Config_Exception 
    */
    string operator[](const string &sName) const;

    /**
     * @brief 获取值, 注意如果没有不抛出异常,返回空字符串. 
     * 对于型如/Main/Domain<Param>的字符串，获取域/Main/Domain下的配置项名字为Param的值
     * @param sName 参数名称,  例如: /Main/Domain<Param>
     * @return     配置项对应的值
     */
    string get(const string &sName, const string &sDefault="") const;

    /**
	 * @brief 设置值. 
	 * @param sName 参数名称,  例如: /Main/Domain<Param>
     */
    void set(const string &sName, const string &value);

    /**
    * @brief GetDomainParamMap获取域下面的参数值对. 
    *  
    * @param path 域名称, 域标识, 例如: /Main/Domain
    * @param m    map<string, string>类型，域内的map列表
    * @return     bool, 返回域下面的参数值对
    */
    bool getDomainMap(const string &path, map<string, string> &m) const;

    /**
     * @brief 获取域下面的参数值对,不存则返回空map. 
     *  
     * @param path 域名称, 域标识, 例如: /Main/Domain 
     * @return     map<string,string>,对应域下面的所有参数值对
     */
    map<string, string> getDomainMap(const string &path) const;

    /**
     * @brief 获取域下面的所有key, 按照文件的行顺序返回 
     * @param path 域名称, 域标识, 例如: /Main/Domain 
     * @return     vector<string>，某个标识下的所有key
     */
    vector<string> getDomainKey(const string &path) const;

    /**
     * @brief 获取域下面的所有非域行, 按照文件的行顺序返回 
     *        区别于getDomainKey只能获取到key,该接口对带"="的配置也整行返回
     * @param path 域名称, 域标识, 例如: /Main/Domain 
     * @return     vector<string>，某个标识下的所有非域行
     */
    vector<string> getDomainLine(const string &path) const;

    /**
    * @brief getDomainMap 获取域下面的子域. 
    *  
    * @param path  域名称, 域标识, 例如: /Main/Domain
    * @param v     要获取的子域名字
    * @param       vector<string>域下面的域名称
    * @return      成功获取返回true，否则返回false
    */
    bool getDomainVector(const string &path, vector<string> &v) const;

    /**
     * @brief 获取域下面的子域, 
     *        不存在则返回空vector按照在文件中的顺序返回
     * @param path 域名称, 域标识, 例如: /Main/Domain 
     * @return     vector<string>目标域下面的子域
     */
    vector<string> getDomainVector(const string &path) const;

    /**
     * @brief 是否存在域. 
     *  
     * @param path 域名称, 域标识, 例如: /Main/Domain 
     * @return     存在返回true，否则返回false
     */
    bool hasDomainVector(const string &path) const;

    /** 
     * @brief 增加域，在当前域下面增加域, 如果已经存在sAddDomain域, 
     *        则认为成功
     * @param sCurDomain 域标识符, 例如:/Main/Domain
     * @param sAddDomain 需要增加的域名称: 例如: sCurDomain
     * @param bCreate    sCurDomain域不存在的情况下, 是否自动创建
     * @return           0-成功增加, 1-sCurDomain不存在 
     */
    int insertDomain(const string &sCurDomain, const string &sAddDomain, bool bCreate);


    /** 
     * @brief 增加参数，即配置项，当前域下面增加配置项参数, 
     *        如果已经有相关参数, 则忽略(不替换)
     * @param sCurDomain  域标识符, 例如:/Main/Domain
     * @param m           map类型，存参数值对
     * @param bCreate     sCurDomain域不存在的情况下, 是否自动创建
     * @return            0: 成功, 1:sCurDomain不存在 
     */
    int insertDomainParam(const string &sCurDomain, const map<string, string> &m, bool bCreate);

    /**
     * @brief 合并配置文件到当前配置文件. 
     *  
     * @param cf
     * @param bUpdate true-冲突项更新本配置, false-冲突项不更新
     */
    void joinConfig(const TC_Config &cf, bool bUpdate);

    /**
    * @brief 转换成配置文件的字符串格式. 
    *  
    * @return 配置字符串
    */
    string tostr() const;

protected:
    /**
    * @brief Parse输入流， 
    * 最终把输入流解析成为一个TC_ConfigDomain装入stack中 
    * @param  要解析的输入流，按行解析 
    * @throws TC_Config_Exception
    * @return
    */
    void parse(istream &is);

    /**
    * @brief create New Domain 生成子域. 
    *  
    * @param sName 域名称
    * @return      指向新生成的子域的指针 
    * @throws TC_Config_Exception 
    */
    TC_ConfigDomain *newTcConfigDomain(const string& sName);

    /**
    * @brief Search Domain 搜索域. 
    *  
    * @param sDomainName 参数名称,支持子域搜索
    * @return value
    */
    TC_ConfigDomain *searchTcConfigDomain(const vector<string>& domains);
    const TC_ConfigDomain *searchTcConfigDomain(const vector<string>& domains) const;

protected:

    /**
    * 根domain
    */
    TC_ConfigDomain _root;
};

}
#endif //_TC_CONFIG_H_
