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

#include <fstream>
#include <cstdlib>
#include <string>
#include <cassert>


// According to the problem, there can be at most 25 cheeses in the warehouse
// inventory.  Declaring a named constant avoids the dreaded magic number.

   static const int max_inventory = 25;


// Each item in the inventory has four fields.

  struct inventory_item {
    string cheese;	// The name of the cheese.
    int max;		// The maximum quantity at hand.
    int current;	// The current quantity at hand.
    int min;		// The minimum quantity at hand.
    };


// Reading the problem statement, it seems it can be solved if we can do four
// things: 
//
//  1 Read in the inventory.

      static int read_inventory(string, inventory_item[]);

//  2 Read in the daily sales and adjust the inventory.

      static void process_orders(string, inventory_item[], int);

//  3 Write the inventory to a file.

      static void make_new_inventory_file(string, inventory_item[], int);

//  4 Write the reorder file.

      static void make_reorders_file(string, inventory_item[], int);


static void process_inventory(void) {

     inventory_item inventory[max_inventory];
     const int inventory_size = read_inventory("inventory.dat", inventory);

     process_orders("sales.dat", inventory, inventory_size);

     make_new_inventory_file("new-inventory.dat", inventory, inventory_size);

     make_reorders_file("reorders.dat", inventory, inventory_size);
  }


static int read_inventory(
  string inventory_filename, inventory_item inventory[max_inventory]) {

  // Read the inventory information in the file named inventory_filename; store
  // the inventory information in the array inventory.  Return the number of
  // inventory items read and stored in invetory.

  ifstream inventory_file(inventory_filename.c_str());
  if (!inventory_file) {
    cerr << "Can't open inventory file \"" << inventory_filename << "\".\n";
    exit(EXIT_FAILURE);
    }

  int items = 0;
  while (items < max_inventory) {

    // Read an inventory item.  This code belongs in a separate procedure, but
    // we don't yet know enough c++ to correctly pass around file streams
    // between procedures via parameters (and, of course, we recoil at the
    // thought of using globals to pass values between procedures).  Also,
    // bailing out at the first error is a bit draconian, but again we don't
    // know enough c++ (and we're not going to learn enough c++ in this class)
    // to sensibly handle I-O errors, and dealing with I-O errors is hard in
    // any event.

       inventory_item item;

       if (!(inventory_file >> item.max)) {
	 if (inventory_file.eof())
	   break;
	 cerr << "Error while reading maximum inventory quantity.\n";
	 exit(EXIT_FAILURE);
	 }

       if (!(inventory_file >> item.current)) {
	 cerr << "Error while reading current inventory quantity.\n";
	 exit(EXIT_FAILURE);
	 }

       if (!(inventory_file >> item.min)) {
	 cerr << "Error while reading minimum inventory quantity.\n";
	 exit(EXIT_FAILURE);
	 }

       if (!getline(inventory_file, item.cheese)) {
	 cerr << "Error while reading cheese name.\n";
	 exit(EXIT_FAILURE);
	 }

       inventory[items] = item;
       items++;
    }

  inventory_file.close();

  return items;
  }


static int find_cheese(
  const inventory_item in[], int inventory_size, string c) {

  // Return the index of the cheese c in the inventory in or -1 if c isn't in
  // the inventory.

  assert((0 <= inventory_size) && (inventory_size <= max_inventory));

  for (int i = 0; i < max_inventory; i++)
    if (in[i].cheese == c) 
      return i;

  return -1;
  }


static void process_orders(
  string sales_filename, inventory_item inventory[], int inventory_size) {

  ifstream sales_file(sales_filename.c_str());
  if (!sales_file) {
    cerr << "Can't open the sales file \"" << sales_filename << "\".\n";
    exit(EXIT_FAILURE);
    }

  while (true) {

    // Read the next sales item from the file.  What was written about reading
    // inventory items in read_inventory() also applies here.

       struct sales_item {
	 string store;		// The store making the sale.
	 string cheese;		// The name of the cheese sold.
	 int quantity;		// The amount of cheese sold.
	 } s;

       if (!(sales_file >> s.store)) {
	 if (sales_file.eof())
	   break;
	 cerr << "Error while reading store identifier.\n";
	 exit(EXIT_FAILURE);
	 }

       if (!(sales_file >> s.quantity)) {
	 cerr << "Error while reading cheese sales quantity.\n";
	 exit(EXIT_FAILURE);
	 }

       if (!getline(sales_file, s.cheese)) {
	 cerr << "Error while reading cheese name.\n";
	 exit(EXIT_FAILURE);
	 }

    // Find the inventory entry for the cheese sold.

       const int i = find_cheese(inventory, inventory_size, s.cheese);
       if (i < 0) {
	 cerr << "Unknown cheese \"" << s.cheese << "\" in sales file.\n";
	 exit(EXIT_FAILURE);
	 }

    // Adjust the quantity on hand, remembering the amount can't go below 0.

       if ((inventory[i].current -= s.quantity) < 0)
	 inventory[i].current = 0;
    }

  sales_file.close();
  }


static void make_new_inventory_file(
  string inventory_filename, inventory_item inventory[], int inventory_size) {

  // Write out the inventory_size items in inventory to the file having the
  // name inventory_filename.

  ofstream outs(inventory_filename.c_str());
  if (!outs) {
    cerr << "Can't open new inventory file \"" << inventory_filename 
	 << "\".\n"; 
    exit(EXIT_FAILURE);
    }

  while (--inventory_size >= 0) {
    outs << inventory[inventory_size].max << " " 
	 << inventory[inventory_size].current << " "
         << inventory[inventory_size].min << " " 
         << inventory[inventory_size].cheese << "\n";
    if (!outs.good()) {
      cerr << "Error during inventory item write.\n";
      exit(EXIT_FAILURE);
      }
    }

  outs.close();
  if (!outs.good()) {
    cerr << "Error closing new inventory file.\n";
    exit(EXIT_FAILURE);
    }
  }


static void make_reorders_file(
  string reorder_filename, inventory_item inventory[], int inventory_size) {

  // Write out the reorder items to the file with the name given by
  // reorder_filename based on the state of the inventory_size items in
  // inventory.

  ofstream outs(reorder_filename.c_str());
  if (!outs) {
    cerr << "Can't open new inventory file \"" << reorder_filename 
	 << "\".\n"; 
    exit(EXIT_FAILURE);
    }

  while (--inventory_size >= 0) {
    inventory_item item = inventory[inventory_size];
    if (item.current < item.min) {
      outs << item.max - item.current<< " " << item.cheese << "\n";
      if (!outs.good()) {
	cerr << "Error during reorder item write.\n";
	exit(EXIT_FAILURE);
	}
      }
    }

  // Check for close errors on open-for-write files because errors could
  // indicate a loss of data.

     outs.close();
     if (!outs.good()) {
       cerr << "Error closing reorders file.\n";
       exit(EXIT_FAILURE);
       }
  }


// This solution works, but it isn't a good solution because a lot of the
// support code for inventories is out there hanging in the breeze, making
// inventories difficult to understand and complicated to use.  There might be
// some advantage to encapsulating inventories in an object.

   class Inventory {

     public:

       // Create a new inventory by reading the inventory description in the
       // file named f.

          Inventory(string f);

       // Adjust the inventory based on the sales stored in the file named f.

          void process_sales(string f);

       // Write the inventory to the file named f.

          void write_inventory(string f);

       // Write the inventory reorders to the file named f.

          void write_reorders(string f);

     private:

       // Here's an advantage to using classes: the support code needed to
       // implement an inventory can be hidden in the class's private section.
       // Clients using the Inventory class can ignore this code, and Inventory
       // class implementors have the freedom to change the code as needed.

       static const int max_inventory = 25;

       struct inventory_item {
	 string cheese;		// The name of the cheese.
	 int max;		// The maximum quantity at hand.
	 int current;		// The current quantity at hand.
	 int min;		// The minimum quantity at hand.
	 };

       inventory_item inventory[max_inventory];
       int inventory_size;

       int find_cheese(string);
     };


// And here's the payoff: simpler parameter lists (because the extra code
// needed to implement an inventory is kept in the class and doesn't need to be
// passed around as parameters) leading to a simpler interface for inventory
// processing.

static void process_inventory_class(void) {

  Inventory inventory("inventory.dat");

  inventory.process_sales("sales.dat");
  inventory.write_inventory("new-inventory.dat");
  inventory.write_reorders("reorders.dat");
  }


// The code that follows is essentially the same as the previous code, so it
// will be shortened by dropping the comments.

Inventory::Inventory(string inventory_filename) {

  ifstream inventory_file(inventory_filename.c_str());
  if (!inventory_file) {
    cerr << "Can't open inventory file \"" << inventory_filename << "\".\n";
    exit(EXIT_FAILURE);
    }

  inventory_size = 0;
  while (inventory_size < max_inventory) {
    inventory_item item;

    if (!(inventory_file >> item.max)) {
      if (inventory_file.eof())
	break;
      cerr << "Error while reading maximum inventory quantity.\n";
      exit(EXIT_FAILURE);
      }

    if (!(inventory_file >> item.current)) {
      cerr << "Error while reading current inventory quantity.\n";
      exit(EXIT_FAILURE);
      }

    if (!(inventory_file >> item.min)) {
      cerr << "Error while reading minimum inventory quantity.\n";
      exit(EXIT_FAILURE);
      }

    if (!getline(inventory_file, item.cheese)) {
      cerr << "Error while reading cheese name.\n";
      exit(EXIT_FAILURE);
      }

    inventory[inventory_size] = item;
    inventory_size++;
    }

  inventory_file.close();
  }


void Inventory::process_sales(string sales_filename) {

  ifstream sales_file(sales_filename.c_str());
  if (!sales_file) {
    cerr << "Can't open the sales file \"" << sales_filename << "\".\n";
    exit(EXIT_FAILURE);
    }

  while (true) {

    struct sales_item {
      string store;	// The store making the sale.
      string cheese;	// The name of the cheese sold.
      int quantity;	// The amount of cheese sold.
      } s;

    if (!(sales_file >> s.store)) {
      if (sales_file.eof())
	break;
      cerr << "Error while reading store identifier.\n";
      exit(EXIT_FAILURE);
      }

    if (!(sales_file >> s.quantity)) {
      cerr << "Error while reading cheese sales quantity.\n";
      exit(EXIT_FAILURE);
      }

    if (!getline(sales_file, s.cheese)) {
      cerr << "Error while reading cheese name.\n";
      exit(EXIT_FAILURE);
      }

    const int i = find_cheese(s.cheese);
    if (i < 0) {
      cerr << "Unknown cheese \"" << s.cheese << "\" in sales file.\n";
      exit(EXIT_FAILURE);
      }

    if ((inventory[i].current -= s.quantity) < 0)
      inventory[i].current = 0;
    }

  sales_file.close();
  }


void Inventory::write_inventory(string inventory_filename) {

  ofstream outs(inventory_filename.c_str());
  if (!outs) {
    cerr << "Can't open inventory file \"" << inventory_filename 
	 << "\".\n"; 
    exit(EXIT_FAILURE);
    }

  for (int i = 0; i < inventory_size; i++) {
    outs << inventory[i].max << " " 
	 << inventory[i].current << " "
         << inventory[i].min << " " 
         << inventory[i].cheese << "\n";
    if (!outs.good()) {
      cerr << "Error during inventory item write.\n";
      exit(EXIT_FAILURE);
      }
    }

  outs.close();
  if (!outs.good()) {
    cerr << "Error closing inventory file.\n";
    exit(EXIT_FAILURE);
    }
  }


void Inventory::write_reorders(string reorder_filename) {

  ofstream outs(reorder_filename.c_str());
  if (!outs) {
    cerr << "Can't open the reorder file \"" << reorder_filename 
	 << "\".\n"; 
    exit(EXIT_FAILURE);
    }

  for (int i = 0; i < inventory_size; i++) {
    inventory_item item = inventory[i];
    if (item.current < item.min) {
      outs << item.max - item.current<< " " << item.cheese << "\n";
      if (!outs.good()) {
	cerr << "Error during reorder item write.\n";
	exit(EXIT_FAILURE);
	}
      }
    }

  outs.close();
  if (!outs.good()) {
    cerr << "Error closing reorders file.\n";
    exit(EXIT_FAILURE);
    }
  }


int Inventory::find_cheese(string c) {

  // Return the index of the cheese c in the inventory in or -1 if c isn't in
  // the inventory.

  assert((0 <= inventory_size) && (inventory_size <= max_inventory));

  for (int i = 0; i < max_inventory; i++)
    if (inventory[i].cheese == c) 
      return i;

  return -1;
  }


int main() {
  process_inventory();
  }




syntax highlighted by Code2HTML, v. 0.9