/*
 ********************************************************************************
 * This applet implements the Cable TV Consumer Preference model presented      *
 * in Example 2 on p.91 of Larson and Edwards' book, Elementary Linear Algebra, *
 * 4th Ed., Houghton-Mifflin (2000)                                             * 
 *                                                                              *
 * Author : Tom Kimber                                                          *
 * Date   : 2 January 2001                                                      *
 ********************************************************************************
 */
import java.awt.* ;
import java.applet.* ;
import java.awt.event.* ;
import java.util.* ;
public class tvcpmApplet extends Applet implements ActionListener
{
// --------- Variables ------------
   Matrix mat = new Matrix( 3 , 3 , 0.0 ) ;
   Matrix vec = new Matrix( 3 , 1 , 0.0 ) ;
   int intNumYears ;
// --------- GUI elements ---------
   MatrixPanel pnlMatrix = new MatrixPanel( "Transition Matrix:" , mat ) ;
   MatrixPanel pnlVector = new MatrixPanel( "Initial States:" , vec ) ;
   LabelTextBox txtNumYears = new LabelTextBox( "Number of Years:" ) ;
   Label lblActions = new Label( "Actions:" , Label.CENTER ) ;
   Button bttnCalculate = new Button( "Calculate" ) ;
   Button bttnDefaults = new Button( "Restore Default Inputs" ) ;
   Button bttnClear = new Button( "Clear Output" ) ;
   Label lblOutput = new Label( "Model Output:" , Label.CENTER ) ;
   TextArea txtOutput = new TextArea( "Year | Company A | Company B | No cable \n-----|-----------|-----------|---------" , 20 , 40 ) ;
   public void init( ) 
   {  // First layout GUI:
      setBackground( Color.cyan ) ;
      setLayout( new BorderLayout( ) ) ;
      Panel pnlNorth = new Panel( ) ;
      pnlNorth.setLayout( new GridLayout( 1 , 3 ) ) ;
      pnlNorth.add( pnlMatrix ) ;
      pnlNorth.add( pnlVector ) ;
      pnlNorth.add( txtNumYears ) ;
      add( BorderLayout.NORTH , pnlNorth ) ;
      Panel pnlWest = new Panel( ) ;
      Panel pnlButtons = new Panel( ) ;
      pnlButtons.setLayout( new GridLayout( 4 , 1 ) ) ;
      pnlButtons.setFont( new Font( "Monospaced" , Font.PLAIN , 12 ) ) ;
      pnlButtons.add( lblActions ) ;
      Panel pnlCalculate = new Panel( ) ;
      pnlCalculate.add( bttnCalculate ) ;
      pnlButtons.add( pnlCalculate ) ;
      Panel pnlDefaults = new Panel( ) ;
      pnlDefaults.add( bttnDefaults ) ;
      pnlButtons.add( pnlDefaults ) ;
      Panel pnlClear = new Panel( ) ;
      pnlClear.add( bttnClear ) ;
      pnlButtons.add( pnlClear ) ;
      pnlWest.add( pnlButtons ) ;
      add( BorderLayout.WEST , pnlWest ) ;     
      Panel pnlCenter = new Panel( ) ;
      pnlCenter.setLayout( new BorderLayout( ) ) ;
      pnlCenter.setFont( new Font( "Monospaced" , Font.PLAIN , 12 ) ) ;
      txtOutput.setBackground( Color.white ) ;
      pnlCenter.add( BorderLayout.NORTH , lblOutput ) ;
      Panel pnlOutputText = new Panel( ) ;
      pnlOutputText.add( txtOutput ) ;
      pnlCenter.add( BorderLayout.CENTER , pnlOutputText ) ;
      add( BorderLayout.CENTER , pnlCenter ) ;
// now set initial input values ... 
      RestoreDefaultInputs( ) ; 
// ...  and register the buttons to listen for clicks
      bttnCalculate.addActionListener( this ) ;
      bttnDefaults.addActionListener( this ) ;
      bttnClear.addActionListener( this ) ;
   }
   public void actionPerformed( ActionEvent e ) 
   {
      Object src = e.getSource( ) ;
      if ( src == bttnDefaults ) 
      {
         RestoreDefaultInputs( ) ;
      }
      if ( src == bttnCalculate ) 
      {
         Calculate( ) ;
      }
      if ( src == bttnClear )
      {
         ClearOutput( ) ;
      }
   }
   private void RestoreDefaultInputs( ) 
   {
      mat.setEntry( 0 , 0 , .70 ) ;
      mat.setEntry( 0 , 1 , .15 ) ;
      mat.setEntry( 0 , 2 , .15 ) ;
      mat.setEntry( 1 , 0 , .20 ) ;
      mat.setEntry( 1 , 1 , .80 ) ;
      mat.setEntry( 1 , 2 , .15 ) ;
      mat.setEntry( 2 , 0 , .10 ) ;
      mat.setEntry( 2 , 1 , .05 ) ;
      mat.setEntry( 2 , 2 , .70 ) ;
      vec.setEntry( 0 , 0 , 15000 ) ;
      vec.setEntry( 1 , 0 , 20000 ) ;
      vec.setEntry( 2 , 0 , 65000 ) ;
      pnlMatrix.updatePanel( mat ) ;
      for ( int i = 0 ; i < vec.getNumRows( ) ; i++ ) 
      {  // display state vector as ints, since these are numbers of people
         pnlVector.setValue( i , 0 , ( int ) vec.getEntry( i , 0 ) ) ;
      }
      txtNumYears.setText( "15" ) ;
   }
   private void Calculate( ) 
   {
      mat.updateFromPanel( pnlMatrix ) ;              // get values from panel into matrix
      vec.updateFromPanel( pnlVector ) ;              // get values from panel into vector
      intNumYears = ( int ) txtNumYears.getValue( ) ; // get number of years from text box
      if ( isInputOK( ) == false ) 
      {
         displayInputErrorMessage( txtOutput ) ;
      }
      else
      {
         ClearOutput( ) ;
         DisplayOutputLine( 0 ) ;                        // display year 0 (initial) system state
         for ( int i = 1 ; i <= intNumYears ; i++ ) 
         {  // loop to calculate and display 
	    // system state for each year
            CalculateNextState( ) ;
            DisplayOutputLine( i ) ;
         }
      }
   }
   private boolean isInputOK( ) 
   {
      int n , m ;
      n = mat.getNumRows( ) ;
      m = mat.getNumCols( ) ;
// check that entries are positive
      for ( int i = 0 ; i < n ; i++ )
      {
         for ( int j = 0 ; j < m ; j++ )
	 {
            if ( mat.getEntry( i , j ) < 0 ) 
            {
               return false ;
            }
	 }
      }
// check that column totals = 1
      double x = 0 ;
      for( int j = 0 ; j < m ; j++ ) 
      {
         x = 0 ;
         for( int i = 0 ; i < n ; i++ )
	 {
            x = x + mat.getEntry( i , j ) ;
	 }
	 if ( Math.abs( x - 1 ) > 0.0001 )
	 {
            return false ;
	 }
      }
// check that the total population doesn't exceed 1,000,000
      n = vec.getNumRows( ) ;
      x = 0 ;
      for ( int i = 0 ; i < n ; i++ )
      {
         x = x + vec.getEntry( i , 0 ) ;  
      }
      if( x > 1000000.0 )
      {
         return false ;
      }
// check that number  of years is <= 100 
      if( intNumYears > 100 )
      {
         return false ;
      }
// getting here means input OK
      return true ;
   }
   private void displayInputErrorMessage( TextArea x ) 
   {
      ClearOutput( ) ;
      x.append( "\n*******************\n*** INPUT ERROR ***\n*******************" ) ;
      x.append( "\n- Matrix entries should be >= 0" ) ;
      x.append( "\n- Column totals should be 1" ) ;
      x.append( "\n- Total population should be <= 1000000" ) ;
      x.append( "\n- Number of years should be <= 100" ) ;
   }
   private void DisplayOutputLine( int year ) 
   {
      TextArea x = txtOutput ;  // just a shorter name ( x ) for the display area ( txtOutput ) 
      int[] vals = new int[ 3 ] ; // array to hold integer values to be displayed
      int[] pipePositions = { 6 , 18 , 30 } ; // these are the 'tab' positions where the pipes ( | ) appear in the output display
      int line_num = 0 , pos = 0 , width = 0 , num_blanks = 0 ;
      line_num = year + 2 ; // the first two lines (i.e., lines 0 and 1 ) of output are for column headings,
                            // so year i state appears on line i + 2 of text area
      width = x.getColumns( ) ;
      for ( int i = 0 ; i < vals.length ;i++ )
      {  // loop to cast values as ints ( and round ) - for display purposes 
         vals[ i ] = ( int ) ( vec.getEntry( i , 0 ) + 0.5 ) ;
      }
      txtOutput.append( "\n" + year ) ;
      for ( int i = 0 ; i < vals.length ; i++ )
      {  // loop to display values, seperate by pipes, at proper places
	 // calculations depend on the # of characters in each line 
	 // being equal to the width of the textarea txtOutput
         pos = x.getCaretPosition( ) ;
         pos = pos - line_num * width ;
         num_blanks = pipePositions[ i ] - pos ;
         displayBlanks( x , num_blanks ) ;
         x.append( "| " + vals[ i ] ) ;
      }
      // now fill rest of line with blanks, so that # chars in line = width of textarea
      pos = x.getCaretPosition( ) ;
      pos = pos - line_num * width ;
      num_blanks = width - pos ;
      displayBlanks( x , num_blanks ) ;
   }
   private void displayBlanks( TextArea x , int n ) 
   {
      for( int i = 0 ; i < n ; i++ )
      {
         x.append( " " ) ;
      }
   }
   private void CalculateNextState( ) 
   {  // next state vector is matrix times current state vector
      vec = MatrixMath.Product( mat , vec ) ;
   }
   private void ClearOutput( ) 
   {
      txtOutput.setText( "Year | Company A | Company B | No cable \n-----|-----------|-----------|---------" ) ;
   }
}