#include "table.h"
#include "pqxx.h"
#include <maps/wikimap/mapspro/tools/ymapsdf-conversion/json2ymapsdf/lib/ymapsdf/record.h>

#include <maps/libs/common/include/exception.h>

#include <sstream>

namespace maps::wiki::json2ymapsdf::ymapsdf::schema {

Column&
Table::operator[](const std::string& columnName)
{
    const auto& it = columnNames_.find(columnName);
    REQUIRE(it != columnNames_.end(),
        "Unknown column: " << columnName << " in table " << name_);
    return (*this)[it->second];
}

const Column&
Table::operator[](const std::string& columnName) const
{
    return (const_cast<Table&>(*this))[columnName];
}

Column&
Table::operator[](size_t id)
{
    REQUIRE(id < columns_.size(),
        "Unknown column: #" << id << " in table " << name_);

    Column& column = columns_[id];
    return column;
}

const Column&
Table::operator[](size_t id) const
{
    return (const_cast<Table&>(*this))[id];
}

bool
Table::operator<(const Table& rhs) const
{
    return name() < rhs.name();
}

Table::Table(const Schema* schema, std::string name)
    : schema_(schema)
    , name_(std::move(name))
{
    ASSERT(schema != nullptr);

    Column servicePrimary(
        /* table = */ this,
        /* name = */ PRIMARY_KEY,
        /* defaultValue = */ std::string(),
        /* isPrimary = */ true,
        /* type = */ Type::Text,
        /* foreign = */ nullptr,
        /* isService = */ true
    );
    addColumn(std::move(servicePrimary));

    Column serviceDelete(
        /* table = */ this,
        /* name = */ DELETE,
        /* defaultValue = */ VALUE_FALSE,
        /* isPrimary = */ false,
        /* type = */ Type::Bool,
        /* foreign = */ nullptr,
        /* isService = */ true
    );
    addColumn(std::move(serviceDelete));
}

bool
Table::hasColumn(const std::string& name) const
{
    return columnNames_.find(name) != columnNames_.end();
}

std::vector<const Column*>
Table::foreign() const
{
    std::vector<const Column*> keys;
    for(const auto& column : columns_) {
        if (column.foreignPtr() != nullptr) {
            keys.push_back(&column);
        }
    }
    return keys;
}

std::vector<std::string>
Table::columnNames() const
{
    std::vector<std::string> names;
    for (const auto& kv : columnNames_) {
        names.push_back(kv.first);
    }
    return names;
}

const Column&
Table::primary() const
{
    return columns_[PRIMARY_KEY_ID];
}

void
Table::addColumn(Column&& column)
{
    columnNames_[column.name()] = column.id();

    if (column.id() == PRIMARY_KEY_ID  && columns_.size() > 0) { // 0 == PRIMARY_KEY_ID
        columns_[PRIMARY_KEY_ID] = std::move(column);
    } else {
        ASSERT(columns_.size() == column.id());
        columns_.emplace_back(std::move(column));
    }
}

std::string
Table::insertClause() const
{
    std::stringstream result;

    result << "INSERT INTO " << schema_->name() << "." << name() << "(";
    bool first = true;
    for (size_t id = 0; id < columns_.size(); ++id) {
        if (!(*this)[id].isService()) {
            if (first) {
                first = false;
            } else {
                result << ", ";
            }
            result << (*this)[id].name();
        } else if (log8::isEnabled(log8::Level::DEBUG)) {
            result << " /*" << (*this)[id].name() << ", */ ";
        }
    }
    result << ") VALUES ";

    return result.str();
}

} // namespace maps::wiki::json2ymapsdf::ymapsdf::schema
