// 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.

#pragma once
#ifndef JSON_TO_SPIRV
#define JSON_TO_SPIRV

#include <algorithm>
#include <string>
#include <vector>
#include <assert.h>

namespace spv {

    // Reads the file in the given |path|. Returns true and the contents of the
// file on success; otherwise, returns false and an empty string.
std::pair<bool, std::string> ReadFile(const std::string& path);

// Fill in all the parameters
void jsonToSpirv(const std::string& jsonPath, bool buildingHeaders);

// For parameterizing operands.
enum OperandClass {
    OperandNone,
    OperandId,
    OperandVariableIds,
    OperandOptionalLiteral,
    OperandOptionalLiteralString,
    OperandOptionalLiteralStrings,
    OperandVariableLiterals,
    OperandVariableIdLiteral,
    OperandVariableLiteralId,
    OperandAnySizeLiteralNumber,
    OperandLiteralNumber,
    OperandLiteralString,
    OperandSource,
    OperandExecutionModel,
    OperandAddressing,
    OperandMemory,
    OperandExecutionMode,
    OperandStorage,
    OperandDimensionality,
    OperandSamplerAddressingMode,
    OperandSamplerFilterMode,
    OperandSamplerImageFormat,
    OperandImageChannelOrder,
    OperandImageChannelDataType,
    OperandImageOperands,
    OperandFPFastMath,
    OperandFPRoundingMode,
    OperandLinkageType,
    OperandAccessQualifier,
    OperandFuncParamAttr,
    OperandDecoration,
    OperandBuiltIn,
    OperandSelect,
    OperandLoop,
    OperandFunction,
    OperandMemorySemantics,
    OperandMemoryOperands,
    OperandScope,
    OperandGroupOperation,
    OperandKernelEnqueueFlags,
    OperandKernelProfilingInfo,
    OperandCapability,
    OperandRayFlags,
    OperandRayQueryIntersection,
    OperandRayQueryCommittedIntersectionType,
    OperandRayQueryCandidateIntersectionType,
    OperandFragmentShadingRate,

    OperandOpcode,

    OperandCount
};

// For direct representation of the JSON grammar "instruction_printing_class".
struct PrintingClass {
    std::string tag;
    std::string heading;
};
using PrintingClasses = std::vector<PrintingClass>;

// Any specific enum can have a set of capabilities that allow it:
typedef std::vector<std::string> EnumCaps;

// A set of extensions.
typedef std::vector<std::string> Extensions;

// Parameterize a set of operands with their OperandClass(es) and descriptions.
class OperandParameters {
public:
    OperandParameters() { }
    void push(OperandClass oc, const std::string& d, bool opt = false)
    {
        opClass.push_back(oc);
        desc.push_back(d);
        optional.push_back(opt);
    }
    void setOptional();
    OperandClass getClass(int op) const { return opClass[op]; }
    const char* getDesc(int op) const { return desc[op].c_str(); }
    bool isOptional(int op) const { return optional[op]; }
    int getNum() const { return (int)opClass.size(); }

protected:
    std::vector<OperandClass> opClass;
    std::vector<std::string> desc;
    std::vector<bool> optional;
};

// An ordered sequence of EValue.  We'll preserve the order found in the
// JSON file.  You can look up a value by enum or by name.  If there are
// duplicate values, then take the first.  We assume names are unique.
// The EValue must have an unsigned |value| field and a string |name| field.
template <typename EValue>
class EnumValuesContainer {
public:
    using ContainerType = std::vector<EValue>;
    using iterator = typename ContainerType::iterator;
    using const_iterator = typename ContainerType::const_iterator;

    EnumValuesContainer() {}

    // Constructs an EValue in place as a new element at the end of the
    // sequence.
    template <typename... Args>
    void emplace_back(Args&&... args) {
        values.emplace_back(std::forward<Args>(args)...);
    }

    // Returns the first EValue in the sequence with the given value.
    // More than one EValue might have the same value.
    EValue& operator[](unsigned value) {
        auto where = std::find_if(begin(), end(), [&value](const EValue& e) {
           return value == e.value;
        });
        assert((where != end()) && "Could not find enum in the enum list");
        return *where;
    }
    // gets *all* entries for the value, including the first one
    void gatherAliases(unsigned value, std::vector<EValue*>& aliases) {
        std::for_each(begin(), end(), [&](EValue& e) {
            if (value == e.value)
                aliases.push_back(&e);});
    }
    // Returns the EValue with the given name.  We assume uniqueness
    // by name.
    EValue& at(std::string name) {
        auto where = std::find_if(begin(), end(), [&name](const EValue& e) {
           return name == e.name;
        });
        assert((where != end()) && "Could not find name in the enum list");
        return *where;
    }

    iterator begin() { return values.begin(); }
    iterator end() { return values.end(); }

private:
    ContainerType values;
};

// A single enumerant value.  Corresponds to a row in an enumeration table
// in the spec.
class EnumValue {
public:
    EnumValue() : value(0), desc(nullptr) {}
    EnumValue(unsigned int the_value, const std::string& the_name, EnumCaps&& the_caps,
        const std::string& the_firstVersion, const std::string& the_lastVersion,
        Extensions&& the_extensions, OperandParameters&& the_operands) :
      value(the_value), name(the_name), capabilities(std::move(the_caps)),
      firstVersion(std::move(the_firstVersion)), lastVersion(std::move(the_lastVersion)),
      extensions(std::move(the_extensions)), operands(std::move(the_operands)), desc(nullptr) { }

    // For ValueEnum, the value from the JSON file.
    // For BitEnum, the index of the bit position represented by this mask.
    // (That is, what you shift 1 by to get the mask.)
    unsigned value;
    std::string name;
    EnumCaps capabilities;
    std::string firstVersion;
    std::string lastVersion;
    // A feature only be enabled by certain extensions.
    // An empty list means the feature does not require an extension.
    // Normally, only Capability enums are enabled by extension.  In turn,
    // other enums and instructions are enabled by those capabilities.
    Extensions extensions;
    OperandParameters operands;
    const char* desc;
};

using EnumValues = EnumValuesContainer<EnumValue>;

// Parameterize a set of enumerants that form an enum
class EnumDefinition {
public:
    EnumDefinition() :
        desc(0), bitmask(false), enumValues(nullptr) { }
    void set(const std::string& enumName, EnumValues* enumValuesArg, bool mask = false)
    {
        codeName = enumName;
        bitmask = mask;
        enumValues = enumValuesArg;
    }
    // Returns the first EnumValue in the sequence with the given value.
    // More than one EnumValue might have the same value.  Only valid
    // if enumValues has been populated.
    EnumValue& operator[](unsigned value) {
        assert(enumValues != nullptr);
        return (*enumValues)[value];
    }
    // Returns the name of the first EnumValue with the given value.
    // Assumes enumValues has been populated.
    const char* getName(unsigned value) {
        return (*this)[value].name.c_str();
    }

    using iterator = EnumValues::iterator;
    iterator begin() { return enumValues->begin(); }
    iterator end() { return enumValues->end(); }

    std::string codeName; // name to use when declaring headers for code
    const char* desc;
    bool bitmask;  // true if these enumerants combine into a bitmask
    EnumValues* enumValues; // parameters for each individual enumerant
};

// Parameterize an instruction's logical format, including its known set of operands,
// per OperandParameters above.
class InstructionValue : public EnumValue {
public:
    InstructionValue(EnumValue&& e, const std::string& printClass, bool has_type, bool has_result)
     : EnumValue(std::move(e)),
       printingClass(printClass),
       opDesc("TBD"),
       typePresent(has_type),
       resultPresent(has_result),
       alias(this) { }
    InstructionValue(const InstructionValue& v)
    {
        *this = v;
        alias = this;
    }

    bool hasResult() const { return resultPresent != 0; }
    bool hasType()   const { return typePresent != 0; }
    void setAlias(const InstructionValue& a) { alias = &a; }
    const InstructionValue& getAlias() const { return *alias; }
    bool isAlias() const { return alias != this; }

    std::string printingClass;
    const char* opDesc;

protected:
    int typePresent   : 1;
    int resultPresent : 1;
    const InstructionValue* alias;    // correct only after discovering the aliases; otherwise points to this
};

using InstructionValues = EnumValuesContainer<InstructionValue>;

// Parameterization info for all instructions.
extern InstructionValues InstructionDesc;
extern PrintingClasses InstructionPrintingClasses;

// These hold definitions of the enumerants used for operands.
// This is indexed by OperandClass, but not including OperandOpcode.
extern EnumDefinition OperandClassParams[];

};  // end namespace spv

#endif // JSON_TO_SPIRV
