/***************************************************************************
 *   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 "store_walker.h"
#include "ast_utils.h"
#include "urlutil.h"

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

StoreWalker::StoreWalker( const QString& fileName, CodeModel* store )
    : m_store( store ), m_anon( 0 )
{
    m_fileName = URLUtil::canonicalPath(fileName);

    //kdDebug(9007) << "StoreWalker::StoreWalker(" << m_fileName << ")" << endl;
}

StoreWalker::~StoreWalker()
{
}

void StoreWalker::parseTranslationUnit( TranslationUnitAST* ast )
{
    m_file = m_store->create<FileModel>();
    m_file->setName( m_fileName ); /// @todo ?!?

    m_currentScope.clear();
    m_currentNamespace.clear();
    m_currentClass.clear();
    
    m_currentAccess = CodeModelItem::Public;
    m_inSlots = false;
    m_inSignals = false;
    m_inStorageSpec = false;
    m_inTypedef = false;
    m_currentDeclarator = 0;
    m_anon = 0;
    m_imports.clear();

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

void StoreWalker::parseDeclaration( DeclarationAST* ast )
{
    TreeParser::parseDeclaration( ast );
}

void StoreWalker::parseLinkageSpecification( LinkageSpecificationAST* ast )
{
    int inStorageSpec = m_inStorageSpec;
    m_inStorageSpec = true;
    TreeParser::parseLinkageSpecification( ast );
    m_inStorageSpec = inStorageSpec;
}

void StoreWalker::parseNamespace( NamespaceAST* ast )
{
    if( !m_currentClass.isEmpty() ){
        kdDebug(9007) << "!!!!!!!!!!!!!!!!!!!!!!!!!! **error** !!!!!!!!!!!!!!!!!!!!" << endl;
	return;
    }

    QString nsName;
    if( !ast->namespaceName() || ast->namespaceName()->text().isEmpty() ){
	QFileInfo fileInfo( m_fileName );
	QString shortFileName = fileInfo.baseName();

	nsName.sprintf( "(%s_%d)", shortFileName.local8Bit().data(), m_anon++ );
    } else {
	nsName = ast->namespaceName()->text();
    }

    NamespaceDom ns = findOrInsertNamespace( ast, nsName );

    m_currentScope.push_back( nsName );
    m_currentNamespace.push( ns );

    TreeParser::parseNamespace( ast );

    m_currentNamespace.pop();
    m_currentScope.pop_back();
}

void StoreWalker::parseNamespaceAlias( NamespaceAliasAST* ast )
{
    TreeParser::parseNamespaceAlias( ast );
}

void StoreWalker::parseUsing( UsingAST* ast )
{
    TreeParser::parseUsing( ast );
}

void StoreWalker::parseUsingDirective( UsingDirectiveAST* ast )
{
    QString name = ast->name()->unqualifiedName()->text();
    m_imports.back().push_back( name );
}

void StoreWalker::parseTypedef( TypedefAST* ast )
{
#if 0
    DeclaratorAST* oldDeclarator = m_currentDeclarator;
    
    if( ast && ast->initDeclaratorList() && ast->initDeclaratorList()->initDeclaratorList().count() > 0 ) {
	    QPtrList<InitDeclaratorAST> lst( ast->initDeclaratorList()->initDeclaratorList() );
	    m_currentDeclarator = lst.at( 0 )->declarator();
    }
    
    m_inTypedef = true;    
    
    TreeParser::parseTypedef( ast );
    
    m_inTypedef = false;
    m_currentDeclarator = oldDeclarator;
#else 
    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();
	    }

	    TypeAliasDom typeAlias = m_store->create<TypeAliasModel>();
	    typeAlias->setFileName( m_fileName );
	    typeAlias->setName( id );
	    typeAlias->setType( type );
	    
	    int line, col;
	    initDecl->getStartPosition( &line, &col );
	    typeAlias->setStartPosition( line, col );
	    
	    initDecl->getEndPosition( &line, &col );
	    typeAlias->setEndPosition( line, col );
	    
	    if( m_currentClass.top() )
                m_currentClass.top()->addTypeAlias( typeAlias );
            else if( m_currentNamespace.top() )
                m_currentNamespace.top()->addTypeAlias( typeAlias );
            else
                m_file->addTypeAlias( typeAlias );

#if 0
	    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 );
#endif

	    ++it;
	}

    }
#endif
}

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

    TreeParser::parseTemplateDeclaration( ast );
}

void StoreWalker::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() ){
	    parseDeclaration(  ast->functionSpecifier(), ast->storageSpecifier(), typeSpec, it.current() );
	    ++it;
	}
    }
}

void StoreWalker::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;
	}
    }

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

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

    QStringList scope = scopeOfDeclarator( d, m_currentScope );

    FunctionDefinitionDom method = m_store->create<FunctionDefinitionModel>();
    method->setScope( scope );
    method->setName( id );

    parseFunctionArguments( d, model_cast<FunctionDom>(method) );

    QString text = typeOfDeclaration( typeSpec, d );
    if( !text.isEmpty() )
	method->setResultType( text );

    method->setFileName( m_fileName );
    method->setStartPosition( startLine, startColumn );
    method->setEndPosition( endLine, endColumn );

    if( m_inSignals )
        method->setSignal( true );

    if( m_inSlots )
        method->setSlot( true );

    if( m_currentClass.top() || (method->name() == "main" && scope.isEmpty()) ){
	method->setConstant( d->constant() != 0 );
	method->setAccess( m_currentAccess );
	method->setStatic( isStatic );
	method->setVirtual( isVirtual );

	if( m_currentClass.top() )
	    m_currentClass.top()->addFunction( model_cast<FunctionDom>(method) );
	else 
	    m_file->addFunction( model_cast<FunctionDom>(method) );
    }

    if( m_currentClass.top() )
	m_currentClass.top()->addFunctionDefinition( method );
    else if( m_currentNamespace.top() )
	m_currentNamespace.top()->addFunctionDefinition( method );
    else
	m_file->addFunctionDefinition( method );
}

void StoreWalker::parseLinkageBody( LinkageBodyAST* ast )
{
    TreeParser::parseLinkageBody( ast );
}

void StoreWalker::parseTypeSpecifier( TypeSpecifierAST* ast )
{
    TreeParser::parseTypeSpecifier( ast );
}

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

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

    QString kind = ast->classKey()->text();
    if( kind == "class" )
	m_currentAccess = CodeModelItem::Private;
    else
	m_currentAccess = CodeModelItem::Public;
    m_inSlots = false;
    m_inSignals = false;

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

    if( !scopeOfName( ast->name(), QStringList() ).isEmpty() ){
        kdDebug(9007) << "skip private class declarations" << endl;
        return;
    }

    ClassDom klass = m_store->create<ClassModel>();
    klass->setStartPosition( startLine, startColumn );
    klass->setEndPosition( endLine, endColumn );
    klass->setFileName( m_fileName );

    klass->setName( className );

    klass->setScope( m_currentScope );

    if( m_currentClass.top() )
	m_currentClass.top()->addClass( klass );
    else if( m_currentNamespace.top() )
	m_currentNamespace.top()->addClass( klass );
    else
	m_file->addClass( klass );

    if ( ast->baseClause() )
        parseBaseClause( ast->baseClause(), klass );

    m_currentScope.push_back( className );
    m_currentClass.push( klass );

    m_imports.push_back( QStringList() );

    TreeParser::parseClassSpecifier( ast );

    m_imports.pop_back();
    m_currentClass.pop();

    m_currentScope.pop_back();

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

void StoreWalker::parseEnumSpecifier( EnumSpecifierAST* ast )
{
    QPtrList<EnumeratorAST> l = ast->enumeratorList();
    QPtrListIterator<EnumeratorAST> it( l );
    while( it.current() ){
        VariableDom attr = m_store->create<VariableModel>();
	attr->setName( it.current()->id()->text() );
	attr->setFileName( m_fileName );
	attr->setAccess( m_currentAccess );
	attr->setType( "int" );
	attr->setStatic( true );

	int startLine, startColumn;
	int endLine, endColumn;
	it.current()->getStartPosition( &startLine, &startColumn );
	attr->setStartPosition( startLine, startColumn );

	it.current()->getEndPosition( &endLine, &endColumn );
	attr->setEndPosition( endLine, endColumn );

	if( m_currentClass.top() )
	    m_currentClass.top()->addVariable( attr );
	else if( m_currentNamespace.top() )
	    m_currentNamespace.top()->addVariable( attr );
	else
	    m_file->addVariable( attr );

	++it;
    }
}

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

void StoreWalker::parseTypeDeclaratation( TypeSpecifierAST* typeSpec )
{
    parseTypeSpecifier( typeSpec );
}

void StoreWalker::parseDeclaration( GroupAST* funSpec, GroupAST* storageSpec, TypeSpecifierAST* typeSpec, InitDeclaratorAST* decl )
{
    if( m_inStorageSpec )
	    return;
    
    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();

    if( !scopeOfDeclarator(d, QStringList()).isEmpty() ){
        kdDebug(9007) << "skip declaration" << endl;
	return;
    }

    VariableDom attr = m_store->create<VariableModel>();
    attr->setName( id );
    attr->setFileName( m_fileName );

    if( m_currentClass.top() )
	m_currentClass.top()->addVariable( attr );
    else if( m_currentNamespace.top() )
	m_currentNamespace.top()->addVariable( attr );
    else
	m_file->addVariable( attr );

    attr->setAccess( m_currentAccess );

    QString text = typeOfDeclaration( typeSpec, d );
    if( !text.isEmpty() )
	attr->setType( text );

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

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

    attr->setStartPosition( startLine, startColumn );
    attr->setEndPosition( endLine, endColumn );
    attr->setStatic( isStatic );
}

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

    QString accessStr = l.at( 0 )->text();
    if( accessStr == "public" )
	m_currentAccess = CodeModelItem::Public;
    else if( accessStr == "protected" )
	m_currentAccess = CodeModelItem::Protected;
    else if( accessStr == "private" )
	m_currentAccess = CodeModelItem::Private;
    else if( accessStr == "signals" )
	m_currentAccess = CodeModelItem::Protected;
    else
	m_currentAccess = CodeModelItem::Public;

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

NamespaceDom StoreWalker::findOrInsertNamespace( NamespaceAST* ast, const QString & name )
{
    if( m_currentNamespace.top() && m_currentNamespace.top()->hasNamespace(name) )
	return m_currentNamespace.top()->namespaceByName( name );

    if( m_file->hasNamespace(name) )
        return m_file->namespaceByName( name );

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

    NamespaceDom ns = m_store->create<NamespaceModel>();
    ns->setFileName( m_fileName );
    ns->setName( name );
    ns->setStartPosition( startLine, startColumn );
    ns->setEndPosition( endLine, endColumn );

    ns->setScope( m_currentScope );

    if( m_currentNamespace.top() )
	m_currentNamespace.top()->addNamespace( ns );
    else
	m_file->addNamespace( ns );

    return ns;
}

void StoreWalker::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();

    FunctionDom method = m_store->create<FunctionModel>();
    method->setName( id );

    method->setFileName( m_fileName );
    method->setStartPosition( startLine, startColumn );
    method->setEndPosition( endLine, endColumn );
    method->setAccess( m_currentAccess );
    method->setStatic( isStatic );
    method->setVirtual( isVirtual );
    method->setAbstract( isPure );
    parseFunctionArguments( d, method );

    if( m_inSignals )
        method->setSignal( true );

    if( m_inSlots )
        method->setSlot( true );

    QString text = typeOfDeclaration( typeSpec, d );
    if( !text.isEmpty() )
        method->setResultType( text );

    method->setConstant( d->constant() != 0 );
    method->setScope( scopeOfDeclarator(d, m_currentScope) );

    if( m_currentClass.top() )
	m_currentClass.top()->addFunction( method );
    else if( m_currentNamespace.top() )
	m_currentNamespace.top()->addFunction( method );
    else
	m_file->addFunction( method );
}

void StoreWalker::parseFunctionArguments( DeclaratorAST* declarator, FunctionDom method )
{
    ParameterDeclarationClauseAST* clause = declarator->parameterDeclarationClause();

    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;

	    ArgumentDom arg = m_store->create<ArgumentModel>();

	    if( param->declarator() ){
		QString text = declaratorToString(param->declarator(), QString::null, true );
		if( !text.isEmpty() )
		    arg->setName( text );
	    }

	    QString tp = typeOfDeclaration( param->typeSpec(), param->declarator() );
	    if( !tp.isEmpty() )
		arg->setType( tp );

	    method->addArgument( arg );
	}
    }
}

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

    QString text;

    text += typeSpec->text();

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

    return text;
}

void StoreWalker::parseBaseClause( BaseClauseAST * baseClause, ClassDom klass )
{
    QPtrList<BaseSpecifierAST> l = baseClause->baseSpecifierList();
    QPtrListIterator<BaseSpecifierAST> it( l );
    while( it.current() ){
	BaseSpecifierAST* baseSpecifier = it.current();

	QString baseName;
	if( baseSpecifier->name() )
	    baseName = baseSpecifier->name()->text();

	klass->addBaseClass( baseName );

	++it;
    }
}

QStringList StoreWalker::scopeOfName( NameAST* id, const QStringList& startScope )
{
    QStringList scope = startScope;
    if( id && id->classOrNamespaceNameList().count() ){
        if( id->isGlobal() )
	    scope.clear();
	QPtrList<ClassOrNamespaceNameAST> l = id->classOrNamespaceNameList();
	QPtrListIterator<ClassOrNamespaceNameAST> it( l );
	while( it.current() ){
	    if( it.current()->name() ){
	       scope << it.current()->name()->text();
	    }
	    ++it;
	}
    }

    return scope;
}

QStringList StoreWalker::scopeOfDeclarator( DeclaratorAST* d, const QStringList& startScope )
{
    return scopeOfName( d->declaratorId(), startScope );
}
