#if 0
File name: GL1_0_templateProgram.cc
Author: Given-name, FAMILY-NAME, e-mail  //to be completed
Creation date: February 2013     Last modification date: ?/?/2014 //to be completed
Goal: student training; for details, see http://www.phmartin.info/cours/GL/GL1_0.html#(29)

   To re-use this .cc.html file:
   1. Save it into a .cc.  Within this .cc file, via a search&replace, outside comments
      a) remove the <b> and </b> marks, and b) replace all the "&lt;" strings by "<".
   2. Use a text editor (not a Word/HTML/... editor).
      Below each comment that includes "to be implemented", insert your code
      (however, if you need to add some code elsewhere, do it).
      Put <b> and </b> around every addition you make
      New: do NOT remove my comments (even those that include to be implemented)
      Make sure your program is displayed in a readable way by a Web browser;
      to test if your program compiles, copy-paste the text displayed by the browser into
      a .cc file.
      Update the above attribute-values in order to have a proper header.
      Do not remove the content of this header after these attribute-values.
   3. When you have completed your implementation, copy you .cc file into a .cc.html file
      and add the first and last line that this .cc.html file has.
      Indeed, you must send me only one HTML file which, if copy-pasted to a text file,
      is a valid working source code for the asked exercise.

   Keep the code structure into sections (the "//------------ " lines) and put
   your data structures and functions in the relevant sections.

   Reminder: to understand a program in C/C++ structured like this one,
   first read the data structures at the beginning of the program,
   then start from the end with the function 'main' and work your way upward.

Instructions for compiling and then linking ('-Wall' is to get all warnings):
  g++ -Wall  -o GL1_0_templateProgram   GL1_0_templateProgram.cc

Way to test it:
  GL1_0_templateProgram ...  //to be completed

extern "C" {
#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <map>  //to use 'maps' (associators) from the 
   // associative containers of the C++ standard library
#include <utility>  //idem
#include <string>   //dynamic strings in C++

typedef FILE  Medium;  //media are assimilated to files

//--------- Forward declarations

class ProcessParameters;
typedef ProcessParameters PP;  //PP is a short alias

//--------- Data structures and associated methods

class Str  //this class is only for code structuring purposes: it has no instance
{ public:      //"static" in next line: "usable even if this class has no instances"
  static bool equal (const char *s1, const char *s2)
  { return !strcmp(s1,s2); }

class Error
{ public:
  //static std::string lastError; //"static" -> "usable even if this class has no instances"
  static const int NO_MESSAGE= 0;  //a good way to define a constant
  static const int NO_ERROR= 0; 
  static const int ERROR= -1; 
  Medium *medium/*= stderr*/;  //ISO C++ forbids default values for 
                               // non-const variables, so I use comments
  Error ()  { medium= stdout;  //for test purposes
  Error (Medium *aMedium)  { medium= aMedium; }
  Medium *get_medium ()  { return medium; }
  int print (ProcessParameters *pp, const char *format,
             ...);  //in C++, "..." is for optional arguments
    //this last method uses ProcessParameters and hence is defined after it (see below)
Error ErrMedium;

class Output  //to be implemented (similar to Error but 'print' generates a
{             //     "good" error message if &MenuMedium is the medium to output into
  Medium *medium/*= stdout*/;
  Output ()  { medium= stdout; }
  int print (ProcessParameters *pp, const char *format, ...); 
      //to be defined after ProcessParameters
Output OutMedium, MenuMedium;

class Input  //to be implemented
{ //like for Output but with "get" instead of "print"

//typedef std::pair<const char*,const void*> AttrValue; //AssociatorNode: 1 pair attribute-value
typedef std::map<const char*, const void*> Associator;  //an array of AssociatorNode

class ProcessParameters : public Associator
{ public:
  ProcessParameters () {}
//ProcessParameters (const ProcessParameters &p) { /* copy 'p' into 'this' */ } 

  ProcessParameters (const Medium *parameterMedium) {} //parses a parameter medium; if
     // stdin is given, questions are asked to the user via this medium

  ProcessParameters (int argc, const char *argv[]) {} //parses command line parameters 
  void inferOtherParameters () {}  //common end to all the ProcessParameters()
                                   //to be filled/implemented when/if needed

  void set (const char *aName, const void *value)
  { this->insert(std::make_pair(aName, value)); }

  const void* get (const char *aName, int generateAndDisplayErrorMessageIfValueNotFound= 1)
  { /* original version:
       const void *value= Associator::get(aName);
       if (!value && generateAndDisplayErrorMessageIfValueNotFound)
       ErrMedium.print(NULL,"No value provided for '%s' in the parameters. "
                            "This program requires a value.",aName);
       return value;
    Associator::iterator attrValuePair= this->find(aName); 
    if ((attrValuePair == this->end()) && generateAndDisplayErrorMessageIfValueNotFound)
    { ErrMedium.print(NULL,"No value provided for '%s' in the parameters. "
                           "This program requires a value.",aName);   return NULL;
    return attrValuePair->second;  //the 'value' par of the attrValuePair
ProcessParameters G_mainFct_p,  G_mainFct_explPosConstr_p,  G_mainFct_explNegConstr_p,
                  G_mainFct_filterNegConstr_p,  G_e1_p,  G_e1_explNegConstr_p,  G_atEnd_p;

int Error::print (ProcessParameters *pp, const char *format,
                  ...)  //in C++, "..." is for optional arguments
{ int r;  Medium *m= (Medium *)(pp ? pp->get("error_medium",Error::NO_MESSAGE) : NULL);
  if (!m) m= medium;  //hence, inherited if not set in local pp
  if ( format[0] && ((format[0] != ' ') || format[1]) )  //i.e., not " "
  { va_list pArgs;  va_start(pArgs,format);  r= vfprintf(m,format,pArgs);  va_end(pArgs); }
  fprintf(m," \n");  return r;

int Output::print (ProcessParameters *pp, const char *format, ...)
{ int r;  Medium *m= (Medium *)(pp ? pp->get("output_medium",Error::NO_MESSAGE) : NULL);
  //to be implemented here: delivering a "good" error message if m == &MenuMedium
  if (!m) m= medium;  //hence, inherited if not set in local pp
  if ( format[0] && ((format[0] != ' ') || format[1]) ) //i.e., not " "
  { va_list pArgs;  va_start(pArgs,format); r= vfprintf(m,format,pArgs); va_end(pArgs); }
  return r;

//--------- Application-dependent classes and functions

class Xfct1
{ public:
  int nbBytes, nbWords, nbLines;  const char *wordX;
  int positionOfTheWordToDisplay, maxNbWordXByLine;
  int maxNbLinesWithWordX, nbLinesWith_maxNbWordXByLine;
  std::string lastLine, lastWord;   int nbWordsInLastLine;
  int nbBytes_average, nbBytes_average_nbElem,
      nbWords_average, nbWords_average_nbElem,
      nbLines_average, nbLines_average_nbElem,

  Xfct1 ()
  { nbBytes= nbWords= nbLines= 0;   wordX= "";
    positionOfTheWordToDisplay= maxNbWordXByLine= 0;
    maxNbLinesWithWordX= nbLinesWith_maxNbWordXByLine= 0;
    nbWordsInLastLine= nbBytes_average= nbBytes_average_nbElem= 0;
    nbWords_average= nbWords_average_nbElem= nbLines_average= 0;
    nbLines_average_nbElem= nbLinesWith_maxNbWordXByLine_average= 0;
    nbLinesWith_maxNbWordXByLine_average_nbElem= 0;

  void computedValuesThatAreNotAverages_reset ()
  { nbBytes= nbWords= nbLines= nbLinesWith_maxNbWordXByLine= nbWordsInLastLine= 0;  }

  int average_incrementalComputing (int lastAverage, int newNum, int newNbElem)
  { return lastAverage + (newNum - lastAverage)/newNbElem; }

  void averages_incrementalComputing ()
  { nbBytes_average= average_incrementalComputing(nbBytes_average,
                                                  nbBytes, ++nbBytes_average_nbElem);
    nbWords_average= average_incrementalComputing(nbWords_average,
                                                  nbWords, ++nbWords_average_nbElem);
    nbLines_average= average_incrementalComputing(nbLines_average,
                                                  nbLines, ++nbLines_average_nbElem);
    nbLinesWith_maxNbWordXByLine_average= average_incrementalComputing
        (nbLinesWith_maxNbWordXByLine_average, nbLinesWith_maxNbWordXByLine,

  int averages_display (ProcessParameters *pp)
  { const char *displayNbBytesAverage= 
       (const char *) pp->get("display_of_nbBytes_average",Error::NO_MESSAGE);
    bool nbBytesAverageInBold= Str::equal(displayNbBytesAverage,"bold");
    if (nbBytesAverageInBold) OutMedium.print(pp,"<b>");
       //better:  OutMedium.print(pp,beginHTMLmarkfor(displayNbBytesAverage));
    OutMedium.print(pp,"The average of the number of bytes in each selected"
                       " information is: %d",nbBytes_average);
    if (nbBytesAverageInBold) OutMedium.print(pp,"</b>");
       //better:  OutMedium.print(pp,endHTMLmarkfor(displayNbBytesAverage));
    //to be implemented: the conditional display of the other averages;
    //  "conditional" means that an average 'a' is displayed if 'pp' has an attribute which
    //  specifies the display of 'a' (as for 'nbBytes_average' above since the attribute
    //  'display_of_nbBytes_average' exists in 'pp') and if the  value of this attribute
    //  is not "invisible"
    return Error::NO_ERROR;

  int info_exploitationAtEnd (ProcessParameters *pp)
  { averages_incrementalComputing();
    OutMedium.print(pp,"The number of bytes in the selected information is: %d",nbBytes);
    /* to be implemented here:
       - the display of variables that must be done in Xfct1.1 and Xfct1.2 (not Xfct1.3)
    return Error::NO_ERROR;

  int info_exploitation (ProcessParameters *pp, char c)
  { nbBytes++;  lastLine+= c;  lastWord+= c;
    if (isspace(c))
    { nbWords++;  nbWordsInLastLine++;  lastWord= ""; 
      if (c == '\n')
      { nbLines++;  lastLine= "";  nbWordsInLastLine= 0; }
    //to be implemented here: the features of Xfct1.2 (use nbWordsInLastLine)
    //warning: the above "lastWord= ""; may actually have to be done a bit later
    return Error::NO_ERROR;

  void hardcoding_of_processParameters (/*initP*/ ProcessParameters *pp)
  { //This function is only for test/debugging purposes since the user should provide
    //  parameters via a file, a menu, the command line, ...
    //These lines show how parameters must be stored/structured to be handled by this program.
#if 0
    pp=  /* this way of assigning 'pp' would be nice but is not implemented; this is why this
            code is within a '#if 0' area;  it is given here for readability reasons */
      & (Associator)
      { {"input_medium",stdin},
        {"output_medium",stdout},  //or: {"output_medium",&MenuMedium}
           { {"@id", "Xfct1"},
             {"@type", "info_selection_and_exploitation"},
             {"info_exploration_content_type", "file_directory"},
             {"info_exploration_content", "/usr/lib/"},
             {"info_exploration_method", "depth_first_exploration"},
             {"info_exploration_depth", -1},
             {"info_exploration_positive_constraint", { {"@type","file_directory"} } },
             {"info_exploration_negative_constraint", { {"@type","symbolic_link" } } },
             {"info_filtering_negative_constraint",   { {"@type","file_directory"} } },
                { {"@id", "selection_and_exploration_of_each_selected_file"},
                  {"@type", "info_selection_and_exploitation"},
                  {"info_exploration_content_type", "within_a_file"},
                  {"info_exploration_starting_point", 0},
                  {"info_exploration_method", "sequential"},
                  {"info_exploration_depth", -1},
                  {"info_filtering_negative_constraint", { {"@type","index_file"} } },
                     { {"@id", "Xfct1_exploitation_of_each_selected_character_or_record"},
                       {"input_medium", "menu"},  {"positionOfTheWordToDisplay",10},
                       {"wordX", "hello"},  {"maxNbWordXByLine",12},
                       {"display_of_nbBytes_average", "bold"}, 
                       {"display_of_nbWords_average", "invisible"},
                       {"info_exploitationAtEnd", "Xfct1::exploitationAtEnd"}
           } /* alternatively, if you want to explore a list, not a file directory, try:
           { {"@id", "Xfct1"},
             {"@type", "info_selection_and_exploitation}",
             {"info_exploration_content_type", "list"},
             {"info_exploration_content", "{a b {c d e {f} g} h i}"}, //or (next line):
           //{"info_exploration_content", {a b {c d e {f} g} h i} }, //-> no parsing needed
             {"info_exploration_method", "depth_first_exploration"},
             {"info_exploration_depth", -1},
             //no negative constraint on the exploration of sublists but, below,
             // a negative constraint on the selection of sublist
             {"info_filtering_negative_constraint", { {"@type","list"} } },
                     { {"@id", "Xfct1_exploitation_of_each_selected_character_or_record"},
                       //... (same sub-bloc as above)
           } */
      };  //end of the assignment of pp
    //this part is the "step by step" version of the part above (without the comment)
    //static ProcessParameters mainFct_p;
    //   problems with 'static' in methods -> 'global G_mainFct_p' used instead
       G_mainFct_p.set("@id", "Xfct1");
       G_mainFct_p.set("@type", "info_selection_and_exploitation");
       G_mainFct_p.set("info_exploration_content_type", "file_directory");
       G_mainFct_p.set("info_exploration_content", "/tmp/");
       G_mainFct_p.set("info_exploration_method", "depth_first_exploration");
       G_mainFct_p.set("info_exploration_depth", "-1");
       //static ProcessParameters G_mainFct_explPosConstr_p;
          G_mainFct_p.set("info_exploration_positive_constraint", &G_mainFct_explPosConstr_p);
       //static ProcessParameters G_mainFct_explNegConstr_p;
          G_mainFct_p.set("info_exploration_negative_constraint", &G_mainFct_explNegConstr_p);
       //static ProcessParameters G_mainFct_filterNegConstr_p;
          G_mainFct_p.set("info_filtering_negative_constraint", &G_mainFct_filterNegConstr_p);
       //static ProcessParameters G_e1_p;
          G_mainFct_p.set("info_exploitation_process_on_selected_info", &G_e1_p);
          G_e1_p.set("@id", "selection_and_exploration_of_each_selected_file");
          G_e1_p.set("@type", "info_selection_and_exploitation");
          G_e1_p.set("info_exploration_content_type", "within_a_file");
          G_e1_p.set("info_exploration_starting_point", "0");
          G_e1_p.set("info_exploration_method", "sequential");
          G_e1_p.set("info_exploration_depth", "-1");
          //static ProcessParameters G_e1_explNegConstr_p;
             G_e1_p.set("info_filtering_negative_constraint", &G_e1_explNegConstr_p);
          //static ProcessParameters G_atEnd_p;
             G_e1_p.set("info_exploitation_process_on_selected_info", &G_atEnd_p);
             G_atEnd_p.set("@id", "Xfct1_exploitation_of_each_selected_character_or_record");
             G_atEnd_p.set("input_medium", "menu");
             G_atEnd_p.set("positionOfTheWordToDisplay", "10");
             G_atEnd_p.set("Xfct1-2_wordX", "hello");
             G_atEnd_p.set("maxNbWordXByLine", "12");
             G_atEnd_p.set("display_of_nbBytes_average", "bold");
             G_atEnd_p.set("display_of_nbWord_average", "invisible");
             G_atEnd_p.set("info_exploitationAtEnd", "Xfct1::exploitationAtEnd");

};//end of the class Xfct1

Xfct1 Global_xFct1;

//--------- Other classes and functions

class ISE  //abbreviation of "Information Selection and Exploitation"
{ public:

  int inList_selectionAndExploitation (ProcessParameters *inListSelAndExpl_pp,
                                       void *list_content) //see example in hardcoded parts
  { /* To be implemented (somewhat like 'inFile_selectionAndExploitation') if and only
         if you do not complete the implementation of the function
         'fileDirectory_recursiveSelectionAndExploitation' below.
         If in the function 'hardcoding_of_processParameters', you use
             {"info_exploration_content", "{a b {c d e {f} g} h i}"}
         then you have to implementing the parsing of the string (-> bonus points for you) 
              and you should write "char *list_content" instead of "void *list_content" above,
         otherwise you use  {"info_exploration_content", {a b {c d e {f} g} h i} }  and hence
                   you do not have to do the parsing (-> do not change "void *list_content").
    //... lines to implement
    ProcessParameters *expl_pp= (ProcessParameters *)
    return Global_xFct1.info_exploitationAtEnd(expl_pp);

  int inFile_selectionAndExploitation (ProcessParameters *inFileSelAndExpl_pp,
                                       const char *fileName)
  { const char *method= (const char *) inFileSelAndExpl_pp->get("info_exploration_method");
    if (!method) return Error::ERROR;
    int indexInFile;
    const char *indexInFileStr= (const char *)
    if (!indexInFileStr) indexInFile= 0;
    else if (sscanf(indexInFileStr,"%d",/*initP*/&indexInFile) != 1)
         { ErrMedium.print(inFileSelAndExpl_pp,
                           "You indicated '%s' for the 'info_exploration_starting_point of a"
                           " file (hence, the index from which to begin selecting info."
                           "This is an invalid value. Valid values are integers"
                           " from 0 and above", indexInFileStr);   return Error::ERROR;
    if ( !Str::equal(method,"sequential"))
    { ErrMedium.print(inFileSelAndExpl_pp,
                      "So far, the only implemented exploration method for exploring a file is"
                      " sequentially, character by character, from the beginning (or a"
                      " starting point) to the end. You indicated:\n   %s", method);
      return Error::ERROR;
    ProcessParameters *expl_pp= (ProcessParameters *)
    if (!expl_pp) return Error::ERROR;
    const char *explAtEnd_fctName= (const char *)
    if (!explAtEnd_fctName)  explAtEnd_fctName= "Xfct1::exploitationAtEnd";
    if (!Str::equal(explAtEnd_fctName,"Xfct1::exploitationAtEnd"))
    { ErrMedium.print(expl_pp,"The only implemented exploitation method is"
                              " 'Xfct1::exploitationAtEnd'."
                              " You indicated '%s'.",explAtEnd_fctName);
      return Error::ERROR;
    /* This function is too long, its above part should have been isolated into another
         function with a name such as inFile_selectionAndExploitation_precondition and 
         then here called as follows :
            int r= inFile_selectionAndExploitation_precondition();  if (r<0) return r;
    FILE *fp= fopen(fileName,"r");  char c;
    if (!fp) { ErrMedium.print(expl_pp,"'%s' cannot be read: %s",fileName,strerror(errno));
               return Error::ERROR;
    for (int i= 0; ((c= fgetc(fp)) != EOF); i++)
       if (i >= indexInFile)  Global_xFct1.info_exploitation(expl_pp,c);
    return Global_xFct1.info_exploitationAtEnd(expl_pp);
  int fileDirectory_recursiveSelectionAndExploitation (ProcessParameters *dirSelAndExpl_pp,
                                        const char *directoryName, int depth, int maxDepth)
  { if ((maxDepth>0) && (depth>=maxDepth)) return Error::NO_ERROR;
    DIR *directory= opendir(directoryName);  //opendir or any other equivalent function
    if (!directory)
    { ErrMedium.print(dirSelAndExpl_pp,"'%s' is not an accessible directory: %s",
                      directoryName, strerror(errno));   return Error::ERROR;
    ProcessParameters *posExplConstraints_pp= (ProcessParameters *)
    ProcessParameters *negExplConstraints_pp= (ProcessParameters *)
    ProcessParameters *posSelConstraints_pp= (ProcessParameters *)
    ProcessParameters *negSelConstraints_pp=  (ProcessParameters *)
    ProcessParameters *inFileSelAndExpl_pp= (ProcessParameters *)
      if (!inFileSelAndExpl_pp) return Error::ERROR;
#if 0
     to be implemented: the lines below (according to what your language or operating system
      allows you to use) and replace the above "#if 0" by "#if 1"
          for each file $f in $directory
          { if ($f satisfies each constraint in posSelConstraints_pp and 
                             no constraint in negSelConstraints_pp)
            if ($f is a directory &&
                $f satisfies each constraint in posExplConstraints_pp and
                             no constraint in negExplConstraints_pp)
    //until you implement the above part, the next 2 lines avoid warnings about unused variables 
    posExplConstraints_pp= negExplConstraints_pp= posSelConstraints_pp= negSelConstraints_pp;
    dirSelAndExpl_pp= posExplConstraints_pp;
    return Error::NO_ERROR;

  int fileDirectory_selectionAndExploitation (ProcessParameters *pp, const char *contentType,
                                              const char *fileOrDirToExplore)
  { if (Str::equal(contentType,"within_a_file"))
    { ProcessParameters *inFileSelAndExpl_pp= (ProcessParameters *)
      if (!inFileSelAndExpl_pp) return Error::ERROR;
      return inFile_selectionAndExploitation(inFileSelAndExpl_pp,fileOrDirToExplore);
    const char *method= (const char *) pp->get("info_exploration_method");
    if (!method) return Error::ERROR;
    if (!Str::equal(method,"depth_first_exploration"))
    { ErrMedium.print(pp,"So far, the only implemented exploration method for"
                      " a 'directory exploration-selection-and-exploitation' function"
                      " is 'depth_first_exploration'.\n"
                      "You have indicated '%s'.", method);   return Error::ERROR;
    int depth;
    const char *depthStr= (const char *) pp->get("info_exploration_depth",Error::NO_MESSAGE);
    if (!depthStr) depth= Error::ERROR;
    else if (sscanf(depthStr,"%d",/*initP*/&depth) != 1)
         { ErrMedium.print(pp,"You indicated '%s' for the 'info_exploration_depth' of a"
                              " directory. This is an invalid value. Valid values are integers"
                              " from -1 and above", depthStr);   return Error::ERROR;
    return fileDirectory_recursiveSelectionAndExploitation(pp,fileOrDirToExplore,0,depth);
  int mainFunction (ProcessParameters *pp)
  { ProcessParameters *mainFct_pp= (ProcessParameters *) pp->get("mainFct");
    if (!mainFct_pp) return Error::ERROR;
    const char *type= (const char *) mainFct_pp->get("@type");
    if (!type) return Error::ERROR;
    if (!Str::equal(type,"info_selection_and_exploitation"))
    { ErrMedium.print(pp,"This indicated main function '%s' is not an"
                         " 'information selection and exploitation' function.\n"
                         "This program cannot yet compute it");  return Error::ERROR;
    const char *contentType= (const char *) mainFct_pp->get("info_exploration_content_type");
    if (!contentType) return Error::ERROR;
    const char *content= (const char *) mainFct_pp->get("info_exploration_content");
    if (!content) return Error::ERROR;
    if ( Str::equal(contentType,"file_directory") ||
         Str::equal(contentType,"within_a_file") )
      return fileDirectory_selectionAndExploitation(mainFct_pp,contentType,content);
    if (Str::equal(contentType,"list"))
      return inList_selectionAndExploitation(mainFct_pp,(void *)content);
    ErrMedium.print(pp,"You indicated '%s' for the content type of the main function.\n"
                       "This program can only compute content of type 'file-directory',"
                       " 'within_a_file' and 'list'.",contentType);
    return Error::ERROR;


int main (int argc, char *argv[]
         ) //to take into account command-line parameters, if you wish
{ ProcessParameters p;   //or:  ProcessParameters p(argc,argv);
  ProcessParameters *pp= &p;
  /*Xfct1 xFct1; */  //if C++ allowed pointers on (static) methods,
                     //  xFct1 could be used instead of Global_xFct1
  ISE infoSelectionAndExploitation;
  int r= infoSelectionAndExploitation.mainFunction(pp);
  return !r ? Global_xFct1.averages_display(&G_atEnd_p) : r;   //returns 0 if no error