/*
 *  Copyright (C) GridGain Systems. All Rights Reserved.
 *  _________        _____ __________________        _____
 *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
 *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
 *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
 *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
 */

#include <utility>

#include "ignite/odbc/log.h"
#include "ignite/odbc/odbc_error.h"
#include "ignite/odbc/query/table_metadata_query.h"
#include "ignite/odbc/sql_connection.h"
#include "ignite/common/detail/string_utils.h"

namespace {

enum class result_column {
    /** Catalog name. NULL if not applicable to the data source. */
    TABLE_CAT = 1,

    /** Schema name. NULL if not applicable to the data source. */
    TABLE_SCHEM,

    /** Table name. */
    TABLE_NAME,

    /** Table type. */
    TABLE_TYPE,

    /** A description of the column. */
    REMARKS
};

} // namespace

namespace ignite {

table_metadata_query::table_metadata_query(diagnosable_adapter &diag, sql_connection &connection, std::string catalog,
    std::string schema, std::string table, std::string table_type)
    : query(diag, query_type::TABLE_METADATA)
    , m_connection(connection)
    , m_catalog(std::move(catalog))
    , m_schema(std::move(schema))
    , m_table(std::move(table))
    , m_table_type(std::move(table_type)) {
    m_columns_meta.reserve(5);

    const std::string sch;
    const std::string tbl;

    m_columns_meta.emplace_back(sch, tbl, "TABLE_CAT", ignite_type::STRING);
    m_columns_meta.emplace_back(sch, tbl, "TABLE_SCHEM", ignite_type::STRING);
    m_columns_meta.emplace_back(sch, tbl, "TABLE_NAME", ignite_type::STRING);
    m_columns_meta.emplace_back(sch, tbl, "TABLE_TYPE", ignite_type::STRING);
    m_columns_meta.emplace_back(sch, tbl, "REMARKS", ignite_type::STRING);
}

sql_result table_metadata_query::execute() {
    if (m_executed)
        close();

    sql_result result = make_request_get_tables_meta();

    if (result == sql_result::AI_SUCCESS) {
        m_executed = true;
        m_fetched = false;

        m_cursor = m_meta.begin();
    }

    return result;
}

const protocol::column_meta_vector *table_metadata_query::get_meta() {
    return &m_columns_meta;
}

sql_result table_metadata_query::fetch_next_row(column_binding_map &column_bindings) {
    if (!m_executed) {
        m_diag.add_status_record(sql_state::SHY010_SEQUENCE_ERROR, "Query was not executed.");

        return sql_result::AI_ERROR;
    }

    if (!m_fetched)
        m_fetched = true;
    else
        ++m_cursor;

    if (m_cursor == m_meta.end())
        return sql_result::AI_NO_DATA;

    for (auto &binding : column_bindings)
        get_column(binding.first, binding.second);

    return sql_result::AI_SUCCESS;
}

sql_result table_metadata_query::get_column(std::uint16_t column_idx, application_data_buffer &buffer) {
    if (!m_executed) {
        m_diag.add_status_record(sql_state::SHY010_SEQUENCE_ERROR, "Query was not executed.");

        return sql_result::AI_ERROR;
    }

    if (m_cursor == m_meta.end()) {
        m_diag.add_status_record(sql_state::S24000_INVALID_CURSOR_STATE, "Cursor has reached end of the result set.");

        return sql_result::AI_ERROR;
    }

    const table_meta &current_column = *m_cursor;

    switch (result_column(column_idx)) {
        case result_column::TABLE_CAT: {
            buffer.put_string(current_column.get_catalog_name());
            break;
        }

        case result_column::TABLE_SCHEM: {
            buffer.put_string(current_column.get_schema_name());
            break;
        }

        case result_column::TABLE_NAME: {
            buffer.put_string(current_column.get_table_name());
            break;
        }

        case result_column::TABLE_TYPE: {
            buffer.put_string(current_column.get_table_type());
            break;
        }

        case result_column::REMARKS: {
            buffer.put_null();
            break;
        }

        default:
            break;
    }

    return sql_result::AI_SUCCESS;
}

sql_result table_metadata_query::close() {
    m_meta.clear();

    m_executed = false;

    return sql_result::AI_SUCCESS;
}

bool table_metadata_query::is_data_available() const {
    return m_cursor != m_meta.end();
}

int64_t table_metadata_query::affected_rows() const {
    return 0;
}

sql_result table_metadata_query::next_result_set() {
    return sql_result::AI_NO_DATA;
}

sql_result table_metadata_query::make_request_get_tables_meta() {
    auto table_types = detail::split(m_table_type, ',');

    auto success = m_diag.catch_errors([&] {
        auto response =
            m_connection.sync_request(protocol::client_operation::JDBC_TABLE_META, [&](protocol::writer &writer) {
                writer.write(m_schema);
                writer.write(m_table);

                writer.write(static_cast<int32_t>(table_types.size()));
                for (auto table_type : table_types) {
                    writer.write(table_type);
                }
            });

        protocol::reader reader{response.get_bytes_view()};

        auto status = reader.read_int32();
        auto err_msg = reader.read_string_nullable();
        if (err_msg)
            throw odbc_error(response_status_to_sql_state(status), *err_msg);

        m_meta = read_table_meta_vector(reader);

        m_executed = true;
    });

    if (!success)
        return sql_result::AI_ERROR;

    int idx = 0;
    for (auto &column : m_meta) {
        LOG_MSG("\n[" << idx << "] catalog_name: " << column.get_catalog_name() << "\n[" << idx << "] schema_name:  "
                      << column.get_schema_name() << "\n[" << idx << "] table_name:   " << column.get_table_name()
                      << "\n[" << idx << "] table_type:   " << column.get_table_type());
        ++idx;
    }

    return sql_result::AI_SUCCESS;
}

} // namespace ignite
