/***************************************************************************
 *   Copyright (C) 2003 by Roberto Raggi                                   *
 *   roberto@kdevelop.org                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "tag_creator.h"
#include "catalog.h"
#include "ast_utils.h"
#include "cpp_tags.h"
#include "doxydoc.h"

#include <kdebug.h>
#include <qfileinfo.h>
#include <qregexp.h>

DoxyDoc* TagCreator::m_documentation = new DoxyDoc( QStringList() );

TagCreator::TagCreator( const QString& fileName, Catalog* c )
    : m_catalog( c ), m_fileName( fileName ), m_anon( 0 )
{
}

TagCreator::~TagCreator()
{
}

void TagCreator::destroyDocumentation(){
        delete m_documentation;
}

void TagCreator::setDocumentationDirectories(const QStringList& str){
	if (m_documentation)
		delete m_documentation;
	m_documentation = new DoxyDoc(str);
}

void TagCreator::parseDeclaration( DeclarationAST* ast )
{
    if( ast->nodeType() == NodeType_AccessDeclaration ||
	m_currentAccess.isEmpty() ||
	m_currentAccess.contains("public") ||
	m_currentAccess.contains("protected") ||
	m_currentAccess.contains("signals") )

	   TreeParser::parseDeclaration( ast );
}

void TagCreator::parseTranslationUnit( TranslationUnitAST* ast )
{
    m_currentScope.clear();
    m_currentAccess = QString::null;
    m_inSlots = false;
    m_inSignals = false;
    m_anon = 0;
    m_imports.clear();
    m_inClass = false;

    m_imports << QStringList();
    TreeParser::parseTranslationUnit( ast );
    m_imports.pop_back();
}

void TagCreator::parseNamespace( NamespaceAST* ast )
{
    QString nsName;
    if( !ast->namespaceName() || ast->namespaceName()->text().isEmpty() ){
        // anonymous namespace
    } else {
	nsName = ast->namespaceName()->text();
    }

    Tag tag;
    tag.setKind( Tag::Kind_Namespace );
    tag.setFileName( m_fileName );
    tag.setName( nsName );
    tag.setScope( m_currentScope );

    int line, col;
    ast->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    ast->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    m_catalog->addItem( tag );

    m_currentScope.push_back( nsName );
    TreeParser::parseNamespace( ast );
    m_currentScope.pop_back();
}

void TagCreator::parseElaboratedTypeSpecifier( ElaboratedTypeSpecifierAST* ast )
{
    TreeParser::parseElaboratedTypeSpecifier( ast );
}

void TagCreator::parseUsingDirective( UsingDirectiveAST* ast )
{
    QString name;
    if( ast->name() )
        name = ast->name()->text();

    if( !name.isNull() ){
        Tag tag;
        tag.setKind( Tag::Kind_UsingDirective );
        tag.setFileName( m_fileName );
        tag.setName( name );
        tag.setScope( m_currentScope );

        int line, col;
        ast->getStartPosition( &line, &col );
        tag.setStartPosition( line, col );

        ast->getEndPosition( &line, &col );
        tag.setEndPosition( line, col );

        m_catalog->addItem( tag );
    }

    m_imports.back().push_back( name );
}

void TagCreator::parseTypedef( TypedefAST* ast )
{
    TypeSpecifierAST* typeSpec = ast->typeSpec();
    InitDeclaratorListAST* declarators = ast->initDeclaratorList();

    if( typeSpec && declarators ){
        QString typeId;

        if( typeSpec->name() )
            typeId = typeSpec->name()->text();

        QPtrList<InitDeclaratorAST> l( declarators->initDeclaratorList() );
        QPtrListIterator<InitDeclaratorAST> it( l );

	InitDeclaratorAST* initDecl = 0;
	while( 0 != (initDecl = it.current()) ){

	    QString type, id;
	    if( initDecl->declarator() ){
	       type = typeOfDeclaration( typeSpec, initDecl->declarator() );

	       DeclaratorAST* d = initDecl->declarator();
	       while( d->subDeclarator() ){
	           d = d->subDeclarator();
	       }

	       if( d->declaratorId() )
	          id = d->declaratorId()->text();
	    }

	    Tag tag;
	    tag.setKind( Tag::Kind_Typedef );
	    tag.setFileName( m_fileName );
	    tag.setName( id );
	    tag.setScope( m_currentScope );
            tag.setAttribute( "t", type );

	    int line, col;
	    initDecl->getStartPosition( &line, &col );
	    tag.setStartPosition( line, col );

	    initDecl->getEndPosition( &line, &col );
	    tag.setEndPosition( line, col );

	    m_catalog->addItem( tag );

	    ++it;
	}

    }
}

void TagCreator::parseTemplateDeclaration( TemplateDeclarationAST* ast )
{
    if( ast->declaration() )
	parseDeclaration( ast->declaration() );

    TreeParser::parseTemplateDeclaration( ast );
}

void TagCreator::parseSimpleDeclaration( SimpleDeclarationAST* ast )
{
    TypeSpecifierAST* typeSpec = ast->typeSpec();
    InitDeclaratorListAST* declarators = ast->initDeclaratorList();

    if( typeSpec )
	parseTypeSpecifier( typeSpec );

    if( declarators ){
	QPtrList<InitDeclaratorAST> l = declarators->initDeclaratorList();

	QPtrListIterator<InitDeclaratorAST> it( l );
	while( it.current() ){
	    parseMyDeclaration(  ast->functionSpecifier(), ast->storageSpecifier(), typeSpec, it.current() );
	    ++it;
	}
    }
}

void TagCreator::parseFunctionDefinition( FunctionDefinitionAST* ast )
{
    TypeSpecifierAST* typeSpec = ast->typeSpec();
    GroupAST* funSpec = ast->functionSpecifier();
    GroupAST* storageSpec = ast->storageSpecifier();

    if( !ast->initDeclarator() )
	return;

    DeclaratorAST* d = ast->initDeclarator()->declarator();

    if( !d->declaratorId() )
	return;

    bool isFriend = false;
    bool isVirtual = false;
    bool isStatic = false;
    bool isInline = false;

    if( funSpec ){
	QPtrList<AST> l = funSpec->nodeList();
	QPtrListIterator<AST> it( l );
	while( it.current() ){
	    QString text = it.current()->text();
	    if( text == "virtual" ) isVirtual = true;
	    else if( text == "inline" ) isInline = true;
	    ++it;
	}
    }

    if( storageSpec ){
	QPtrList<AST> l = storageSpec->nodeList();
	QPtrListIterator<AST> it( l );
	while( it.current() ){
	    QString text = it.current()->text();
	    if( text == "friend" ) isFriend = true;
	    else if( text == "static" ) isStatic = true;
	    ++it;
	}
    }


    QString id = d->declaratorId()->unqualifiedName()->text().stripWhiteSpace();
    QString scopeStr = scopeOfDeclarator( d );

    Tag tag;
    CppFunction<Tag> tagBuilder( tag );
    tag.setKind( Tag::Kind_Function );

    tag.setFileName( m_fileName );
    tag.setName( id );
    tag.setScope( QStringList::split(".", scopeStr) );

    int line, col;
    ast->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    ast->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    tagBuilder.setType( typeOfDeclaration(typeSpec, d) );

    parseFunctionArguments( tag, d );

    QString arguments = tag.attribute("a").toStringList().join(",");
    tag.setAttribute("description", m_documentation->functionDescription(scopeStr.replace(QRegExp("."),":"), id, typeOfDeclaration(typeSpec, d), arguments));

    tagBuilder.setAccess( TagUtils::stringToAccess(m_currentAccess) );

    tagBuilder.setFriend( isFriend );
    tagBuilder.setVirtual( isVirtual );
    tagBuilder.setStatic( isStatic );
    tagBuilder.setInline( isInline );
    tagBuilder.setPure( false );
    tagBuilder.setConst( d->constant() != 0 );
    tagBuilder.setSignal( m_inSignals );
    tagBuilder.setSlot( m_inSlots );

    m_catalog->addItem( tag );

    if( !m_currentAccess.isEmpty() ){
        tag.setKind( Tag::Kind_FunctionDeclaration );
        m_catalog->addItem( tag );
    }
}

void TagCreator::parseLinkageBody( LinkageBodyAST* ast )
{
    QPtrList<DeclarationAST> l = ast->declarationList();
    QPtrListIterator<DeclarationAST> it( l );
    while( it.current() ){
	parseDeclaration( it.current() );
	++it;
    }
}

void TagCreator::parseClassSpecifier( ClassSpecifierAST* ast )
{
    int startLine, startColumn;
    int endLine, endColumn;
    ast->getStartPosition( &startLine, &startColumn );
    ast->getEndPosition( &endLine, &endColumn );

    QString oldAccess = m_currentAccess;
    bool oldInSlots = m_inSlots;
    bool oldInSignals = m_inSignals;

    QString kind = ast->classKey()->text();
    if( kind == "class" )
	m_currentAccess = "private";
    else
	m_currentAccess = "public";
    m_inSlots = false;
    m_inSignals = false;

    QString className;
    if( !ast->name() ){
	//QFileInfo fileInfo( m_fileName );
	//QString shortFileName = fileInfo.baseName();
	//className.sprintf( "(%s_%d)", shortFileName.local8Bit(), m_anon++ );
    } else {
	className = ast->name()->text();
    }

    Tag tag;
    tag.setKind( Tag::Kind_Class );

    tag.setFileName( m_fileName );
    tag.setName( className );
    tag.setScope( m_currentScope );

    int line, col;
    ast->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    ast->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    m_catalog->addItem( tag );

    if ( ast->baseClause() )
        parseBaseClause( tag.path(), ast->baseClause() );

    m_currentScope.push_back( className );
    int oldInClass = m_inClass;
    m_inClass = true;
    TreeParser::parseClassSpecifier( ast );
    m_currentScope.pop_back();
    m_inClass = oldInClass;

    m_currentAccess = oldAccess;
    m_inSlots = oldInSlots;
    m_inSignals = oldInSignals;
}

void TagCreator::parseEnumSpecifier( EnumSpecifierAST* ast )
{
    Tag tag;
    tag.setKind( Tag::Kind_Enum );

    tag.setFileName( m_fileName );
    if( ast->name() )
        tag.setName( ast->name()->text() );
    tag.setScope( m_currentScope );

    int line, col;
    ast->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    ast->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    m_catalog->addItem( tag );

    QPtrList<EnumeratorAST> l = ast->enumeratorList();
    QPtrListIterator<EnumeratorAST> it( l );
    while( it.current() ){
	QString name = it.current()->id()->text();

	Tag tag;
	tag.setKind( Tag::Kind_Enumerator );

	tag.setFileName( m_fileName );
	tag.setName( name );
	tag.setScope( m_currentScope );

	int line, col;
	it.current()->getStartPosition( &line, &col );
	tag.setStartPosition( line, col );

	it.current()->getEndPosition( &line, &col );
	tag.setEndPosition( line, col );

	m_catalog->addItem( tag );

	++it;
    }

    TreeParser::parseEnumSpecifier( ast );
}

void TagCreator::parseMyDeclaration( GroupAST* funSpec, GroupAST* storageSpec, TypeSpecifierAST* typeSpec, InitDeclaratorAST* decl )
{
    DeclaratorAST* d = decl->declarator();

    if( !d )
	return;

    if( !d->subDeclarator() && d->parameterDeclarationClause() )
	return parseFunctionDeclaration( funSpec, storageSpec, typeSpec, decl );

    DeclaratorAST* t = d;
    while( t && t->subDeclarator() )
	t = t->subDeclarator();

    QString id;
    if( t && t->declaratorId() && t->declaratorId()->unqualifiedName() )
	id = t->declaratorId()->unqualifiedName()->text();

    QString scopeStr = scopeOfDeclarator( d );

    QString type = typeOfDeclaration( typeSpec, d );


    bool isFriend = false;
    //bool isVirtual = false;
    bool isStatic = false;
    //bool isInline = false;
    //bool isInitialized = decl->initializer() != 0;

    if( storageSpec ){
	QPtrList<AST> l = storageSpec->nodeList();
	QPtrListIterator<AST> it( l );
	while( it.current() ){
	    QString text = it.current()->text();
	    if( text == "friend" ) isFriend = true;
	    else if( text == "static" ) isStatic = true;
	    ++it;
	}
    }

    Tag tag;
    CppVariable<Tag> tagBuilder( tag );

    tag.setKind( Tag::Kind_Variable );
    tag.setFileName( m_fileName );
    tag.setName( id );
    tag.setScope( QStringList::split(".",scopeStr) );

    int line, col;
    decl->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    decl->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    tagBuilder.setType( type );
    tagBuilder.setFriend( isFriend );
    tagBuilder.setStatic( isStatic );
    tagBuilder.setAccess( TagUtils::stringToAccess(m_currentAccess) );

    m_catalog->addItem( tag );
}

void TagCreator::parseAccessDeclaration( AccessDeclarationAST * access )
{
    QPtrList<AST> l = access->accessList();

    m_currentAccess = l.at( 0 )->text();
    if( m_currentAccess == "signals" )
	m_currentAccess = "public"; //!?

    m_inSlots = l.count() > 1 ? l.at( 1 )->text() == "slots" : false;
    m_inSignals = l.count() >= 1 ? l.at( 0 )->text() == "signals" : false;
}

void TagCreator::parseFunctionDeclaration(  GroupAST* funSpec, GroupAST* storageSpec,
					     TypeSpecifierAST * typeSpec, InitDeclaratorAST * decl )
{
    bool isFriend = false;
    bool isVirtual = false;
    bool isStatic = false;
    bool isInline = false;
    bool isPure = decl->initializer() != 0;

    if( funSpec ){
	QPtrList<AST> l = funSpec->nodeList();
	QPtrListIterator<AST> it( l );
	while( it.current() ){
	    QString text = it.current()->text();
	    if( text == "virtual" ) isVirtual = true;
	    else if( text == "inline" ) isInline = true;
	    ++it;
	}
    }

    if( storageSpec ){
	QPtrList<AST> l = storageSpec->nodeList();
	QPtrListIterator<AST> it( l );
	while( it.current() ){
	    QString text = it.current()->text();
	    if( text == "friend" ) isFriend = true;
	    else if( text == "static" ) isStatic = true;
	    ++it;
	}
    }

    int startLine, startColumn;
    int endLine, endColumn;
    decl->getStartPosition( &startLine, &startColumn );
    decl->getEndPosition( &endLine, &endColumn );

    DeclaratorAST* d = decl->declarator();
    QString id = d->declaratorId()->unqualifiedName()->text();

    QString type = typeOfDeclaration( typeSpec, d );

    Tag tag;
    CppFunction<Tag> tagBuilder( tag );

    tag.setKind( Tag::Kind_FunctionDeclaration );
    tag.setFileName( m_fileName );
    tag.setName( id );
    tag.setScope( m_currentScope );

    int line, col;
    decl->getStartPosition( &line, &col );
    tag.setStartPosition( line, col );

    decl->getEndPosition( &line, &col );
    tag.setEndPosition( line, col );

    tagBuilder.setType( type );
    tagBuilder.setFriend( isFriend );
    tagBuilder.setVirtual( isVirtual );
    tagBuilder.setStatic( isStatic );
    tagBuilder.setInline( isInline );
    tagBuilder.setPure( isPure );
    tagBuilder.setConst( d->constant() != 0 );
    tagBuilder.setSignal( m_inSignals );
    tagBuilder.setSlot( m_inSlots );

    parseFunctionArguments( tag, d );

	QString arguments = tag.attribute("a").toStringList().join(",");
    QString scopeStr = m_currentScope.join("::");
    tag.setAttribute("description", m_documentation->functionDescription(scopeStr, id, type, arguments));

    m_catalog->addItem( tag );
}

void TagCreator::parseFunctionArguments( Tag& tag, DeclaratorAST* declarator )
{
    ParameterDeclarationClauseAST* clause = declarator->parameterDeclarationClause();

    QStringList types;
    QStringList args;
    if( clause && clause->parameterDeclarationList() ){
        ParameterDeclarationListAST* params = clause->parameterDeclarationList();
	QPtrList<ParameterDeclarationAST> l( params->parameterList() );
	QPtrListIterator<ParameterDeclarationAST> it( l );

	while( it.current() ){
	    ParameterDeclarationAST* param = it.current();
	    ++it;

	    QString name;
	    if( param->declarator() ){
		name = declaratorToString(param->declarator(), QString::null, true );
	    }

	    QString type = typeOfDeclaration( param->typeSpec(), param->declarator() );

	    types << type;
	    args << name;
	}

        if( clause->ellipsis() ){
            types << "...";
            args << "";
        }

    }

    CppFunction<Tag> tagBuilder( tag );

    tagBuilder.setArguments( types );
    tagBuilder.setArgumentNames( args );
}

QString TagCreator::typeOfDeclaration( TypeSpecifierAST* typeSpec, DeclaratorAST* declarator )
{
    if( !typeSpec || !declarator )
        return QString::null;

    QString text;

    text += typeSpec->text();
    text = text.simplifyWhiteSpace();

    QPtrList<AST> ptrOpList = declarator->ptrOpList();
    for( QPtrListIterator<AST> it(ptrOpList); it.current(); ++it ){
	text += it.current()->text();
    }

    return text;
}

void TagCreator::parseBaseClause( const QString& className, BaseClauseAST * baseClause )
{
    QPtrList<BaseSpecifierAST> l = baseClause->baseSpecifierList();
    QPtrListIterator<BaseSpecifierAST> it( l );
    while( it.current() ){
	BaseSpecifierAST* baseSpecifier = it.current();

	QString access;
	if( baseSpecifier->access() )
	    access = baseSpecifier->access()->text();
	bool isVirtual = baseSpecifier->isVirtual() != 0;

	QString baseName;

	QPtrList<ClassOrNamespaceNameAST> l = baseSpecifier->name()->classOrNamespaceNameList();
	QPtrListIterator<ClassOrNamespaceNameAST> nameIt( l );
	while( nameIt.current() ){
	    if( nameIt.current()->name() ){
		baseName += nameIt.current()->name()->text() + "::";
	    }
	    ++nameIt;
	}

	if( baseSpecifier->name()->unqualifiedName() && baseSpecifier->name()->unqualifiedName()->name() )
	    baseName += baseSpecifier->name()->unqualifiedName()->name()->text();

	Tag tag;
	CppBaseClass<Tag> tagBuilder( tag );

	tag.setKind( Tag::Kind_Base_class );
	tag.setFileName( m_fileName );
	tag.setName( className );
	tag.setScope( m_currentScope );

	tagBuilder.setBaseClass( baseName );
	tagBuilder.setVirtual( isVirtual );
	tagBuilder.setAccess( TagUtils::stringToAccess(access) );

	m_catalog->addItem( tag );

	int line, col;
	baseClause->getStartPosition( &line, &col );
	tag.setStartPosition( line, col );

	baseClause->getEndPosition( &line, &col );
	tag.setEndPosition( line, col );

	++it;
    }
}

QString TagCreator::scopeOfDeclarator( DeclaratorAST* d )
{
    QStringList scope = m_currentScope;
    if( d && d->declaratorId() && d->declaratorId()->classOrNamespaceNameList().count() ){
        if( d->declaratorId()->isGlobal() )
	    scope.clear();
	QPtrList<ClassOrNamespaceNameAST> l = d->declaratorId()->classOrNamespaceNameList();
	QPtrListIterator<ClassOrNamespaceNameAST> it( l );
	while( it.current() ){
	    if( it.current()->name() ){
	       scope << it.current()->name()->text();
	    }
	    ++it;
	}
    }

    return scope.join( "." );
}

int TagUtils::stringToAccess( const QString & access )
{
    QStringList l = QStringList()
		    << "public" << "protected" << "private"
		    << "public slots" << "protected slots" << "private slots"
		    << "signals";

    int idx = l.findIndex( access );
    return idx == -1 ? 0 : idx+1;
}

QString TagUtils::accessToString( int id )
{
    QStringList l = QStringList()
		    << "public" << "protected" << "private"
		    << "public slots" << "protected slots" << "private slots"
		    << "signals";

    if( l.at(id-1) != l.end() )
	return l[ id-1 ];

    return QString::null;
}
