#pragma once

#include "dumper.h"

#include <util/folder/path.h>

constexpr size_t MRU_SIZE = 1000;

class TPcapHolder {
private:
    std::unique_ptr<pcap_t, void (*)(pcap_t*)> Pd_{nullptr, PcapCloser};
    std::unique_ptr<pcap_dumper_t, void (*)(pcap_dumper_t*)> Dumper_{nullptr, PcapDumperCloser};
private:
    static void PcapCloser(pcap_t* fd) {
        if (fd != nullptr)
            pcap_close(fd);
    }

    static void PcapDumperCloser(pcap_dumper_t* fd) {
        if (fd != nullptr) {
            pcap_dump_flush(fd);
            pcap_dump_close(fd);
        }
    }
public:
    TPcapHolder(const TString& fileName, int linkType, int snapSize) {
        TFsPath path(fileName);

        // fake descriptor, no need to check the result
        Pd_.reset(pcap_open_dead(linkType, snapSize));
        if (!Pd_) {
            ythrow yexception() << "PCAP Error: could not create fake descriptor\n";
        }

        if (!path.Exists())
            Dumper_.reset(pcap_dump_open(Pd_.get(), fileName.c_str()));
        else
            Dumper_.reset(pcap_dump_open_append(Pd_.get(), fileName.c_str()));

        if (!Dumper_) {
            ythrow yexception() << "PCAP Error: " << pcap_geterr(Pd_.get()) << "\n";
        }
    }

    void DumpData(const TPcapPacketHeader* pkthdr, const u_char* pkt) {
        pcap_dump((u_char*)Dumper_.get(), pkthdr, pkt);
    }
};

class TFilePcapDumper final: public IPcapDumper {
private:
    class TMruNode;
    std::unordered_map<ui64, TMruNode*> NodeMap_;
    TMruNode* Head_ = nullptr;
    TMruNode* Tail_ = nullptr;

    int LinkType_ = 0;
    int SnapSize_ = 0;
    TString OutputDir_;
    int Depth_ = 0;
private:
    class TMruNode final {
    private:
        TMruNode* Next_ = nullptr;
        TMruNode* Prev_ = nullptr;

        ui64 Idx_ = 0;
        std::unique_ptr<TPcapHolder> Dumper_{nullptr};
        TString FileName_;
    public:
        TMruNode(ui64 idx, const TString& fileName, int linkType, int snapSize)
            : Idx_(idx)
            , FileName_(fileName)
        {
            Dumper_ = std::make_unique<TPcapHolder>(fileName, linkType, snapSize);
        }

        ui64 Idx() const {
            return Idx_;
        }

        const TString& FileName() const {
            return FileName_;
        }

        TMruNode* Next() const {
            return Next_;
        }
        TMruNode* Prev() const {
            return Prev_;
        }

        void Next(TMruNode* node) {
            Next_ = node;
        }

        void Prev(TMruNode* node) {
            Prev_ = node;
        }

        void DumpData(const TPcapPacketHeader* pkthdr, const u_char* pkt) {
            Dumper_->DumpData(pkthdr, pkt);
        }
    };
private:
    TMruNode* Find(ui64 idx);
    TMruNode* Add(ui64 idx, const TString& fileName);
    inline bool HasKey(ui64 idx) const {
        return NodeMap_.find(idx) != NodeMap_.end();
    }

public:
    explicit TFilePcapDumper(IPcapOptions* options);
    void Write(const TDumpMessage& message) override;
    ~TFilePcapDumper();
};
