/*
 * Copyright Johannes Sixt
 * This file is licensed under the GNU General Public License Version 2.
 * See the file COPYING in the toplevel directory of the source directory.
 */

#include <tqdir.h>
#include <tqptrlist.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <ksimpleconfig.h>
#include <list>
#include <algorithm>
#include <functional>
#include <iterator>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "typetable.h"
#include "mydebug.h"

//! the TypeTables of all known libraries
static std::list<TypeTable> typeTables;
bool typeTablesInited = false;


//! an indentifier for wchar_t
TypeInfo TypeInfo::m_wchartType("");
//! the unknown type
TypeInfo TypeInfo::m_unknownType("");


void TypeTable::initTypeLibraries()
{
    if (!typeTablesInited) {
	TypeTable::loadTypeTables();
    }
}

void TypeTable::loadTypeTables()
{
    typeTablesInited = true;

    const TQStringList files = TDEGlobal::dirs()->findAllResources("types", "*.kdbgtt",
			false, true);
    
    if (files.isEmpty()) {
	TRACE("no type tables found");
	return;
    }

    for (TQValueListConstIterator<TQString> p = files.begin(); p != files.end(); ++p) {
	typeTables.push_back(TypeTable());
	typeTables.back().loadFromFile(*p);
    }
}


TypeTable::TypeTable() :
	m_printTQStringDataCmd(0)
{
    m_typeDict.setAutoDelete(true);
    // aliasDict keeps only pointers to items into typeDict
    m_aliasDict.setAutoDelete(false);
}

TypeTable::~TypeTable()
{
    delete[] m_printTQStringDataCmd;
    while (!m_templates.empty()) {
	delete m_templates.begin()->second;
	m_templates.erase(m_templates.begin());
    }
}


static const char TypeTableGroup[] = "Type Table";
static const char LibDisplayName[] = "LibDisplayName";
static const char ShlibRE[] = "ShlibRE";
static const char EnableBuiltin[] = "EnableBuiltin";
static const char PrintTQStringCmd[] = "PrintTQStringCmd";
static const char TypesEntryFmt[] = "Types%d";
static const char DisplayEntry[] = "Display";
static const char AliasEntry[] = "Alias";
static const char TemplateEntry[] = "Template";
static const char ExprEntryFmt[] = "Expr%d";
static const char FunctionGuardEntryFmt[] = "FunctionGuard%d";


void TypeTable::loadFromFile(const TQString& fileName)
{
    TRACE("reading file " + fileName);
    KSimpleConfig cf(fileName, true);	/* read-only */

    /*
     * Read library name and properties.
     */
    cf.setGroup(TypeTableGroup);
    m_displayName = cf.readEntry(LibDisplayName);
    if (m_displayName.isEmpty()) {
	// use file name instead
	TQFileInfo fi(fileName);
	m_displayName = fi.baseName(true);
    }

    m_shlibNameRE = TQRegExp(cf.readEntry(ShlibRE));
    m_enabledBuiltins = cf.readListEntry(EnableBuiltin);

    TQString printTQString = cf.readEntry(PrintTQStringCmd);
    const char* ascii = printTQString.ascii();
    if (ascii == 0)
	ascii = "";
    m_printTQStringDataCmd = new char[strlen(ascii)+1];
    strcpy(m_printTQStringDataCmd, ascii);

    /*
     * Get the types. We search for entries of kind Types1, Types2, etc.
     * because a single entry Types could get rather long for large
     * libraries.
     */
    TQString typesEntry;
    for (int i = 1; ; i++) {
	// next bunch of types
	cf.setGroup(TypeTableGroup);
	typesEntry.sprintf(TypesEntryFmt, i);
	if (!cf.hasKey(typesEntry))
	    break;

	TQStringList typeNames = cf.readListEntry(typesEntry, ',');

	// now read them
	TQString alias;
	for (TQStringList::iterator it = typeNames.begin(); it != typeNames.end(); ++it)
	{
	    cf.setGroup(*it);
	    // check if this is an alias
	    alias = cf.readEntry(AliasEntry);
	    if (alias.isEmpty()) {
		readType(cf, *it);
	    } else {
		// look up the alias type and insert it
		TypeInfo* info = m_typeDict[alias];
		if (info == 0) {
		    TRACE(*it + ": alias " + alias + " not found");
		} else {
		    m_aliasDict.insert(alias, info);
		    TRACE(*it + ": alias " + alias);
		}
	    }
	}
    } // for all Types%d
}

void TypeTable::readType(TDEConfigBase& cf, const TQString& type)
{
    // the display string
    TQString expr = cf.readEntry(DisplayEntry);

    TypeInfo* info = new TypeInfo(expr);
    if (info->m_numExprs == 0) {
	TRACE("bogus type " + type + ": no %% in Display: " + expr);
	delete info;
	return;
    }

    info->m_templatePattern = cf.readEntry(TemplateEntry);

    // Expr1, Expr2, etc...
    TQString exprEntry;
    TQString funcGuardEntry;
    for (int j = 0; j < info->m_numExprs; j++) {
	exprEntry.sprintf(ExprEntryFmt, j+1);
	expr = cf.readEntry(exprEntry);
	info->m_exprStrings[j] = expr;

	funcGuardEntry.sprintf(FunctionGuardEntryFmt, j+1);
	expr = cf.readEntry(funcGuardEntry);
	info->m_guardStrings[j] = expr;
    }

    // add the new type
    if (info->m_templatePattern.find('<') < 0)
	m_typeDict.insert(type, info);
    else
	m_templates[type] = info;
    TRACE(type + TQString(": %1 exprs").arg(info->m_numExprs));
}

void TypeTable::copyTypes(TQDict<TypeInfo>& dict)
{
    for (TQDictIterator<TypeInfo> it = m_typeDict; it != 0; ++it) {
	dict.insert(it.currentKey(), it);
    }
    for (TQDictIterator<TypeInfo> it = m_aliasDict; it != 0; ++it) {
	dict.insert(it.currentKey(), it);
    }
}

bool TypeTable::isEnabledBuiltin(const TQString& feature) const
{
    return m_enabledBuiltins.find(feature) != m_enabledBuiltins.end();
}

TypeInfo::TypeInfo(const TQString& displayString)
{
    // decompose the input into the parts
    int i = 0;
    int startIdx = 0;
    int idx;
    while (i < typeInfoMaxExpr &&
	   (idx = displayString.find('%', startIdx)) >= 0)
    {
	m_displayString[i] = displayString.mid(startIdx, idx-startIdx);
	startIdx = idx+1;
	i++;
    }
    m_numExprs = i;
    /*
     * Remaining string; note that there's one more display string than
     * sub-expressions.
     */
    m_displayString[i] = displayString.right(displayString.length()-startIdx);
}

TypeInfo::~TypeInfo()
{
}


ProgramTypeTable::ProgramTypeTable() :
	m_parseTQt2TQStrings(false),
	m_QCharIsShort(false),
	m_printTQStringDataCmd(0)
{
    m_types.setAutoDelete(false);	/* paranoia */
    m_aliasDict.setAutoDelete(false);	/* paranoia */
}

ProgramTypeTable::~ProgramTypeTable()
{
}

void ProgramTypeTable::loadTypeTable(TypeTable* table)
{
    table->copyTypes(m_types);

    // add templates
    const TypeTable::TypeMap& t = table->templates();
    std::transform(t.begin(), t.end(),
		std::inserter(m_templates, m_templates.begin()),
		std::ref(template2Info));

    // check whether to enable builtin TQString support
    if (!m_parseTQt2TQStrings) {
	m_parseTQt2TQStrings = table->isEnabledBuiltin("TQString::Data");
    }
    if (!m_QCharIsShort) {
	m_QCharIsShort = table->isEnabledBuiltin("TQCharIsShort");
    }
    if (!m_printTQStringDataCmd && *table->printTQStringDataCmd()) {
	m_printTQStringDataCmd = table->printTQStringDataCmd();
    }
}

ProgramTypeTable::TemplateMap::value_type
	ProgramTypeTable::template2Info(const TypeTable::TypeMap::value_type& tt)
{
    TQStringList args = splitTemplateArgs(tt.second->m_templatePattern);

    TemplateMap::value_type result(args.front(), TemplateInfo());
    result.second.type = tt.second;
    args.pop_front();
    result.second.templateArgs = args;
    return result;
}

/**
 * Splits the name \a t into the template name and its arguments.
 * The first entry of the returned list is the template name, the remaining
 * entries are the arguments.
 */
TQStringList ProgramTypeTable::splitTemplateArgs(const TQString& t)
{
    TQStringList result;
    result.push_back(t);

    int i = t.find('<');
    if (i < 0)
	return result;

    // split off the template name
    result.front().truncate(i);

    i++;	// skip '<'
    // look for the next comma or the closing '>', skipping nested '<>'
    int nest = 0;
    int start = i;
    for (; unsigned(i) < t.length() && nest >= 0; i++)
    {
	if (t[i] == '<')
	    nest++;
	else if (t[i] == '>')
	    nest--;
	else if (nest == 0 && t[i] == ',') {
	    // found end of argument
	    TQString arg = t.mid(start, i-start);
	    result.push_back(arg);
	    start = i+1;	// skip ','
	}
    }
    // accept the template only if the closing '>' is the last character
    if (nest < 0 && unsigned(i) == t.length()) {
	TQString arg = t.mid(start, i-start-1);
	result.push_back(arg);
    } else {
	result.clear();
	result.push_back(t);
    }
    return result;
}

TypeInfo* ProgramTypeTable::lookup(TQString type)
{
    /*
     * Registered aliases contain the complete template parameter list.
     * Check for an alias first so that this case is out of the way.
     */
    if (TypeInfo* result = m_aliasDict[type])
	return result;

    /*
     * Check for a normal type. Even if type is a template instance,
     * it could have been registered as a normal type instead of a pattern.
     */
    if (TypeInfo* result = m_types[type])
	return result;

    /*
     * The hard part: Look up a template.
     */
    TQStringList parts = splitTemplateArgs(type);
    if (parts.size() == 1)
	return 0;	// not a template

    // We can have several patterns for the same template name.
    std::pair<TemplateMap::const_iterator, TemplateMap::const_iterator> range =
	m_templates.equal_range(parts.front());
    // We pick the one that has the wildcards in the later parameters.
    unsigned minPenalty = ~0U;
    TypeInfo* result = 0;
    parts.pop_front();

    for (TemplateMap::const_iterator i = range.first; i != range.second; ++i)
    {
	const TQStringList& pat = i->second.templateArgs;
	if (parts.size() < pat.size())
	    continue;	// too few arguments

	// a "*" in the last position of the pattern matches all arguments
	// at the end of the template's arguments
	if (parts.size() > pat.size() && pat.back() != "*")
	    continue;	// too many arguments and no wildcard

	TQStringList::const_iterator t = parts.begin();
	TQStringList::const_iterator p = pat.begin();
	unsigned accumPenalty = 0;
	bool equal = true;
	unsigned penalty = ~(~0U>>1);	// 1 in the leading bit
	while (equal && p != pat.end())
	{
	    if (*p == "*")
		accumPenalty |= penalty;	// penalize wildcards
	    else
	    	equal = *p == *t;
	    ++p, ++t, penalty >>= 1;
	}
	if (equal)
	{
	    if (accumPenalty == 0)
		return i->second.type;
	    if (accumPenalty < minPenalty) {
		result = i->second.type;
		minPenalty = accumPenalty;
	    }
	}
    }
    return result;
}

void ProgramTypeTable::registerAlias(const TQString& name, TypeInfo* type)
{
    ASSERT(lookup(name) == 0 || lookup(name) == type);
    m_aliasDict.insert(name, type);
}

void ProgramTypeTable::loadLibTypes(const TQStringList& libs)
{
    for (TQStringList::const_iterator it = libs.begin(); it != libs.end(); ++it)
    {
	// look up the library
	for (std::list<TypeTable>::iterator t = typeTables.begin(); t != typeTables.end(); ++t)
	{
	    if (t->matchFileName(*it))
	    {
		TRACE("adding types for " + *it);
		loadTypeTable(&*t);
	    }
	}
    }
}
