import java.io.*;
import java.util.*;

/******************************************************************************
* This class develops a hardware list from a flat text file.  It also keeps 
* track of what purchases the user has made.  This class replaces one from the 
* prototype version that just kept track of CPU and video card purchases.<br>
*
* <br>Here is the format for the file that it reads:<br>
* <b>#ComponentName<br>
* Price, Description<br>
* #ENDCOMPONENT<br>
* #Printer<br>
* 50, HP Deskjet 841C<br>
* 100, Lexmark Z43<br>
* #ENDCOMPONENT<br>
* #END</b><br><br>
*
* The component name field must match exactly with the component name in the
* assert statements of JESS.  There can be as many components in the file as
* you would like.  There can be as many models as the user likes in each
* component catagory.<BR><BR>
*
* There should be not extra white space in the text file.  The models should
* be listed from smallest/weakest/slowest to the largest/strongest/fastest
* models.
*
* @author Michael Wales <a href="http://www.mwales.net">Homepage</a>
* @version 1.0 Flat File Reading Version 
******************************************************************************/
public class HardwareCosts
{
   /*************************************************************************
   * This object holds the contents of the HardwareList.txt file when it is
   * loaded into memory.
   *************************************************************************/
   private Vector HardwareList;
   
   /** This is the list of components the user has purchased */
   private Vector PurchasedList;
   
   /*************************************************************************
   * This is where the class should output the text to.  If this variabe is
   * null (the default), it outputs with the standard System.out.print
   * output.  Otherwise, it outputs to the PrintWriter specified.
   *************************************************************************/
   private PrintWriter StandardOut;
   
   /***************************************************************************
   * Constructor creates all the hardware component objects.
   ***************************************************************************/
   public HardwareCosts()
   {
      StandardOut = null;
      
      // Initialize the vectors
      HardwareList = new Vector();
      PurchasedList = new Vector();
      
      // Parse the info out of the hardware list flat file
      parseHardwareListFile();
      
      // Used to debug the HardwareList.txt file
      // printHardwareList();
   }
   
   /***************************************************************************
   * This method parses the HardwareList.txt file for all the hardware pricing
   * information.  The data is put into the HardwareList vector.
   ***************************************************************************/
   private void parseHardwareListFile()
   {
      BufferedReader FlatFile = null;
      String CurrentLine = null;
      HardwareListCatagory CurrentCatagory = null;
      HWComponent CurrentComponent = null;
      boolean EndComponentFlag = false;
      int LevelOfComponent = 0;
      String ComponentPriceString = null;
      
      // Open the HardwareList.txt file
      try
      {
         FlatFile = new BufferedReader( new FileReader("HardwareList.txt"));
      }
      catch (Exception E)
      {
         // Couldn't open file
         System.out.println("Couldn't open hardware file");
         System.exit(0);
      }
      
      do
      {
         // Initialize the catagory vector for new data
         CurrentCatagory = new HardwareListCatagory();
         
         // Read the component name line of the file
         try
         {
            CurrentLine = FlatFile.readLine();
         }
         catch (Exception E)
         {
            System.out.println("Error trying to read the component name line");
            System.exit(0);
         }
         
         if ( !CurrentLine.equals("#END") )
         {
            // Store that catagory name in the catagory object
            CurrentCatagory.ComponentName = CurrentLine.substring(1);
            LevelOfComponent = 0;
            
            // Reset the end component flag
            EndComponentFlag = false;
            
            while (EndComponentFlag == false)
            {
               CurrentComponent = new HWComponent();
               
               // Read the next component
               try
               {
                  CurrentLine = FlatFile.readLine();
               }
               catch(Exception E)
               {
                  System.out.println("Error reading component info of " + CurrentCatagory.ComponentName );
                  System.exit(0);
               }
               
               if (CurrentLine.equals("#ENDCOMPONENT"))
               {
                  // No more components for this hardware catagory
                  EndComponentFlag = true;
               }
               else
               {
                  // New component, add to catagory vector
                  
                  // Save the level of the component into the vector
                  CurrentComponent.Level = LevelOfComponent;
                  
                  // Read the price out of the file
                  ComponentPriceString = CurrentLine.substring( 0, CurrentLine.indexOf(','));
                  try
                  {
                     CurrentComponent.Price = Integer.parseInt(ComponentPriceString);        
                  }
                  catch (Exception E)
                  {
                     // If there is an error reading the price, set the price really high
                     CurrentComponent.Price = 20000;        
                  }
                  
                  // Read the description of the item
                  CurrentComponent.Description = CurrentLine.substring(CurrentLine.indexOf(',') + 1);

                  // Add the component to the catagory vector
                  CurrentCatagory.ComponentList.add(CurrentComponent);                  
                  
               } // endif (CurrentLine.equals("#ENDCOMPONENT"))
            
            LevelOfComponent++;
            } // endwhile (EndComponentFlag == false)
            
         // After fininshing reading the entire catagory, add it to the big list
         HardwareList.add(CurrentCatagory);
            
         } // endif ( !CurrentLine.equals("#END") )
         
      } while ( !CurrentLine.equals("#END") );
      
      // Resize the purchased list
      PurchasedList.setSize(HardwareList.size());
   }
   
   /***************************************************************************
   * This method actually handles the logic of the hardware purchase.  It first
   * searches for the appropriate hardware catagory and item.  It then decides
   * if the user can afford the new component, and then it purchase it.
   * 
   * @param Name The name of the catagory of component to purchase (Example:
   *   CPU, VideoCard, and SoundCard.
   * @param Money The amount of money JESS has left to spend on hardware.
   * @returns The amount of money left over after the purchase that JESS has
   *   to spend on hardware.  If no hardware was purchased, this is the same
   *   amount of money sent to the method at the start.
   ***************************************************************************/
   private int purchaseComponent(String Name, int Money)
   {
      int CatCounter, OldLevel;
      HardwareListCatagory CurrentCatagory;
      HWComponent OldComponent, NewComponent;
      
      for(CatCounter = 0; CatCounter < HardwareList.size(); CatCounter++)
      {
         CurrentCatagory = (HardwareListCatagory) HardwareList.elementAt(CatCounter);
         
         if (CurrentCatagory.ComponentName.equals( Name ))
         {
            // Found the component
            
            // Find out how much they spent on previous models
            OldComponent = (HWComponent) PurchasedList.elementAt(CatCounter);
            
            if (OldComponent == null)
            {
               // First purchase from this catagory
               NewComponent = (HWComponent) CurrentCatagory.ComponentList.elementAt(0);
               
               if (Money >= NewComponent.Price)
               {
                  // Has enough money to make the purchase
                  PurchasedList.setElementAt(NewComponent, CatCounter);
                  
                  Money = Money - NewComponent.Price;
                  
                  // Debugging output
                  // writeToUser("Updgaded the " + Name + "to a " + NewComponent.Description);
               }
            }
            else
            {
               // Have made a previous purchase from this catagory
               OldLevel = OldComponent.Level;
               
               if (OldLevel + 1 < CurrentCatagory.ComponentList.size() )
               {
                  NewComponent = (HWComponent) CurrentCatagory.ComponentList.elementAt(OldLevel + 1);
                  
                  if (Money >= NewComponent.Price - OldComponent.Price)
                  {
                     // Has enough money to make the purchase
                     PurchasedList.setElementAt(NewComponent, CatCounter);
                     
                     Money = Money + OldComponent.Price - NewComponent.Price;
                     
                     // Debugging output
                     // writeToUser("Updgaded the " + Name + " to a " + NewComponent.Description);
                  }  
               }
            } // endif (OldComponent == null)
         
         } // endif (CurrentCatagory.ComponentName.equals( Name ))
         
      }
      
      return Money;
   }
   
   /***************************************************************************
   * This command is passed the JESS hardware request command.  It parses out
   * all the relevant information from that command, and purchases the hardware
   * that they selected.
   *
   * @param JessRequest This is the entire JESS-HWCDB:Item:Money message.
   * @return The amount of money left over after the purchase is returned
   *    the JessInterface object so it can pass it back to JESS.
   ***************************************************************************/
   public String upgradeHardware(String JessRequest)
   {
      int MoneyLeft, IndexNumberB, IndexNumberA;
      String WhatJessWants, OutputToJess;
      
      IndexNumberB = JessRequest.lastIndexOf(':');
      IndexNumberA = JessRequest.indexOf(':');
      
      // Parse out the money left field
      WhatJessWants = JessRequest.substring(IndexNumberB + 1);
      MoneyLeft = Integer.valueOf(WhatJessWants).intValue();
      
      // Parse out the description field
      WhatJessWants = JessRequest.substring(IndexNumberA + 1, IndexNumberB);
      
      // Try to purchase the component
      MoneyLeft = purchaseComponent(WhatJessWants, MoneyLeft);
            
      // Return the amount of money left after the purchase
      return String.valueOf(MoneyLeft);      
   }
   
   /***************************************************************************
   * This method displays all the hardware that JESS has purchased for the 
   * reccomended system.  Any type of hardware that hasn't been purchased at 
   * all (ex:  everyone doesn't need a DVD-RW drive) will not be displayed in
   * the output.
   ***************************************************************************/
   public void displayFullSystem()
   {
      int TotalPrice = 0;
      int CatCounter;
      HWComponent CurrentComponent;
      
      // Print out the table header
      writeToUser("\n\n\nLevel\tPrice\tDescription");
      writeToUser("-------------------------------------------------------");
      
      for (CatCounter = 0; CatCounter < PurchasedList.size(); CatCounter++)
      {
         CurrentComponent = (HWComponent) PurchasedList.elementAt(CatCounter);
         
         if (CurrentComponent != null)
         {
            writeToUser( CurrentComponent.componentInfo() );
            TotalPrice += CurrentComponent.Price;
            
         }
      }
      
      // CPU.printComponentInfo();
      // TotalPrice += CPU.Price;
      
      writeToUser("-------------------------------------------------------");
      writeToUser("Total:\t$" + TotalPrice);      
   }
   
   /***************************************************************************
   * This prints all the hardware in the hardware database.
   ***************************************************************************/
   public void printHardwareList()
   {
      int CatCounter, ComponentCounter;
      HardwareListCatagory CurrentCatagory;
      HWComponent CurrentComponent;
      
      for (CatCounter = 0; CatCounter < HardwareList.size(); CatCounter++)
      {
         CurrentCatagory = (HardwareListCatagory) HardwareList.elementAt(CatCounter);
         writeToUser("Catagory = "  + CurrentCatagory.ComponentName);
         
         for (ComponentCounter = 0; ComponentCounter < CurrentCatagory.ComponentList.size(); ComponentCounter++)
         {
            CurrentComponent = (HWComponent) CurrentCatagory.ComponentList.elementAt(ComponentCounter);
            writeToUser( CurrentComponent.componentInfo() );
         }
         
         writeToUser(" ");
         
      }
   }
   
   /***************************************************************************
   * This prints all the hardware in the hardware database.
   ***************************************************************************/
   public void resetPurchases()
   {
      int ComponentCounter;
      HWComponent CurrentComponent = null;
      
      // Erase all components stored
      for (ComponentCounter = 0; ComponentCounter < PurchasedList.size(); ComponentCounter++)
      {
         PurchasedList.setElementAt(CurrentComponent, ComponentCounter);
      }
   }
   
   /***************************************************************************
   * Sets the user's standard output variable.
   * 
   * @param SO Standard output object.  This is null to write directly to the
   *  standard system output, or a reference to a PrintWriter object to write 
   *  to.
   ***************************************************************************/
   public void setStandardOut(PrintWriter SO)
   {
      StandardOut = SO;  
   }
   
   /*************************************************************************
   * Emulates the replace() method of String, but uses Strings to replace
   * instead of just characters.
   *
   * @param Source The string that needs the modifications.
   * @param Remove The string to remove out of the source string.
   * @param Insert The string to insert into all the places the other string 
   * is removed.
   * @return A String where all of the replacements have taken place.
   *************************************************************************/
   public static String substringReplace(String Source, String Remove, String Insert)
   {
      int IndexOfRemove;
      
      int LengthOfRemove = Remove.length();
      IndexOfRemove = Source.indexOf( Remove );
      
      String First, End;
      
      while(IndexOfRemove != -1)
      {
         // Get substrings before and after removed part
         First = Source.substring(0, IndexOfRemove);
         End = Source.substring(IndexOfRemove + LengthOfRemove);
         
         // Piece together a new string
         Source = First + Insert + End;
         
         // Calculate IndexOfRemove again
         IndexOfRemove = Source.indexOf( Remove );
      }
      
      // Return the string after replacing is done
      return Source;
   }
   
   /***************************************************************************
   * Write to user terminal
   *
   * @param Data The string to send to the user's terminal.  This method was
   *   created because there are now two basic standard user terminals.  The
   *   ComputerAdvice application, and the web client applet.
   ***************************************************************************/
   private void writeToUser(String Data)
   {
      // Don't print empty strings
      if (Data.equals("") )
         return;
      
      if ( StandardOut == null )
      {
         // The user is using the ComputerAdvice application
         System.out.println(Data);
      }
      else
      {
         // Data = substringReplace(Data, "\t", "           ");
         
         // The user is using the ComputerAdvice web client applet
         try
         {
            StandardOut.println(Data);
            StandardOut.flush();
         }
         catch (Exception E)
         {
            // Error writing to client application
            System.out.println("Error sending data to client web applet");
         }
      }
   }
}

/******************************************************************************
* This class holds information for generic hardware components.
*
* @author Michael Wales <a href="http://www.mwales.net">Homepage</a>
* @version Prototype Class V0.0 
******************************************************************************/
class HWComponent
{
   /** Holds the english language description of the hardware */
   public String Description;
   
   /** The price of the current component */
   public int Price;
   
   /** The level of the current hardware.  1 is the lowest hardware level */
   public int Level;
   
   /***************************************************************************
   * Constructor initializes all the data fields of the class.
   ***************************************************************************/
   public HWComponent()
   {
      Description = "None";
      Price = 0;
      Level = 0;
   }
   
   /***************************************************************************
   * Prints out the information on the piece of hardware, as long as it's 
   * description doesn't equal one (meaning they never bought this component).
   ***************************************************************************/
   public String componentInfo()
   {
      // Don't print non-existing parts
      if(Description.equals("None"))
         return "";
         
      return (Level + "\t$" + Price + "\t" + Description );
   }
}


