// This program records the number of occurances of
// all 95 printable ASCII characters

// Computer Science III
// Nick Cash -- August 22, 2006

// Input:
// The program will prompt for the file name of an input file to read from.

// Output:
// The program will then prompt for a file name of an output file to write to.

// Assumptions:
//  The input file contains only printable characters from space to tilde
//    and control characters marking the end of lines and the end of file.
//  File names contain no more than 80 characters.


// includes
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <map>

using namespace std;

// for easier coding, make a typedef
typedef map<char, int> CharMap;

// function prototypes
void GetInput( ifstream& in, ofstream& out );
void ParseInput( ifstream& in, CharMap& map );
bool WriteData( ofstream& out, CharMap& map );

// Main function. Calls
int main(int argc, char *argv[])
{
    CharMap char_occur;      // store the occurances of each printable character
    CharMap::iterator nl;    // used to find the newline entry in the map
    ifstream ifile;          // input file handle
    ofstream ofile;          // output file handle

    // Gather input and perform error checking
    // When complete we have two open file handles
    GetInput( ifile, ofile );

    // Fill the map by reading the data
    ParseInput( ifile, char_occur );
    
    // display the number of lines in the input file, remove newlines from the map
    nl = char_occur.find('\n');
    
    if ( nl == char_occur.end() )
      cout << "\n\nThe input was 1 line long.\n\n";
    else
      printf( "\n\nThe input was %d line%s long.\n\n", nl->second, (nl->second == 1 ? "" : "s" ));
    
    // delete newline from map
    char_occur.erase(nl);

    // Write out the data. The function closes the file.
    return WriteData(ofile, char_occur);
}

// GetInput: Gather and check file names supplied by the user.
// PreCondition: Supplied two closed file handles
// PostCondition: in/out are opened
void GetInput( ifstream& in, ofstream& out )
{
    string input, output;    // input/output filenames

    // Validate references
    if ( !in || !out )
    {
     cout << "\n\nGetInput: Critical error, exiting...\n\n";
     exit(EXIT_FAILURE);
    }

    // When finished we will have an input file name stored in input
    //  of no more than 80 characters and an open file
    // It prompts for input and checks the length each iteration.
    while ( input.length() <= 0 )
    {
      cout << "\n\nInput file> ";
      cin >> input;

      if ( input.length() > 80 || input.length() <= 0 )
      {
        cout << "\n\nError: Bad filename. Try a shorter one.\n\n";
        input = ""; // clear the string
        continue;
      }
      
      // make sure input exists
      in.open(input.c_str(), ios_base::in);

      if ( !in.is_open())
      {
         cout << "\n\nError: Input file does not exist.\n\n";
         input = "";
      }
    }

    // When finished we will have an output file name stored in output
    //  of no more than 80 characters and an open output file
    // It prompts for output and checks the length each iteration.
    while ( output.length() <= 0 )
    {
      cout << "\n\nOutput file> ";
      cin >> output;

      if ( output.length() > 80 || output.length() <= 0 )
      {
        cout << "\n\nError: Bad filename. Try a shorter one.\n\n";
        output = ""; // clear the string
        continue;
      }

      // make sure file names are not the same
      if ( input == output )
      {
         cout << "\n\nError: Input and Output file names match. Enter different file names.\n\n";
         output = "";
         continue;
      }

      // make sure the output name is valid
      out.open(output.c_str(), ios_base::out);

      if ( !out.is_open())
      {
         cout << "\n\nError: Bad output filename.\n\n";
         out.close(); // close in-file
         output = "";
      }
    }
}

// ParseInput: Read in each character and update counts.
// PreCondition: Supplied an open file stream to read from, and a map to insert into
// PostCondition: The data is in the map, the file stream is closed
void ParseInput( ifstream& in, CharMap& map )
{
  char c;                  // temporary holder for characters
  CharMap::iterator i;     // iterator used in the loop

  if ( !in.is_open() )
  {
     cout << "\n\nParseInput: Critical error, exiting...\n\n";
     exit(EXIT_FAILURE);
  }
  
  // Grab a character and add it to the map. If it exists in the map, increase its count.
  // Case-sensitive. Loop until end of file is reached.
  // When complete the character map will be full of printable characters.
  while ( !in.eof() )
  {
    c = in.get();

    // end of file
    if ( c == '\0' )
     break;
    
    // we don't want to record anything unprintable
    if ( (c < ' '  || c > '~') && c != '\n' )
     continue;
    
    i = map.find(c);
    
    // if it wasnt found in the map, add it, otherwise increment its occurance
    if ( i == map.end() )
     map[c] = 1;
    else
     i->second++;
  }
  
  in.close();
}

// WriteData: Write out data, close file
// PreCondition: Supplied the open file stream to write to and the CharMap data to write
// PostCondition: Returns EXIT_SUCESS or EXIT_FAILURE to complete the program
bool WriteData( ofstream& out, CharMap& map )
{
  char buf[32]; // temporary buffer to hold string contents

  // make sure ofstream is valid
  if (!out.is_open())
  {
     cout << "\n\nWriteData: Critical error, exiting...\n\n";
     return EXIT_FAILURE;
  }

  // Print out the number of occurances for each printable character
  //  in alphabetical order
  // When complete, the program is finished
  for ( CharMap::const_iterator i = map.begin(); i != map.end(); i++ )
  {
    sprintf( buf, "%c occurred %4d times\n", i->first, i->second );
    out << buf;
    buf[0] = '\0';
  }

  out.close();

  return EXIT_SUCCESS;
}