//@copyright_begin
// ================================================================
// Copyright Notice
// Copyright (C) 1998-2004 by Joe Linoff
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL JOE LINOFF BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// Comments and suggestions are always welcome.
// Please report bugs to http://ccdoc.sourceforge.net/ccdoc
// ================================================================
//@copyright_end
#include "phase3.h"
#include "phase3_html.h"
#include <cstdio>

// ================================================================
// This variable allows the header version
// to be queried at runtime.
// ================================================================
namespace {
  char ccdoc_rcsid[] = "$Id: phase3.cc,v 1.15 2004/09/30 04:16:07 jlinoff Exp $";
}

// ================================================================
// Local methods.
// ================================================================
namespace {
  // ================================================================
  // Move the children from one parent to the other.
  // ================================================================
  void move_namespace_children( ccdoc::statement::base* from,
                                ccdoc::statement::base* to)
  {
    // Move over all of the children so that later
    // code references can find them.
    ccdoc::statement::base::stmts_t& children = from->get_children();
    if( children.size() ) {
      // Make a copy to avoid corruption problems.
      vector<ccdoc::statement::base*> vec;
      copy(children.begin(),
           children.end(),
           back_inserter<vector<ccdoc::statement::base*> >(vec));
      ccdoc::statement::base::stmts_t::iterator citr;
      for(citr = vec.begin();citr!=vec.end();++citr) {
        ccdoc::statement::base* child = *citr;
        child->set_parent(to);
      }
    }
  }
  // ================================================================
  // Populate the comment statement.
  // ================================================================
  void create_namespace_comment(string& real_id,
                                ccdoc::statement::base* comment_stmt,
                                ccdoc::switches& sw)
  {
    ccdoc::statement::base* begin_stmt = comment_stmt->get_comment();
    if( begin_stmt->get_next() &&
        begin_stmt->get_next()->get_next() == 0 ) {
      // There is only one related namespace with a comment.
      ccdoc::statement::base* comment = begin_stmt->get_next()->get_comment();
      if( comment ) {
        // Copy the comment over verbatim.
        const vector<const char*>& vec = comment->get_tokens();
        vector<const char*>::const_iterator itr = vec.begin();
        for(;itr!=vec.end();++itr) {
          comment_stmt->add_token(*itr);
        }
        // Break the chain because there is no need
        // to access the "other" comment.
        begin_stmt->set_next(0);
        return;
      }
    }
    vector<string> txt;
    comment_stmt->add_token("@{");
    comment_stmt->add_token("@file");
    comment_stmt->add_token("2");
    comment_stmt->add_token("generated");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@type");
    comment_stmt->add_token("1");
    comment_stmt->add_token("@prefix");
    comment_stmt->add_token("@short_desc");
    comment_stmt->add_token("1");
    if( real_id == "-anonymous-" )
      comment_stmt->add_token("Anonymous namespace.");
    else {
      string short_desc;
      short_desc = "Namespace ";
      if( real_id.size() > sw.rptmlcei() ) {
        string short_id;
        short_id = '"';
        short_id += real_id.substr(0,sw.rptmlcei());
        short_id += "..\"";
        short_desc += short_id;
      }
      else {
        short_desc += real_id;
      }
      short_desc += ".";
      comment_stmt->add_token(short_desc.c_str());
    }
    comment_stmt->add_token("@long_desc");
    
    // Create the long description.
    int num_reported_files = 0;
    ccdoc::statement::base* stmt = begin_stmt->get_next();
    for(;stmt; stmt=stmt->get_next()) {
      if( !sw.rptcfuns() && !stmt->get_comment() ) {
        // Don't report namespaces with empty comments.
        continue;
      }
      ++num_reported_files;
    }
    if( num_reported_files ) {
      txt.push_back("It is generated from \"namespace ");
      if( real_id[0] != '-' )
        txt.push_back(real_id.c_str());
      txt.push_back(" { .. }\" declarations in the ");
      txt.push_back("following source files:<ul>");
      
      // Write out the summary info.
      stmt = begin_stmt->get_next();
      for(;stmt; stmt=stmt->get_next()) {
        if( !sw.rptcfuns() && !stmt->get_comment() ) {
          // Don't report namespaces with empty comments.
          continue;
        }
        const char* pid = stmt->get_id();
        for(;*pid!='+' && *pid;++pid);
        if( *pid )
          ++pid;
        for(;*pid!='+' && *pid;++pid);
        if( *pid )
          ++pid;
        txt.push_back("<li>");
        txt.push_back(pid);
        txt.push_back("</li>");
      }
      txt.push_back("</ul>");
    }
    else {
      // There weren't any files with documentation.
      // Tell the user.
      txt.push_back("It is generated from \"namespace ");
      if( real_id[0] != '-' )
        txt.push_back(real_id.c_str());
      txt.push_back(" { .. }\" declarations. ");
      txt.push_back("<p>None of the namespaces were documented ");
      txt.push_back("so the files won't show up unless you use the ");
      txt.push_back("-rptcfuns switch during phase 3 processing.");
    }

    // Write out the long description.
    char num[32];
    sprintf(num,"%d",txt.size());
    comment_stmt->add_token(num);
    vector<string>::iterator sitr = txt.begin();
    for(;sitr!=txt.end();++sitr) {
      comment_stmt->add_token((*sitr).c_str());
    }
    
    comment_stmt->add_token("@params"); // append "s"
    comment_stmt->add_token("0");
    comment_stmt->add_token("@returns");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@exceptions"); // append "s"
    comment_stmt->add_token("0");
    comment_stmt->add_token("@deprecated");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@authors");
    comment_stmt->add_token("1");
    comment_stmt->add_token("ccdoc");
    comment_stmt->add_token("@version");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@sees"); // append "s"
    comment_stmt->add_token("0");
    comment_stmt->add_token("@since");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@source");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@pkg");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@pkgdoc");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@todo");
    comment_stmt->add_token("0");
    comment_stmt->add_token("@}");
  }
  // ================================================================
  // Move namespace children.
  // ================================================================
  void move_namespace_children(ccdoc::statement::base* begin_stmt,
                               vector<ccdoc::statement::base*>& related_nsps)
  {
    vector<ccdoc::statement::base*>::iterator itr;
    itr = related_nsps.begin();
    for(;itr!=related_nsps.end();++itr) {
      ccdoc::statement::base* stmt = *itr;
      move_namespace_children( stmt, begin_stmt );
    }
  }
  // ================================================================
  // Chain related namespaces.
  // ================================================================
  void chain_related_namespaces(ccdoc::statement::base* begin_stmt,
                                vector<ccdoc::statement::base*>& chained_nsps)
  {
    ccdoc::statement::base* chain = begin_stmt;
    vector<ccdoc::statement::base*>::iterator itr;
    itr = chained_nsps.begin();
    for(;itr!=chained_nsps.end();++itr) {
      ccdoc::statement::base* stmt = *itr;
      chain->set_next(stmt);
      chain = stmt;
    }
    chain->set_next(0);
  }
  // ================================================================
  // Erase generated namespaces.
  // ================================================================
  void erase_generated_namespaces(ccdoc::database& db)
  {
    vector<ccdoc::statement::base*> stmts;
    db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN);
    db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_END);
    vector<ccdoc::statement::base*>::iterator itr;

    // Remove the internally generated namespaces.
    // This guarantees that things will work when
    // an existing db is read.
    if( !stmts.empty() ) {
      vector<ccdoc::statement::base*> erased_stmts;
      for(itr = stmts.begin();itr!=stmts.end();++itr) {
        ccdoc::statement::base* stmt = *itr;
        const char* pid = stmt->get_id();
        if( *pid != '+' ) {
          // This is an internally generated namespace.
          erased_stmts.push_back(stmt);
          if( stmt->get_comment() ) {
            // Make sure that the comment is erased as well.
            erased_stmts.push_back( stmt->get_comment() );
          }
        }
      }
      if( erased_stmts.size() ) {
        vector<ccdoc::statement::base*>::reverse_iterator ritr;
        for(ritr=erased_stmts.rbegin();ritr!=erased_stmts.rend();++ritr) {
          delete *ritr;
        }
      }
    }
  }
  // ================================================================
  // Issue 0133
  // Combine all of the related namespaces.
  // ================================================================
  void combine_related_namespaces(ccdoc::database& db,
                                  ccdoc::switches& sw)
  {
    vector<ccdoc::statement::base*> stmts;
    db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN);
    db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_END);
    vector<ccdoc::statement::base*>::iterator itr;
    erase_generated_namespaces(db);
    
    // Walk through and re-create the generated namespace
    // names.
    stmts.clear();
    db.load(stmts,ccdoc::statement::base::STMT_NAMESPACE_BEGIN);
    if( !stmts.empty() ) {
      set<string> matches;
      for(itr = stmts.begin();itr!=stmts.end();++itr) {
        ccdoc::statement::base* stmt = *itr;
        const char* pid = stmt->get_id();
        
        if( *pid == '+' ) {
          // This is the special case form of the namespace
          // that is tagged in the parser to indicate that
          // the file name has been attached.
          // See: ccdoc::phase1::parser::parse_scoping_stmt_beg
          // Format: +<name>+<file>:<line>
          
          // Extract the "real" namespace name ("+"<name>"+").
          string real_id;
          for(++pid;*pid!='+';++pid)
            real_id += *pid;
          
          // Get the parent.
          ccdoc::statement::base* parent = stmt->get_parent();
          ccdoc::statement::base* file = stmt;
          
          // Has this real_id has already been processed
          // for this parent?
          // Remember that namespaces with the same name
          // can exist in different parents.
          string key;
          parent->get_hier_id(key);
          key += " ";
          key += real_id;
          if( matches.find(key) != matches.end() ) {
            continue;
          }
          matches.insert(key);
          
          // First figure out exactly how many
          // related namespaces there so that
          // I can determine whether a new comment
          // record is needed.
          vector<ccdoc::statement::base*> related_nsps;
          vector<ccdoc::statement::base*>::iterator nitr = itr;
          related_nsps.push_back( stmt );
          for(++nitr;nitr!=stmts.end();++nitr) {
            ccdoc::statement::base* stmt1 = *nitr;
            if( stmt1->get_parent() == parent ) {
              pid = stmt1->get_id();
              if( *pid == '+' ) {
                const char* pr = real_id.c_str();
                for(++pid;*pid == *pr;++pid,++pr) ;
                if( *pid == '+' && *pr == 0 ) {
                  // This is a match save it.
                  related_nsps.push_back( stmt1 );
                }
              }
            }
          }
          
          // Now define the namespaces that are chained together
          // for documentation purposes. This is different than
          // the related namespaces because the related namespaces
          // define the entities that are moved into the generated
          // namespace whereas the chained namespaces are only used
          // for documentation.
          vector<ccdoc::statement::base*> chained_nsps;
          if( !related_nsps.empty() ) {
            vector<ccdoc::statement::base*>::iterator nitr;
            nitr = related_nsps.begin();
            for(;nitr!=related_nsps.end();++nitr ) {
              ccdoc::statement::base* nsp = *nitr;
              if( !sw.rptcfuns() && !nsp->get_comment() ) {
                // Issue 0155.
                // Don't report namespaces with empty comments.
                continue;
              }
              chained_nsps.push_back( nsp );
            }
            // If none of the namespaces have comments,
            // copy over first one.
            if( chained_nsps.empty() ) {
              nitr = related_nsps.begin();
              chained_nsps.push_back(*nitr);
            }
          }
          
          // Create a dummy comment for use with the generated namespace
          // if there are multiple related namespaces or if there
          // is one related namespace that is associated with
          // a comment.
          ccdoc::statement::base* comment_stmt = 0;
          if( chained_nsps.size() > 1 ||
              ( chained_nsps.size() == 1 &&
                !chained_nsps.back()->get_comment() ) )
          {
            string comment_id;
            char num[32];
            sprintf(num,"%d",stmt->get_lineno());
            db.get_next_comment_id(comment_id);
            comment_stmt = new ccdoc::statement::base;
            comment_stmt->set_type( ccdoc::statement::base::STMT_COMMENT_PREFIX );
            comment_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC );
            comment_stmt->set_id( comment_id );
            comment_stmt->set_lineno( file->get_lineno() );
            comment_stmt->set_file( file->get_file() );
            parent->add_child(comment_stmt);
          }
          
          // Create a new namespace with the "real" name
          // and attach the members from the reference
          // namespaces.
          ccdoc::statement::base* begin_stmt = 0;
          begin_stmt = new ccdoc::statement::base;
          begin_stmt->set_id( real_id );
          begin_stmt->set_type( ccdoc::statement::base::STMT_NAMESPACE_BEGIN );
          begin_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC );
          begin_stmt->set_file( file->get_file() );
          begin_stmt->set_lineno( file->get_lineno() );
          parent->add_child(begin_stmt);
          
          // Create the end statement.
          ccdoc::statement::base* end_stmt = 0;
          end_stmt = new ccdoc::statement::base;
          end_stmt->set_id( real_id );
          end_stmt->set_type( ccdoc::statement::base::STMT_NAMESPACE_END );
          end_stmt->set_access( ccdoc::statement::base::STMT_PUBLIC );
          end_stmt->set_file( file->get_file() );
          end_stmt->set_lineno( file->get_lineno() );
          parent->add_child(end_stmt);
          
          // Move the namespace children to the generated namespace.
          move_namespace_children(begin_stmt,related_nsps);

          if( comment_stmt ) {
            // Chain together the associated comments.
            chain_related_namespaces(begin_stmt,chained_nsps);
          
            // Associate the comment with the statement.
            begin_stmt->set_comment(comment_stmt);
            comment_stmt->set_comment(begin_stmt);

            // Populate the comment.
            create_namespace_comment(real_id,comment_stmt,sw);

            // Clean up the begin/end stmt stuff.
            if( chained_nsps.size() > 1 ) {
              begin_stmt->set_file( "generated" );
              begin_stmt->set_lineno( 0 );
              end_stmt->set_file( "generated" );
              end_stmt->set_lineno( 0  );
            }
          }
          else {
            if( chained_nsps.size() == 1 &&
                chained_nsps.back()->get_comment() )
            {
              // Associate the umbrella comment with the new
              // namespace.
              comment_stmt = chained_nsps.back()->get_comment();
              begin_stmt->set_comment(comment_stmt);
              comment_stmt->set_comment(begin_stmt);
            }
          }
        }
      }
    }
  }
  // ================================================================
  // Issue 0144:
  // Set the static and template flags for statements.
  // This cannot be done earlier because they are not
  // stored in the database.
  // In the next version of ccdoc, it would be convient to
  // to store them their and do this in phase1.
  // ================================================================
  void set_stmt_flags(ccdoc::database& db,
                      ccdoc::switches& sw)
  {
    vector<ccdoc::statement::base*> stmts;
    vector<ccdoc::statement::base*>::iterator itr;

    // ================================================
    // Check for class scoped methods and attributes
    // by looking for the static keyword.
    // It is unfortunate that the static keyword
    // denotes both class scoping and module level
    // scoping. Fortunately, namespaces will eliminate
    // the latter use over time.
    // ================================================
    db.load(stmts,ccdoc::statement::base::STMT_METHOD);
    db.load(stmts,ccdoc::statement::base::STMT_ATTRIBUTE);
    db.load(stmts,ccdoc::statement::base::STMT_ATTRIBUTE_FUNCTION);

    for(itr=stmts.begin();itr!=stmts.end();++itr) {
      ccdoc::statement::base* stmt = *itr;
      const ccdoc::statement::base::cstrs_t tokens = stmt->get_tokens();
      ccdoc::statement::base::cstrs_citr_t itr1 = tokens.begin();
      for(;itr1!=tokens.end();++itr1) {
        string tmp = *itr1;
        if( tmp == "static" ) {
          stmt->set_static(true);
          break;
        }
      }
    }

    // ================================================
    // Check for templates.
    // ================================================
    stmts.clear();
    db.load(stmts,ccdoc::statement::base::STMT_FUNCTION);
    db.load(stmts,ccdoc::statement::base::STMT_CLASS_BEGIN);

    for(itr=stmts.begin();itr!=stmts.end();++itr) {
      ccdoc::statement::base* stmt = *itr;
      const ccdoc::statement::base::cstrs_t tokens = stmt->get_tokens();
      ccdoc::statement::base::cstrs_citr_t itr1 = tokens.begin();
      for(;itr1!=tokens.end();++itr1) {
        string tmp = *itr1;
        if( tmp == "template" ) {
          stmt->set_template(true);
          break;
        }
      }
    }
  }
  // ================================================================
  // delete_duplicate_macros
  // ================================================================
  void delete_duplicate_macros(ccdoc::database& db,
                               ccdoc::switches& sw)
  {
    ccdoc::statement::base::stmts_t stmts;
    db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_0);
    db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_1);
    db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_0_N);
    db.load_top(stmts,ccdoc::statement::base::STMT_MACRODEF_N_N);
    ccdoc::statement::base::stmts_itr_t itr = stmts.begin();
    set<string> names;
    string key;
    for(;itr!=stmts.end();++itr) {
      ccdoc::statement::base* stmt = *itr;

      // Remove rptmac1() type macros.
      if( sw.rptmac1() && stmt->is_rptmac1_id() ) {
        delete stmt;
        continue;
      }

      key = stmt->get_id();
      set<string>::iterator itr1 = names.find(key);
      if( itr1 != names.end() ) {
        // Found a duplicate, schedule
        // it for deletion.
        delete stmt;
        ccdoc::s_log.warning()
          << "Multiply defined macro '"
          << key
          << "' ignored at line "
          << stmt->get_lineno()
          << " in "
          << stmt->get_file()
          << ".\n"
          << ccdoc::s_log.enable();
      }
      else {
        names.insert(key);
      }
    }
  }
}

// ================================================================
// Run
// ================================================================
bool ccdoc::phase3::run(switches& sw,database& db)
{
  if(sw.verbose()) {
    s_log << "phase3: begins\n";
  }
  bool debug = false;
  if( ::getenv("CCDOC_PHASE3_DEBUG") ) {
    debug = true;
    s_log << "CCDOC_PHASE3_DEBUG: "
	  << "================================================\n";
    s_log << "CCDOC_PHASE3_DEBUG: file: " << sw.db() << "\n";
    db.debug_dump("CCDOC_PHASE3_DEBUG: ");
  }

  // Issue 0152:
  // Remove duplicate macros and rptmac1 type macros.
  // This is done here for performance reasons.
  // Originally I did this in phase 1 but that
  // led to O(N^2) behavior for large systems.
  // This must be done before load_path_map().
  // It would be interesting
  delete_duplicate_macros(db,sw);

  // This must be done here because I chose not to change the db
  // format for r33 (or later).
  set_stmt_flags(db,sw);
  
  // This must be done here because I chose not to change the db
  // format for r27 (or later) which means that the chain is not
  // persistent.
  combine_related_namespaces(db,sw);

  html h(sw,db);
  h.set_debug(debug);
  bool status = h.run();
  if(sw.verbose()) {
    s_log << "phase3: ends\n";
  }

  return status;
}
