/* This file is part of KDevelop
    Copyright 2006 Hamish Rodda <rodda@kde.org>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "usebuilder.h"

#include "cppeditorintegrator.h"
#include "name_compiler.h"
#include <language/duchain/declaration.h>
#include <language/duchain/use.h>
#include <language/duchain/topducontext.h>
#include <language/duchain/duchain.h>
#include <language/duchain/duchainlock.h>
#include <rpp/pp-location.h>

#include "expressionvisitor.h"
#include <parsesession.h>

#include <KLocalizedString>

using namespace KDevelop;

UseBuilder::UseBuilder (ParseSession* session)
  : UseBuilderBase(session)
{
}

UseBuilder::~UseBuilder()
{
}

void UseBuilder::buildUses(AST *node)
{
  TopDUContextPointer topContext;
  {
    DUChainReadLocker lock(DUChain::lock());
    topContext = TopDUContextPointer(node->ducontext->topContext());
  }
  //We will have some caching in TopDUContext until this objects lifetime is over
  Cpp::TypeConversionCacheEnabler enableConversionCache;

  UseBuilderBase::buildUses(node);
}

void UseBuilder::visitExpressionOrDeclarationStatement(ExpressionOrDeclarationStatementAST * exp) {
  if( exp->expressionChosen )
    visitExpression(exp->expression);
  else
    visit(exp->declaration);
}

void UseBuilder::visitCondition(ConditionAST *node)
{
  visit(node->type_specifier);
  visit(node->declarator);
  visit(node->expression);
}

void UseBuilder::visitExpressionStatement(ExpressionStatementAST * exp) {
  visitExpression(exp);
}

void UseBuilder::visitBinaryExpression(BinaryExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitCastExpression(CastExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitConditionalExpression(ConditionalExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitCppCastExpression(CppCastExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitNewExpression(NewExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitPostfixExpression(PostfixExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitSizeofExpression(SizeofExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitSubscriptExpression(SubscriptExpressionAST * exp) {
  visitExpression(exp);
}
void UseBuilder::visitUnaryExpression(UnaryExpressionAST * exp) {
  visitExpression(exp);
}

void UseBuilder::visitPrimaryExpression (PrimaryExpressionAST* exp)
{
  visitExpression(exp);
/*  UseBuilderBase::visitPrimaryExpression(node);

  if (node->name)
    newUse(node->name);*/
}

void UseBuilder::visitTypeIDOperator(TypeIDOperatorAST* node)
{
  visitExpression(node);
}

void UseBuilder::visitQPropertyDeclaration(QPropertyDeclarationAST* node)
{
  buildUsesForName(node->getter);
  buildUsesForName(node->setter);
  buildUsesForName(node->resetter);
  buildUsesForName(node->notifier);
  buildUsesForName(node->designableMethod);
  buildUsesForName(node->scriptableMethod);
  DefaultVisitor::visitQPropertyDeclaration(node);
}

class UseExpressionVisitor : public Cpp::ExpressionVisitor {
  public:
  UseExpressionVisitor(ParseSession* session, UseBuilder* useBuilder, bool dumpProblems = false, bool mapAst = false)
    : Cpp::ExpressionVisitor(session, 0, false, false, mapAst), m_builder(useBuilder), m_dumpProblems(dumpProblems) {
    reportRealProblems(true);
  }
  ~UseExpressionVisitor() {
    typedef KSharedPtr<KDevelop::Problem> P;
    foreach(const P& problem, realProblems())
      m_builder->addProblem(problem);
  }
  private:

    virtual void usingDeclaration(AST* node,
                                  size_t start_token, size_t end_token,
                                  const KDevelop::DeclarationPointer& decl) {
      RangeInRevision range = m_builder->editor()->findRange(start_token, end_token);
      m_builder->newUse(node, range, decl);

      if (decl && decl->isExplicitlyDeleted()) {
        KSharedPtr<KDevelop::Problem> problem(new Problem);
        problem->setDescription(i18n("use of deleted function"));

        problem->setSource(KDevelop::ProblemData::SemanticAnalysis);

        CppEditorIntegrator editor(session());
        Q_ASSERT(topContext());
        problem->setFinalLocation(DocumentRange(topContext()->url(), editor.findRange(node).castToSimpleRange()));

        if(!problem->range().isEmpty() && !editor.findRangeForContext(node->start_token, node->end_token).isEmpty())
          realProblem(problem);
      }
    }

    virtual void problem(AST* node, const QString& str) {
      if(m_dumpProblems)
        Cpp::ExpressionVisitor::problem(node, str);
/*      else
        kDebug(9007) << "problem";*/
    }

    UseBuilder* m_builder;
    bool m_dumpProblems;
};

void UseBuilder::visitExpression(AST* node) {

  UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
  if( !node->ducontext )
    node->ducontext = currentContext();
  
  visitor.parse( node );
}

void UseBuilder::visitNamespaceAliasDefinition(NamespaceAliasDefinitionAST* node) {
  DefaultVisitor::visitNamespaceAliasDefinition(node);
  buildUsesForName(node->alias_name);
}

void UseBuilder::visitUsingDirective(UsingDirectiveAST* node) {
  DefaultVisitor::visitUsingDirective(node);
  buildUsesForName(node->name);
}

void UseBuilder::buildUsesForName(NameAST* name) {
  if(name) {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if(name) {
      if( !name->ducontext )
        name->ducontext = currentContext();
      
      visitor.parse( name );
    }
  }
}

void UseBuilder::visitBaseSpecifier(BaseSpecifierAST* node) {
  buildUsesForName(node->name);
}

void UseBuilder::visitMemInitializer(MemInitializerAST * node)
{
  if( !node->ducontext )
    node->ducontext = currentContext();
  
  UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    
  visitor.parse( node );
}

void UseBuilder::visitElaboratedTypeSpecifier(ElaboratedTypeSpecifierAST* node)
{
  UseBuilderBase::visitElaboratedTypeSpecifier(node);
  
  if(!node->isDeclaration) {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->ducontext ) {
      if(lastContext() && lastContext()->type() == DUContext::Template && lastContext()->parentContext() == currentContext())
        node->ducontext = lastContext();//Use the template-context so we can build uses for the template-parameters of template functions
      else
        node->ducontext = currentContext();
    }
    
    visitor.parse( node );
  }
}

void UseBuilder::visitSimpleDeclaration(SimpleDeclarationAST* node)
{
  // Only perform special-handling for constructors when this isn't a real class specifier, else we
  // will not properly process the contents of the class.
  if(node->init_declarators && node->type_specifier && node->type_specifier->kind != AST::Kind_ClassSpecifier)
  {
    //Overridden so we can build uses for constructors like "A a(3);"
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->ducontext ) {
      if(lastContext() && lastContext()->type() == DUContext::Template && lastContext()->parentContext() == currentContext())
        node->ducontext = lastContext();//Use the template-context so we can build uses for the template-parameters of template functions
      else
        node->ducontext = currentContext();
    }
    
    visitor.parse( node );
    
    // Build uses for the name-prefixes of init declarators
    const ListNode<InitDeclaratorAST*>
      *it = node->init_declarators->toFront(),
      *end = it;

    do {
      InitDeclaratorAST* initDecl = it->element;
      if(initDecl->declarator && initDecl->declarator->id)
      {
        UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
        initDecl->declarator->id->ducontext = currentContext();
        visitor.parseNamePrefix(initDecl->declarator->id);
      }
      it = it->next;
    } while (it != end);
  }else{
    DefaultVisitor::visitSimpleDeclaration(node);    
  }
}

void UseBuilder::visitSimpleTypeSpecifier(SimpleTypeSpecifierAST* node)
{
  UseBuilderBase::visitSimpleTypeSpecifier(node);
  
  UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
  if( !node->ducontext ) {
    if(lastContext() && lastContext()->type() == DUContext::Template && lastContext()->parentContext() == currentContext())
      node->ducontext = lastContext();//Use the template-context so we can build uses for the template-parameters of template functions
    else
      node->ducontext = currentContext();
  }
  
  visitor.parse( node );
}

void UseBuilder::visitDeclarator(DeclaratorAST* node)
{
  if(node->id) {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->id->ducontext )
      node->id->ducontext = currentContext();
    visitor.parseNamePrefix(node->id);
  }

  UseBuilderBase::visitDeclarator(node);
}

void UseBuilder::visitClassSpecifier(ClassSpecifierAST* node)
{
  if(node->name) {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->name->ducontext )
      node->name->ducontext = currentContext();

    visitor.parseNamePrefix(node->name);
  }

  UseBuilderBase::visitClassSpecifier(node);
}

void UseBuilder::visitTypeId(TypeIdAST* node) {
  {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->ducontext )
      node->ducontext = currentContext();

    visitor.parse( node );
  }
  
  UseBuilderBase::visitTypeId(node);
}

void UseBuilder::visitUsing(UsingAST *node)
{
  if(node->name) {
    UseExpressionVisitor visitor( editor()->parseSession(), this, false, m_mapAst );
    if( !node->name->ducontext )
      node->name->ducontext = currentContext();

    visitor.parse( node->name );
  }
  UseBuilderBase::visitUsing(node);
}

void UseBuilder::addProblem(KSharedPtr< KDevelop::Problem > problem) {
  m_problems << problem;
}

QList< KSharedPtr< KDevelop::Problem > > UseBuilder::problems() const {
  return m_problems;
}
