/*
 * Copyright (c) 2016 The ZLToolKit project authors. All Rights Reserved.
 *
 * This file is part of ZLToolKit(https://github.com/ZLMediaKit/ZLToolKit).
 *
 * Use of this source code is governed by MIT license that can be found in the
 * LICENSE file in the root of the source tree. All contributing project authors
 * may be found in the AUTHORS file in the root of the source tree.
 */

#ifndef SQL_SQLCONNECTION_H_
#define SQL_SQLCONNECTION_H_

#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <deque>
#include <sstream>
#include <iostream>
#include <stdexcept>
#include "logger.h"
#include "util.h"
#include <mysql.h>

#if defined(_WIN32)
#pragma  comment (lib,"libmysql") 
#endif

namespace toolkit {

/**
 * 数据库异常类
 * Database exception class
 
 * [AUTO-TRANSLATED:f92df85e]
 */
class SqlException : public std::exception {
public:
    SqlException(const std::string &sql, const std::string &err) {
        _sql = sql;
        _err = err;
    }

    virtual const char *what() const noexcept {
        return _err.data();
    }

    const std::string &getSql() const {
        return _sql;
    }

private:
    std::string _sql;
    std::string _err;
};

/**
 * mysql连接
 * MySQL connection
 
 * [AUTO-TRANSLATED:a2deb48d]
 */
class SqlConnection {
public:
    /**
     * 构造函数
     * @param url 数据库地址
     * @param port 数据库端口号
     * @param dbname 数据库名
     * @param username 用户名
     * @param password 用户密码
     * @param character 字符集
     * Constructor
     * @param url Database address
     * @param port Database port number
     * @param dbname Database name
     * @param username Username
     * @param password User password
     * @param character Character set
     
     * [AUTO-TRANSLATED:410a33a6]
     */
    SqlConnection(const std::string &url, unsigned short port,
                  const std::string &dbname, const std::string &username,
                  const std::string &password, const std::string &character = "utf8mb4") {
        mysql_init(&_sql);
        unsigned int timeout = 3;
        mysql_options(&_sql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
        if (!mysql_real_connect(&_sql, url.data(), username.data(),
                                password.data(), dbname.data(), port, nullptr, 0)) {
            mysql_close(&_sql);
            throw SqlException("mysql_real_connect", mysql_error(&_sql));
        }
        //兼容bool与my_bool  [AUTO-TRANSLATED:7d8d4190]
        //Compatible with bool and my_bool
        uint32_t reconnect = 0x01010101;
        mysql_options(&_sql, MYSQL_OPT_RECONNECT, &reconnect);
        mysql_set_character_set(&_sql, character.data());
    }

    ~SqlConnection() {
        mysql_close(&_sql);
    }

    /**
     * 以printf样式执行sql,无数据返回
     * @param rowId insert时的插入rowid
     * @param fmt printf类型fmt
     * @param arg 可变参数列表
     * @return 影响行数
     * Execute SQL in printf style, no data returned
     * @param rowId Insert rowid when inserting
     * @param fmt printf type fmt
     * @param arg Variable argument list
     * @return Affected rows
     
     * [AUTO-TRANSLATED:7c72ab80]
     */
    template<typename Fmt, typename ...Args>
    int64_t query(int64_t &rowId, Fmt &&fmt, Args &&...arg) {
        check();
        auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
        if (doQuery(tmp)) {
            throw SqlException(tmp, mysql_error(&_sql));
        }
        rowId = mysql_insert_id(&_sql);
        return mysql_affected_rows(&_sql);
    }

    /**
     * 以printf样式执行sql,并且返回list类型的结果(不包含数据列名)
     * @param rowId insert时的插入rowid
     * @param ret 返回数据列表
     * @param fmt printf类型fmt
     * @param arg 可变参数列表
     * @return 影响行数
     * Execute SQL in printf style, and return list type result (excluding column names)
     * @param rowId Insert rowid when inserting
     * @param ret Returned data list
     * @param fmt printf type fmt
     * @param arg Variable argument list
     * @return Affected rows
     
     * [AUTO-TRANSLATED:57baa44e]
     */
    template<typename Fmt, typename ...Args>
    int64_t query(int64_t &rowId, std::vector<std::vector<std::string> > &ret, Fmt &&fmt, Args &&...arg) {
        return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
    }

    template<typename Fmt, typename... Args>
    int64_t query(int64_t &rowId, std::vector<std::list<std::string>> &ret, Fmt &&fmt, Args &&...arg) {
        return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
    }

    template<typename Fmt, typename ...Args>
    int64_t query(int64_t &rowId, std::vector<std::deque<std::string> > &ret, Fmt &&fmt, Args &&...arg) {
        return queryList(rowId, ret, std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
    }

    /**
     * 以printf样式执行sql,并且返回Map类型的结果(包含数据列名)
     * @param rowId insert时的插入rowid
     * @param ret 返回数据列表
     * @param fmt printf类型fmt
     * @param arg 可变参数列表
     * @return 影响行数
     * Execute SQL in printf style, and return Map type result (including column names)
     * @param rowId Insert rowid when inserting
     * @param ret Returned data list
     * @param fmt printf type fmt
     * @param arg Variable argument list
     * @return Affected rows
     
     * [AUTO-TRANSLATED:a12a695e]
     */
    template<typename Map, typename Fmt, typename ...Args>
    int64_t query(int64_t &rowId, std::vector<Map> &ret, Fmt &&fmt, Args &&...arg) {
        check();
        auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
        if (doQuery(tmp)) {
            throw SqlException(tmp, mysql_error(&_sql));
        }
        ret.clear();
        MYSQL_RES *res = mysql_store_result(&_sql);
        if (!res) {
            rowId = mysql_insert_id(&_sql);
            return mysql_affected_rows(&_sql);
        }
        MYSQL_ROW row;
        unsigned int column = mysql_num_fields(res);
        MYSQL_FIELD *fields = mysql_fetch_fields(res);
        while ((row = mysql_fetch_row(res)) != nullptr) {
            ret.emplace_back();
            auto &back = ret.back();
            for (unsigned int i = 0; i < column; i++) {
                back[std::string(fields[i].name, fields[i].name_length)] = (row[i] ? row[i] : "");
            }
        }
        mysql_free_result(res);
        rowId = mysql_insert_id(&_sql);
        return mysql_affected_rows(&_sql);
    }

    std::string escape(const std::string &str) {
        char *out = new char[str.length() * 2 + 1];
        mysql_real_escape_string(&_sql, out, str.c_str(), str.size());
        std::string ret(out);
        delete[] out;
        return ret;
    }

    template<typename ...Args>
    static std::string queryString(const char *fmt, Args &&...arg) {
        char *ptr_out = nullptr;
        if (asprintf(&ptr_out, fmt, arg...) > 0 && ptr_out) {
            std::string ret(ptr_out);
            free(ptr_out);
            return ret;
        }
        return "";
    }

    template<typename ...Args>
    static std::string queryString(const std::string &fmt, Args &&...args) {
        return queryString(fmt.data(), std::forward<Args>(args)...);
    }

    static const char *queryString(const char *fmt) {
        return fmt;
    }

    static const std::string &queryString(const std::string &fmt) {
        return fmt;
    }

private:
    template<typename List, typename Fmt, typename... Args>
    int64_t queryList(int64_t &rowId, std::vector<List> &ret, Fmt &&fmt, Args &&...arg) {
        check();
        auto tmp = queryString(std::forward<Fmt>(fmt), std::forward<Args>(arg)...);
        if (doQuery(tmp)) {
            throw SqlException(tmp, mysql_error(&_sql));
        }
        ret.clear();
        MYSQL_RES *res = mysql_store_result(&_sql);
        if (!res) {
            rowId = mysql_insert_id(&_sql);
            return mysql_affected_rows(&_sql);
        }
        MYSQL_ROW row;
        unsigned int column = mysql_num_fields(res);
        while ((row = mysql_fetch_row(res)) != nullptr) {
            ret.emplace_back();
            auto &back = ret.back();
            for (unsigned int i = 0; i < column; i++) {
                back.emplace_back(row[i] ? row[i] : "");
            }
        }
        mysql_free_result(res);
        rowId = mysql_insert_id(&_sql);
        return mysql_affected_rows(&_sql);
    }

    inline void check() {
        if (mysql_ping(&_sql) != 0) {
            throw SqlException("mysql_ping", "Mysql connection ping failed");
        }
    }

    int doQuery(const std::string &sql) {
        return mysql_query(&_sql, sql.data());
    }

    int doQuery(const char *sql) {
        return mysql_query(&_sql, sql);
    }

private:
    MYSQL _sql;
};

} /* namespace toolkit */
#endif /* SQL_SQLCONNECTION_H_ */
