#include "file_dumper.h"

#include <arpa/inet.h>
#include <netinet/in.h>

void TFilePcapDumper::Write(const TDumpMessage& message) {
    ui64 hash = CalcHash(message.Frame.SrcAdr(), message.Frame.DstAdr(),
                         message.Frame.SrcPort(), message.Frame.DstPort());
    ui64 hashInv = CalcHash(message.Frame.DstAdr(), message.Frame.SrcAdr(),
                            message.Frame.DstPort(), message.Frame.SrcPort());

    TMruNode* node = nullptr;

    if (HasKey(hashInv)) {
        node = Find(hashInv);
    } else if (HasKey(hash)) {
        node = Find(hash);
    } else {
        char src[INET6_ADDRSTRLEN]{0};
        char dst[INET6_ADDRSTRLEN]{0};

        if (inet_ntop(message.Frame.AFInet(), message.Frame.SrcAdr(), src, INET6_ADDRSTRLEN) == nullptr)
            return;
        if (inet_ntop(message.Frame.AFInet(), message.Frame.DstAdr(), dst, INET6_ADDRSTRLEN) == nullptr)
            return;

        TStringStream srcStreamName;
        TStringStream dstStreamName;

        srcStreamName << src << TStringBuf(".") << message.Frame.SrcPort() << TStringBuf("-") << dst << TStringBuf(".") << message.Frame.DstPort();
        dstStreamName << dst << TStringBuf(".") << message.Frame.DstPort() << TStringBuf("-") << src << TStringBuf(".") << message.Frame.SrcPort();

        TStringStream srcFileName(OutputDir_);
        TStringStream dstFileName(OutputDir_);

        if (OutputDir_.empty()) {
            srcFileName << "./";
            dstFileName << "./";
        } else if (OutputDir_[OutputDir_.size() - 1] != '/') {
            srcFileName << "/";
            dstFileName << "/";
        }

        size_t pos = 0;
        int count = 0;
        char c = 0;
        while (count < Depth_ && pos < srcStreamName.Str().size()) {
            c = srcStreamName.Str()[pos];
            if (std::isdigit(c) || std::isalpha(c)) {
                srcFileName << srcStreamName.Str()[pos] << "/";
                ++count;
            }
            ++pos;
        }

        pos = 0;
        count = 0;
        while (count < Depth_ && pos < dstStreamName.Str().size()) {
            c = dstStreamName.Str()[pos];
            if (std::isdigit(c) || std::isalpha(c)) {
                dstFileName << dstStreamName.Str()[pos] << "/";
                ++count;
            }
            ++pos;
        }

        TFsPath srcPath(srcFileName.Str());

        if (!srcPath.Exists())
            srcPath.MkDirs();

        srcFileName << srcStreamName.Str() << ".pcap";
        dstFileName << dstStreamName.Str() << ".pcap";

        TFsPath dstFilePath(dstFileName.Str());

        if (dstFilePath.Exists()) {
            node = Add(hashInv, dstFileName.Str());
        } else {
            node = Add(hash, srcFileName.Str());
        }
    }

    if (node == nullptr)
        ythrow yexception() << "Something went wrong. Empty YT writer node.";

    node->DumpData(message.PktHdr, message.Pkt);
}

TFilePcapDumper::TMruNode* TFilePcapDumper::Find(ui64 idx) {
    TMruNode* node = nullptr;

    auto it = NodeMap_.find(idx);
    if (it != NodeMap_.end()) {
        node = it->second;

        if (node->Prev() != nullptr) {
            node->Prev()->Next(node->Next());
            if (node->Next() != nullptr) {
                node->Next()->Prev(node->Prev());
            } else {
                Tail_ = node->Prev();
            }
            node->Prev(nullptr);
            node->Next(Head_);
            Head_->Prev(node);
        }

        Head_ = node;
    }

    return node;
}

TFilePcapDumper::TMruNode* TFilePcapDumper::Add(ui64 idx, const TString& fileName) {
    auto node = new TMruNode(idx, fileName, LinkType_, SnapSize_);

    if (Head_ != nullptr) {
        node->Next(Head_);
        Head_->Prev(node);
    } else {
        Tail_ = node;
    }
    NodeMap_[idx] = node;

    if (NodeMap_.size() >= MRU_SIZE) {
        TMruNode* last = Tail_->Prev();

        if (last != nullptr) {
            last->Next(nullptr);

            NodeMap_.erase(Tail_->Idx());
            if (NodeMap_.find(Tail_->Idx()) == NodeMap_.end()) {
            }
            delete Tail_;
            Tail_ = last;
        }
    }

    Head_ = node;

    return node;
}

TFilePcapDumper::TFilePcapDumper(IPcapOptions* options) {
    LinkType_ = options->LinkType();
    SnapSize_ = options->SnapSize();
    OutputDir_ = options->OutputDir();
    Depth_ = options->Depth();
};

TFilePcapDumper::~TFilePcapDumper() {
    TMruNode* curr;
    while (Head_ != nullptr) {
        curr = Head_;
        Head_ = Head_->Next();
        delete curr;
    }
}
