#ifndef _GLUVARTYPE_HPP
#define _GLUVARTYPE_HPP
/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES Utilities
 * ------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Shader variable type.
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "gluShaderUtil.hpp"

#include <vector>
#include <string>
#include <ostream>

namespace glu
{

class StructType;

/*--------------------------------------------------------------------*//*!
 * \brief Shader variable type.
 *
 * Variable type represents data type. No storage qualifiers are supported
 * since they are associated to a declaration, not to the variable type.
 *
 * \note Structs are handled using struct pointers since it is often desirable
 *         to maintain unique list of struct declarations.
 *//*--------------------------------------------------------------------*/
class VarType
{
public:
    VarType(void);
    VarType(const VarType &other);

    VarType(DataType basicType, Precision precision);   //!< Basic type constructor.
    VarType(const VarType &elementType, int arraySize); //!< Array type constructor.
    explicit VarType(const StructType *structPtr);      //!< Struct type constructor.
    ~VarType(void);

    bool isBasicType(void) const
    {
        return m_type == VARTYPE_BASIC;
    }
    bool isArrayType(void) const
    {
        return m_type == VARTYPE_ARRAY;
    }
    bool isStructType(void) const
    {
        return m_type == VARTYPE_STRUCT;
    }

    DataType getBasicType(void) const
    {
        DE_ASSERT(isBasicType());
        return m_data.basic.type;
    }
    Precision getPrecision(void) const
    {
        DE_ASSERT(isBasicType());
        return m_data.basic.precision;
    }

    const VarType &getElementType(void) const
    {
        DE_ASSERT(isArrayType());
        return *m_data.array.elementType;
    }
    int getArraySize(void) const
    {
        DE_ASSERT(isArrayType());
        return m_data.array.size;
    }

    const StructType *getStructPtr(void) const
    {
        DE_ASSERT(isStructType());
        return m_data.structPtr;
    }

    int getScalarSize(void) const;

    VarType &operator=(const VarType &other);

    bool operator==(const VarType &other) const;
    bool operator!=(const VarType &other) const;

    enum
    {
        UNSIZED_ARRAY = -1 //!< Array length for unsized arrays.
    };

private:
    enum Type
    {
        VARTYPE_BASIC,
        VARTYPE_ARRAY,
        VARTYPE_STRUCT,

        VARTYPE_LAST
    };

    Type m_type;
    union Data
    {
        // TYPE_BASIC
        struct
        {
            DataType type;
            Precision precision;
        } basic;

        // TYPE_ARRAY
        struct
        {
            VarType *elementType;
            int size;
        } array;

        // TYPE_STRUCT
        const StructType *structPtr;

        Data(void)
        {
            array.elementType = DE_NULL;
            array.size        = 0;
        }
    } m_data;
} DE_WARN_UNUSED_TYPE;

template <typename T>
inline VarType varTypeOf(Precision prec = PRECISION_LAST)
{
    return VarType(dataTypeOf<T>(), prec);
}

class StructMember
{
public:
    StructMember(const char *name, const VarType &type) : m_name(name), m_type(type)
    {
    }
    StructMember(void)
    {
    }

    const char *getName(void) const
    {
        return m_name.c_str();
    }
    const VarType &getType(void) const
    {
        return m_type;
    }

    bool operator==(const StructMember &other) const;
    bool operator!=(const StructMember &other) const;

private:
    std::string m_name;
    VarType m_type;
} DE_WARN_UNUSED_TYPE;

class StructType
{
public:
    typedef std::vector<StructMember>::iterator Iterator;
    typedef std::vector<StructMember>::const_iterator ConstIterator;

    StructType(const char *typeName) : m_typeName(typeName)
    {
    }
    ~StructType(void)
    {
    }

    bool hasTypeName(void) const
    {
        return !m_typeName.empty();
    }
    const char *getTypeName(void) const
    {
        return hasTypeName() ? m_typeName.c_str() : DE_NULL;
    }

    void addMember(const char *name, const VarType &type);

    int getNumMembers(void) const
    {
        return (int)m_members.size();
    }
    const StructMember &getMember(int ndx) const
    {
        return m_members[ndx];
    }

    inline Iterator begin(void)
    {
        return m_members.begin();
    }
    inline ConstIterator begin(void) const
    {
        return m_members.begin();
    }
    inline Iterator end(void)
    {
        return m_members.end();
    }
    inline ConstIterator end(void) const
    {
        return m_members.end();
    }

    bool operator==(const StructType &other) const;
    bool operator!=(const StructType &other) const;

private:
    std::string m_typeName;
    std::vector<StructMember> m_members;
} DE_WARN_UNUSED_TYPE;

enum Storage
{
    STORAGE_IN = 0,
    STORAGE_OUT,
    STORAGE_CONST,
    STORAGE_UNIFORM,
    STORAGE_BUFFER,
    STORAGE_PATCH_IN,
    STORAGE_PATCH_OUT,
    STORAGE_LAST
};

const char *getStorageName(Storage storage);

enum Interpolation
{
    INTERPOLATION_SMOOTH = 0,
    INTERPOLATION_FLAT,
    INTERPOLATION_CENTROID,
    INTERPOLATION_LAST
};

const char *getInterpolationName(Interpolation interpolation);

enum FormatLayout
{
    FORMATLAYOUT_RGBA32F = 0,
    FORMATLAYOUT_RGBA16F,
    FORMATLAYOUT_R32F,
    FORMATLAYOUT_RGBA8,
    FORMATLAYOUT_RGBA8_SNORM,

    FORMATLAYOUT_RGBA32I,
    FORMATLAYOUT_RGBA16I,
    FORMATLAYOUT_RGBA8I,
    FORMATLAYOUT_R32I,

    FORMATLAYOUT_RGBA32UI,
    FORMATLAYOUT_RGBA16UI,
    FORMATLAYOUT_RGBA8UI,
    FORMATLAYOUT_R32UI,

    FORMATLAYOUT_LAST
};

const char *getFormatLayoutName(FormatLayout layout);

enum MemoryAccessQualifier
{
    MEMORYACCESSQUALIFIER_COHERENT_BIT  = 0x01,
    MEMORYACCESSQUALIFIER_VOLATILE_BIT  = 0x02,
    MEMORYACCESSQUALIFIER_RESTRICT_BIT  = 0x04,
    MEMORYACCESSQUALIFIER_READONLY_BIT  = 0x08,
    MEMORYACCESSQUALIFIER_WRITEONLY_BIT = 0x10,

    MEMORYACCESSQUALIFIER_MASK = (MEMORYACCESSQUALIFIER_WRITEONLY_BIT << 1) - 1
};

const char *getMemoryAccessQualifierName(MemoryAccessQualifier qualifier);

enum MatrixOrder
{
    MATRIXORDER_COLUMN_MAJOR = 0,
    MATRIXORDER_ROW_MAJOR,

    MATRIXORDER_LAST
};

const char *getMatrixOrderName(MatrixOrder qualifier);

// Declaration utilities.

struct Layout
{
    Layout(int location_ = -1, int binding_ = -1, int offset_ = -1, FormatLayout format_ = FORMATLAYOUT_LAST,
           MatrixOrder matrixOrder_ = MATRIXORDER_LAST);

    bool operator==(const Layout &other) const;
    bool operator!=(const Layout &other) const;

    int location;
    int binding;
    int offset;
    FormatLayout format;
    MatrixOrder matrixOrder;
} DE_WARN_UNUSED_TYPE;

struct VariableDeclaration
{
    VariableDeclaration(const VarType &varType_, const std::string &name_, Storage storage_ = STORAGE_LAST,
                        Interpolation interpolation_ = INTERPOLATION_LAST, const Layout &layout_ = Layout(),
                        uint32_t memoryAccessQualifierBits_ = 0);

    bool operator==(const VariableDeclaration &other) const;
    bool operator!=(const VariableDeclaration &other) const;

    Layout layout;
    Interpolation interpolation;
    Storage storage;
    VarType varType;
    uint32_t memoryAccessQualifierBits;
    std::string name;
} DE_WARN_UNUSED_TYPE;

struct InterfaceBlock
{
    InterfaceBlock(void);

    glu::Layout layout;
    Storage storage;
    int memoryAccessQualifierFlags;
    std::string interfaceName;
    std::string instanceName;
    std::vector<glu::VariableDeclaration> variables;
    std::vector<int> dimensions;
} DE_WARN_UNUSED_TYPE;

//! Internals for declare() utilities.
namespace decl
{

struct Indent
{
    int level;
    Indent(int level_) : level(level_)
    {
    }
};

struct DeclareStructTypePtr
{
    DeclareStructTypePtr(const StructType *structPtr_, int indentLevel_)
        : structPtr(structPtr_)
        , indentLevel(indentLevel_)
    {
    }

    const StructType *structPtr;
    int indentLevel;
};

struct DeclareStructType
{
    DeclareStructType(const StructType &structType_, int indentLevel_)
        : structType(structType_)
        , indentLevel(indentLevel_)
    {
    }

    StructType structType;
    int indentLevel;
};

struct DeclareVariable
{
    DeclareVariable(const VarType &varType_, const std::string &name_, int indentLevel_)
        : varType(varType_)
        , name(name_)
        , indentLevel(indentLevel_)
    {
    }

    VarType varType;
    std::string name;
    int indentLevel;
};

std::ostream &operator<<(std::ostream &str, const Indent &indent);
std::ostream &operator<<(std::ostream &str, const DeclareStructTypePtr &decl);
std::ostream &operator<<(std::ostream &str, const DeclareStructType &decl);
std::ostream &operator<<(std::ostream &str, const DeclareVariable &decl);

} // namespace decl

inline decl::Indent indent(int indentLevel)
{
    return decl::Indent(indentLevel);
}
inline decl::DeclareStructTypePtr declare(const StructType *structPtr, int indentLevel = 0)
{
    return decl::DeclareStructTypePtr(structPtr, indentLevel);
}
inline decl::DeclareStructType declare(const StructType &structType, int indentLevel = 0)
{
    return decl::DeclareStructType(structType, indentLevel);
}
inline decl::DeclareVariable declare(const VarType &varType, const std::string &name, int indentLevel = 0)
{
    return decl::DeclareVariable(varType, name, indentLevel);
}

std::ostream &operator<<(std::ostream &str, const Layout &decl);
std::ostream &operator<<(std::ostream &str, const VariableDeclaration &decl);

} // namespace glu

#endif // _GLUVARTYPE_HPP
