/*-------------------------------------------------------------------------
 * drawElements Quality Program Random Shader Generator
 * ----------------------------------------------------
 *
 * 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 Variable Type class.
 *//*--------------------------------------------------------------------*/

#include "rsgVariableType.hpp"
#include "rsgToken.hpp"

using std::vector;

namespace rsg
{

VariableType &VariableType::operator=(const VariableType &other)
{
    if (this == &other)
        return *this;

    delete m_elementType;

    m_elementType = DE_NULL;
    m_baseType    = other.m_baseType;
    m_precision   = other.m_precision;
    m_typeName    = other.m_typeName;
    m_numElements = other.m_numElements;
    m_members     = other.m_members;
    m_elementType = DE_NULL;

    if (other.m_elementType)
        m_elementType = new VariableType(*other.m_elementType);

    return *this;
}

VariableType::VariableType(const VariableType &other) : m_elementType(DE_NULL)
{
    *this = other;
}

bool VariableType::operator!=(const VariableType &other) const
{
    if (m_baseType != other.m_baseType)
        return true;
    if (m_precision != other.m_precision)
        return true;
    if (m_numElements != other.m_numElements)
        return true;
    if (!!m_elementType != !!other.m_elementType)
        return true;
    if (m_elementType && *m_elementType != *other.m_elementType)
        return true;
    if (m_members != other.m_members)
        return true;
    return false;
}

bool VariableType::operator==(const VariableType &other) const
{
    return !(*this != other);
}

int VariableType::getScalarSize(void) const
{
    switch (m_baseType)
    {
    case TYPE_VOID:
    case TYPE_FLOAT:
    case TYPE_INT:
    case TYPE_BOOL:
    case TYPE_SAMPLER_2D:
    case TYPE_SAMPLER_CUBE:
        return m_numElements;

    case TYPE_STRUCT:
    {
        int sum = 0;
        for (vector<Member>::const_iterator i = m_members.begin(); i != m_members.end(); i++)
            sum += i->getType().getScalarSize();
        return sum;
    }

    case TYPE_ARRAY:
    {
        DE_ASSERT(m_elementType);
        return m_elementType->getScalarSize() * m_numElements;
    }

    default:
        DE_ASSERT(false);
        return 0;
    }
}

int VariableType::getMemberScalarOffset(int memberNdx) const
{
    DE_ASSERT(isStruct());

    int curOffset = 0;
    for (vector<Member>::const_iterator i = m_members.begin(); i != m_members.begin() + memberNdx; i++)
        curOffset += i->getType().getScalarSize();

    return curOffset;
}

int VariableType::getElementScalarOffset(int elementNdx) const
{
    DE_ASSERT(isArray());
    return elementNdx * getElementType().getScalarSize();
}

const VariableType &VariableType::getScalarType(Type baseType)
{
    switch (baseType)
    {
    case TYPE_FLOAT:
    {
        static const VariableType s_floatTypes[] = {
            VariableType(TYPE_FLOAT, 1)
            // \todo [pyry] Extend with different precision variants?
        };
        return s_floatTypes[0];
    }

    case TYPE_INT:
    {
        static const VariableType s_intTypes[] = {VariableType(TYPE_INT, 1)};
        return s_intTypes[0];
    }

    case TYPE_BOOL:
    {
        static const VariableType s_boolTypes[] = {VariableType(TYPE_BOOL, 1)};
        return s_boolTypes[0];
    }

    case TYPE_SAMPLER_2D:
    {
        static const VariableType sampler2DType = VariableType(TYPE_SAMPLER_2D, 1);
        return sampler2DType;
    }

    case TYPE_SAMPLER_CUBE:
    {
        static const VariableType samplerCubeType = VariableType(TYPE_SAMPLER_CUBE, 1);
        return samplerCubeType;
    }

    default:
        DE_ASSERT(false);
        throw Exception("VariableType::getScalarType(): unsupported type");
    }
}

const VariableType &VariableType::getElementType(void) const
{
    DE_ASSERT(m_precision == PRECISION_NONE); // \todo [pyry] Precision
    switch (m_baseType)
    {
    case TYPE_FLOAT:
    case TYPE_INT:
    case TYPE_BOOL:
    case TYPE_SAMPLER_2D:
    case TYPE_SAMPLER_CUBE:
        return getScalarType(m_baseType);

    case TYPE_ARRAY:
    {
        DE_ASSERT(m_elementType);
        return *m_elementType;
    }

    default:
        DE_ASSERT(false);
        throw Exception("VariableType::getElementType(): unsupported type");
    }
}

void VariableType::tokenizeShortType(TokenStream &str) const
{
    switch (m_precision)
    {
    case PRECISION_LOW:
        str << Token::LOW_PRECISION;
        break;
    case PRECISION_MEDIUM:
        str << Token::MEDIUM_PRECISION;
        break;
    case PRECISION_HIGH:
        str << Token::HIGH_PRECISION;
        break;
    default: /* nothing */
        break;
    }

    switch (m_baseType)
    {
    case TYPE_VOID:
        str << Token::VOID;
        break;

    case TYPE_FLOAT:
        switch (m_numElements)
        {
        case 1:
            str << Token::FLOAT;
            break;
        case 2:
            str << Token::VEC2;
            break;
        case 3:
            str << Token::VEC3;
            break;
        case 4:
            str << Token::VEC4;
            break;
        default:
            DE_ASSERT(false);
            break;
        }
        break;

    case TYPE_INT:
        switch (m_numElements)
        {
        case 1:
            str << Token::INT;
            break;
        case 2:
            str << Token::IVEC2;
            break;
        case 3:
            str << Token::IVEC3;
            break;
        case 4:
            str << Token::IVEC4;
            break;
        default:
            DE_ASSERT(false);
            break;
        }
        break;

    case TYPE_BOOL:
        switch (m_numElements)
        {
        case 1:
            str << Token::BOOL;
            break;
        case 2:
            str << Token::BVEC2;
            break;
        case 3:
            str << Token::BVEC3;
            break;
        case 4:
            str << Token::BVEC4;
            break;
        default:
            DE_ASSERT(false);
            break;
        }
        break;

    case TYPE_SAMPLER_2D:
        str << Token::SAMPLER2D;
        break;
    case TYPE_SAMPLER_CUBE:
        str << Token::SAMPLERCUBE;
        break;

    case TYPE_STRUCT:
        DE_ASSERT(m_typeName != "");
        str << Token(m_typeName.c_str());
        break;

    case TYPE_ARRAY:
        DE_ASSERT(m_elementType);
        m_elementType->tokenizeShortType(str);
        str << Token::LEFT_BRACKET << Token(m_numElements) << Token::RIGHT_BRACKET;
        break;

    default:
        DE_ASSERT(false);
        break;
    }
}

} // namespace rsg
