/*
** symbols.cpp
** Implements the symbol types and symbol table
**
**---------------------------------------------------------------------------
** Copyright 1998-2016 Randy Heit
** Copyright 2006-2017 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include <float.h>
#include "dobject.h"
#include "templates.h"
#include "serializer.h"
#include "types.h"
#include "vm.h"

// PUBLIC DATA DEFINITIONS -------------------------------------------------

FNamespaceManager Namespaces;

// Symbol tables ------------------------------------------------------------

IMPLEMENT_CLASS(PSymbol, true, false);
IMPLEMENT_CLASS(PSymbolConst, false, false);
IMPLEMENT_CLASS(PSymbolConstNumeric, false, false);
IMPLEMENT_CLASS(PSymbolConstString, false, false);
IMPLEMENT_CLASS(PSymbolTreeNode, false, false)
IMPLEMENT_CLASS(PSymbolType, false, false)
IMPLEMENT_CLASS(PSymbolVMFunction, false, false)
IMPLEMENT_CLASS(PFunction, false, false)

//==========================================================================
//
//
//
//==========================================================================

PSymbolConstString::PSymbolConstString(FName name, const FString &str)
	: PSymbolConst(name, TypeString), Str(str) 
{
}

//==========================================================================
//
// PFunction :: AddVariant
//
// Adds a new variant for this function. Does not check if a matching
// variant already exists.
//
//==========================================================================

unsigned PFunction::AddVariant(PPrototype *proto, TArray<uint32_t> &argflags, TArray<FName> &argnames, VMFunction *impl, int flags, int useflags)
{
	Variant variant;

	// I do not think we really want to deal with overloading here...
	assert(Variants.Size() == 0);

	variant.Flags = flags;
	variant.UseFlags = useflags;
	variant.Proto = proto;
	variant.ArgFlags = std::move(argflags);
	variant.ArgNames = std::move(argnames);
	variant.Implementation = impl;
	if (impl != nullptr) impl->Proto = proto;

	// SelfClass can differ from OwningClass, but this is variant-dependent.
	// Unlike the owner there can be cases where different variants can have different SelfClasses.
	// (Of course only if this ever gets enabled...)
	if (flags & VARF_Method)
	{
		assert(proto->ArgumentTypes.Size() > 0);
		auto selftypeptr = proto->ArgumentTypes[0]->toPointer();
		assert(selftypeptr != nullptr);
		variant.SelfClass = selftypeptr->PointedType->toContainer();
		assert(variant.SelfClass != nullptr);
	}
	else
	{
		variant.SelfClass = nullptr;
	}

	return Variants.Push(variant);
}

//==========================================================================
//
//
//
//==========================================================================

int PFunction::GetImplicitArgs()
{
	if (Variants[0].Flags & VARF_Action) return 3;
	else if (Variants[0].Flags & VARF_Method) return 1;
	return 0;
}

/* PField *****************************************************************/

IMPLEMENT_CLASS(PField, false, false)

//==========================================================================
//
// PField - Default Constructor
//
//==========================================================================

PField::PField()
: PSymbol(NAME_None), Offset(0), Type(nullptr), Flags(0)
{
}


PField::PField(FName name, PType *type, uint32_t flags, size_t offset, int bitvalue)
	: PSymbol(name), Offset(offset), Type(type), Flags(flags)
{
	if (bitvalue != 0)
	{
		BitValue = 0;
		unsigned val = bitvalue;
		while ((val >>= 1)) BitValue++;

		if (type->isInt() && unsigned(BitValue) < 8u * type->Size)
		{
			// map to the single bytes in the actual variable. The internal bit instructions operate on 8 bit values.
#ifndef __BIG_ENDIAN__
			Offset += BitValue / 8;
#else
			Offset += type->Size - 1 - BitValue / 8;
#endif
			BitValue &= 7;
			Type = TypeBool;
		}
		else
		{
			// Just abort. Bit fields should only be defined internally.
			I_Error("Trying to create an invalid bit field element: %s", name.GetChars());
		}
	}
	else BitValue = -1;
}

VersionInfo PField::GetVersion()
{
	VersionInfo Highest = { 0,0,0 };
	if (!(Flags & VARF_Deprecated)) Highest = mVersion;
	if (Type->mVersion > Highest) Highest = Type->mVersion;
	return Highest;
}

/* PProperty *****************************************************************/

IMPLEMENT_CLASS(PProperty, false, false)

//==========================================================================
//
// PField - Default Constructor
//
//==========================================================================

PProperty::PProperty()
	: PSymbol(NAME_None)
{
}

PProperty::PProperty(FName name, TArray<PField *> &fields)
	: PSymbol(name)
{
	Variables = std::move(fields);
}

//==========================================================================
//
//
//
//==========================================================================

PSymbolTable::PSymbolTable()
: ParentSymbolTable(nullptr)
{
}

PSymbolTable::PSymbolTable(PSymbolTable *parent)
: ParentSymbolTable(parent)
{
}

PSymbolTable::~PSymbolTable ()
{
	ReleaseSymbols();
}

//==========================================================================
//
// this must explicitly delete all content because the symbols have
// been released from the GC.
//
//==========================================================================

void PSymbolTable::ReleaseSymbols()
{
	auto it = GetIterator();
	MapType::Pair *pair;
	while (it.NextPair(pair))
	{
		delete pair->Value;
	}
	Symbols.Clear();
}

//==========================================================================
//
//
//
//==========================================================================

void PSymbolTable::SetParentTable (PSymbolTable *parent)
{
	ParentSymbolTable = parent;
}

//==========================================================================
//
//
//
//==========================================================================

PSymbol *PSymbolTable::FindSymbol (FName symname, bool searchparents) const
{
	PSymbol * const *value = Symbols.CheckKey(symname);
	if (value == nullptr && searchparents && ParentSymbolTable != nullptr)
	{
		return ParentSymbolTable->FindSymbol(symname, searchparents);
	}
	return value != nullptr ? *value : nullptr;
}

//==========================================================================
//
//
//
//==========================================================================

PSymbol *PSymbolTable::FindSymbolInTable(FName symname, PSymbolTable *&symtable)
{
	PSymbol * const *value = Symbols.CheckKey(symname);
	if (value == nullptr)
	{
		if (ParentSymbolTable != nullptr)
		{
			return ParentSymbolTable->FindSymbolInTable(symname, symtable);
		}
		symtable = nullptr;
		return nullptr;
	}
	symtable = this;
	return *value;
}

//==========================================================================
//
//
//
//==========================================================================

PSymbol *PSymbolTable::AddSymbol (PSymbol *sym)
{
	// Symbols that already exist are not inserted.
	if (Symbols.CheckKey(sym->SymbolName) != nullptr)
	{
		return nullptr;
	}
	Symbols.Insert(sym->SymbolName, sym);
	sym->Release();	// no more GC, please!
	return sym;
}

//==========================================================================
//
//
//
//==========================================================================

PField *PSymbolTable::AddField(FName name, PType *type, uint32_t flags, unsigned &Size, unsigned *Align)
{
	PField *field = Create<PField>(name, type, flags);

	// The new field is added to the end of this struct, alignment permitting.
	field->Offset = (Size + (type->Align - 1)) & ~(type->Align - 1);

	// Enlarge this struct to enclose the new field.
	Size = unsigned(field->Offset + type->Size);

	// This struct's alignment is the same as the largest alignment of any of
	// its fields.
	if (Align != nullptr)
	{
		*Align = MAX(*Align, type->Align);
	}

	if (AddSymbol(field) == nullptr)
	{ // name is already in use
		field->Destroy();
		return nullptr;
	}
	return field;
}

//==========================================================================
//
// PStruct :: AddField
//
// Appends a new native field to the struct. Returns either the new field
// or nullptr if a symbol by that name already exists.
//
//==========================================================================

PField *PSymbolTable::AddNativeField(FName name, PType *type, size_t address, uint32_t flags, int bitvalue)
{
	PField *field = Create<PField>(name, type, flags | VARF_Native | VARF_Transient, address, bitvalue);

	if (AddSymbol(field) == nullptr)
	{ // name is already in use
		field->Destroy();
		return nullptr;
	}
	return field;
}

//==========================================================================
//
// PClass :: WriteFields
//
//==========================================================================

void PSymbolTable::WriteFields(FSerializer &ar, const void *addr, const void *def) const
{
	auto it = MapType::ConstIterator(Symbols);
	MapType::ConstPair *pair;

	while (it.NextPair(pair))
	{
		const PField *field = dyn_cast<PField>(pair->Value);
		// Skip fields without or with native serialization
		if (field && !(field->Flags & (VARF_Transient | VARF_Meta | VARF_Static)))
		{
			// todo: handle defaults in WriteValue
			//auto defp = def == nullptr ? nullptr : (const uint8_t *)def + field->Offset;
			field->Type->WriteValue(ar, field->SymbolName.GetChars(), (const uint8_t *)addr + field->Offset);
		}
	}
}


//==========================================================================
//
// PClass :: ReadFields
//
//==========================================================================

bool PSymbolTable::ReadFields(FSerializer &ar, void *addr, const char *TypeName) const
{
	bool readsomething = false;
	bool foundsomething = false;
	const char *label;
	while ((label = ar.GetKey()))
	{
		foundsomething = true;

		const PSymbol *sym = FindSymbol(FName(label, true), false);
		if (sym == nullptr)
		{
			DPrintf(DMSG_ERROR, "Cannot find field %s in %s\n",
				label, TypeName);
		}
		else if (!sym->IsKindOf(RUNTIME_CLASS(PField)))
		{
			DPrintf(DMSG_ERROR, "Symbol %s in %s is not a field\n",
				label, TypeName);
		}
		else if ((static_cast<const PField *>(sym)->Flags & (VARF_Transient | VARF_Meta)))
		{
			DPrintf(DMSG_ERROR, "Symbol %s in %s is not a serializable field\n",
				label, TypeName);
		}
		else
		{
			readsomething |= static_cast<const PField *>(sym)->Type->ReadValue(ar, nullptr,
				(uint8_t *)addr + static_cast<const PField *>(sym)->Offset);
		}
	}
	return readsomething || !foundsomething;
}

//==========================================================================
//
//
//
//==========================================================================

void PSymbolTable::RemoveSymbol(PSymbol *sym)
{
	auto mysym = Symbols.CheckKey(sym->SymbolName);
	if (mysym == nullptr || *mysym != sym) return;
	Symbols.Remove(sym->SymbolName);
	delete sym;
}

//==========================================================================
//
//
//
//==========================================================================

void PSymbolTable::ReplaceSymbol(PSymbol *newsym)
{
	// If a symbol with a matching name exists, take its place and return it.
	PSymbol **symslot = Symbols.CheckKey(newsym->SymbolName);
	if (symslot != nullptr)
	{
		PSymbol *oldsym = *symslot;
		delete oldsym;
		*symslot = newsym;
	}
	// Else, just insert normally and return nullptr since there was no
	// symbol to replace.
	newsym->Release();	// no more GC, please!
	Symbols.Insert(newsym->SymbolName, newsym);
}

//==========================================================================
//
//
//
//==========================================================================

//==========================================================================
//
//
//
//==========================================================================

PNamespace::PNamespace(int filenum, PNamespace *parent)
{
	Parent = parent;
	if (parent) Symbols.SetParentTable(&parent->Symbols);
	FileNum = filenum;
}

//==========================================================================
//
//
//
//==========================================================================

FNamespaceManager::FNamespaceManager()
{
	GlobalNamespace = nullptr;
}

//==========================================================================
//
//
//
//==========================================================================

PNamespace *FNamespaceManager::NewNamespace(int filenum)
{
	PNamespace *parent = nullptr;
	// The parent will be the last namespace with this or a lower filenum.
	// This ensures that DECORATE won't see the symbols of later files.
	for (int i = AllNamespaces.Size() - 1; i >= 0; i--)
	{
		if (AllNamespaces[i]->FileNum <= filenum)
		{
			parent = AllNamespaces[i];
			break;
		}
	}
	auto newns = new PNamespace(filenum, parent);
	AllNamespaces.Push(newns);
	return newns;
}

//==========================================================================
//
// Deallocate the entire namespace manager.
//
//==========================================================================

void FNamespaceManager::ReleaseSymbols()
{
	for (auto ns : AllNamespaces)
	{
		delete ns;
	}
	GlobalNamespace = nullptr;
	AllNamespaces.Clear();
}

//==========================================================================
//
// removes all symbols from the symbol tables.
// After running the compiler these are not needed anymore.
// Only the namespaces themselves are kept because the type table references them.
//
//==========================================================================

int FNamespaceManager::RemoveSymbols()
{
	int count = 0;
	for (auto ns : AllNamespaces)
	{
		count += ns->Symbols.Symbols.CountUsed();
		ns->Symbols.ReleaseSymbols();
	}
	return count;
}

//==========================================================================
//
// Clean out all compiler-only data from the symbol tables
//
//==========================================================================

void RemoveUnusedSymbols()
{
	int count = Namespaces.RemoveSymbols();

	// We do not need any non-field and non-function symbols in structs and classes anymore.
	// struct/class fields and functions are still needed so that the game can access the script data,
	// but all the rest serves no purpose anymore and can be entirely removed.
	for (size_t i = 0; i < countof(TypeTable.TypeHash); ++i)
	{
		for (PType *ty = TypeTable.TypeHash[i]; ty != nullptr; ty = ty->HashNext)
		{
			if (ty->isContainer())
			{
				auto it = ty->Symbols.GetIterator();
				PSymbolTable::MapType::Pair *pair;
				while (it.NextPair(pair))
				{
					if (!pair->Value->IsKindOf(RUNTIME_CLASS(PField)) && !pair->Value->IsKindOf(RUNTIME_CLASS(PFunction)))
					{
						ty->Symbols.RemoveSymbol(pair->Value);
						count++;
					}
				}
			}
		}
	}
	DPrintf(DMSG_SPAMMY, "%d symbols removed after compilation\n", count);
}
