#include "incomplete_manager.h"
#include <sstream>

namespace maps::wiki::json2ymapsdf {

IncompleteRecords::IncompleteRecords()
    : size_(0)
{ }

std::string
IncompleteRecords::stat() const
{
    std::ostringstream stat;
    stat << "Total: " << size_;
    for (auto it: statistics_) {
        stat << ", " << it.first->name() << ":" << it.second;
    }
    return stat.str();
}

void
IncompleteRecords::add(ymapsdf::Record&& record)
{
    ++statistics_[&record.table()];
    ++size_;
    records_[record.key()].push_back(std::move(record));
}

ymapsdf::Records
IncompleteRecords::popRecords(const std::string& key)
{
    return popRecords(records_.find(key));
}

ymapsdf::Records
IncompleteRecords::popRecords()
{
    return popRecords(records_.begin());
}

ymapsdf::Records
IncompleteRecords::popRecords(RecordsMap::iterator it)
{
    ymapsdf::Records records;
    if (it != records_.end()) {
        records = std::move(it->second);
        records_.erase(it);
        for (const auto& record: records) {
            --statistics_[&record.table()];
            --size_;
        }
    }
    return records;
}

namespace {

void
overwriteIfEmpty(ymapsdf::Value& v1, ymapsdf::Value& v2)
{
    if (v1.empty() && v2.filled()) {
        v1 = v2;
    }
}

void
mergePair(ymapsdf::Record& r1, ymapsdf::Record& r2)
{
    if (&r1.table() != &r2.table()
        || r1.key() != r2.key()
        || r1.key().empty())
    {
        return;
    }

    for (size_t fieldId = 0; fieldId < r1.size(); ++fieldId) {
        overwriteIfEmpty(r1[fieldId], r2[fieldId]);
        overwriteIfEmpty(r2[fieldId], r1[fieldId]);
    }
}

void
mergeRecords(ymapsdf::Records& records)
{
    for (auto it = records.begin(); it != records.end(); ++it) {
        for (auto jt = std::next(it); jt != records.end(); ++jt) {
            mergePair(*it, *jt);
            if (*it == *jt) {
                it = records.erase(it);
                jt = it;
            }
        }
    }
}

} // namespace

void
IncompleteManager::subjoinIncomplete(ymapsdf::Records& records)
{
    ++waiters_;
    std::lock_guard<std::mutex> lock(mutex_);
    --waiters_;

    for (auto it = records.begin(); it != records.end(); ++it) {
        records.splice(records.begin(), incompleteRecords_.popRecords(it->key()));
    }
}

void
IncompleteManager::mergeIncomplete(ymapsdf::Records& records)
{
    subjoinIncomplete(records);
    mergeRecords(records);

    ymapsdf::Records toIncompleteRecords;

    auto it = records.begin();
    while (it != records.end()) {
        if (it->toDelete()) {
            it = records.erase(it);
        } else if (it->completed()) { // NOLINT
            ++it;
        } else if (it->key().empty() || it->broken()) { // NOLINT
            ++it; // try to write it only to get extended error msg
        } else {
            toIncompleteRecords.emplace_back(std::move(*it));
            it = records.erase(it);
        }
    }

    ++waiters_;
    std::lock_guard<std::mutex> lock(mutex_);
    --waiters_;

    for (auto& record : toIncompleteRecords) {
        incompleteRecords_.add(std::move(record));
    }
}

ymapsdf::Records
IncompleteManager::popRecords()
{
    std::lock_guard<std::mutex> lock(mutex_);
    auto records = incompleteRecords_.popRecords();
    mergeRecords(records);
    return records;
}

std::string
IncompleteManager::stat() const
{
    std::lock_guard<std::mutex> lock(mutex_);
    return incompleteRecords_.stat();
}

size_t
IncompleteManager::size() const
{
    std::lock_guard<std::mutex> lock(mutex_);
    return incompleteRecords_.size();
}

} // namespace maps::wiki::json2ymapsdf
