#include <strstream.h>
#include <fstream.h>
#include "cccc_db.h"
#include "cccc.h"

#define LINE_BUFFER_SIZE 1000

extern CCCC_Project *prj;

// the file scope variable last_supplier is used to supress repeated
// output of the supplier name in the use relationship section where
// the current record has the same supplier as the previous one
// the indentation makes this reasonably clear
static CCCC_String last_supplier="";


// when we add a record to, for example, the extent table for a member of
// a module, we need to merge the information in the new extent
// with what is already known
// there are two kinds of merge:
// 1. ordinary fields like module_type should either be consistent or 
// blank for all extents relating to the same module, so where the old 
// field is blank, we overwrite with the new field
// 2. the flags field in CCCC_Member contains a variety of single character
// flags giving the visibility, constness, etc. of the member, with '?' being
// used to reflect a state of lack of knowledge : in these cases, 
void Resolve_Fields(CCCC_Field& field1, CCCC_Field& field2)
{
  if(strlen(field1)==0)
  {
    field1=field2;
  }
}

void CCCC_Record::merge_flags(CCCC_Field& new_flags)
{
  char *new_flag_array=new_flags;
  char *flag_array=flags;
  int len=strlen(flag_array);
  if(strlen(new_flag_array)==len)
  {
    char buf[100];
    int i;
    for(i=0; i<len;i++)
    {
      if(flag_array[i]=='?')
      {
	buf[i]=new_flag_array[i];
      }
      else
      {
	buf[i]=flag_array[i];
      }
    }
    buf[len]='\0';
    flags=buf;
  } 
  else 
  {
    // if the parent record has just been created it may have
    // an empty flags member, so we use Resolve_Fields to copy
    // the flags from the first extent
    Resolve_Fields(flags,new_flags);
  }
}

void CCCC_Record::add_extent(istream& is)
{
  CCCC_Extent *new_extent=new CCCC_Extent(is);
  CCCC_Extent *inserted_extent=extent_table.find_or_insert(new_extent);
  merge_flags(new_extent->flags);
  if(new_extent != inserted_extent)
  {
    delete new_extent;
  }
}


char* CCCC_Record::name(int level) const { return ""; }
char* CCCC_Record::ranking_string() const { return name(nlRANK);}
char* CCCC_Record::key() const { return name(nlRANK); }
void  CCCC_Record::generate_report( ostream& os) 
{
  os << name(nlRANK) << endl;
}
	
int rank_by_string(const void *p1, const void *p2) {
  CCCC_Record *pr1=*((CCCC_Record**)p1);
  CCCC_Record *pr2=*((CCCC_Record**)p2);
   
  // we collect and use local copies of the two strings
  // this allows the ranking_string functions to use static buffers
  CCCC_String s1=pr1->ranking_string();
  CCCC_String s2=pr2->ranking_string();

  return strcmp(s1,s2);
}

char* CCCC_Module::name(int name_level) const
{ 
  static CCCC_String retval;
  switch(name_level)
  {
  case nlMODULE_TYPE: 
    retval=module_type;
    break;

  case nlMODULE_NAME:
    retval=module_name;
    break;

  case nlMODULE_TYPE_AND_NAME: 
    retval=module_type;
    if(strlen(retval)>0)
    {
      retval=retval+" ";
    }
    retval=retval+module_name;
    break;

  default: 
    retval=module_name;
    if(strlen(retval)==0)
    {
      retval="<anonymous>";
    }
  }
  return retval;
}

int CCCC_Record::get_count(const char* count_tag) {
  // CCCC_Record is really an abstract base class, so we don't expect
  // anyone to call this method
  // if they do, warn them off...
  cerr << "[CCCC_Record::get_count] virtual function, please overload" 
       << endl;
  return 0;
}

void CCCC_Project::assign_anonymous_members()
{
  cerr << "Assigning non-member functions to source modules" << endl; 

  int i;
  for(i=0; i<member_table.records(); i++)
  {
    CCCC_Member *member_ptr=member_table.record_ptr(i);
    CCCC_String member_name=member_ptr->name(nlSEARCH);
    CCCC_String parent_name=member_ptr->parent->name(nlMODULE_NAME);

    if(strlen(parent_name)==0)
    {
      // search the extent list for the best parent
      // our rule for assigning parentage to source files is that it will be
      // the first seen definition of the function, or if no definitons
      // are seen the first seen declaration
      // providing that this function is run before the database is sorted,
      // the records should be in order of creation
      // we can assume that at least one extent has been seen
      CCCC_Extent *best_extent_ptr=member_ptr->extent_table.record_ptr(0);
      int j;
      for(j=1; j<member_ptr->extent_table.records(); j++)
      {
	// if the candidate we already have is a definition, we should
	// never replace it
	if(best_extent_ptr->ut == utDEFINITION)
	{
	  break;
	}
	else if(member_ptr->extent_table.record_ptr(j)->ut == utDEFINITION)
	{
	  best_extent_ptr=member_ptr->extent_table.record_ptr(j);
	}
      }
      CCCC_String source_module_name=best_extent_ptr->name(nlFILENAME);

      cerr << "Assigning " << member_name
	   << " to source module " << source_module_name << endl;
      MAKE_STRSTREAM(str);
      str << source_module_name << "@Source file@" << ends; 
      CCCC_Module *new_mod_ptr= new CCCC_Module(str);
      CCCC_Module *lookup_mod_ptr=module_table.find_or_insert(new_mod_ptr);
      if(lookup_mod_ptr != new_mod_ptr)
      {
	delete new_mod_ptr;
      }
      member_ptr->parent=lookup_mod_ptr;

      RELEASE_STRSTREAM(str);
    }
  }
}

void CCCC_Project::sort() {
  int i;

  cerr << "Sorting modules" << endl;
  module_table.sort();
  for(i=0; i<module_table.records(); i++) 
  {
    module_table.record_ptr(i)->sort();
  }

  cerr << "Sorting members" << endl;
  member_table.sort();
  for(i=0; i<member_table.records(); i++) 
  {
    CCCC_Member *member_ptr=member_table.record_ptr(i);
    member_ptr->sort();
    member_ptr->parent->member_table.append(member_ptr);
  }

  cerr << "Sorting relationships" << endl;
  userel_table.sort();
  for(i=0; i<userel_table.records(); i++) 
  {
    userel_table.record_ptr(i)->sort();
  }
}

CCCC_Project::CCCC_Project(const CCCC_String& name)
  : project_name(name)
{
  // we prime the database with knowledge of the builtin base types
  // we also add a record for the anonymous class which we will treat
  // as the parent of all non-member functions
  char *builtin_type_info[]=
  {
    "void@builtin@<nofile>@0@builtin definition@d?????@@0d",
    "int@builtin@<nofile>@0@builtin definition@d?????@@0d",
    "char@builtin@<nofile>@0@builtin definition@d?????@@0d",
    "long@builtin@<nofile>@0@builtin definition@d?????@@0d",
    "float@builtin@<nofile>@0@builtin definition@d?????@@0d",
    "double@builtin@<nofile>@0@builtin definition@d?????@@0d",
    NULL
  };
  for(char **ptr=builtin_type_info; *ptr!=NULL; ptr++)
  {
    MAKE_STRSTREAM(type_info);
    type_info << *ptr << ends;
    add_module(CONVERT_STRSTREAM(type_info));
    RELEASE_STRSTREAM(type_info);
  }
}

void CCCC_Project::add_module(istream& module_data_line) {
  CCCC_Module *module_ptr=new CCCC_Module(module_data_line);
  CCCC_Extent *extent_ptr=new CCCC_Extent(module_data_line);
  CCCC_Module *lookup_module_ptr=module_table.find_or_insert(module_ptr);
  if(lookup_module_ptr != NULL) 
  {
    lookup_module_ptr->extent_table.find_or_insert(extent_ptr);
	
    if(lookup_module_ptr!=module_ptr) {
      // do some work to transfer knowledge from the new module object
      // then delete it
      Resolve_Fields(lookup_module_ptr->module_type,module_ptr->module_type);
      delete module_ptr;
    }
  }
}

void CCCC_Project::add_member(istream& member_data_line) 
{
  CCCC_Module *module_ptr=new CCCC_Module(member_data_line);
  CCCC_Module *lookup_module_ptr=module_table.find_or_insert(module_ptr);

  CCCC_Member *member_ptr=
    new CCCC_Member(member_data_line,lookup_module_ptr);
  CCCC_Member *lookup_member_ptr=member_table.find_or_insert(member_ptr);
  if(lookup_member_ptr != NULL) 
  {
    lookup_member_ptr->add_extent(member_data_line);
  }
}    

void CCCC_Project::add_userel(istream& userel_data_line) {
  CCCC_UseRelationship *new_userel_ptr =
    new CCCC_UseRelationship(userel_data_line);
  CCCC_UseRelationship *lookup_userel_ptr = 
    userel_table.find_or_insert(new_userel_ptr);  
  
  if(lookup_userel_ptr != NULL)
  {
    if(new_userel_ptr != lookup_userel_ptr)
    {
      delete new_userel_ptr;
    }
    lookup_userel_ptr->add_extent(userel_data_line);
  }

}

void CCCC_Project::add_rejected_extent(istream& rejected_data_line)
{
  CCCC_Extent *new_extent=new CCCC_Extent(rejected_data_line);
  rejected_extent_table.find_or_insert(new_extent);
}

int CCCC_Project::get_count(const char* count_tag) {
  // we could give this method knowledge of which tags are likely
  // to be found in which table, ... or we could be lazy like this
  int retval=0;
  retval+=module_table.get_count(count_tag);
  retval+=userel_table.get_count(count_tag);
  return retval;
}

void CCCC_Project::generate_report(ostream& os) {
  os << "CCCC Report" << endl << flush;
  sort();
  int i;
  for (i=0; i<module_table.records(); i++) {
    CCCC_Module *mptr=module_table.record_ptr(i);
    if( strcmp(mptr->name(nlMODULE_TYPE),"builtin") != 0)
    {
      mptr->generate_report(os);
    }
  }
  for(i=0; i<userel_table.records(); i++) {
    CCCC_UseRelationship *uptr=userel_table.record_ptr(i);
    uptr->generate_report(os);
  }
} 

CCCC_Module::CCCC_Module(istream& module_data_line) {
  module_data_line >> module_name >> module_type;
}

void CCCC_Module::generate_report(ostream& os) {
  if(strlen(module_name)>0)
  {
    os << module_type << " " << module_name << endl;
  }
  else
  {
    os << "<non-member functions and variables>" << endl;
  }
  int i;
  for(i=0; i<extent_table.records(); i++) {
    extent_table.record_ptr(i)->generate_report(os);
  }
  os << endl;
    
  os << "Methods:" << endl;
  for(i=0; i<member_table.records(); i++) {
    member_table.record_ptr(i)->generate_report(os);
  }
  os << endl;

  os << "\f" << endl;
}

int CCCC_Module::get_count(const char* count_tag) {
  int retval=0;
  CCCC_UseRelationship** dummy_peers;
  
  if( (strcmp(count_tag,"NOM")==0) )
  {
    retval=!is_trivial();
  } 
  else if( 
    (strncmp(count_tag,"FI",2)==0) ||
    (strncmp(count_tag,"FO",2)==0)
    )
  { 
    int role;
    int vis=rmeHIDDEN_OR_VISIBLE;
    int con=rmeABSTRACT_OR_CONCRETE;
    
    switch(count_tag[1])
    {
    case 'O':
      role=rmeSUPPLIER;
      break;
    case 'I':
      role=rmeCLIENT;
      break;
    }

    switch(count_tag[2])
    {
    case 'v':
      vis=rmeVISIBLE;
      break;
    case 'c':
      con=rmeCONCRETE;
      break;
    case 0:
      // no qualifier
      break;
    default:
      cerr << "Unexpected FI/FO qualifier" << endl;
      return 0;
    }
    retval=get_relationships(prj, role|vis|con,dummy_peers);
  }
  else if( strncmp(count_tag,"HKS",3)==0) 
  {
    char fitag[5], fotag[5];
    const char *qualifier=&(count_tag[3]);
    strcpy(fitag,"FI");
    strcat(fitag,qualifier);
    strcpy(fotag,"FO");
    strcat(fotag,qualifier);

    int fanout=get_count(fotag);
    int fanin=get_count(fitag);
    retval=fanout*fanout*fanin*fanin;
  }
  else
  {
    retval=member_table.get_count(count_tag);
  }
  return retval;
}

CCCC_Member::CCCC_Member(istream& member_data_line, CCCC_Module *parent_ptr) 
{
  parent=parent_ptr;
  member_data_line >> member_name >> member_type >>  param_list; 
}

char *CCCC_Member::name(int name_level) const
{ 
  static CCCC_String  retval;

  MAKE_STRSTREAM(namestr);
  switch(name_level)
  {
  case nlRANK:
  case nlSEARCH:
    // there is no scoping for C-style functions ...
    if( 
      (strcmp(parent->name(nlMODULE_NAME),"")!=0) && 
      (strcmp(parent->name(nlMODULE_TYPE),"file")!=0) 
      )
    {
      namestr << parent->name(nlMODULE_NAME) << "::";
    }
    namestr << member_name << param_list << ends;
    break;

  case nlMEMBER_NAME:
  case nlSIMPLE:
    namestr << member_name << ends;
    break;
    
  case nlMEMBER_TYPE:
    namestr << member_type << ends;
    break;

  case nlMEMBER_PARAMS:
    namestr << param_list << ends;
    break;
  case nlLOCAL:
    namestr << member_type << " " << member_name << param_list << ends;
    break;
	
  default:
    cerr << "unexpected name level" << endl;
  }

  retval=namestr.str();
  RELEASE_STRSTREAM(namestr);
  return retval; 
}	


void CCCC_Member::generate_report(ostream& os) { 
  os << endl << setw(65) << name(nlLOCAL) << flags << endl;
  int i;
  for(i=0; i<extent_table.records(); i++) {
    extent_table.record_ptr(i)->generate_report(os);
  }
}

int CCCC_Member::get_count(const char* count_tag) {
  return extent_table.get_count(count_tag);
}

void CCCC_Extent::generate_report(ostream& os)
{ 
  os.setf(ios::right,ios::right|ios::left);
  os << setw(20) << filename;
  os << setw(5) << linenumber << "  ";
  os.setf(ios::left,ios::left|ios::right); 
  os << setw(20) << description 
     << flags << " " << count_buffer << endl;
}

CCCC_Extent::CCCC_Extent() { 
  // we can trust the CCCC_String constructor to give us empty strings,
  // but we need to initialise these
  v=vDONTKNOW;
  ut=utDONTKNOW;
}

CCCC_Extent::CCCC_Extent(istream& is) { 
  is >> filename;
  if(!is.eof())
  {
    is >> linenumber >> description >> flags >> count_buffer 
       >> v >> ut ; 
  }
  else
  {
    // we can trust the CCCC_String constructor to give us empty strings,
    // but we need to initialise these
    v=vDONTKNOW;
    ut=utDONTKNOW;
  }
}

char *CCCC_Extent::name(int name_level) const
{ 
  static CCCC_String retval;

  MAKE_STRSTREAM(namestr);
  switch(name_level)
  {

  case nlFILENAME:
    namestr << filename << ends;
    break;

  case nlLINENUMBER:
    namestr << linenumber << ends;
    break;

  case nlDESCRIPTION:
    namestr << description << ends;
    break;

  case nlRANK:
  case nlLOCAL:
    namestr << flags << " " << filename << " " << linenumber 
	    << " " << get_description() << ends;
    break;

  case nlSIMPLE:
    namestr << filename << ":" << linenumber << ends;
    break;


  default:
    cerr << "unexpected name level" << endl;
  }

  retval=namestr.str(); 
  RELEASE_STRSTREAM(namestr);
  return retval;
}	

int CCCC_Extent::get_count(const char* count_tag) {
  int retval=0;
  char local_count_buffer[100], *count_tag_ptr, *count_value_ptr;
  strcpy(local_count_buffer,count_buffer);
  count_tag_ptr=strtok(local_count_buffer,":");
  while(count_tag_ptr!=NULL)
  {
    count_value_ptr=strtok(NULL," ");
    if(strcmp(count_tag_ptr, count_tag) ==0)
    {
      retval+=atoi(count_value_ptr);
    }
    count_tag_ptr=strtok(NULL,":");
  }

  return retval;
}

CCCC_UseRelationship::CCCC_UseRelationship(istream& is) 
{
  is >> client >> member >> supplier;
  visible=abDONTKNOW;
  concrete=abDONTKNOW;
}

char *CCCC_UseRelationship::name(int name_level) const
{ 
  static CCCC_String retval;
  MAKE_STRSTREAM(namestr);
  switch(name_level)
  {
  case nlRANK:
  case nlSIMPLE:
    namestr << client << " uses " << supplier << ends;
    break;
      
  case nlSUPPLIER:
    namestr << supplier << ends;
    break;

  case nlCLIENT:
    namestr << client << ends;
    break;

  default:
    cerr << "unexpected name level" << endl;
  }

  retval=namestr.str();

  RELEASE_STRSTREAM(namestr);
  return retval;
}	

void CCCC_UseRelationship::add_extent(istream& is)
{
  // processing is similar to the CCCC_Record method, except that we update
  // the visibility and concreteness data members
  // but do not do merge_flags
  CCCC_Extent *new_extent=new CCCC_Extent(is);
  CCCC_Extent *inserted_extent=extent_table.find_or_insert(new_extent);

  switch(new_extent->get_visibility())
  {
  case vPUBLIC:
  case vPROTECTED:
    visible=abTRUE;
    break;
  case vPRIVATE:
  case vIMPLEMENTATION:
    visible=abFALSE;
    break;

  default:
    // nothing required
    ;;
  }

  switch(new_extent->get_usetype())
  {
  case utINHERITS:
  case utHASBYVAL:
  case utPARBYVAL:
  case utVARBYVAL:
    concrete=abTRUE;
    break;
  default:
    // no change required
    ;;
  }

  if(new_extent != inserted_extent)
  {
    delete new_extent;
  }
}

void CCCC_UseRelationship::generate_report(ostream& os)
{
  os << setw(20) << client << " uses " << setw(20) << supplier 
     << " [" << member << " " << flags << "]" << endl;
  for(int i=0; i<extent_table.records(); i++)
  {
    os << extent_table.record_ptr(i)->name(nlLOCAL) << endl;
  }
}

int CCCC_UseRelationship::get_count(const char* count_tag) {
  return extent_table.get_count(count_tag);
}

CCCC_Module* CCCC_UseRelationship::supplier_module_ptr(CCCC_Project *prj)
{
  CCCC_Module* retval=0;
  int i;
  for(i=0; i<prj->module_table.records(); i++)
  {
    CCCC_String mod_name =
      prj->module_table.record_ptr(i)->name(nlMODULE_NAME);
    if(strcmp(mod_name,supplier)==0)
    {
      retval=prj->module_table.record_ptr(i);
      break;
    }
  }
  return retval;
}

CCCC_Module* CCCC_UseRelationship::client_module_ptr(CCCC_Project *prj)
{
  CCCC_Module* retval=0;
  int i;
  for(i=0; i<prj->module_table.records(); i++)
  {
    CCCC_String mod_name =
      prj->module_table.record_ptr(i)->name(nlMODULE_NAME);
    if(strcmp(mod_name,client)==0)
    {
      retval=prj->module_table.record_ptr(i);
      break;
    }
  }
  return retval;
}

int CCCC_Module::is_trivial()
{
  int retval=1;

  // these are the expected module types which are always non-trivial
  retval&=strcmp(module_type,"class");
  retval&=strcmp(module_type,"C++ class");
  retval&=strcmp(module_type,"namespace");
  retval&=strcmp(module_type,"Java class");
  retval&=strcmp(module_type,"Java interface");
  retval&=strcmp(module_type,"Ada package");

  // the other possible types for non-trivial modules are files and
  // unknown types (i.e. we have seen datatype X being passed as a parameter
  // but don't know whether it is a struct, typedef, class, union or #define)
  // for these, our test of triviality is whether we have identified any
  // members
  retval&=(member_table.records()==0);

  if(retval != 0)
  {
    retval=1;
  }
  return retval;
}

int CCCC_Module::get_relationships(
  CCCC_Project *prj, int mask, CCCC_UseRelationship**& rel_array)
{
  int number_of_relationships=0;
  const int max_relationships=100;

  static CCCC_UseRelationship *rel_ptrs[max_relationships];
  memset(rel_ptrs,sizeof(rel_ptrs),0);

  int i;
  for(i=0; i<prj->userel_table.records(); i++)
  {
    CCCC_UseRelationship *userel_ptr = prj->userel_table.record_ptr(i);
    CCCC_String compare_name;
    CCCC_String peer_name;
    switch(mask&(rmeCLIENT|rmeSUPPLIER))
    {
    case rmeCLIENT:
      compare_name=userel_ptr->name(nlSUPPLIER);
      peer_name=userel_ptr->name(nlCLIENT);
      break;
    case rmeSUPPLIER:
      compare_name=userel_ptr->name(nlCLIENT);
      peer_name=userel_ptr->name(nlSUPPLIER);
      break;
    default:
      cerr << "[get_relationships] did not specify CLIENT or SUPPLIER"
	   << endl;
      return 0;
    }

    if(
      (strcmp(compare_name,this->name(nlSIMPLE))==0) &&
      (strcmp(compare_name,peer_name)!=0)
      )
    {
      CCCC_Module *supplier_ptr=userel_ptr->supplier_module_ptr(prj);
      CCCC_Module *client_ptr=userel_ptr->client_module_ptr(prj);
	  
      int nontrivial_flag=
	(supplier_ptr != NULL) &&
	(!supplier_ptr->is_trivial()) &&
	(client_ptr != NULL) &&
	(!client_ptr->is_trivial());

      // we don't give the benefit of the doubt on visibility:
      // if we have seen a declaration scoped as private, we
      // exclude this item from the visible count, otherwise we
      // include it
      int visibility_flag;
      if(userel_ptr->is_visible()!=abFALSE)
      {
	visibility_flag=mask&rmeHIDDEN;
      }
      else
      {
	visibility_flag=mask&rmeVISIBLE;
      }

      int concrete_abstract_flag;
      if(userel_ptr->is_concrete()==abTRUE)
      {
	concrete_abstract_flag=mask&rmeCONCRETE;
      }
      else
      {
	concrete_abstract_flag=mask&rmeABSTRACT;
      }

      if(nontrivial_flag && visibility_flag && concrete_abstract_flag)
      {
	// a suitable case for treatment...
	rel_ptrs[number_of_relationships]=userel_ptr;
	number_of_relationships++;
      }
    }
  }
  rel_array=rel_ptrs;
  return number_of_relationships;  
}









