#include "cpdecoder.hh"
#include "components/ffmsep/cpdecoder.hh"

#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <mutex>
#include <string_view>
#include <vector>

namespace ffmsep {
namespace {

std::vector<const CPCodec*>& codec_registry() {
    static std::vector<const CPCodec*> registry;
    return registry;
}

std::mutex& registry_mutex() {
    static std::mutex m;
    return m;
}

void attach_codec(CPCodecContext* ctx, const CPCodec* codec) {
    if (!ctx) {
        return;
    }

    ctx->codec = codec;
    if (!codec) {
        ctx->codec_type = CPMediaType::Data;
        ctx->priv_data = nullptr;
        ctx->release_priv_storage();
        return;
    }

    ctx->codec_type = codec->type;
    ctx->priv_data = ctx->ensure_priv_storage(codec->priv_data_size);
}

bool codec_name_equals(const CPCodec* codec, std::string_view name) {
    if (!codec || !codec->name) {
        return false;
    }

    return std::string_view(codec->name) == name;
}
} // namespace

using namespace ffmsep;
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
    if (size == 0U) {
        priv_storage.clear();
        priv_data = nullptr;
        return nullptr;
    }
    if (priv_storage.size() != size) {
        priv_storage.assign(size, static_cast<std::uint8_t>(0));
    }
    priv_data = priv_storage.data();
    return priv_data;
}

void CPCodecContext::release_priv_storage() noexcept {
    priv_storage.clear();
    priv_data = nullptr;
}

void cpcodec_register(const CPCodec *codec) {
    if (!codec || !codec->name) {
        return;
    }

    std::lock_guard<std::mutex> lock(registry_mutex());
    auto& reg = codec_registry();
    auto already = std::find(reg.begin(), reg.end(), codec);
    if (already != reg.end()) {
        return;
    }
    
    auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
        return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknow;
    });
    if (same_id != reg.end()) {
        *same_id = codec;
        return;
    }

    reg.push_back(codec);
}

void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs) {
    for (const CPCodec* codec : codecs) {
        cpcodec_register(codec);
    }
}

const CPCodec* cpcodec_find_decoder(CPCodecID id) {
    std::lock_guard<std::mutex> lock(registry_mutex());
    const auto& reg = codec_registry();
    auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec){
        return codec && codec->id == id;
    });
    return it == reg.end() ? nullptr : *it;
}

const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
    std::lock_guard<std::mutex> lock(registry_mutex());
    const auto& reg = codec_registry();
    auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec){
        return codec_name_equals(codec, name);
    });

    return it ==reg.end() ? nullptr : *it;
}

std::vector<const CPCodec*> cpcodec_list_codecs() {
    std::lock_guard<std::mutex> lock(registry_mutex());
    return codec_registry();
}

CPCodecContext* cpcodec_alloc_context(const CPCodec* codec) {
    auto* ctx = new CPCodecContext();
    if (codec) {
        attach_codec(ctx, codec);
    }

    return ctx;
}

int cpcodec_open(CPCodecContext* ctx, const CPCodec* codec) {
    if (!ctx) {
        return CP_ERROR_INVALID_ARGUMENT;
    }

    if (ctx->is_open) {
        return CP_ERROR_INVALID_STATE;
    }

    if (codec) {
        attach_codec(ctx, codec);
    }

    if (!ctx->codec) {
        return CP_ERROR_INVALID_ARGUMENT;
    }

    ctx->is_open = true;
    if (ctx->codec->init) {
        int rc = ctx->codec->init(ctx);
        if (rc < 0) {
            ctx->is_open = false;
            if (ctx->codec->close) {
                ctx->codec->close(ctx);
            }
            return rc;
        }
    }

    return CP_SUCCESS;
}

int cpcodec_close(CPCodecContext *ctx) {
    if (!ctx) {
        return CP_ERROR_INVALID_ARGUMENT;
    }

    if (!ctx->is_open) {
        return CP_SUCCESS;
    }

    if (ctx->codec && ctx->codec->close) {
        ctx->codec->close(ctx);
    }

    ctx->is_open = false;
    ctx->release_priv_storage();
    ctx->codec_type = CPMediaType::Unknow;
    ctx->codec = nullptr;
    ctx->priv_data = nullptr;
    return CP_SUCCESS;
}

void cpcodec_free_context(CPCodecContext **ctx) {
    if (!ctx || !*ctx) {
        return;
    }

    cpcodec_close(*ctx);
    delete *ctx;
    *ctx = nullptr;
}

int cpcodec_send_packet(CPCodecContext *ctx, const CPPacket *packet) {
    if (!ctx || !packet) {
        return CP_ERROR_INVALID_ARGUMENT;
    }
    if (!ctx ||  !ctx->codec) {
        return CP_ERROR_NOT_OPEN;
    }
    if (!ctx->codec->send_packet) {
        return CP_ERROR_INVALID_STATE;
    }
    return ctx->codec->send_packet(ctx, *packet);
}

int cpcodec_receive_frame(CPCodecContext *ctx, CPFrame *frame) {
    if (!ctx || !frame) {
        return CP_ERROR_INVALID_ARGUMENT;
    }
    if (!ctx->is_open || !ctx->codec) {
        return CP_ERROR_NOT_OPEN;
    }
    if (!ctx->codec->receive_frame) {
        return CP_ERROR_INVALID_STATE;
    }
    return ctx->codec->receive_frame(ctx, *frame);
}
}
