//
// Copyright (c) 2002-2012 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#ifndef _TYPES_INCLUDED
#define _TYPES_INCLUDED

#include "common/angleutils.h"

#include "compiler/BaseTypes.h"
#include "compiler/Common.h"
#include "compiler/debug.h"

struct TPublicType;
class TType;

class TField
{
public:
    POOL_ALLOCATOR_NEW_DELETE();
    TField(TType* type, TString* name) : mType(type), mName(name) {}

    // TODO(alokp): We should only return const type.
    // Fix it by tweaking grammar.
    TType* type() { return mType; }
    const TType* type() const { return mType; }

    const TString& name() const { return *mName; }

private:
    DISALLOW_COPY_AND_ASSIGN(TField);
    TType* mType;
    TString* mName;
};

typedef TVector<TField*> TFieldList;
inline TFieldList* NewPoolTFieldList()
{
    void* memory = GetGlobalPoolAllocator()->allocate(sizeof(TFieldList));
    return new(memory) TFieldList;
}

class TStructure
{
public:
    POOL_ALLOCATOR_NEW_DELETE();
    TStructure(TString* name, TFieldList* fields)
        : mName(name),
          mFields(fields),
          mObjectSize(0),
          mDeepestNesting(0) {
    }

    const TString& name() const { return *mName; }
    const TFieldList& fields() const { return *mFields; }

    const TString& mangledName() const {
        if (mMangledName.empty())
            mMangledName = buildMangledName();
        return mMangledName;
    }
    size_t objectSize() const {
        if (mObjectSize == 0)
            mObjectSize = calculateObjectSize();
        return mObjectSize;
    };
    int deepestNesting() const {
        if (mDeepestNesting == 0)
            mDeepestNesting = calculateDeepestNesting();
        return mDeepestNesting;
    }
    bool containsArrays() const;

private:
    DISALLOW_COPY_AND_ASSIGN(TStructure);
    TString buildMangledName() const;
    size_t calculateObjectSize() const;
    int calculateDeepestNesting() const;

    TString* mName;
    TFieldList* mFields;

    mutable TString mMangledName;
    mutable size_t mObjectSize;
    mutable int mDeepestNesting;
};

//
// Base class for things that have a type.
//
class TType
{
public:
    POOL_ALLOCATOR_NEW_DELETE();
    TType() {}
    TType(TBasicType t, TPrecision p, TQualifier q = EvqTemporary, int s = 1, bool m = false, bool a = false) :
            type(t), precision(p), qualifier(q), size(s), matrix(m), array(a), arraySize(0), structure(0)
    {
    }
    explicit TType(const TPublicType &p);
    TType(TStructure* userDef, TPrecision p = EbpUndefined) :
            type(EbtStruct), precision(p), qualifier(EvqTemporary), size(1), matrix(false), array(false), arraySize(0), structure(userDef)
    {
    }

    TBasicType getBasicType() const { return type; }
    void setBasicType(TBasicType t) { type = t; }

    TPrecision getPrecision() const { return precision; }
    void setPrecision(TPrecision p) { precision = p; }

    TQualifier getQualifier() const { return qualifier; }
    void setQualifier(TQualifier q) { qualifier = q; }

    // One-dimensional size of single instance type
    int getNominalSize() const { return size; }
    void setNominalSize(int s) { size = s; }
    // Full size of single instance of type
    size_t getObjectSize() const;

    int elementRegisterCount() const
    {
        if (structure)
        {
            const TFieldList &fields = getStruct()->fields();
            int registerCount = 0;

            for (size_t i = 0; i < fields.size(); i++)
            {
                registerCount += fields[i]->type()->totalRegisterCount();
            }

            return registerCount;
        }
        else if (isMatrix())
        {
            return getNominalSize();
        }
        else
        {
            return 1;
        }
    }

    int totalRegisterCount() const
    {
        if (array)
        {
            return arraySize * elementRegisterCount();
        }
        else
        {
            return elementRegisterCount();
        }
    }

    bool isMatrix() const { return matrix ? true : false; }
    void setMatrix(bool m) { matrix = m; }

    bool isArray() const  { return array ? true : false; }
    int getArraySize() const { return arraySize; }
    void setArraySize(int s) { array = true; arraySize = s; }
    void clearArrayness() { array = false; arraySize = 0; }

    bool isVector() const { return size > 1 && !matrix; }
    bool isScalar() const { return size == 1 && !matrix && !structure; }

    TStructure* getStruct() const { return structure; }
    void setStruct(TStructure* s) { structure = s; }

    const TString& getMangledName() const {
        if (mangled.empty()) {
            mangled = buildMangledName();
            mangled += ';';
        }
        return mangled;
    }

    bool sameElementType(const TType& right) const {
        return      type == right.type   &&
                    size == right.size   &&
                  matrix == right.matrix &&
               structure == right.structure;
    }
    bool operator==(const TType& right) const {
        return      type == right.type   &&
                    size == right.size   &&
                  matrix == right.matrix &&
                   array == right.array  && (!array || arraySize == right.arraySize) &&
               structure == right.structure;
        // don't check the qualifier, it's not ever what's being sought after
    }
    bool operator!=(const TType& right) const {
        return !operator==(right);
    }
    bool operator<(const TType& right) const {
        if (type != right.type) return type < right.type;
        if (size != right.size) return size < right.size;
        if (matrix != right.matrix) return matrix < right.matrix;
        if (array != right.array) return array < right.array;
        if (arraySize != right.arraySize) return arraySize < right.arraySize;
        if (structure != right.structure) return structure < right.structure;

        return false;
    }

    const char* getBasicString() const { return ::getBasicString(type); }
    const char* getPrecisionString() const { return ::getPrecisionString(precision); }
    const char* getQualifierString() const { return ::getQualifierString(qualifier); }
    TString getCompleteString() const;

    // If this type is a struct, returns the deepest struct nesting of
    // any field in the struct. For example:
    //   struct nesting1 {
    //     vec4 position;
    //   };
    //   struct nesting2 {
    //     nesting1 field1;
    //     vec4 field2;
    //   };
    // For type "nesting2", this method would return 2 -- the number
    // of structures through which indirection must occur to reach the
    // deepest field (nesting2.field1.position).
    int getDeepestStructNesting() const {
        return structure ? structure->deepestNesting() : 0;
    }

    bool isStructureContainingArrays() const {
        return structure ? structure->containsArrays() : false;
    }

private:
    TString buildMangledName() const;

    TBasicType type      : 6;
    TPrecision precision;
    TQualifier qualifier : 7;
    int size             : 8; // size of vector or matrix, not size of array
    unsigned int matrix  : 1;
    unsigned int array   : 1;
    int arraySize;

    TStructure* structure;      // 0 unless this is a struct

    mutable TString mangled;
};

//
// This is a workaround for a problem with the yacc stack,  It can't have
// types that it thinks have non-trivial constructors.  It should
// just be used while recognizing the grammar, not anything else.  Pointers
// could be used, but also trying to avoid lots of memory management overhead.
//
// Not as bad as it looks, there is no actual assumption that the fields
// match up or are name the same or anything like that.
//
struct TPublicType
{
    TBasicType type;
    TQualifier qualifier;
    TPrecision precision;
    int size;          // size of vector or matrix, not size of array
    bool matrix;
    bool array;
    int arraySize;
    TType* userDef;
    TSourceLoc line;

    void setBasic(TBasicType bt, TQualifier q, const TSourceLoc& ln)
    {
        type = bt;
        qualifier = q;
        precision = EbpUndefined;
        size = 1;
        matrix = false;
        array = false;
        arraySize = 0;
        userDef = 0;
        line = ln;
    }

    void setAggregate(int s, bool m = false)
    {
        size = s;
        matrix = m;
    }

    void setArray(bool a, int s = 0)
    {
        array = a;
        arraySize = s;
    }

    bool isStructureContainingArrays() const
    {
        if (!userDef)
        {
            return false;
        }

        return userDef->isStructureContainingArrays();
    }
};

#endif // _TYPES_INCLUDED_
