#ifndef _GLUVARTYPEUTIL_HPP
#define _GLUVARTYPEUTIL_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 utilities.
 *//*--------------------------------------------------------------------*/

#include "tcuDefs.hpp"
#include "gluVarType.hpp"

#include <vector>
#include <string>
#include <iterator>

namespace glu
{

// Variable path tokenizer

class VarTokenizer
{
public:
    enum Token
    {
        TOKEN_IDENTIFIER = 0,
        TOKEN_LEFT_BRACKET,
        TOKEN_RIGHT_BRACKET,
        TOKEN_PERIOD,
        TOKEN_NUMBER,
        TOKEN_END,

        TOKEN_LAST
    };

    VarTokenizer(const char *str);
    ~VarTokenizer(void)
    {
    }

    Token getToken(void) const
    {
        return m_token;
    }
    std::string getIdentifier(void) const
    {
        return std::string(m_str + m_tokenStart, m_str + m_tokenStart + m_tokenLen);
    }
    int getNumber(void) const;
    int getCurrentTokenStartLocation(void) const
    {
        return m_tokenStart;
    }
    int getCurrentTokenEndLocation(void) const
    {
        return m_tokenStart + m_tokenLen;
    }
    void advance(void);

private:
    const char *m_str;

    Token m_token;
    int m_tokenStart;
    int m_tokenLen;
};

// VarType subtype path utilities.

struct VarTypeComponent
{
    enum Type
    {
        STRUCT_MEMBER = 0,
        ARRAY_ELEMENT,
        MATRIX_COLUMN,
        VECTOR_COMPONENT,

        VTCTYPE_LAST
    };

    VarTypeComponent(Type type_, int index_) : type(type_), index(index_)
    {
    }
    VarTypeComponent(void) : type(VTCTYPE_LAST), index(0)
    {
    }

    bool operator==(const VarTypeComponent &other) const
    {
        return type == other.type && index == other.index;
    }
    bool operator!=(const VarTypeComponent &other) const
    {
        return type != other.type || index != other.index;
    }

    Type type;
    int index;
};

typedef std::vector<VarTypeComponent> TypeComponentVector;

// TypeComponentVector utilties.

template <typename Iterator>
bool isValidTypePath(const VarType &type, Iterator begin, Iterator end);

template <typename Iterator>
VarType getVarType(const VarType &type, Iterator begin, Iterator end);

inline bool isValidTypePath(const VarType &type, const TypeComponentVector &path)
{
    return isValidTypePath(type, path.begin(), path.end());
}
inline VarType getVarType(const VarType &type, const TypeComponentVector &path)
{
    return getVarType(type, path.begin(), path.end());
}

std::string parseVariableName(const char *nameWithPath);
void parseTypePath(const char *nameWithPath, const VarType &type, TypeComponentVector &path);

// Type path formatter.

struct TypeAccessFormat
{
    TypeAccessFormat(const VarType &type_, const TypeComponentVector &path_) : type(type_), path(path_)
    {
    }

    const VarType &type;
    const TypeComponentVector &path;
};

std::ostream &operator<<(std::ostream &str, const TypeAccessFormat &format);

// Subtype path builder.

class SubTypeAccess
{
public:
    SubTypeAccess(const VarType &type);

    SubTypeAccess &member(int ndx)
    {
        m_path.push_back(VarTypeComponent(VarTypeComponent::STRUCT_MEMBER, ndx));
        DE_ASSERT(isValid());
        return *this;
    } //!< Access struct element.
    SubTypeAccess &element(int ndx)
    {
        m_path.push_back(VarTypeComponent(VarTypeComponent::ARRAY_ELEMENT, ndx));
        DE_ASSERT(isValid());
        return *this;
    } //!< Access array element.
    SubTypeAccess &column(int ndx)
    {
        m_path.push_back(VarTypeComponent(VarTypeComponent::MATRIX_COLUMN, ndx));
        DE_ASSERT(isValid());
        return *this;
    } //!< Access column.
    SubTypeAccess &component(int ndx)
    {
        m_path.push_back(VarTypeComponent(VarTypeComponent::VECTOR_COMPONENT, ndx));
        DE_ASSERT(isValid());
        return *this;
    } //!< Access component.
    SubTypeAccess &parent(void)
    {
        DE_ASSERT(!m_path.empty());
        m_path.pop_back();
        return *this;
    }

    SubTypeAccess member(int ndx) const
    {
        return SubTypeAccess(*this).member(ndx);
    }
    SubTypeAccess element(int ndx) const
    {
        return SubTypeAccess(*this).element(ndx);
    }
    SubTypeAccess column(int ndx) const
    {
        return SubTypeAccess(*this).column(ndx);
    }
    SubTypeAccess component(int ndx) const
    {
        return SubTypeAccess(*this).component(ndx);
    }
    SubTypeAccess parent(void) const
    {
        return SubTypeAccess(*this).parent();
    }

    bool isValid(void) const
    {
        return isValidTypePath(m_type, m_path);
    }
    VarType getType(void) const
    {
        return getVarType(m_type, m_path);
    }
    const TypeComponentVector &getPath(void) const
    {
        return m_path;
    }

    bool empty(void) const
    {
        return m_path.empty();
    }

    bool operator==(const SubTypeAccess &other) const
    {
        return m_path == other.m_path && m_type == other.m_type;
    }
    bool operator!=(const SubTypeAccess &other) const
    {
        return m_path != other.m_path || m_type != other.m_type;
    }

private:
    VarType m_type;
    TypeComponentVector m_path;
};

// Subtype iterator.

// \note VarType must be live during iterator usage.
template <class IsExpanded>
class SubTypeIterator
{
public:
    using iterator_category = std::forward_iterator_tag;
    using value_type        = VarType;
    using difference_type   = std::ptrdiff_t;
    using pointer           = VarType *;
    using reference         = VarType &;

    static SubTypeIterator<IsExpanded> begin(const VarType *type)
    {
        return SubTypeIterator(type);
    }
    static SubTypeIterator<IsExpanded> end(const VarType *type)
    {
        DE_UNREF(type);
        return SubTypeIterator(DE_NULL);
    }

    bool operator==(const SubTypeIterator<IsExpanded> &other) const
    {
        return m_type == other.m_type && m_path == other.m_path;
    }
    bool operator!=(const SubTypeIterator<IsExpanded> &other) const
    {
        return m_type != other.m_type || m_path != other.m_path;
    }

    SubTypeIterator<IsExpanded> &operator++(void);
    SubTypeIterator<IsExpanded> operator++(int)
    {
        SubTypeIterator<IsExpanded> copy(*this);
        ++(*this);
        return copy;
    }

    void toStream(std::ostream &str) const
    {
        str << TypeAccessFormat(*m_type, m_path);
    }
    VarType getType(void) const
    {
        return getVarType(*m_type, m_path.begin(), m_path.end());
    }
    const TypeComponentVector &getPath(void) const
    {
        return m_path;
    }

    VarType operator*(void) const
    {
        return getType();
    }

private:
    SubTypeIterator(const VarType *type);

    void removeTraversed(void);
    void findNext(void);

    const VarType *m_type;
    TypeComponentVector m_path;
};

struct IsBasicType
{
    bool operator()(const VarType &type) const
    {
        return type.isBasicType();
    }
};
struct IsScalarType
{
    bool operator()(const VarType &type) const
    {
        return type.isBasicType() && isDataTypeScalar(type.getBasicType());
    }
};
struct IsVectorOrScalarType
{
    bool operator()(const VarType &type) const
    {
        return type.isBasicType() && isDataTypeScalarOrVector(type.getBasicType());
    }
};

typedef SubTypeIterator<IsBasicType> BasicTypeIterator;
typedef SubTypeIterator<IsVectorOrScalarType> VectorTypeIterator;
typedef SubTypeIterator<IsScalarType> ScalarTypeIterator;

template <class IsExpanded>
std::ostream &operator<<(std::ostream &str, const SubTypeIterator<IsExpanded> &iter)
{
    iter.toStream(str);
    return str;
}

template <class IsExpanded>
SubTypeIterator<IsExpanded>::SubTypeIterator(const VarType *type) : m_type(type)
{
    if (m_type)
        findNext();
}

template <class IsExpanded>
SubTypeIterator<IsExpanded> &SubTypeIterator<IsExpanded>::operator++(void)
{
    if (!m_path.empty())
    {
        // Remove traversed nodes.
        removeTraversed();

        if (!m_path.empty())
            findNext();
        else
            m_type = DE_NULL; // Unset type to signal end.
    }
    else
    {
        // First type was already expanded.
        DE_ASSERT(IsExpanded()(getVarType(*m_type, m_path)));
        m_type = DE_NULL;
    }

    return *this;
}

template <class IsExpanded>
void SubTypeIterator<IsExpanded>::removeTraversed(void)
{
    DE_ASSERT(m_type && !m_path.empty());

    // Pop traversed nodes.
    while (!m_path.empty())
    {
        VarTypeComponent &curComp = m_path.back();
        VarType parentType        = getVarType(*m_type, m_path.begin(), m_path.end() - 1);

        if (curComp.type == VarTypeComponent::MATRIX_COLUMN)
        {
            DE_ASSERT(isDataTypeMatrix(parentType.getBasicType()));
            if (curComp.index + 1 < getDataTypeMatrixNumColumns(parentType.getBasicType()))
                break;
        }
        else if (curComp.type == VarTypeComponent::VECTOR_COMPONENT)
        {
            DE_ASSERT(isDataTypeVector(parentType.getBasicType()));
            if (curComp.index + 1 < getDataTypeScalarSize(parentType.getBasicType()))
                break;
        }
        else if (curComp.type == VarTypeComponent::ARRAY_ELEMENT)
        {
            DE_ASSERT(parentType.isArrayType());
            if (curComp.index + 1 < parentType.getArraySize())
                break;
        }
        else if (curComp.type == VarTypeComponent::STRUCT_MEMBER)
        {
            DE_ASSERT(parentType.isStructType());
            if (curComp.index + 1 < parentType.getStructPtr()->getNumMembers())
                break;
        }

        m_path.pop_back();
    }
}

template <class IsExpanded>
void SubTypeIterator<IsExpanded>::findNext(void)
{
    if (!m_path.empty())
    {
        // Increment child counter in current level.
        VarTypeComponent &curComp = m_path.back();
        curComp.index += 1;
    }

    for (;;)
    {
        VarType curType = getVarType(*m_type, m_path);

        if (IsExpanded()(curType))
            break;

        // Recurse into child type.
        if (curType.isBasicType())
        {
            DataType basicType = curType.getBasicType();

            if (isDataTypeMatrix(basicType))
                m_path.push_back(VarTypeComponent(VarTypeComponent::MATRIX_COLUMN, 0));
            else if (isDataTypeVector(basicType))
                m_path.push_back(VarTypeComponent(VarTypeComponent::VECTOR_COMPONENT, 0));
            else
                DE_ASSERT(false); // Can't expand scalars - IsExpanded() is buggy.
        }
        else if (curType.isArrayType())
            m_path.push_back(VarTypeComponent(VarTypeComponent::ARRAY_ELEMENT, 0));
        else if (curType.isStructType())
            m_path.push_back(VarTypeComponent(VarTypeComponent::STRUCT_MEMBER, 0));
        else
            DE_ASSERT(false);
    }
}

template <typename Iterator>
bool isValidTypePath(const VarType &type, Iterator begin, Iterator end)
{
    const VarType *curType = &type;
    Iterator pathIter      = begin;

    // Process struct member and array element parts of path.
    while (pathIter != end)
    {
        if (pathIter->type == VarTypeComponent::STRUCT_MEMBER)
        {
            if (!curType->isStructType() || !de::inBounds(pathIter->index, 0, curType->getStructPtr()->getNumMembers()))
                return false;

            curType = &curType->getStructPtr()->getMember(pathIter->index).getType();
        }
        else if (pathIter->type == VarTypeComponent::ARRAY_ELEMENT)
        {
            if (!curType->isArrayType() || (curType->getArraySize() != VarType::UNSIZED_ARRAY &&
                                            !de::inBounds(pathIter->index, 0, curType->getArraySize())))
                return false;

            curType = &curType->getElementType();
        }
        else
            break;

        ++pathIter;
    }

    if (pathIter != end)
    {
        DE_ASSERT(pathIter->type == VarTypeComponent::MATRIX_COLUMN ||
                  pathIter->type == VarTypeComponent::VECTOR_COMPONENT);

        // Current type should be basic type.
        if (!curType->isBasicType())
            return false;

        DataType basicType = curType->getBasicType();

        if (pathIter->type == VarTypeComponent::MATRIX_COLUMN)
        {
            if (!isDataTypeMatrix(basicType))
                return false;

            basicType = getDataTypeFloatVec(getDataTypeMatrixNumRows(basicType));
            ++pathIter;
        }

        if (pathIter != end && pathIter->type == VarTypeComponent::VECTOR_COMPONENT)
        {
            if (!isDataTypeVector(basicType))
                return false;

            basicType = getDataTypeScalarType(basicType);
            ++pathIter;
        }
    }

    return pathIter == end;
}

template <typename Iterator>
VarType getVarType(const VarType &type, Iterator begin, Iterator end)
{
    TCU_CHECK(isValidTypePath(type, begin, end));

    const VarType *curType = &type;
    Iterator pathIter      = begin;

    // Process struct member and array element parts of path.
    while (pathIter != end)
    {
        if (pathIter->type == VarTypeComponent::STRUCT_MEMBER)
            curType = &curType->getStructPtr()->getMember(pathIter->index).getType();
        else if (pathIter->type == VarTypeComponent::ARRAY_ELEMENT)
            curType = &curType->getElementType();
        else
            break;

        ++pathIter;
    }

    if (pathIter != end)
    {
        DataType basicType  = curType->getBasicType();
        Precision precision = curType->getPrecision();

        if (pathIter->type == VarTypeComponent::MATRIX_COLUMN)
        {
            basicType = getDataTypeFloatVec(getDataTypeMatrixNumRows(basicType));
            ++pathIter;
        }

        if (pathIter != end && pathIter->type == VarTypeComponent::VECTOR_COMPONENT)
        {
            basicType = getDataTypeScalarType(basicType);
            ++pathIter;
        }

        DE_ASSERT(pathIter == end);
        return VarType(basicType, precision);
    }
    else
        return VarType(*curType);
}

} // namespace glu

#endif // _GLUVARTYPEUTIL_HPP
