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

#include "ignite/odbc/app/parameter_set.h"
#include "ignite/tuple/binary_tuple_builder.h"

#include <algorithm>

namespace ignite {

void parameter_set_impl::set_param_set_size(SQLULEN size) {
    m_param_set_size = size;
}

void parameter_set_impl::bind_parameter(std::uint16_t param_idx, const parameter &param) {
    m_params[param_idx] = param;
}

void parameter_set_impl::unbind_parameter(std::uint16_t param_idx) {
    m_params.erase(param_idx);
}

void parameter_set_impl::unbind_all() {
    m_params.clear();
}

std::uint16_t parameter_set_impl::get_parameters_number() const {
    return static_cast<std::uint16_t>(m_params.size());
}

void parameter_set_impl::set_param_bind_offset_ptr(int *ptr) {
    m_param_bind_offset = ptr;
}

int *parameter_set_impl::get_param_bind_offset_ptr() {
    return m_param_bind_offset;
}

void parameter_set_impl::prepare() {
    m_param_set_pos = 0;

    for (auto &param : m_params)
        param.second.reset_stored_data();
}

bool parameter_set_impl::is_data_at_exec_needed() const {
    return std::any_of(
        m_params.begin(), m_params.end(), [](const auto &param) { return !param.second.is_data_ready(); });
}

void parameter_set_impl::set_params_processed_ptr(SQLULEN *ptr) {
    m_processed_param_rows = ptr;
}

SQLULEN *parameter_set_impl::get_params_processed_ptr() const {
    return m_processed_param_rows;
}

void parameter_set_impl::set_params_status_ptr(SQLUSMALLINT *value) {
    m_params_status = value;
}

SQLUSMALLINT *parameter_set_impl::get_params_status_ptr() const {
    return m_params_status;
}

void parameter_set_impl::set_params_status(int64_t idx, SQLUSMALLINT status) const {
    if (idx < 0 || !m_params_status || idx >= static_cast<int64_t>(m_param_set_size))
        return;

    m_params_status[idx] = status;
}

void parameter_set_impl::set_params_processed(SQLULEN processed) {
    if (m_processed_param_rows)
        *m_processed_param_rows = processed;
}

bool parameter_set_impl::is_parameter_selected() const {
    return m_current_param_idx != 0;
}

parameter *parameter_set_impl::get_parameter(std::uint16_t idx) {
    auto it = m_params.find(idx);
    if (it != m_params.end())
        return &it->second;

    return nullptr;
}

const parameter *parameter_set_impl::get_parameter(std::uint16_t idx) const {
    auto it = m_params.find(idx);
    if (it != m_params.end())
        return &it->second;

    return nullptr;
}

parameter *parameter_set_impl::get_selected_parameter() {
    return get_parameter(m_current_param_idx);
}

parameter *parameter_set_impl::select_next_parameter() {
    for (auto it = m_params.begin(); it != m_params.end(); ++it) {
        std::uint16_t param_idx = it->first;
        parameter &param = it->second;

        if (!param.is_data_ready()) {
            m_current_param_idx = param_idx;
            return &param;
        }
    }

    return nullptr;
}

void parameter_set_impl::write(protocol::writer &writer) const {
    auto args_num = calculate_row_len();
    if (args_num == 0) {
        writer.write_nil();
        return;
    }

    writer.write(args_num);

    write_row(writer, 0);
}

void parameter_set_impl::write(protocol::writer &writer, SQLULEN begin, SQLULEN end, bool last) const {
    std::int32_t row_len = calculate_row_len();

    writer.write(row_len);

    SQLULEN interval_end = std::min(m_param_set_size, end);

    assert(begin < interval_end);

    auto interval_len = static_cast<std::int32_t>(interval_end - begin);

    writer.write(interval_len);
    writer.write_bool(last);

    if (row_len) {
        for (SQLULEN i = begin; i < interval_end; ++i)
            write_row(writer, i);
    }
}

void parameter_set_impl::write_row(protocol::writer &writer, SQLULEN idx) const {
    auto args_num = calculate_row_len();
    binary_tuple_builder row_builder{args_num * 3};

    row_builder.start();

    std::uint16_t prev = 0;

    int app_offset = m_param_bind_offset ? *m_param_bind_offset : 0;
    for (const auto &pair : m_params) {
        std::uint16_t param_idx = pair.first;
        const parameter &param = pair.second;

        while ((param_idx - prev) > 1) {
            row_builder.claim_null();
            ++prev;
        }

        param.claim(row_builder, app_offset, idx);

        prev = param_idx;
    }

    row_builder.layout();

    for (const auto &pair : m_params) {
        std::uint16_t param_idx = pair.first;
        const parameter &param = pair.second;

        while ((param_idx - prev) > 1) {
            row_builder.append_null();
            ++prev;
        }

        param.append(row_builder, app_offset, idx);

        prev = param_idx;
    }

    auto args_data = row_builder.build();
    writer.write_binary(args_data);
}

std::int32_t parameter_set_impl::calculate_row_len() const {
    if (!m_params.empty())
        return static_cast<std::int32_t>(m_params.rbegin()->first);

    return 0;
}

std::int32_t parameter_set_impl::get_param_set_size() const {
    return static_cast<std::int32_t>(m_param_set_size);
}

} // namespace ignite
