// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// 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 "runtime/mysql_table_writer.h"

#include <sstream>
#include "runtime/row_batch.h"
#include "runtime/tuple_row.h"
#include "exprs/expr.h"

namespace doris {

std::string MysqlConnInfo::debug_string() const {
    std::stringstream ss;
    
    ss << "(host=" << host << ",port=" << port << ",user=" << user
        << ",db=" << db << ",passwd=" << passwd << ")";
    return ss.str();
}

MysqlTableWriter::MysqlTableWriter(const std::vector<ExprContext*>& output_expr_ctxs) :
        _output_expr_ctxs(output_expr_ctxs) {
}

MysqlTableWriter::~MysqlTableWriter() {
    if (_mysql_conn) {
        mysql_close(_mysql_conn);
    }
}

Status MysqlTableWriter::open(const MysqlConnInfo& conn_info, const std::string& tbl) {
    _mysql_conn = mysql_init(nullptr);
    if (_mysql_conn == nullptr) {
        return Status("Call mysql_init failed.");
    }

    MYSQL* res = mysql_real_connect(
            _mysql_conn,
            conn_info.host.c_str(), 
            conn_info.user.c_str(),
            conn_info.passwd.c_str(),
            conn_info.db.c_str(),
            conn_info.port,
            NULL,   // unix socket
            0);     // flags
    if (res == nullptr) {
        std::stringstream ss;
        ss << "mysql_real_connect failed because " << mysql_error(_mysql_conn);
        return Status(ss.str());
    }

    // set character
    if (mysql_set_character_set(_mysql_conn, "utf8")) {
        std::stringstream ss;
        ss << "mysql_set_character_set failed because " << mysql_error(_mysql_conn);
        return Status(ss.str());
    }

    _mysql_tbl = tbl;

    return Status::OK;
}

Status MysqlTableWriter::insert_row(TupleRow* row) {
    std::stringstream ss;

    // Construct Insert statement of mysql
    ss << "INSERT INTO `" << _mysql_tbl << "` VALUES (";
    int num_columns = _output_expr_ctxs.size();
    for (int i = 0; i < num_columns; ++i) {
        if (i != 0) {
            ss << ", ";
        }
        void* item = _output_expr_ctxs[i]->get_value(row);
        if (item == nullptr) {
            ss << "NULL";
            continue;
        }
        switch (_output_expr_ctxs[i]->root()->type().type) {
        case TYPE_BOOLEAN:
        case TYPE_TINYINT:
            ss << (int)*static_cast<int8_t*>(item);
            break;
        case TYPE_SMALLINT:
            ss << *static_cast<int16_t*>(item);
            break;
        case TYPE_INT:
            ss << *static_cast<int32_t*>(item);
            break;
        case TYPE_BIGINT:
            ss << *static_cast<int64_t*>(item);
            break;
        case TYPE_FLOAT:
            ss << *static_cast<float*>(item);
            break;
        case TYPE_DOUBLE:
            ss << *static_cast<double*>(item);
            break;
        case TYPE_DATE:
        case TYPE_DATETIME: {
            char buf[64];
            const DateTimeValue* time_val = (const DateTimeValue*)(item);
            time_val->to_string(buf);
            ss << "\'" << buf << "\'";
            break;
        }
        case TYPE_VARCHAR:
        case TYPE_CHAR: {
            const StringValue* string_val = (const StringValue*)(item);

            if (string_val->ptr == NULL) {
                if (string_val->len == 0) {
                    ss << "\'\'";
                } else {
                    ss << "NULL";
                }
            } else {
                char *buf = new char[2 * string_val->len + 1];
                mysql_real_escape_string(_mysql_conn, buf, string_val->ptr, string_val->len);
                ss << "\'" << buf << "\'";
                delete[] buf;
            }
            break;
        }
        case TYPE_DECIMAL: {
            const DecimalValue* decimal_val = reinterpret_cast<const DecimalValue*>(item);
            std::string decimal_str;
            int output_scale = _output_expr_ctxs[i]->root()->output_scale();

            if (output_scale > 0 && output_scale <= 30) {
                decimal_str = decimal_val->to_string(output_scale);
            } else {
                decimal_str = decimal_val->to_string();
            }
            ss << decimal_str;
            break;
        }
        default: {
            std::stringstream err_ss;
            err_ss << "can't convert this type to mysql type. type = " <<
                _output_expr_ctxs[i]->root()->type();
            return Status(err_ss.str());
        }
        }
    }
    ss << ")";

    // Insert this to MySQL server
    std::string insert_stmt = ss.str();
    LOG(INFO) << insert_stmt;
    if (mysql_real_query(_mysql_conn, insert_stmt.c_str(), insert_stmt.length())) {
        std::stringstream err_ss;
        err_ss << "Insert to mysql server(" << mysql_get_host_info(_mysql_conn)
            << ") failed, because: " << mysql_error(_mysql_conn);
        return Status(err_ss.str());
    }

    return Status::OK;
}

Status MysqlTableWriter::append(RowBatch* batch) {
    if (batch == nullptr || batch->num_rows() == 0) {
        return Status::OK;
    }

    int num_rows = batch->num_rows();
    for (int i = 0; i < num_rows; ++i) {
        RETURN_IF_ERROR(insert_row(batch->get_row(i)));
    }

    return Status::OK;
}

}

