/*
 * ccccmain.cc
 * command line interface implementation for the cccc project
 */

#ifndef VERSION
#define VERSION " <unnumbered> "
#endif


#include "cccc.h"
#include "cccc_ast.h"
#include "cccc_met.h"
#include "cccc_db.h"
#include "cccc_utl.h"
#include "cccc_htm.h"
#include "CParser.h"
#include "JParser.h"
#include "CLexer.h"
#include "JLexer.h"
#include "AdaPrser.h"
#include "ALexer.h"

#include <fstream.h>

#define MAX_FILES 1000
#define NEW_PAGE "\f\r"

#define DEFAULT_PROJECT_NAME    ""
#define DEFAULT_OUTFILE_NAME    "cccc.htm"

CCCC_Project *prj=NULL;
Metric_Treatment_Table *treatment_table=NULL;
// ofstream cmod, cmem, cuse;
int DebugMask=0;
int dont_free=0;

// in the future we may integrate multiple parsers, but for now we only
// have a single parser, with two modes for defining modularity:
// in C++ mode, a module is broadly a class, encompassing members of the class
// in C mode, a module is a file, encompassing items _defined_ in the file
Language global_language=lAUTO, file_language=lCPLUSPLUS;

AST **_current_root;
ASTBase *root;

char *skip_identifiers[SKIP_IDENTIFIERS_ARRAY_SIZE];

/* global variables used by the getopt() routine */
extern char *optarg;
extern int optind, opterr, optopt;

/*
** global streams used for output of various code features
*/
fstream cls_str;
fstream mth_str;
fstream use_str;

/*
** global variables to hold default values for various things
*/
CCCC_String current_filename, current_rule;

/*
** The signal handler indicates which signal caused the exit
*/
void SigExit(int n) {
  cerr << "Exiting due to signal " <<  n << endl;
  // force a core dump so that we can attempt to gdb the core file
  char *pc=NULL;
  char c=*pc;
}

/*
** If a memory allocation operation for a C++ object fails, this 
** function is called
*/
void NewHandler(void) {
  cerr << "Memory overflow in NewHandler"  << endl;
  SigExit(-999);
}

/*
** class Main encapsulates the top level of control for the program
*/
class Main {
  // command line arguments and switches
  int arg_count;
  char **arg_values;

  // a name given to the project
  char project_name[100];

  // source directory for configuration and support files
  char libdir[100];

  // the output filename
  char outfile[100];

  // storage for filenames, options collected from the command line
  char *filename[MAX_FILES];
  int number_of_options, number_of_files;
  int quiet_flag;
  int report_mask; 
  
  char* libfile(const char*);
  Language filename_to_language(const char*);

  void HandleOptions();
  void SetupInputFiles();
  void HandleDebugOption(char *arg);
  void HandleReportOption(char *arg);

  void PrintCredits(ostream& os);
  void PrintUsage(ostream& os);


public:

  Main(int argc, char **argv);
  int ParseFiles();
  void GenerateHtml();
};

Main *app=NULL;

/*
** The constructor for class Main processes input options and filenames,
** and also processes filenames received on standard input, allowing the
** program to be used with the Unix 'find' utility
*/
Main::Main(int argc, char **argv)
{
  arg_count=argc;
  arg_values=argv;

  strcpy(libdir,DEFAULT_LIBDIR);
  strcpy(project_name,DEFAULT_PROJECT_NAME);
  strcpy(outfile,DEFAULT_OUTFILE_NAME);

  number_of_files=0; 
  number_of_options=0;
  quiet_flag=0;
  report_mask=0xFFFF;  // everything

  HandleOptions();
  SetupInputFiles();

  // check that we can open the output file
  ofstream of(outfile);
  if(of.good() == 0)
  {
    cerr << "Couldn't open " << outfile << " for output" << endl;
    exit(1);
  }
  // the file will be closed automatically at the end of this function

  if(quiet_flag != 1)
  {
    PrintCredits(cerr);
  }

  // create the treatment table
  ifstream tmt_file(libfile("cccc_tmt.dat"));
  treatment_table=new Metric_Treatment_Table(tmt_file);

  // populate the skip_identifiers array
  memset(skip_identifiers,0,sizeof(char*)*SKIP_IDENTIFIERS_ARRAY_SIZE);
  ifstream skipid_file(libfile("cccc_ign.dat"));
  int i=0;
  if(skipid_file.good())
  { 
    cerr << "Ignoring identifiers listed in " 
	 << libfile("cccc_ign.dat") << ": ";
    while(skipid_file.good())
    {
      char skipid[100];
      skipid_file >> skipid;
      if(skipid[0]=='#')
      {
	skipid_file.ignore(1000,'\n');
      }
      else
      {
	cerr << skipid << " ";
	skip_identifiers[i]=strdup(skipid);
	i++;
      }
    }
    cerr << endl;
  }
  
  prj=new CCCC_Project(project_name);
}


char *Main::libfile(const char* s)
{
  static char filenamebuf[256];
  strcpy(filenamebuf,libdir);
  strcat(filenamebuf,"/");
  strcat(filenamebuf,s);
  return filenamebuf;
}

Language Main::filename_to_language(const char* filename)
{
  Language retval=lCPLUSPLUS;
  char* extension_ptr=(char*) &(filename[strlen(filename)-1]);
  while(extension_ptr>filename && *extension_ptr!='.') { extension_ptr--; }

  // lowercase c and h are interpreted as ANSI C
  if(strcmp(extension_ptr,".c")==0) { retval=lANSIC; }
  else if(strcmp(extension_ptr,".h")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".cc")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".cpp")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".cxx")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".c++")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".C")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".CC")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".CPP")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".CXX")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".hh")==0) { retval=lCPLUSPLUS; } 
  else if(strcmp(extension_ptr,".hpp")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".hxx")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".h++")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".H")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".HH")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".HPP")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".HXX")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".H++")==0) { retval=lCPLUSPLUS; }
  else if(strcmp(extension_ptr,".j")==0) { retval=lJAVA; }
  else if(strcmp(extension_ptr,".jav")==0) { retval=lJAVA; }
  else if(strcmp(extension_ptr,".java")==0) { retval=lJAVA; }
  else if(strcmp(extension_ptr,".ada")==0) { retval=lADA; }
  else if(strcmp(extension_ptr,".ads")==0) { retval=lADA; }
  else if(strcmp(extension_ptr,".adb")==0) { retval=lADA; }
  else if(strcmp(extension_ptr,".a")==0) { retval=lADA; }
  else if(strcmp(extension_ptr,".a95")==0) { retval=lADA; }
  else
  {
    cerr << "failed to find recognized extension, assuming C++" << endl;
  }
  return retval;
}


/* 
** Main::HandleOptions uses the standard ANSI C getopt function to parse
** the set of options listed on the command line.
** Please read the source code below for a complete set of options as
** the usage messages and other documentation are not always up to 
** date with regard to the available options
*/
void Main::HandleOptions() {
  int argc=arg_count;
  char **argv=arg_values;

  int more_options=1;

  while(more_options) {
    int opt=getopt(argc, argv,"qd:r:l:n:f:o:x:h?");
    switch (opt) {

    case 'x':
      if(strcmp(optarg,"c")==0)
      {
	global_language=lANSIC;
      }
      else if(strcmp(optarg,"c++")==0)
      {
	global_language=lCPLUSPLUS;
      }
      else if(strcmp(optarg,"ada")==0)
      {
	global_language=lADA;
      }
      else if(strcmp(optarg,"auto")==0)
      {
	global_language=lAUTO;
      }
      else
      {
	cerr << "Unrecognized language specifier " << optarg 
	     << " ignored" << endl;
      }
      break;

    case 'q' : 
      quiet_flag=1;
      break;

    case 'n' :
      strcpy(project_name,optarg);
      break;

    case 'o' :
      strcpy(outfile,optarg);
      break;

    case 'd' :
      HandleDebugOption(optarg);
      break;
      
    case 'r' :
      HandleReportOption(optarg);
      break;

    case 'l' :
      strcpy(libdir,optarg);
      break;
      
    case 'h' :
    case '?' :
      PrintUsage(cout);
      exit(0);
      break;

    case -1 :
      more_options=0;
      break;

    case 'f' :
    {
      char filename_buffer[1024];
      ifstream file_list(optarg);
      while( (file_list.good()) && (number_of_files<MAX_FILES) )
      {
	file_list >> filename_buffer;
	if(file_list.good()) 
	{
	  filename[number_of_files]=strdup(filename_buffer);
	  number_of_files++;
	}
      }
    }
    break;

    default:
      cerr << "Unexpected option -" << opt << endl << endl;
      PrintUsage(cerr);
      exit(1);
    }
  }
  number_of_options=optind;
}
      
/*
** The files to be analysed may be supplied in either of two ways:
**   1) listed on the command line
**   2) listed in a file supplied as the argument to a -f option
*/
void Main::SetupInputFiles() 
{
  int argc=arg_count;
  char **argv=arg_values;

  char filename_buffer[500];

  /* 
  ** add files listed on the command line to the list of files to be
  ** processed  
  */
  int i;
  for(i=number_of_options; (i<argc) && (number_of_files<MAX_FILES); i++) 
  {
    if(strcmp(argv[i],"-") ==0)
    {
      /*
      ** add files listed on standard input to the list of files 
      ** to be processed
      */
      while( (!cin.eof()) && (number_of_files<MAX_FILES) ) 
      {
	cin >> filename_buffer;
	filename[number_of_files]=strdup(filename_buffer);
	number_of_files++;
      }
    }
    else
    {
      filename[number_of_files]=argv[i];
      number_of_files++;
    }
  }
}

/*
** method to parse all of the supplied list of files
*/
int Main::ParseFiles() {
  FILE *f;

  for (int i=0; i<number_of_files; i++) {
    f=fopen(filename[i],"r");
    if( f == NULL ) {
      cerr << "Couldn't open " << filename[i] << endl;
    } else {
      // initialise the parser and lexer 
      root=NULL;
      ANTLRToken::ZeroCounts();

      // setup global variables 
      if(global_language==lAUTO)
      {
	file_language=filename_to_language(filename[i]);
      }
      else
      {
	file_language=global_language;
      }
      
      // show progress 
      cerr << "Processing " << filename[i];
      switch(file_language)
      {
      case lCPLUSPLUS:
      case lANSIC:
	cerr << " as ANSI C/C++" << endl;
	{
	  // boilerplate code for initialising a parse  
	  DLGFileInput in(f);
	  CLexer theLexer(&in);
	  ANTLRTokenBuffer thePipe(&theLexer);
	  theLexer.setToken(&currentLexerToken);
	  CParser theParser(&thePipe);
	  theParser.init(filename[i]);
	  
	  // parse generating entries in the database for each significant extent 
	  theParser.start(&root);
	}
	break;
      case lJAVA:
	cerr << " as Java" << endl;
	{
	  // boilerplate code for initialising a parse  
	  DLGFileInput in(f);
	  JLexer theLexer(&in);
	  ANTLRTokenBuffer thePipe(&theLexer);
	  theLexer.setToken(&currentLexerToken);
	  JParser theParser(&thePipe);
	  theParser.init(filename[i]);

	  // parse generating entries in the database for each significant extent 
	  theParser.compilationUnit(&root);
	}
	break;

      case lADA:
	cerr << " as Ada 95" << endl;
	{
	  // boilerplate code for initialising a parse  
	  DLGFileInput in(f);
	  ALexer theLexer(&in);
	  ANTLRTokenBuffer thePipe(&theLexer);
	  theLexer.setToken(&currentLexerToken);
	  AdaPrser theParser(&thePipe);
	  theParser.init(filename[i]);

	  // parse generating entries in the database for each significant extent 
	  theParser.goal_symbol(&root);
	}
	break;

      default:
	cerr << " [unexpected language]" << endl;
      }

      delete root;

      // close the file
      fclose(f);
    }
  }

  return 0;
}

void Main::GenerateHtml()
{

#if 0
  // the function below is development in progress, and is commented out for 
  // the present.
  // the function reassigns anonymous member functions to a module
  // based on the name of the file where they are defined
  // this might make reports on C programs make more sense, although I am not
  // sure whether it helps for C++
  prj->assign_anonymous_members();
#endif

  prj->sort();
  CCCC_Html_Stream(*prj,outfile,report_mask,libdir);

  // make sure the user knows where the real output went
  cerr << endl << "Primary HTML output is in " << outfile 
       << endl << endl;

}

void Main::HandleDebugOption(char *arg) {
  /*
  ** arg may either be a number, or a string of letters denoting
  ** facilities to be debugged.  
  ** the string of letters is the public way - allowing input of a 
  ** number is just there to support quickly adding a category of
  ** debug messages without having to change this file immediately
  */
  DebugMask=atoi(arg);
  for (int i=0; arg[i]!='\0'; i++) {
    switch (arg[i]) {
    case 'p' :
      DebugMask |= PARSER;
      break;

    case 'l' :
      DebugMask |= LEXER;
      break;
 
    case 'c' :
      DebugMask |= COUNTER;
      break;

    case 'm' :
      DebugMask |= MEMORY;
      break;

      // this is deliberately undocumented
    case 'f':
      dont_free=1;
      break;

    case 'e' :
      DebugMask |= EXTENT;
#if 0 
      cmod.open("cccc_mod.ext",ios::trunc);
      cmem.open("cccc_mem.ext",ios::trunc);
      cuse.open("cccc_use.ext",ios::trunc);
      cerr << "Extent files opened" << endl;
#endif
      break;

    case 'd':
      DebugMask |= DATABASE;
      break;

    case 'x' :
    case 'X' :
      DebugMask = 0xFF;
      break;
    }
  }
}

void Main::HandleReportOption(char *arg) {
  /*
  ** arg may either be a number, or a string of letters denoting
  ** reports to be generated  
  */
  report_mask=atoi(arg);
  for (int i=0; arg[i]!='\0'; i++) {
    switch (arg[i]) {

    case 'c':
      report_mask |= rtCONTENTS;
      break;

    case 's' :
      report_mask |= rtSUMMARY;
      break;

    case 'p' :
      report_mask |= rtPROC1;
      break;
 
    case 'P':
      report_mask |= rtPROC2;
      break;

    case 'r' :
      report_mask |= rtSTRUCT1;
      break;

    case 'R' :
      report_mask |= rtSTRUCT2;
      break;

    case 'j' :
      report_mask |= rtREJECTED;
      break;

    case 'h' :
      report_mask |= rtCCCC;
      break;

    default:
      cerr << "Unexpected report requested:" << arg[i] << endl;
      PrintUsage(cerr);
      exit(-1);
    }
  }
}

/*
** giving credit where it is due
*/
void Main::PrintCredits(ostream& os) {
  os << endl;
  os << "CCCC - a code counter for C and C++" << endl;
  os << "===================================" << endl;
  os << endl;
  os << "A program to analyse C and C++ source code and report on" << endl;
  os << "some simple software metrics" << endl;
  os << "Version " << VERSION << endl;
  os << "Copyright Tim Littlefair, 1995, 1996, 1997" << endl; 
  os << "with contributions from Bill McLean" << endl << endl;

  os << "The development of this program was heavily dependent on" << endl;
  os << "the Purdue Compiler Construction Tool Set (PCCTS) " << endl;
  os << "by Terence Parr, Will Cohen, Hank Dietz, Russel Quoung" << endl;
  os << "and others." << endl << endl;

  os << "This software is provided with NO WARRANTY" << endl;
  os << endl;
  os << NEW_PAGE;
}

/* 
** the usage message is printed on cerr if unexpected options are found,
** and on cout if option -h is found.  Option -h also causes the
** extension to the usage message to be printed on cout
*/
void Main::PrintUsage(ostream& os) {
  os << "Usage:" << endl;
  os << endl;
  os << "   cccc [options] file1.c ...  " << endl;
  os << "     * process files listed on command line" << endl;
  os << endl;
  os << "Options:" << endl;

  os << "   -h    * generate this help message" << endl;

  os << "   -f filename" << endl;
  os << "         * process files listed in the specified file" << endl;
  os << "           if the filename is '-', reads from standard input" << endl;

  os << "   -l library_directory" << endl;
  os << "         * use the specified directory as the source of the" << endl;
  os << "           cccc support files including the treatment" << endl;
  os << "           configuration file cccc_tmt.dat, and the HTML" << endl;
  os << "           source for the descriptive material at the end " << endl;
  os << "           of the report" << endl;
  
  os << "   -n name_of_project" << endl;
  os << "         * include the specified name in the HTML report" << endl;

  os << "   -o filename" << endl;
  os << "         * generate HTML report to specified file" << endl;
 
  os << "   -rSTRING, -r STRING" << endl;
  os << "         * selectively generate HTML report as indicated" << endl;
  os << "           by letters in STRING " << endl;
  os << "           [c]=table of contents      [s]=project summary" << endl;
  os << "           [p]=procedural summary     [P]=procedural detail" << endl;
  os << "           [r]=structural summary     [R]=structral detail" << endl;
  os << "           [m]=explanation of metrics [h] about cccc" << endl;

  os << "   -dSTRING, -d STRING" << endl;
  os << "        * generate debug output for features indicated" << endl;
  os << "          by letters in STRING " << endl;
  os << "          letters are : " << endl;
  os << "          [l]=lexer                   [p]=parser " << endl;
  os << "          [c]=counter                 [m]=memory allocation" << endl;
  os << "          [e]=extent dump (*.ext)     [d]=database dump" << endl;

  os << NEW_PAGE;
}

int main(int argc, char **argv) {

  // install signal handler
#if 0
  signal(SIGTERM,SigExit);
  signal(SIGINT,SigExit);
  signal(SIGPIPE,SigExit);
  signal(SIGSEGV,SigExit);
#endif
  // define memory allocation failure handler
  set_new_handler(NewHandler);

  // process command line
  app=new Main(argc, argv);

  // process files
  app->ParseFiles();

  cerr << endl << "Generating HTML reports" << endl << endl;

  // generate html output
  app->GenerateHtml();
 
  if(DebugMask&DATABASE)
  {
    // generate diagnostic report on database content
    prj->generate_report(cout); 
  }

  return 0;
}
