// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// 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.

#ifndef _TYPES_INCLUDED
#define _TYPES_INCLUDED

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

#include <algorithm>

class TType;
struct TPublicType;

class TField
{
public:
	POOL_ALLOCATOR_NEW_DELETE();
	TField(TType *type, TString *name, const TSourceLoc &line)
		: mType(type),
		mName(name),
		mLine(line)
	{
	}

	// 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;
	}
	const TSourceLoc &line() const
	{
		return mLine;
	}

private:
	TType *mType;
	TString *mName;
	TSourceLoc mLine;
};

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

class TFieldListCollection
{
public:
	virtual ~TFieldListCollection() { }
	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;
	};

protected:
	TFieldListCollection(const TString *name, TFieldList *fields)
		: mName(name),
		mFields(fields),
		mObjectSize(0)
	{
	}
	TString buildMangledName() const;
	size_t calculateObjectSize() const;
	virtual TString mangledNamePrefix() const = 0;

	const TString *mName;
	TFieldList *mFields;

	mutable TString mMangledName;
	mutable size_t mObjectSize;
};

// May also represent interface blocks
class TStructure : public TFieldListCollection
{
public:
	POOL_ALLOCATOR_NEW_DELETE();
	TStructure(const TString *name, TFieldList *fields)
		: TFieldListCollection(name, fields),
		mDeepestNesting(0),
		mUniqueId(0),
		mAtGlobalScope(false)
	{
	}

	int deepestNesting() const
	{
		if(mDeepestNesting == 0)
			mDeepestNesting = calculateDeepestNesting();
		return mDeepestNesting;
	}
	bool containsArrays() const;
	bool containsType(TBasicType type) const;
	bool containsSamplers() const;

	bool equals(const TStructure &other) const;

	void setUniqueId(int uniqueId)
	{
		mUniqueId = uniqueId;
	}

	int uniqueId() const
	{
		ASSERT(mUniqueId != 0);
		return mUniqueId;
	}

	void setAtGlobalScope(bool atGlobalScope)
	{
		mAtGlobalScope = atGlobalScope;
	}

	bool atGlobalScope() const
	{
		return mAtGlobalScope;
	}

private:
	// TODO(zmo): Find a way to get rid of the const_cast in function
	// setName().  At the moment keep this function private so only
	// friend class RegenerateStructNames may call it.
	friend class RegenerateStructNames;
	void setName(const TString &name)
	{
		TString *mutableName = const_cast<TString *>(mName);
		*mutableName = name;
	}

	virtual TString mangledNamePrefix() const
	{
		return "struct-";
	}
	int calculateDeepestNesting() const;

	mutable int mDeepestNesting;
	int mUniqueId;
	bool mAtGlobalScope;
};

class TInterfaceBlock : public TFieldListCollection
{
public:
	POOL_ALLOCATOR_NEW_DELETE();
	TInterfaceBlock(const TString *name, TFieldList *fields, const TString *instanceName,
		int arraySize, const TLayoutQualifier &layoutQualifier)
		: TFieldListCollection(name, fields),
		mInstanceName(instanceName),
		mArraySize(arraySize),
		mBlockStorage(layoutQualifier.blockStorage),
		mMatrixPacking(layoutQualifier.matrixPacking)
	{
	}

	const TString &instanceName() const
	{
		return *mInstanceName;
	}
	bool hasInstanceName() const
	{
		return mInstanceName != nullptr;
	}
	bool isArray() const
	{
		return mArraySize > 0;
	}
	int arraySize() const
	{
		return mArraySize;
	}
	TLayoutBlockStorage blockStorage() const
	{
		return mBlockStorage;
	}
	TLayoutMatrixPacking matrixPacking() const
	{
		return mMatrixPacking;
	}

private:
	virtual TString mangledNamePrefix() const
	{
		return "iblock-";
	}

	const TString *mInstanceName; // for interface block instance names
	int mArraySize; // 0 if not an array
	TLayoutBlockStorage mBlockStorage;
	TLayoutMatrixPacking mMatrixPacking;
};

//
// Base class for things that have a type.
//
class TType
{
public:
	POOL_ALLOCATOR_NEW_DELETE();
	TType() {}
	TType(TBasicType t, int s0 = 1, int s1 = 1) :
		type(t), precision(EbpUndefined), qualifier(EvqGlobal), invariant(false), layoutQualifier(TLayoutQualifier::create()),
		primarySize(s0), secondarySize(s1), array(false), arraySize(0), maxArraySize(0), arrayInformationType(0), interfaceBlock(0),
		structure(0), deepestStructNesting(0), mangled(0)
	{
	}
	TType(TBasicType t, TPrecision p, TQualifier q = EvqTemporary, int s0 = 1, int s1 = 1, bool a = false) :
		type(t), precision(p), qualifier(q), invariant(false), layoutQualifier(TLayoutQualifier::create()),
		primarySize(s0), secondarySize(s1), array(a), arraySize(0), maxArraySize(0), arrayInformationType(0), interfaceBlock(0),
		structure(0), deepestStructNesting(0), mangled(0)
	{
	}
	explicit TType(const TPublicType &p);
	TType(TStructure* userDef, TPrecision p = EbpUndefined) :
		type(EbtStruct), precision(p), qualifier(EvqTemporary), invariant(false), layoutQualifier(TLayoutQualifier::create()),
		primarySize(1), secondarySize(1), array(false), arraySize(0), maxArraySize(0), arrayInformationType(0), interfaceBlock(0),
		structure(userDef), deepestStructNesting(0), mangled(0)
	{
	}

	TType(TInterfaceBlock *interfaceBlockIn, TQualifier qualifierIn,
		TLayoutQualifier layoutQualifierIn, int arraySizeIn)
		: type(EbtInterfaceBlock), precision(EbpUndefined), qualifier(qualifierIn),
		invariant(false), layoutQualifier(layoutQualifierIn),
		primarySize(1), secondarySize(1), array(arraySizeIn > 0), arraySize(arraySizeIn), maxArraySize(0), arrayInformationType(0),
		interfaceBlock(interfaceBlockIn), structure(0), deepestStructNesting(0), mangled(0)
	{
	}

	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; }

	bool isInvariant() const { return invariant; }

	TLayoutQualifier getLayoutQualifier() const { return layoutQualifier; }
	void setLayoutQualifier(TLayoutQualifier lq) { layoutQualifier = lq; }

	// One-dimensional size of single instance type
	int getNominalSize() const { return primarySize; }
	void setNominalSize(int s) { primarySize = s; }
	// Full size of single instance of type
	size_t getObjectSize() const
	{
		if(isArray())
		{
			return getElementSize() * std::max(getArraySize(), getMaxArraySize());
		}
		else
		{
			return getElementSize();
		}
	}

	size_t getElementSize() const
	{
		if(getBasicType() == EbtStruct)
		{
			return getStructSize();
		}
		else if(isInterfaceBlock())
		{
			return interfaceBlock->objectSize();
		}
		else if(isMatrix())
		{
			return primarySize * secondarySize;
		}
		else   // Vector or scalar
		{
			return primarySize;
		}
	}

	int elementRegisterCount() const
	{
		if(structure || isInterfaceBlock())
		{
			int registerCount = 0;

			const TFieldList& fields = isInterfaceBlock() ? interfaceBlock->fields() : structure->fields();
			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 blockRegisterCount() const
	{
		// If this TType object is a block member, return the register count of the parent block
		// Otherwise, return the register count of the current TType object
		if(interfaceBlock && !isInterfaceBlock())
		{
			int registerCount = 0;
			const TFieldList& fieldList = interfaceBlock->fields();
			for(size_t i = 0; i < fieldList.size(); i++)
			{
				const TType &fieldType = *(fieldList[i]->type());
				registerCount += fieldType.totalRegisterCount();
			}
			return registerCount;
		}
		return totalRegisterCount();
	}

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

	int registerSize() const
	{
		return isMatrix() ? secondarySize : primarySize;
	}

	bool isMatrix() const { return secondarySize > 1; }
	void setSecondarySize(int s1) { secondarySize = s1; }
	int getSecondarySize() const { return secondarySize; }

	bool isArray() const  { return array ? true : false; }
	bool isUnsizedArray() const { return array && arraySize == 0; }
	int getArraySize() const { return arraySize; }
	void setArraySize(int s) { array = true; arraySize = s; }
	int getMaxArraySize () const { return maxArraySize; }
	void setMaxArraySize (int s) { maxArraySize = s; }
	void clearArrayness() { array = false; arraySize = 0; maxArraySize = 0; }
	void setArrayInformationType(TType* t) { arrayInformationType = t; }
	TType* getArrayInformationType() const { return arrayInformationType; }

	TInterfaceBlock *getInterfaceBlock() const { return interfaceBlock; }
	void setInterfaceBlock(TInterfaceBlock *interfaceBlockIn) { interfaceBlock = interfaceBlockIn; }
	bool isInterfaceBlock() const { return type == EbtInterfaceBlock; }
	TInterfaceBlock *getAsInterfaceBlock() const { return isInterfaceBlock() ? getInterfaceBlock() : nullptr; }

	bool isVector() const { return primarySize > 1 && !isMatrix(); }
	bool isScalar() const { return primarySize == 1 && !isMatrix() && !structure && !isInterfaceBlock(); }
	bool isRegister() const { return !isMatrix() && !structure && !array && !isInterfaceBlock(); }   // Fits in a 4-element register
	bool isStruct() const { return structure != 0; }
	bool isScalarInt() const { return isScalar() && IsInteger(type); }

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

	TString& getMangledName() {
		if (!mangled) {
			mangled = NewPoolTString("");
			buildMangledName(*mangled);
			*mangled += ';' ;
		}

		return *mangled;
	}

	bool sameElementType(const TType& right) const {
		return      type == right.type   &&
		     primarySize == right.primarySize &&
		   secondarySize == right.secondarySize &&
		       structure == right.structure;
	}
	bool operator==(const TType& right) const {
		return      type == right.type   &&
		     primarySize == right.primarySize &&
		   secondarySize == right.secondarySize &&
			       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(primarySize != right.primarySize) return (primarySize * secondarySize) < (right.primarySize * right.secondarySize);
		if(secondarySize != right.secondarySize) return secondarySize < right.secondarySize;
		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;
	}

	bool isStructureContainingType(TBasicType t) const
	{
		return structure ? structure->containsType(t) : false;
	}

	bool isStructureContainingSamplers() const
	{
		return structure ? structure->containsSamplers() : false;
	}

protected:
	void buildMangledName(TString&);
	size_t getStructSize() const;
	void computeDeepestStructNesting();

	TBasicType type;
	TPrecision precision;
	TQualifier qualifier;
	bool invariant;
	TLayoutQualifier layoutQualifier;
	unsigned char primarySize;   // size of vector or matrix, not size of array
	unsigned char secondarySize; // secondarySize: 1 for vectors, >1 for matrices
	bool array;
	int arraySize;
	int maxArraySize;
	TType *arrayInformationType;

	// 0 unless this is an interface block, or interface block member variable
	TInterfaceBlock *interfaceBlock;

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

	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;
	TLayoutQualifier layoutQualifier;
	TQualifier qualifier;
	bool invariant;
	TPrecision precision;
	int primarySize;          // size of vector or matrix, not size of array
	int secondarySize;        // 1 for scalars/vectors, >1 for matrices
	bool array;
	int arraySize;
	TType* userDef;
	TSourceLoc line;

	void setBasic(TBasicType bt, TQualifier q, const TSourceLoc &ln)
	{
		type = bt;
		layoutQualifier = TLayoutQualifier::create();
		qualifier = q;
		invariant = false;
		precision = EbpUndefined;
		primarySize = 1;
		secondarySize = 1;
		array = false;
		arraySize = 0;
		userDef = 0;
		line = ln;
	}

	void setAggregate(int s)
	{
		primarySize = s;
		secondarySize = 1;
	}

	void setMatrix(int s0, int s1)
	{
		primarySize = s0;
		secondarySize = s1;
	}

	bool isUnsizedArray() const
	{
		return array && arraySize == 0;
	}

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

	void clearArrayness()
	{
		array = false;
		arraySize = 0;
	}

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

		return userDef->isStructureContainingArrays();
	}

	bool isStructureContainingType(TBasicType t) const
	{
		if(!userDef)
		{
			return false;
		}

		return userDef->isStructureContainingType(t);
	}

	bool isMatrix() const
	{
		return primarySize > 1 && secondarySize > 1;
	}

	bool isVector() const
	{
		return primarySize > 1 && secondarySize == 1;
	}

	int getCols() const
	{
		ASSERT(isMatrix());
		return primarySize;
	}

	int getRows() const
	{
		ASSERT(isMatrix());
		return secondarySize;
	}

	int getNominalSize() const
	{
		return primarySize;
	}

	bool isAggregate() const
	{
		return array || isMatrix() || isVector();
	}
};

#endif // _TYPES_INCLUDED_
