#include "error.h"
#include <map>
#include <iostream>

namespace panda {
namespace error {

static thread_local std::map<std::pair<const std::error_category*, const ErrorCode::NestedCategory*>, ErrorCode::NestedCategory> _cache;

static const ErrorCode::NestedCategory& get_nested_category (const std::error_category& self, const ErrorCode::NestedCategory* next) {
    auto& cache = _cache;
    auto iter = cache.find({&self, next});
    if (iter != cache.end()) {
        return iter->second;
    } else {
        return cache.insert({{&self, next}, {self, next}}).first->second;
    }
}

void ErrorCode::init () {
    if (data) {
        data->codes.clear();
        data->cat = nullptr;
    }
    else data = new Data();
}

void ErrorCode::push (const std::error_code& ec) {
    data->codes.push(ec.value());
    data->cat = &get_nested_category(ec.category(), data->cat);
}

bool ErrorCode::contains_impl(const std::error_code &c) const {
    const NestedCategory* cat = data->cat;
    for (auto v : data->codes) {
        if (v == c.value() && cat->self == c.category()) {
            return true;
        }
        cat = cat->next;
    }
    return false;
}

void ErrorCode::set (const std::error_code& ec) {
    init();
    push(ec);
}

void ErrorCode::set (const std::error_code& ec, const std::error_code& next) {
    init();
    if (next) push(next);
    push(ec);
}

void ErrorCode::set (const std::error_code& ec, const ErrorCode& next) {
    init();
    if (next) {
        data->codes = next.data->codes;
        data->cat   = next.data->cat;
    }
    push(ec);
}

std::string ErrorCode::message () const {
    if (!data) return std::system_category().message(0);
    return data->cat->self.message(data->codes.top());
}

ErrorCode ErrorCode::next () const noexcept {
    if (!data || !data->cat->next) return {};
    ErrorCode ret;
    ret.init();
    ret.data->codes = data->codes;
    ret.data->codes.pop();
    ret.data->cat = data->cat->next;
    return ret;
}

string ErrorCode::what () const {
    if (!data) return {};

    auto stack = data->codes;
    auto cat   = data->cat;

    string ret(stack.size() * 50);

    int i = 0;
    while (cat) {
        auto val = stack.top();
        if (i) ret += " -> ";
        ret += cat->self.message(val).c_str();
        ret += " (";
        ret += to_string(val);
        ret += ":";
        ret += cat->self.name();
        ret += ")";
        cat = cat->next;
        stack.pop();
        ++i;
    }

    return ret;
}

std::ostream& operator<< (std::ostream& os, const ErrorCode& ec) {
    return os << ec.what();
}

}}