#include <library/cpp/resource/registry.h>
#include <util/stream/file.h>
#include <util/folder/path.h>

using namespace NResource;

class TAsmWriter {
private:
    IOutputStream& AsmOut;
    TString AsmPrefix;

public:
    TAsmWriter(IOutputStream& out, const TString& prefix) : AsmOut(out), AsmPrefix(prefix)
    {
    }

    void Write(TStringBuf filename, const TString& data, bool raw) {
        TString constname(Basename(filename));
        if (constname.rfind('.') != TStringBuf::npos) {
            constname = constname.substr(0, constname.rfind('.'));
        }
        WriteHeader(constname);
        if (raw) {
            WriteRaw(constname, data);
        } else {
            WriteIncBin(constname, filename, data);
        }
        WriteFooter(constname);
        WriteSymbolSize(constname);
    }

private:
    void WriteHeader(const TStringBuf& constname) {
        AsmOut << "global " << AsmPrefix << constname << "\n";
        AsmOut << "global " << AsmPrefix << constname << "Size\n";
        AsmOut << "SECTION .rodata\n";
    }

    void WriteFooter(TStringBuf constname) {
        AsmOut << AsmPrefix << constname << "Size:\n";
        AsmOut << "dd " << AsmPrefix << constname << ".end - " << AsmPrefix << constname << "\n";
    }

    void WriteSymbolSize(TStringBuf constname) {
        AsmOut << "%ifidn __OUTPUT_FORMAT__,elf64\n";
        AsmOut << "size " << AsmPrefix << constname << " " << AsmPrefix << constname << ".end - " << AsmPrefix << constname << "\n";
        AsmOut << "size " << AsmPrefix << constname << "Size 4\n";
        AsmOut << "%endif\n";
    }

    void WriteIncBin(TStringBuf constname, TStringBuf filename, const TString& data) {
        AsmOut << AsmPrefix << constname << ":\nincbin \"" << Basename(filename) << "\"\n";
        AsmOut << ".end:\n";
        TFixedBufferFileOutput out(filename.data());
        out << data;
    }

    void WriteRaw(TStringBuf constname, const TString& data) {
        AsmOut << AsmPrefix << constname << ":\ndb ";
        for (size_t i = 0; i < data.size() - 1; i++) {
            unsigned char c = static_cast<unsigned char>(data[i]);
            AsmOut << IntToString<10, unsigned char>(c) << ",";
        }
        AsmOut << IntToString<10, unsigned char>(static_cast<unsigned char>(data[data.size() - 1])) << "\n";
        AsmOut << ".end:\n";
    }

    TString Basename(TStringBuf origin) {
        TString result(origin);
        if (result.rfind('/') != TString::npos) {
            result = result.substr(result.rfind('/') + 1);
        } else if (result.rfind('\\') != TString::npos) {
            result = result.substr(result.rfind('\\') + 1);
        }
        return result;
    }
};

static TString CompressPath(const TVector<TStringBuf>& replacements, TStringBuf in) {
    for (auto r : replacements) {
        TStringBuf from, to;
        r.Split('=', from, to);
        if (in.StartsWith(from)) {
            return Compress(TString(to) + in.SubStr(from.Size()));
        }
    }

    return Compress(in);
}

int main(int argc, char** argv) {
    int ind = 0;
    if (argc < 4) {
        Cerr << "usage: " << argv[ind] << "asm_output --prefix? [-? origin_resource ro_resource]+" << Endl;
        return 1;
    }

    TVector<TStringBuf> replacements;

    ind++;
    TFixedBufferFileOutput asmout(argv[ind]);
    ind++;
    TString prefix;
    if (TStringBuf(argv[ind]) == "--prefix") {
        prefix = "_";
        ind++;
    }
    else {
        prefix = "";
    }

    while (TStringBuf(argv[ind]).StartsWith("--replace=")) {
        replacements.push_back(TStringBuf(argv[ind]).SubStr(AsStringBuf("--replace=").Size()));
        ind++;
    }

    TAsmWriter aw(asmout, prefix);
    bool raw;
    bool error = false;
    while (ind < argc) {
        TString compressed;
        if (AsStringBuf("-") == argv[ind]) {
            ind++;
            if (ind >= argc) {
                error = true;
                break;
            }
            compressed = CompressPath(replacements, TStringBuf(argv[ind]));
            raw = true;
        }
        else {
            TUnbufferedFileInput inp(argv[ind]);
            TString data = inp.ReadAll();
            compressed = Compress(TStringBuf(data.data(), data.size()));
            raw = false;
        }
        ind++;
        if (ind >= argc) {
            error = true;
            break;
        }
        aw.Write(argv[ind], compressed, raw);
        ind++;
    }
    if (error) {
        Cerr << "Incorrect number of parameters at argument " << ind - 1 << argv[ind-1] << Endl;
        return 1;
    }
    return 0;
}
