// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.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.            
//                                                                
// This program 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 General Public License for more details.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                            

#include "IncludeExpander.h"
#include "LineDirectiveMgr.h"

#include "Puma/PreTreeNodes.h"
#include "Puma/PreSonIterator.h"
#include "Puma/MacroUnit.h"
#include "Puma/FileUnit.h"
#include "Puma/CProject.h"
#include "Puma/UnitManager.h"
#include "Puma/CCParser.h"
#include "Puma/CTranslationUnit.h"
#include "Puma/Token.h"
#include "Puma/Location.h"

void IncludeExpander::expand (const char *file) {

  // clear the resulting unit
  _unit.List::clear ();

  // preprocess the translation unit
  Unit *unit = _project.unitManager ().getUnit (file, true);

  // return if the unit is empty (strange property of the UnitManager!)
  if (!unit || !unit->first ())
    return;

  // ok, we have a unit to expand
  _root = unit;
  
  // silently run the preprocessor
  CCParser parser;
  CTranslationUnit *tunit = parser.parse (*_root , _project, 2);
  
  _forced_includes = false;
  _depth = 0;
  _ext_depth = 0;
  _files.push (File (_root));

  // search for include and manipulate them -> fills _tree and _nodes
  iterateNodes (tunit->cpp_tree ());

  // cleanup
  delete tunit; 

  // rewind the include stack until we are back at the input file
  rewind (0);

  // write the rest of the main unit
  finish ();
}

// Go through the nodes.
void IncludeExpander::iterateNodes (PreTree* node) {
  PreSonIterator i (node); // the order is important!

  for (i.first (); ! i.isDone (); i.next ())
    i.currentItem ()->accept (*this);
}

// expand tokens from a unit (write them into the result unit)
void IncludeExpander::write (Unit *unit, Token *first, Token *last) {
  ACUnit linedir (_err);
  if (_line_mgr.directive (linedir, unit, first)) {
    linedir << endu;
    linedir.cut (_unit, (Token*)linedir.first (), (Token*)linedir.last ());
  }
  // copy the tokens
  Token *curr = first;
  while (true) {
    // filter out #pragma once
    if (curr->is_directive () && strcmp ("#pragma", curr->text ()) == 0) {
      Token *dir = (Token*)unit->next (curr);
      bool have_pragma_once = false;
      while (dir->is_directive () && strcmp ("#pragma", dir->text ()) != 0) {
        if (strcmp ("once", dir->text ()) == 0) {
          have_pragma_once = true;
          break;
        }
        dir = (Token*)unit->next (dir);
      }
      if (have_pragma_once)
        curr = (Token*)unit->next (dir);
    }
    _unit.append (*(Token*)curr->duplicate ());
    if (curr == last)
      break;
    curr = (Token*)unit->next (curr);
  }
//  List *copy = unit->copy (first, last);
//  copy->cut (_unit, (Token*)copy->first (), (Token*)copy->last ());
//  delete copy;
}

// write a string into the unit
void IncludeExpander::write (const char *str) {
  ACUnit str_unit (_err);
  str_unit << str << endu;
  str_unit.cut (_unit, (Token*)str_unit.first (), (Token*)str_unit.last ());
}

// print the rest of the topmost unit
void IncludeExpander::finish () {
  Token *first = (Token*)_files.top ()._next;
  Token *last  = (Token*)_files.top ()._unit->last ();
  if (first)
    write (_files.top ()._unit, first, last);
}

// pop elements from the include stack until 'up_to' is the top
void IncludeExpander::rewind (int up_to) {
  while (_depth > up_to) {
    finish ();
    _files.pop ();
    _depth--;
  }
}

// handle include directive node	
void IncludeExpander::visitPreIncludeDirective_Pre (PreIncludeDirective* node)
{
  // this_unit is the unit where the include directive is located
  Token *this_token = node->startToken ();
  Unit *this_unit   = (Unit*)this_token->belonging_to ();
//  cout << "in " << this_unit->name () << " " << this_token->location () << " "
//    << _project.isBelow (this_unit) << endl;
        
  if (node->is_forced () || (this_unit == _root && node->depth () == 0))
    _forced_includes = false;
  else if (_forced_includes)
    return;
    
  // manipulate project files only (and forced includes)
  if (node->is_forced () || _project.isBelow (this_unit) ||
      this_unit == _root) {

    // include if expanded by preprocessor
    if (node->daughters () == 1) {
      
      Unit *included_unit = ((PreInclSemNode*)node->daughter (0))->unit ();
      // a guarded unit is treaded as if it was not included
      if (((PreInclSemNode*)node->daughter (0))->guarded ()) {
        included_unit = 0;
      }

//      cout << "included: " << included_unit->name () << " " <<
//        _project.isBelow (included_unit) << endl; 

      if (_ext_depth) {
//        cout << "node depth = " << node->depth () << endl;
//        cout << "ext depth  = " << _ext_depth << endl;
        if (node->depth () < _ext_depth) {
          // we are back!
          _ext_depth = 0;
//          cout << "cleared last" << endl;
        }
        else {
          // problem: we are in a project file, which was included by an external
          return;
        }
      }

      // expand includes of project files only
      if (!included_unit || _project.isBelow (included_unit)) {

        if (_depth > node->depth ())
          rewind (node->depth ());

        if (!node->is_forced ()) {
          // advance the current position up the include
          Token *first = _files.top ()._next;
          if (first && first != this_token) {
            Token *last = (Token*)this_unit->prev (this_token);
            write (this_unit, first, last);
          }
          Token *last = node->endToken ();
          // this might be a macro-generated include -> find the correct next tok
          if (last->is_macro_generated ()) {
            last = ((MacroUnit*)last->belonging_to ())->ExpansionEnd (last);
            assert (last && last->belonging_to () == this_unit);
          }
          _files.top ()._next = (Token*)this_unit->next (last);
        }
        
        if (included_unit) {
          // enter the next include file
          _files.push (File (included_unit));
          _depth++;
        }
      }
      else {
        if (node->is_forced ()) {
          // print the rest of nested includes first
          rewind (0);
          _depth = 0;
          
          // if the included unit does not belong to the project we generate an
          // #include directive so that the backend compiler does not have to
          // include the unit in any case
          const char *path = ((FileUnit*)included_unit)->absolutePath ();
          ostringstream generated_include;
          generated_include << "#include \"" << path << '\"' << endl << ends;
          write (generated_include.str ().c_str ());
          _forced_includes = true;
        }
        else {
          // remember that we leave the project now
//          cout << "_last = " << this_unit->name () << endl;
          if (!_ext_depth) {
            _ext_depth = node->depth () + 1; // depth of external includes
//            cout << "ext_depth = " << _ext_depth << endl;
          }
        }
      }
    }
    // include was e.g. in false ifdef branch
    else {
//      cout << "no daughter" << endl;
    }
  }
  else {
    // in external file!
    if (node->daughters () == 1) {
      Unit *included_unit = ((PreInclSemNode*)node->daughter (0))->unit ();
      if (included_unit && _project.isBelow (included_unit)) {
        // oops, an external file includes an internal => error!
        _err << sev_error << this_token->location ()
             << "internal file '" << included_unit->name ()
             << "' is included by external '" << this_unit->name () << "'"
             << endMessage;
        return;
      }
    }
  }
}
