// Programming Assignment 6 Example Solution
// CS 176, Summer 2001

#include <cstdlib>
#include <fstream>
#include <string>
#include <cctype>

// This is a fairly complicated problem, not because it's difficult, but
// because it requires lots of steps to solve.  However, by taking deep breaths
// and small steps, it can be sloved with a minimum of anguish.

// First, we need a data structure to represent the contents of a router-tables
// file and some handy operations on the representation.

struct router_record {

  // The name of the network segment to which this record applies.

     string segment_name;

  // The name of the router to which this record applies.

     string router_name;

  // The number of bytes in the router table for this router.

     int table_size;

  // A pointer to a block of dynamic memory containing the router table.

     char * router_table;

  // Initialize a router record.

     router_record() {
       segment_name = "";
       router_name = "";
       table_size = 0;
       router_table = 0;
       }

  // The routing table is a block of dynamic memory, and needs to be deleted
  // before the associated router-record value disappears, otherwise garbage
  // will result.  However, a router-record value need not have a router table,
  // in which case router_table will be 0.

    ~router_record() {
      if (router_table != 0)
	delete [] router_table;
      }

  // Print the contents of this router record to std-err.

     void print(void) const {
       cerr << "segment name is " << segment_name;
       cerr << ", router name is " << router_name;
       cerr << ", table size is " << table_size << ".\n";
       }

  // Compare this router record to the router record rr and return true if this
  // record is greater than rr.  A router record r1 is greater than the router
  // record r2 if either 
  //
  //   the segment name associated with r1 is greater than the segment name
  //   associated with r2; for example "Wilmington" > "Altoona"
  //
  // or
  //
  //   The two segment names are the same and the router name associated with
  //   r1 is greater than the router name associated with r2.
  //
  // The diligent reader will recognize this as a lexicographic order.

     bool operator > (const router_record & rr) const {
       return 
	 (segment_name > rr.segment_name) ||
	 ((segment_name == rr.segment_name) && (router_name > rr.router_name));
       }
  };

// To solve the problem, we need to
//
//  1 Read in the contents of the routing-table file.
//
      static router_record ** read_router_records(const string &);
//    
//  2 Sort the router tables to group network segments and order routers within
//    a segment.
//
      static void sort_router_records(router_record *[]);
//
//  3 Write the router-tables to the proper files.
//
      static void write_segment_files(router_record *[]);
//
//  4 Clean up and exit.


int main() {
  const string rt_filename = "routing-tables";
  router_record ** router_record_ptrs = read_router_records(rt_filename);

  sort_router_records(router_record_ptrs);

  write_segment_files(router_record_ptrs);

  for (int i = 0; router_record_ptrs[i] != 0; i++)
    delete router_record_ptrs[i];
  delete [] router_record_ptrs;
  }


// The tricky thing about reading records from the routing-table file is that
// we don't know how many records are in the file.  However, assuming we know
// how to read the next available record from the file
//
     static router_record * read_router_record(ifstream &);
//
// we can deal with this problem relatively easily by allocating some space for
// the routing-table records and re-allocating more space when needed.  
//
// The return value for read_router_records() should be an array of router
// records.  However, because each router record is large, it would be best to
// minimize copying costs from the old smaller to new larger array.  One way to
// do this is keep an array of pointers to router records; each pointer is
// considerably smaller than an router-record value.  Because the array may be
// larger than the number of records read, the next element after the last
// valid pointer is 0 to indicate the end of valid router-record pointers.

static router_record ** read_router_records(const string & rt_filename) {

  // Read the contents of the router-table file with the name rt_filename.
  // Return a pointer to an array containing pointers to the router-record
  // information read; the end of the list is marked with the 0 pointer.  It is
  // the caller's responsibilty to free the dynamic memory allocated for the
  // both the array and the records.

  ifstream ins(rt_filename.c_str());
  if (!ins) {
    cerr << "Can't open the routine-table file \"" << rt_filename << "\".\n";
    exit(EXIT_FAILURE);
    }

  const int records_increment = 100;
  int records_max = records_increment;
  router_record ** records = new router_record *[records_max];
  int record_cnt = 0;

  do {
    if (record_cnt >= records_max) {
      int new_records_max = records_max + records_increment;
      router_record ** new_records = new router_record *[new_records_max];

      memcpy(new_records, records, sizeof(router_record *)*record_cnt);

      delete [] records;
      records = new_records;
      records_max = new_records_max;
      }

    }
  while ((records[record_cnt++] = read_router_record(ins)) != 0);

  return records;
  }


// A router record has three parts: the segment name, the router name, and the
// routing table data.  Reading the first and third parts are trickey because
// both can have a variable size; segment names can have a varying number of
// words (as in "Washington D. C.") and routing tables can have a varying
// number of bytes.  Because we have to read the segment name before we can get
// to the routing table, let's worry about the segment name here and delay the
// problem of reading routing tables until later.
//
     static void read_router_table(ifstream &, char * &, int &);
//
// The key to reading segment names correctly is to recognize that a router
// name cannot be part of a segment name.  If you keep reading words until you
// read a router name, then all the words you read before must be part of the
// segment name.  This approach assumes we know how to tell when a word is a
// router name.
//
     static bool is_router_name(const string &);
//
// Once the segment and router names have been straightened out, we can
// complete the router record by reading the routing table.

static router_record * read_router_record(ifstream & ins) {

  // Read the next router record from the input stream ins; return a pointer to
  // the router record read.  It is the caller's responsibilty to free the
  // dynamic memory allocated for the record.

  router_record * rr_ptr = new router_record;

  ins >> rr_ptr->segment_name;
  if (!ins.good()) {
    delete rr_ptr;
    return 0;
    }

  while (true) {
    ins >> rr_ptr->router_name;
    if (!ins.good()) {
      cerr << "Error during router name read.\n";
      exit(EXIT_FAILURE);
      }
    if (is_router_name(rr_ptr->router_name))
      break;
    rr_ptr->segment_name += " " + rr_ptr->router_name;
    }

  read_router_table(ins, rr_ptr->router_table, rr_ptr->table_size);

  return rr_ptr;
  }


// A router name starts with an 'R' followed by one or more decimal digits.

static bool is_router_name(const string & word) {

  // Return true if and only if word contains a valid router name.

  if (word[0] != 'R') return false;
  for (int i = word.size() - 1; i > 0; i--)
    if (!isdigit(word[i])) return false;
  return true;
  }


// Reading router tables is complicated not only because the tabels have
// unknown and varying size, but also because each table is delimited by a
// different character pair.  Assuming for the moment we know how to deal with
// the delimiter characters
//
     static void read_delimiter_characters(ifstream &, char &, char &);
//
// we can deal with the variable size in the same way we did for reading
// records from the routing-table file: allocate an array of bytes and grow it
// as needed.  The need to check for two delimiters adds a bit of complication
// to this code, but not too much.

static void read_router_table(
  ifstream & ins, char * & router_table, int & table_size) {

  // Read router table data from the input stream ins.  After the call
  // router_table contains a pointer to the dynamic memory allocated to hold
  // the router table data and table_size contains the number of bytes of
  // router table data read.  It is the caller's responsibility to free the
  // memory allocated for the router-table data.

  char delim1, delim2;
  read_delimiter_characters(ins, delim1, delim2);

  const int table_increment = 100;
  int table_max = 100;
  router_table = new char[table_max];
  table_size = 0;

  while (true) {
    if (table_size + 1 >= table_max) {
      int new_table_max = table_max + table_increment;
      char * new_router_table = new char[new_table_max];

      memcpy(new_router_table, router_table, table_size);

      delete [] router_table;
      router_table = new_router_table;
      table_max = new_table_max;
      }
    
    router_table[table_size] = ins.get();
    if (ins.eof()) {
      cerr << "Error during routing-table data read.\n";
      exit(EXIT_FAILURE);
      }

    if (router_table[table_size] == delim1) {
      char c;
      c = ins.get();
      if (!ins.good()) {
	cerr << "Error during second delimiter character read.\n";
	exit(EXIT_FAILURE);
	}
      if (c == delim2)
	break;
      else
	router_table[++table_size] = c;
      }

    table_size++;
    }
  }


// The first two characters after the space characters following the router
// name are the delimiter characters.  Reading them is simple, complicated only
// by the need to check for I-O errors.

static void read_delimiter_characters(ifstream & ins, char & d1, char & d2) {

  // Read the routing-table delimiter characters from the input stream ins; d1
  // contains the first character and d2 contains the second character.

  do {
    d1 = ins.get();
    if (!ins.good()) {
      cerr << "Error during first delimiter character read.\n";
      exit(EXIT_FAILURE);
      }
    }
  while ((' ' == d1) || ('\n' == d1));

  d2 = ins.get();
  if (!ins.good()) {
    cerr << "Error during second delimiter character read.\n";
    exit(EXIT_FAILURE);
    }
  }


// Once the routing-table records have been read in, they are sorted in
// preparation for writing the individual network-segment files.  The sort is a
// simple selection sort, and uses the operator >() defined for router records.

static void sort_router_records(router_record * router_record_ptrs[]) {

  // Sort the router records given in router_record_ptrs.  After the sort the
  // records in ascending order by network name; within each network segment
  // the records in ascending order by router name.

  for (int top = 0; router_record_ptrs[top] != 0; top++) {
    int smallest = top;
    for (int current = smallest + 1; router_record_ptrs[current] != 0; current++)
      if (*router_record_ptrs[smallest] > *router_record_ptrs[current])
	smallest = current;
    router_record * rrp = router_record_ptrs[smallest];
    router_record_ptrs[smallest] = router_record_ptrs[top];
    router_record_ptrs[top] = rrp;
    }
  }


// Given a sorted list of router records, each block of records with the same
// segment name serves as the basis for creating the network-segment files.
//
     static void write_segment_file(router_record **, int, int);


static void write_segment_files(router_record * router_record_ptrs[]) {

  // Create the network-segment files from the list of router records given in
  // router_record_ptrs; the records are assumed to be sorted by network
  // segments.

  int start = 0;

  while (router_record_ptrs[start] != 0) {
    int end = start + 1;
    while ((router_record_ptrs[end] != 0) && 
	   (router_record_ptrs[start]->segment_name == router_record_ptrs[end]->segment_name))
      end++;
    write_segment_file(router_record_ptrs, start, end);
    start = end;
    }
  }


// Assuming we know how to construct a file name from a network segment name,
//
     static string make_file_name(const string &);
//
// creating the network-segment file involves writing out the individual
// records associated with the segment name.

static void write_segment_file(
  router_record * router_record_ptrs[], int start, int end) {

  // Create a network-segment file and write the records in the block
  // router_record_ptrs[start..end-1] to the file.  All records in the block
  // are assumed to contain the same segment record, and are assumed to be the
  // only records with that segment name.

  const string rt_filename = make_file_name(router_record_ptrs[start]->segment_name);
  ofstream outs(rt_filename.c_str());
  if (!outs) {
    cerr << "Can't open the router-segment file \"" << rt_filename << "\".\n";
    return;
    }

  while (start < end) {
    const router_record * const rrp = router_record_ptrs[start++];
    outs << rrp->router_name << " " << rrp->table_size << " ";
    outs.write(rrp->router_table, rrp->table_size);
    }

  outs.close();
  }


// Turning a segment name into a file name involves converting the segment name
// into lower case, replacing all spaces with hyphens, and adding ".rt".

static string make_file_name(const string & segment_name) {

  // Return a string containing segment_name as a network-segment file name.

  string fn = segment_name;

  for (int i = fn.size() - 1; i >= 0; i--)
    if (' ' == fn[i])
      fn[i] = '-';
    else if (isupper(fn[i]))
      fn[i] = tolower(fn[i]);

  return fn + ".rt";
  }


syntax highlighted by Code2HTML, v. 0.9