/* This file is part of KDevelop
    Copyright 2010 Aleix Pol Gonzalez <aleixpol@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 "controlflowgraphbuilder.h"
#include <language/checks/controlflownode.h>
#include <language/checks/controlflowgraph.h>
#include <language/duchain/topducontext.h>
#include <language/duchain/duchainlock.h>
#include <parsesession.h>
#include <lexer.h>
#include <tokens.h>
#include <util/pushvalue.h>

using namespace KDevelop;
QString nodeToString(const ParseSession* s, AST* node);

ControlFlowGraphBuilder::ControlFlowGraphBuilder(const KDevelop::ReferencedTopDUContext& top, const ParseSession* session, ControlFlowGraph* graph)
  : m_session(session)
  , m_graph(graph)
  , m_currentNode(0)
  , m_returnNode(0)
  , m_breakNode(0)
  , m_continueNode(0)
  , m_top(top)
{}

ControlFlowGraphBuilder::~ControlFlowGraphBuilder()
{}

void ControlFlowGraphBuilder::run(AST* node)
{
  Q_ASSERT(!m_currentNode);
  visit(node);
}

CursorInRevision ControlFlowGraphBuilder::cursorForToken(uint token)
{
  return m_session->positionAt(m_session->token_stream->position(token));
}

RangeInRevision ControlFlowGraphBuilder::nodeRange(AST* node)
{
  RangeInRevision ret = node ? RangeInRevision(cursorForToken(node->start_token), cursorForToken(node->end_token)) : RangeInRevision::invalid();
  if(ret.start>=ret.end) { //I don't understand why I need this
    CursorInRevision start=ret.end;
    ret.start=ret.end;
    ret.end=start;
  }
  return ret;
}

// RangeInRevision rangeBetween(uint start_token, uint end_token);

ControlFlowNode* ControlFlowGraphBuilder::createCompoundStatement(AST* node, ControlFlowNode* next)
{
  ControlFlowNode* startNode = new ControlFlowNode;
  if(node)
    createCompoundStatementFrom(startNode, node, next);
  else {
    startNode->setNext(next);
    m_currentNode = startNode;
  }
  return startNode;
}

void ControlFlowGraphBuilder::createCompoundStatementFrom(ControlFlowNode* startNode, AST* node, ControlFlowNode* next)
{
  Q_ASSERT(node && startNode);
  CursorInRevision startcursor = cursorForToken(node->start_token);
  startNode->setStartCursor(startcursor);
  m_currentNode = startNode;
  visit(node);
  
  if(!m_currentNode->next()) {
    m_currentNode->setNext(next);
    m_currentNode->setEndCursor(cursorForToken(node->end_token));
  }
}

void ControlFlowGraphBuilder::visitFunctionDefinition(FunctionDefinitionAST* node)
{
  if(!node->function_body || !node->function_body->ducontext) //means its ducontext hasn't been built, we probably don't need the flow diagram
    return;
  
  PushValue<ControlFlowNode*> currentNode(m_currentNode);
  m_returnNode = new ControlFlowNode;
  
  Declaration* d;
  {
    KDevelop::DUChainReadLocker lock;
    d=node->function_body->ducontext->owner();
//     qDebug() << "lalala" << nodeToString(m_session, node) << node->function_body->ducontext->owner() << node->function_body;
  }
  
  if(d)
    m_graph->addEntry(d, createCompoundStatement(node->function_body, m_returnNode));
  else
    m_graph->addEntry(createCompoundStatement(node->function_body, m_returnNode));
}

void ControlFlowGraphBuilder::visitEnumerator(EnumeratorAST* node)
{
    bool create=!m_currentNode;
    if(create && node->expression)
      m_graph->addEntry(createCompoundStatement(node->expression, 0));
    else
      DefaultVisitor::visitEnumerator(node);
}


void ControlFlowGraphBuilder::visitIfStatement(IfStatementAST* node)
{
  ControlFlowNode* previous = m_currentNode;
  m_currentNode->setEndCursor(cursorForToken(node->condition->end_token));
  visit(node->condition);
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  
  previous->setConditionRange(nodeRange(node->condition));
  previous->setNext(createCompoundStatement(node->statement, nextNode));
  previous->setAlternative(node->else_statement ? createCompoundStatement(node->else_statement, nextNode) : nextNode);
  
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitWhileStatement(WhileStatementAST* node)
{
  //TODO:   m_currentNode->m_conditionRange = rangeBetween(node->condition->start_token, node->condition->end_token);
  m_currentNode->setEndCursor(cursorForToken(node->start_token));
  ControlFlowNode* previous = m_currentNode;
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  ControlFlowNode* conditionNode = createCompoundStatement(node->condition, 0);
  
  PushValue<ControlFlowNode*> pushBreak(m_breakNode, nextNode);
  PushValue<ControlFlowNode*> pushContinue(m_continueNode, conditionNode);
  ControlFlowNode* bodyNode = createCompoundStatement(node->statement, conditionNode);
  
  previous->setNext(conditionNode);
  conditionNode->setConditionRange(nodeRange(node->condition));
  conditionNode->setNext(bodyNode);
  conditionNode->setAlternative(nextNode);
  
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitForStatement(ForStatementAST* node)
{
  AST* flownode = node->condition ? (AST*) node->condition : (AST*) node->range_declaration;
  visit(node->init_statement);
  m_currentNode->setEndCursor(cursorForToken(flownode ? flownode->start_token : node->init_statement->end_token));
  ControlFlowNode* previous = m_currentNode;
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  ControlFlowNode* conditionNode = createCompoundStatement(flownode, nextNode);
  ControlFlowNode* endCondition = m_currentNode;
  ControlFlowNode* incNode = createCompoundStatement(node->expression, conditionNode);
  
  PushValue<ControlFlowNode*> pushBreak(m_breakNode, nextNode);
  PushValue<ControlFlowNode*> pushContinue(m_continueNode, incNode);
  ControlFlowNode* bodyNode = createCompoundStatement(node->statement, incNode);
  
  endCondition->setAlternative(bodyNode);
  endCondition->setConditionRange(nodeRange(flownode));
  
  previous->setNext(conditionNode);
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitConditionalExpression(ConditionalExpressionAST* node)
{
  visit(node->condition);
  m_currentNode->setEndCursor(cursorForToken(node->condition->end_token));
  ControlFlowNode* previous = m_currentNode;
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  ControlFlowNode* trueNode = createCompoundStatement(node->left_expression, nextNode);
  ControlFlowNode* elseNode = createCompoundStatement(node->right_expression, nextNode);
  
  previous->setNext(trueNode);
  previous->setAlternative(elseNode);
  previous->setConditionRange(nodeRange(node->condition));
  
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitDoStatement(DoStatementAST* node)
{
  m_currentNode->setEndCursor(cursorForToken(node->start_token));
  ControlFlowNode* previous = m_currentNode;
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  ControlFlowNode* condNode = createCompoundStatement(node->expression, nextNode);
  
  PushValue<ControlFlowNode*> pushBreak(m_breakNode, nextNode);
  PushValue<ControlFlowNode*> pushContinue(m_continueNode, condNode);
  ControlFlowNode* bodyNode = createCompoundStatement(node->statement, condNode);
  
  previous->setNext(bodyNode);
  condNode->setAlternative(bodyNode);
  condNode->setConditionRange(nodeRange(node->expression));
  
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitReturnStatement(ReturnStatementAST* node)
{
  DefaultVisitor::visitReturnStatement(node);
  m_currentNode->setEndCursor(cursorForToken(node->end_token));
  m_currentNode->setNext(m_returnNode);
}

void ControlFlowGraphBuilder::visitJumpStatement(JumpStatementAST* node)
{
  m_currentNode->setEndCursor(cursorForToken(node->end_token));
  switch(m_session->token_stream->token(node->start_token).kind) {
    case Token_continue:
//       if(!m_continueNode) addproblem(!!!);
      m_currentNode->setNext(m_continueNode);
      break;
    case Token_break:
//       if(!m_breakNode) addproblem(!!!);
      m_currentNode->setNext(m_breakNode);
      break;
    case Token_goto: {
//       qDebug() << "goto!";
      IndexedString tag = m_session->token_stream->symbol(node->identifier);
      QMap< IndexedString, ControlFlowNode* >::const_iterator tagIt = m_taggedNodes.constFind(tag);
      if(tagIt!=m_taggedNodes.constEnd())
        m_currentNode->setNext(*tagIt);
      else {
        m_pendingGotoNodes[tag] += m_currentNode;
        m_currentNode->setNext(0); //we set null waiting to find a proper node to jump to
      }
    } break;
  }
  
  //here we create a node with the dead code
  ControlFlowNode* deadNode = new ControlFlowNode;
  deadNode->setStartCursor(m_currentNode->nodeRange().end);
//   deadNode->setEndCursor(cursorForToken(node->end_token));
  m_currentNode=deadNode;
  m_graph->addDeadNode(deadNode);
}

void ControlFlowGraphBuilder::visitSwitchStatement(SwitchStatementAST* node)
{
  visit(node->condition);
  m_currentNode->setEndCursor(cursorForToken(node->condition->end_token));
//   ControlFlowNode* previous = m_currentNode;
  
  ControlFlowNode* nextNode = new ControlFlowNode;
  PushValue<ControlFlowNode*> pushBreak(m_breakNode, nextNode);
  PushValue<ControlFlowNode*> pushDefault(m_defaultNode, nextNode);
  
  ControlFlowNode* switchNode = m_currentNode;
  switchNode->setNext(nextNode);
  PushValue<QList< QPair<ControlFlowNode*, ControlFlowNode*> > > pushCases(m_caseNodes, QList<QPair<ControlFlowNode*,ControlFlowNode*> > ());
  visit(node->statement);
  switchNode->setNext(m_defaultNode);
  switchNode->setAlternative(m_caseNodes.isEmpty() ? 0 : m_caseNodes.first().first);
  switchNode->setConditionRange(nodeRange(node->condition));
  nextNode->setStartCursor(cursorForToken(node->end_token));
  m_currentNode = nextNode;
}

void ControlFlowGraphBuilder::visitLabeledStatement(LabeledStatementAST* node)
{
  visit(node->expression);
  
  int token = m_session->token_stream->token(node->start_token).kind;
  
  if(token==Token_default || token==Token_case) {
    ControlFlowNode* condNode = new ControlFlowNode;
    condNode->setStartCursor(cursorForToken(node->start_token));
    condNode->setEndCursor(cursorForToken(node->end_token));
    
    condNode->setNext(createCompoundStatement(node->statement, 0));
    
    if(!m_caseNodes.isEmpty()) {
      m_caseNodes.last().first->setAlternative(condNode);
      if(!m_caseNodes.last().second->next())
        m_caseNodes.last().second->setNext(condNode->next());
    }
    
    m_caseNodes+=qMakePair(condNode, m_currentNode);
    
    if(token==Token_default)
      m_defaultNode = condNode;
    
  } else { //it is a goto tag
    m_currentNode->setEndCursor(cursorForToken(node->start_token));
    
    ControlFlowNode* nextNode = new ControlFlowNode;
    nextNode->setStartCursor(cursorForToken(node->start_token));
    if(!m_currentNode->next()) m_currentNode->setNext(nextNode);
    
    IndexedString tag = m_session->token_stream->symbol(node->label);
    m_taggedNodes.insert(tag, nextNode);
    QList< ControlFlowNode* > pendingNodes = m_pendingGotoNodes.take(tag);
    foreach(ControlFlowNode* pending, pendingNodes)
      pending->setNext(nextNode);
    
    m_currentNode = nextNode;
    visit(node->statement);
  }
}
