// Copyright (c) 2014-2020 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and/or associated documentation files (the "Materials"),
// to deal in the Materials without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Materials, and to permit persons to whom the
// Materials are furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
// IN THE MATERIALS.

//
// Print headers for SPIR-V in several languages.
//
// To change the header information, change the C++-built database in doc.*.
//
// Then, use "spriv -h <language>" - e.g, spriv.{h,hpp,lua,py,etc}:
// replace the auto-generated header, or "spirv -H" to generate all
// supported language headers to predefined names in the current directory.
//

#include <string>
#include <sstream>
#include <fstream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <memory>
#include <cctype>
#include <vector>
#include <utility>
#include <set>

#include "jsoncpp/dist/json/json.h"

#include "header.h"
#include "jsonToSpirv.h"

// snprintf and _snprintf are not quite the same, but close enough
// for our use.
#ifdef _MSC_VER
#pragma warning(disable:4996)
#define snprintf _snprintf
#endif

// This file converts SPIR-V definitions to an internal JSON
// representation, and then generates language specific
// data from that single internal form.

// Initially, the internal form is created from C++ data,
// though this can be changed to a JSON master in time.

namespace {
    class TPrinter {
    protected:
        TPrinter();

        static const int         DocMagicNumber = 0x07230203;
        static const int         DocVersion     = 0x00010600;
        static const int         DocRevision    = 1;
        #define DocRevisionString                "1"
        static const std::string DocCopyright;
        static const std::string DocComment1;
        static const std::string DocComment2;

        enum enumStyle_t {
            enumNoMask,
            enumCount,
            enumShift,
            enumMask,
            enumHex,
        };

        static std::string styleStr(enumStyle_t s) {
            return s == enumShift ? "Shift" :
                   s == enumMask  ? "Mask"  : "";
        }

        friend std::ostream& operator<<(std::ostream&, const TPrinter&);

        virtual void printAll(std::ostream&)      const;
        virtual void printComments(std::ostream&) const;
        virtual void printPrologue(std::ostream&) const { }
        virtual void printDefs(std::ostream&)     const;
        virtual void printEpilogue(std::ostream&) const { }
        virtual void printMeta(std::ostream&)     const;
        virtual void printTypes(std::ostream&)    const { }
        virtual void printHasResultType(std::ostream&)     const { };

        virtual std::string escapeComment(const std::string& s) const;

        // Default printComments() uses these comment strings
        virtual std::string commentBeg() const            { return ""; }
        virtual std::string commentEnd(bool isLast) const { return ""; }
        virtual std::string commentBOL() const            { return ""; }
        virtual std::string commentEOL(bool isLast) const { return ""; }

        typedef std::pair<unsigned, std::string> valpair_t;

        // for printing enum values
        virtual std::string enumBeg(const std::string&, enumStyle_t) const { return ""; }
        virtual std::string enumEnd(const std::string&, enumStyle_t, bool isLast = false) const {
            return "";
        }
        virtual std::string enumFmt(const std::string&, const valpair_t&,
                                    enumStyle_t, bool isLast = false) const {
            return "";
        }
        virtual std::string maxEnumFmt(const std::string&, const valpair_t&,
                                       enumStyle_t) const {
            return "";
        }

        virtual std::string fmtConstInt(unsigned val, const std::string& name,
                                        const char* fmt, bool isLast = false) const {
            return "";
        }

        std::vector<valpair_t> getSortedVals(const Json::Value&) const;

        virtual std::string indent(int count = 1) const {
            return std::string(count * 4, ' ');   // default indent level = 4
        }

        static std::string fmtNum(const char* fmt, unsigned val) {
            char buff[16]; // ample for 8 hex digits + 0x
            snprintf(buff, sizeof(buff), fmt, val);
            buff[sizeof(buff)-1] = '\0';  // MSVC doesn't promise null termination
            return buff;
        }

        static std::string fmtStyleVal(unsigned v, enumStyle_t style);

        // If the enum value name would start with a sigit, prepend the enum name.
        // E.g, "3D" -> "Dim3D".
        static std::string prependIfDigit(const std::string& ename, const std::string& vname) {
            return (std::isdigit(vname[0]) ? ename : std::string("")) + vname;
        }

        void addComment(Json::Value& node, const std::string& str);

        Json::Value spvRoot; // JSON SPIR-V data
    };

    // Format value as mask or value
    std::string TPrinter::fmtStyleVal(unsigned v, enumStyle_t style)
    {
        switch (style) {
        case enumMask:
            return fmtNum("0x%08x", 1<<v);
        case enumHex:
            return fmtNum("0x%08x", v);
        default:
            return std::to_string(v);
        }
    }

    const std::string TPrinter::DocCopyright =
        "Copyright (c) 2014-2020 The Khronos Group Inc.\n"
        "\n"
        "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
        "of this software and/or associated documentation files (the \"Materials\"),\n"
        "to deal in the Materials without restriction, including without limitation\n"
        "the rights to use, copy, modify, merge, publish, distribute, sublicense,\n"
        "and/or sell copies of the Materials, and to permit persons to whom the\n"
        "Materials are furnished to do so, subject to the following conditions:\n"
        "\n"
        "The above copyright notice and this permission notice shall be included in\n"
        "all copies or substantial portions of the Materials.\n"
        "\n"
        "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS\n"
        "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND\n"
        "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ \n"
        "\n"
        "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n"
        "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
        "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n"
        "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
        "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n"
        "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS\n"
        "IN THE MATERIALS.\n";

    const std::string TPrinter::DocComment1 =
        "This header is automatically generated by the same tool that creates\n"
        "the Binary Section of the SPIR-V specification.\n";

    const std::string TPrinter::DocComment2 =
        "Enumeration tokens for SPIR-V, in various styles:\n"
        "  C, C++, C++11, JSON, Lua, Python, C#, D, Beef\n"
        "\n"
        "- C will have tokens with a \"Spv\" prefix, e.g.: SpvSourceLanguageGLSL\n"
        "- C++ will have tokens in the \"spv\" name space, e.g.: spv::SourceLanguageGLSL\n"
        "- C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL\n"
        "- Lua will use tables, e.g.: spv.SourceLanguage.GLSL\n"
        "- Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL']\n"
        "- C# will use enum classes in the Specification class located in the \"Spv\" namespace,\n"
        "    e.g.: Spv.Specification.SourceLanguage.GLSL\n"
        "- D will have tokens under the \"spv\" module, e.g: spv.SourceLanguage.GLSL\n"
        "- Beef will use enum classes in the Specification class located in the \"Spv\" namespace,\n"
        "    e.g.: Spv.Specification.SourceLanguage.GLSL\n"
        "\n"
        "Some tokens act like mask values, which can be OR'd together,\n"
        "while others are mutually exclusive.  The mask-like ones have\n"
        "\"Mask\" in their name, and a parallel enum that has the shift\n"
        "amount (1 << x) for each corresponding enumerant.\n";

    // Construct
    TPrinter::TPrinter()
    {
        Json::Value& meta            = spvRoot["spv"]["meta"];
        Json::Value& enums           = spvRoot["spv"]["enum"];

        meta["MagicNumber"]          = DocMagicNumber;
        meta["Version"]              = DocVersion;
        meta["Revision"]             = DocRevision;
        meta["OpCodeMask"]           = 0xffff;
        meta["WordCountShift"]       = 16;

        int commentId = 0;
        addComment(meta["Comment"][commentId++], DocCopyright);
        addComment(meta["Comment"][commentId++], DocComment1);
        addComment(meta["Comment"][commentId++], DocComment2);

        for (int e = spv::OperandSource; e < spv::OperandOpcode; ++e) {
            auto& enumSet =  spv::OperandClassParams[e];
            const bool        mask     = enumSet.bitmask;
            const std::string enumName = enumSet.codeName;

            for (auto& enumRow : enumSet) {
                std::string name = enumRow.name;
                enums[e - spv::OperandSource]["Values"][name] = enumRow.value;
            }

            enums[e - spv::OperandSource]["Type"] = mask ? "Bit" : "Value";
            enums[e - spv::OperandSource]["Name"] = enumName;
        }

          // Instructions are in their own different table
        {
            auto& entry = enums[spv::OperandOpcode - spv::OperandSource];
            for (auto& enumRow : spv::InstructionDesc) {
                std::string name = enumRow.name;
                entry["Values"][name] = enumRow.value;
            }
            entry["Type"] = "Value";
            entry["Name"] = "Op";
        }
    }

    // Create comment
    void TPrinter::addComment(Json::Value& node, const std::string& str)
    {
        std::istringstream cstream(str);
        std::string        cline;

        int line = 0;
        while (std::getline(cstream, cline))  // fmt each line
            node[line++] = cline;
    }


    // Return a list of values sorted by enum value.  The std::vector
    // returned by value is okay in c++11 due to move semantics.
    std::vector<TPrinter::valpair_t>
    TPrinter::getSortedVals(const Json::Value& p) const
    {
        std::vector<valpair_t> values;

        for (auto e = p.begin(); e != p.end(); ++e)
            values.push_back(valpair_t(e->asUInt(), e.name()));

        // Use a stable sort because we might have aliases, e.g.
        // SubgropuBallot (might be in future core) vs. SubgroupBallotKHR.
        std::stable_sort(values.begin(), values.end());

        return values;
    }

    // Escape comment characters if needed
    std::string TPrinter::escapeComment(const std::string& s) const { return s; }

    // Format comments in language specific way
    void TPrinter::printComments(std::ostream& out) const
    {
        const int commentCount = spvRoot["spv"]["meta"]["Comment"].size();
        int commentNum = 0;

        for (const auto& comment : spvRoot["spv"]["meta"]["Comment"]) {
            out << commentBeg();

            for (int line = 0; line < int(comment.size()); ++line)
                out << commentBOL() << escapeComment(comment[line].asString()) <<
                    commentEOL((line+1) == comment.size()) << std::endl;

            out << commentEnd(++commentNum == commentCount) << std::endl;
        }
    }

    // Format header metadata
    void TPrinter::printMeta(std::ostream& out) const
    {
        const Json::Value& meta = spvRoot["spv"]["meta"];

        const auto print = [&](const char* name, const char* fmt, bool isLast) {
            out << fmtConstInt(meta[name].asUInt(), name, fmt, isLast);
        };

        print("MagicNumber",    "0x%08lx", false);
        print("Version",        "0x%08lx", false);
        print("Revision",       "%d",      false);
        print("OpCodeMask",     "0x%04x",  false);
        print("WordCountShift", "%d",      true);
    }

    // Format value definitions in language specific way
    void TPrinter::printDefs(std::ostream& out) const
    {
        const Json::Value& enums = spvRoot["spv"]["enum"];

        for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
            const bool isMask   = (*opClass)["Type"].asString() == "Bit";
            const auto opName   = (*opClass)["Name"].asString();
            const auto opPrefix = opName == "Op" ? "" : opName;

            for (enumStyle_t style = (isMask ? enumShift : enumCount);
                 style <= (isMask ? enumMask : enumCount); style = enumStyle_t(int(style)+1)) {

                out << enumBeg(opName, style);

                if (style == enumMask)
                    out << enumFmt(opPrefix, valpair_t(0, "MaskNone"), enumNoMask);

                const auto sorted = getSortedVals((*opClass)["Values"]);

                std::string maxEnum = maxEnumFmt(opName, valpair_t(0x7FFFFFFF, "Max"), enumHex);

                bool printMax = (style != enumMask && maxEnum.size() > 0);

                for (const auto& v : sorted)
                    out << enumFmt(opPrefix, v, style, !printMax && v.second == sorted.back().second);

                if (printMax)
                    out << maxEnum;

                auto nextOpClass = opClass;
                out << enumEnd(opName, style, ++nextOpClass == enums.end());
            }
        }
    }

    void TPrinter::printAll(std::ostream& out) const
    {
        printComments(out);
        printPrologue(out);
        printTypes(out);
        printMeta(out);
        printDefs(out);
        printHasResultType(out);
        printEpilogue(out);
    }

    // Stream entire header to output
    std::ostream& operator<<(std::ostream& out, const TPrinter &p)
    {
        p.printAll(out);
        return out;
    }

    // JSON printer.  Rather than use the default printer, we supply our own so
    // we can control the printing order within various containers.
    class TPrinterJSON final : public TPrinter {
    private:
        void printPrologue(std::ostream& out) const override { out << "{\n" + indent() + "\"spv\":\n" + indent() + "{\n"; }
        void printEpilogue(std::ostream& out) const override { out << indent() + "}\n}\n"; }

        std::string escapeComment(const std::string& s) const override {
            std::string newStr;
            for (auto c : s) {
                if (c == '"') {
                    newStr += '\\';
                    newStr += c;
                } else {
                    newStr += c;
                }
            }
            return newStr;
        }

        std::string fmtConstInt(unsigned val, const std::string& name,
                                const char* fmt, bool isLast) const override {
            return indent(3) + '"' + name + "\": " + fmtNum("%d", val) + (isLast ? "\n" : ",\n");
        }

        void printMeta(std::ostream& out) const override
        {
            out << indent(2) + "\"meta\":\n" + indent(2) + "{\n";
            printComments(out);
            TPrinter::printMeta(out);
            out << indent(2) + "},\n";
        }

        std::string commentBeg() const override            { return indent(4) + "[\n"; }
        std::string commentEnd(bool isLast) const override { return indent(4) + (isLast ? "]" : "],"); }
        std::string commentBOL() const override            { return indent(5) + '"'; }
        std::string commentEOL(bool isLast) const override { return (isLast ? "\"" : "\","); }

        void printComments(std::ostream& out) const override
        {
            out << indent(3) + "\"Comment\":\n" + indent(3) + "[\n";
            TPrinter::printComments(out);
            out << indent(3) + "],\n";
        }

        void printDefs(std::ostream& out) const override
        {
            out << indent(2) + "\"enum\":\n" + indent(2) + "[\n";
            TPrinter::printDefs(out);
            out << indent(2) + "]\n";
        }

        void printAll(std::ostream& out) const override
        {
            printPrologue(out);
            printMeta(out);
            printDefs(out);
            printEpilogue(out);
        }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            if (style == enumMask)
                return "";
            return indent(3) + "{\n" +
                indent(4) + "\"Name\": \"" + s + "\",\n" +
                indent(4) + "\"Type\": " + (style == enumShift ? "\"Bit\"" : "\"Value\"") + ",\n" +
                indent(4) + "\"Values\":\n" +
                indent(4) + "{\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            if (style == enumMask)
                return "";
            return indent(4) + "}\n" +
                   indent(3) + "}" + (isLast ? "" : ",") + "\n";
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            if (style == enumMask || style == enumNoMask)
                return "";
            return indent(5) + '"' + prependIfDigit(s, v.second) + "\": " + fmtNum("%d", v.first) +
                (isLast ? "\n" : ",\n");
        }
    };

    // base for C and C++
    class TPrinterCBase : public TPrinter {
    protected:
        virtual void printPrologue(std::ostream& out) const override {
            out << "#ifndef spirv_" << headerGuardSuffix() << std::endl
                << "#define spirv_" << headerGuardSuffix() << std::endl
                << std::endl;
        }

        void printMeta(std::ostream& out) const override {
            out << "#define SPV_VERSION 0x" << std::hex << DocVersion << std::dec << "\n";
            out << "#define SPV_REVISION " << DocRevision << "\n";
            out << "\n";

            return TPrinter::printMeta(out);
        }

        virtual void printEpilogue(std::ostream& out) const override {
            out << "#endif" << std::endl;
        }

        virtual void printTypes(std::ostream& out) const override {
            out << "typedef unsigned int " << pre() << "Id;\n\n";
        }

        virtual std::string fmtConstInt(unsigned val, const std::string& name,
                                        const char* fmt, bool isLast) const override
        {
            return std::string("static const unsigned int ") + pre() + name +
                " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
        }

        virtual std::string pre() const { return ""; } // C name prefix
        virtual std::string headerGuardSuffix() const = 0;

        virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const { return pre() + name; }

        virtual void printHasResultType(std::ostream& out) const override
        {
            const Json::Value& enums = spvRoot["spv"]["enum"];

            std::set<unsigned> seenValues;

            for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
                const auto opName   = (*opClass)["Name"].asString();
                if (opName != "Op") {
                    continue;
                }

                out << "#ifdef SPV_ENABLE_UTILITY_CODE" << std::endl;
                out << "#ifndef __cplusplus" << std::endl;
                out << "#include <stdbool.h>" << std::endl;
                out << "#endif" << std::endl;
                out << "inline void " << pre() << "HasResultAndType(" << pre() << opName << " opcode, bool *hasResult, bool *hasResultType) {" << std::endl;
                out << "    *hasResult = *hasResultType = false;" << std::endl;
                out << "    switch (opcode) {" << std::endl;
                out << "    default: /* unknown opcode */ break;" << std::endl;

                for (auto& inst : spv::InstructionDesc) {

                    // Filter out duplicate enum values, which would break the switch statement.
                    // These are probably just extension enums promoted to core.
                    if (seenValues.find(inst.value) != seenValues.end()) {
                        continue;
                    }
                    seenValues.insert(inst.value);

                    std::string name = inst.name;
                    out << "    case " << fmtEnumUse("Op", name) << ": *hasResult = " << (inst.hasResult() ? "true" : "false") << "; *hasResultType = " << (inst.hasType() ? "true" : "false") << "; break;" << std::endl;
                }

                out << "    }" << std::endl;
                out << "}" << std::endl;
                out << "#endif /* SPV_ENABLE_UTILITY_CODE */" << std::endl << std::endl;
            }
        }
    };

    // C printer
    class TPrinterC final : public TPrinterCBase {
    private:
        std::string commentBeg() const override            { return "/*\n"; }
        std::string commentEnd(bool isLast) const override { return "*/\n"; }
        std::string commentBOL() const override            { return "** ";  }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return std::string("typedef enum ") + pre() + s + styleStr(style) + "_ {\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return "} " + pre() + s + styleStr(style) + ";\n\n";
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent() + pre() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string maxEnumFmt(const std::string& s, const valpair_t& v,
                               enumStyle_t style) const override {
            return enumFmt(s, v, style, true);
        }

        std::string pre() const override { return "Spv"; } // C name prefix
        std::string headerGuardSuffix() const override { return "H"; }
    };

    // C++ printer
    class TPrinterCPP : public TPrinterCBase {
    private:
        void printPrologue(std::ostream& out) const override {
            TPrinterCBase::printPrologue(out);
            out << "namespace spv {\n\n";
        }

        void printEpilogue(std::ostream& out) const override {
            const Json::Value& enums = spvRoot["spv"]["enum"];

            out << "// Overload bitwise operators for mask bit combining\n\n";

            for (auto opClass = enums.begin(); opClass != enums.end(); ++opClass) {
                const bool isMask   = (*opClass)["Type"].asString() == "Bit";
                const auto opName   = (*opClass)["Name"].asString();

                if (isMask) {
                    const auto typeName = opName + styleStr(enumMask);

                    // Overload operator|
                    out << "inline " << typeName << " operator|(" << typeName << " a, " << typeName << " b) { return " <<
                        typeName << "(unsigned(a) | unsigned(b)); }\n";
                    // Overload operator&
                    out << "inline " << typeName << " operator&(" << typeName << " a, " << typeName << " b) { return " <<
                        typeName << "(unsigned(a) & unsigned(b)); }\n";
                    // Overload operator^
                    out << "inline " << typeName << " operator^(" << typeName << " a, " << typeName << " b) { return " <<
                        typeName << "(unsigned(a) ^ unsigned(b)); }\n";
                    // Overload operator~
                    out << "inline " << typeName << " operator~(" << typeName << " a) { return " <<
                        typeName << "(~unsigned(a)); }\n";
                }
            }

            out << "\n}  // end namespace spv\n\n";
            out << "#endif  // #ifndef spirv_" << headerGuardSuffix() << std::endl;
        }

        std::string commentBOL() const override { return "// "; }


        virtual std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return std::string("enum ") + s + styleStr(style) + " {\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return "};\n\n";
        }

        virtual std::string enumFmt(const std::string& s, const valpair_t& v,
                                    enumStyle_t style, bool isLast) const override {
            return indent() + s + v.second + styleStr(style) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        virtual std::string maxEnumFmt(const std::string& s, const valpair_t& v,
                                       enumStyle_t style) const override {
            return enumFmt(s, v, style, true);
        }

        // The C++ and C++11 headers define types with the same name. So they
        // should use the same header guard.
        std::string headerGuardSuffix() const override { return "HPP"; }

        std::string operators;
    };

    // C++11 printer (uses enum classes)
    class TPrinterCPP11 final : public TPrinterCPP {
    private:
        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return std::string("enum class ") + s + styleStr(style) + " : unsigned {\n";
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent() + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string maxEnumFmt(const std::string& s, const valpair_t& v,
                               enumStyle_t style) const override {
            return enumFmt(s, v, style, true);
        }

        // Add type prefix for scoped enum
        virtual std::string fmtEnumUse(const std::string& opPrefix, const std::string& name) const override { return opPrefix + "::" + name; }

        std::string headerGuardSuffix() const override { return "HPP"; }
    };

    // LUA printer
    class TPrinterLua final : public TPrinter {
    private:
        void printPrologue(std::ostream& out) const override { out << "spv = {\n"; }

        void printEpilogue(std::ostream& out) const override { out << "}\n"; }

        std::string commentBOL() const override { return "-- "; }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return indent() + s + styleStr(style) + " = {\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return indent() + "},\n\n";
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent(2) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        virtual std::string fmtConstInt(unsigned val, const std::string& name,
                                        const char* fmt, bool isLast) const override
        {
            return indent() + name + " = " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n");
        }
    };

    // Python printer
    class TPrinterPython final : public TPrinter {
    private:
        void printPrologue(std::ostream& out) const override { out << "spv = {\n"; }

        void printEpilogue(std::ostream& out) const override { out << "}\n"; }

        std::string commentBOL() const override { return "# "; }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return indent() + "'" + s + styleStr(style) + "'" + " : {\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return indent() + "},\n\n";
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent(2) + "'" + prependIfDigit(s, v.second) + "'" + " : " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string fmtConstInt(unsigned val, const std::string& name,
                                const char* fmt, bool isLast) const override
        {
            return indent() + "'" + name + "'" + " : " + fmtNum(fmt, val) + (isLast ? ",\n\n" : ",\n");
        }
    };

    // C# printer
    class TPrinterCSharp final : public TPrinter {
    private:
        std::string commentBOL() const override { return "// ";  }

        void printPrologue(std::ostream& out) const override {
            out << "namespace Spv\n{\n\n";
            out << indent() << "public static class Specification\n";
            out << indent() << "{\n";
        }

        void printEpilogue(std::ostream& out) const override {
            out << indent() << "}\n";
            out << "}\n";
        }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return indent(2) + "public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return indent(2) + "}" + + (isLast ? "\n" : "\n\n");
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string fmtConstInt(unsigned val, const std::string& name,
                                const char* fmt, bool isLast) const override {
            return indent(2) + std::string("public const uint ") + name +
                " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
        }
    };

    // D printer
    class TPrinterD final : public TPrinter {
    private:
        std::string commentBeg() const override            { return "/+\n"; }
        std::string commentBOL() const override            { return " + ";  }
        std::string commentEnd(bool isLast) const override { return " +/\n"; }

        void printPrologue(std::ostream& out) const override {
            out << "module spv;\n\n";
        }

        void printEpilogue(std::ostream& out) const override {
        }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return "enum " + s + styleStr(style) + " : uint\n{\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return std::string("}\n\n");
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
                            enumStyle_t style, bool isLast) const override {
            return indent() + prependIfDigit("_", v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string fmtConstInt(unsigned val, const std::string& name,
                                const char* fmt, bool isLast) const override {
            return std::string("enum uint ") + name +
                " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
        }
    };

    // Beef printer
    class TPrinterBeef final : public TPrinter {
    private:
        std::string commentBOL() const override { return "// "; }

        void printPrologue(std::ostream& out) const override {
            out << "namespace Spv\n{\n";
            out << indent() << "using System;\n\n";
            out << indent() << "public static class Specification\n";
            out << indent() << "{\n";
        }

        void printEpilogue(std::ostream& out) const override {
            out << indent() << "}\n";
            out << "}\n";
        }

        std::string enumBeg(const std::string& s, enumStyle_t style) const override {
            return indent(2) + "[AllowDuplicates, CRepr] public enum " + s + styleStr(style) + "\n" + indent(2) + "{\n";
        }

        std::string enumEnd(const std::string& s, enumStyle_t style, bool isLast) const override {
            return indent(2) + "}" + +(isLast ? "\n" : "\n\n");
        }

        std::string enumFmt(const std::string& s, const valpair_t& v,
            enumStyle_t style, bool isLast) const override {
            return indent(3) + prependIfDigit(s, v.second) + " = " + fmtStyleVal(v.first, style) + ",\n";
        }

        std::string fmtConstInt(unsigned val, const std::string& name,
            const char* fmt, bool isLast) const override {
            return indent(2) + std::string("public const uint32 ") + name +
                " = " + fmtNum(fmt, val) + (isLast ? ";\n\n" : ";\n");
        }
    };

} // namespace

namespace spv {
    void PrintAllHeaders()
    {
        // TODO: Once MSVC 2012 is no longer a factor, use brace initializers here
        std::vector<std::pair<TLanguage, std::string>> langInfo;

        langInfo.push_back(std::make_pair(ELangC,       "spirv.h"));
        langInfo.push_back(std::make_pair(ELangCPP,     "spirv.hpp"));
        langInfo.push_back(std::make_pair(ELangCPP11,   "spirv.hpp11"));
        langInfo.push_back(std::make_pair(ELangJSON,    "spirv.json"));
        langInfo.push_back(std::make_pair(ELangLua,     "spirv.lua"));
        langInfo.push_back(std::make_pair(ELangPython,  "spirv.py"));
        langInfo.push_back(std::make_pair(ELangCSharp,  "spirv.cs"));
        langInfo.push_back(std::make_pair(ELangD,       "spv.d"));
        langInfo.push_back(std::make_pair(ELangBeef,    "spirv.bf"));

        for (const auto& lang : langInfo) {
            std::ofstream out(lang.second, std::ios::out);

            if ((out.rdstate() & std::ifstream::failbit)) {
                std::cerr << "Unable to open file: " << lang.second << std::endl;
            } else {
                PrintHeader(lang.first, out);
            }
        }
    }

    // Print header for given language to given output stream
    void PrintHeader(TLanguage lang, std::ostream& out)
    {
        typedef std::unique_ptr<TPrinter> TPrinterPtr;
        TPrinterPtr p;

        switch (lang) {
            case ELangC:       p = TPrinterPtr(new TPrinterC);       break;
            case ELangCPP:     p = TPrinterPtr(new TPrinterCPP);     break;
            case ELangCPP11:   p = TPrinterPtr(new TPrinterCPP11);   break;
            case ELangJSON:    p = TPrinterPtr(new TPrinterJSON);    break;
            case ELangLua:     p = TPrinterPtr(new TPrinterLua);     break;
            case ELangPython:  p = TPrinterPtr(new TPrinterPython);  break;
            case ELangCSharp:  p = TPrinterPtr(new TPrinterCSharp);  break;
            case ELangD:       p = TPrinterPtr(new TPrinterD);       break;
            case ELangBeef:    p = TPrinterPtr(new TPrinterBeef);    break;
            case ELangAll:     PrintAllHeaders();                    break;
            default:
                std::cerr << "Unknown language." << std::endl;
                return;
        }

        // Print the data in the requested format
        if (p)
            out << *p << std::endl;

        // object is auto-deleted
    }

} // namespace spv
