/** 
  * Current Account Balances: Graphical Display Applet
  * @author Robert J Morton <robmorton@clara.net>
  * @version 12 July 2000
  * @copyright July 2000 Robert J Morton (all rights reserved) */

/* This applet requires specialised data files called savyyyy.dat
   (where yyyy is the year number) located in the same directory 
   as savlim1.class and savlim2.class on the web server. It requires 
   no server-side executables or scripts. The data files are generated
   from input text files by an off-line command-line java program 
   called savlim0.class. This applet conforms to API 1.1  */


import java.applet.*;       //all the gubbins to make the applet work
import java.awt.*;          //for graphics operations (GUI)
import java.awt.event.*;    //for the new-fangled 1.1 event handling
import java.net.*;          //for downloading data from the remote server
import java.io.*;           //for stream handling for the above


public class savlim1 extends Applet implements Runnable {
   AppletContext ac;                      //get details of HTML document this applet is running in
   String cb;                             //code base URL - where this applet's class file came from
   URL url;                               //url of current HTML file 
   InputStream I;                         //input stream for downloading data files
   int L = 0;                             //length of the remote item being loaded
   int l = 0;                             //number of bytes of the above successfully downloaded so far
   byte B[];                              //gigantic byte array to hold the downloaded data
   DataInputStream D;                     //to get input data on the account balances for each day
   int w = 0;                             //days counter used on horizontal axis of graph
   int m = 0;                             //today's balance in integral pence
   int year = 2001;                       //year for inflation calculation
   Thread TH;                             //reference for a separate Internet Data Transfer thread
   int lp = 1, LP = 0;                    //indicates current and previous download phases
   String E;                              //for Exception during downloading + method where it occurred
   int X = 25;                            //horizontal bias from left edge of graph image to start of x-axis
   int Y = 205;                           //vertical bias from top edge of applet to start of y-axis
   int n = 25;                            //off-set from start mark of year to start of its name eg 1999
   int H = 0;                             //savings penalty thresold
   boolean inflate = false;               //show normal or inflated balances 
   String yearFile = "2001";              //name of year data file
   boolean leap = true;                   //indicates a leap year
   boolean ready = false;                 //to make sure init thread finished before starting data laod
   Color bg1 = new Color(210, 210, 210);  //main background colour
   Color bg2 = new Color(208, 176, 176);  //graph background colour ideally with a 24-bit system
   Color bg3 = new Color(128, 128, 128);  //graticule colour
   Image img1, img2;                      //references for off-screen image buffers
   Graphics ga, gb;                       //graphics context for the off-screen images

   String month[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
   int days[]   = {  31,    28,    31,    30,    31,    30,    31,    31,    30,    31,    30,    31  };   //weeks in year
   String hits[] = {"0k", "1k", "2k", "3k", "4k", "5k"};   //balance £ scale

   Checkbox      //checkboxes for selecting year to display
      CB00,      //1991
      CB01,      //1992
      CB02,      //1993
      CB03,      //1994
      CB04,      //1995
      CB05,      //1996
      CB06,      //1997
      CB07,      //1998
      CB08,      //1999
      CB09,      //2000
      CB10,      //2001
      CB11,      //display actual balances
      CB12;      //display inflation-corrected balances
	
   CheckboxGroup CBG, CBI;              //checkbox group for the above


   public void init() {
      TH = new Thread(this);            //create a thread for downloading data from server
      TH.start();                       //start the loading thread
      try {
         TH.suspend();                  //suspend it until underlying applet initialization completes
      } catch(SecurityException e) {}   //fuck browser security
      bg1 = getBackground();            //get background of host document
      setBackground(bg1);               //set the applet's background colour
      setLayout(null);                  //allow the control panels to be laid out manually
      ac = getAppletContext();          //get details of HTML document this applet is running in

      CBG = new CheckboxGroup();        //create checkbox group for radio buttons

      CB00 = new Checkbox("1991", CBG, false);
      CB01 = new Checkbox("1992", CBG, false);
      CB02 = new Checkbox("1993", CBG, false);
      CB03 = new Checkbox("1994", CBG, false);
      CB04 = new Checkbox("1995", CBG, false);
      CB05 = new Checkbox("1996", CBG, false);
      CB06 = new Checkbox("1997", CBG, false);
      CB07 = new Checkbox("1998", CBG, false);
      CB08 = new Checkbox("1999", CBG, false);
      CB09 = new Checkbox("2000", CBG, false);
      CB10 = new Checkbox("2001", CBG, true);

      int k = 20, j = 35;               //set the shape of the check box and its label

      add(CB00); CB00.setBounds(410, j, 50, 15); j += k;
      add(CB01); CB01.setBounds(410, j, 50, 15); j += k;
      add(CB02); CB02.setBounds(410, j, 50, 15); j += k;
      add(CB03); CB03.setBounds(410, j, 50, 15); j += k;
      add(CB04); CB04.setBounds(410, j, 50, 15); j += k;
      add(CB05); CB05.setBounds(410, j, 50, 15); j += k;
      add(CB06); CB06.setBounds(410, j, 50, 15); j += k;
      add(CB07); CB07.setBounds(410, j, 50, 15); j += k;
      add(CB08); CB08.setBounds(410, j, 50, 15); j += k;
      add(CB09); CB09.setBounds(410, j, 50, 15); j += k;
      add(CB10); CB10.setBounds(410, j, 50, 15); j += k;

      CBI = new CheckboxGroup();        //create checkbox group for radio buttons
      CB11 = new Checkbox("Display actual balances.", CBI, true);
      CB12 = new Checkbox("Display balances inflation-corrected to £Y2K.", CBI, false);
      add(CB11); CB11.setBounds(  5, 265, 170, 15);
      add(CB12); CB12.setBounds(180, 265, 270, 15);

      //CREATE A LISTENER FOR EACH YEAR RADIO BUTTON
      savlim2  y00 = new savlim2(savlim2.Y1991, this); CB00.addItemListener(y00);
      savlim2  y01 = new savlim2(savlim2.Y1992, this); CB01.addItemListener(y01);
      savlim2  y02 = new savlim2(savlim2.Y1993, this); CB02.addItemListener(y02);
      savlim2  y03 = new savlim2(savlim2.Y1994, this); CB03.addItemListener(y03);
      savlim2  y04 = new savlim2(savlim2.Y1995, this); CB04.addItemListener(y04);
      savlim2  y05 = new savlim2(savlim2.Y1996, this); CB05.addItemListener(y05);
      savlim2  y06 = new savlim2(savlim2.Y1997, this); CB06.addItemListener(y06);
      savlim2  y07 = new savlim2(savlim2.Y1998, this); CB07.addItemListener(y07);
      savlim2  y08 = new savlim2(savlim2.Y1999, this); CB08.addItemListener(y08);
      savlim2  y09 = new savlim2(savlim2.Y2000, this); CB09.addItemListener(y09);
      savlim2  y10 = new savlim2(savlim2.Y2001, this); CB10.addItemListener(y10);

      //CREATE A LISTENER OF EACH INFATION BUTTON
      savlim2 y11 = new savlim2(savlim2.no,  this); CB11.addItemListener(y11);
      savlim2 y12 = new savlim2(savlim2.yes, this); CB12.addItemListener(y12);

      img1 = createImage(392, 230);                 //Create image in which to build the graph
      ga = img1.getGraphics(); 		            //capture its graphics context
      img2 = createImage(392, 20);                  //Create image in which to build the average balance message
      gb = img2.getGraphics();                      //capture its graphics context
      cb = getCodeBase().toString();                //URL (less file name) from where this applet came
      if(!cb.endsWith("/")) {                       //workaround re Hotjava re Microsoft intranet machine names
         int x = cb.lastIndexOf('/');               //where getCodeBase() wrongly seems to return the
         if(x != -1) cb = cb.substring(0, x + 1);   //document base (cira Nov 1999)
      }
      lp = 1; LP = 0;                               //indicates current and previous download phases
      ready = true;                                 //signal thread finished (for Navigator's benefit)
   }


   public void paint(Graphics g) { update(g); }     //paint/repaint the graph


   public void update(Graphics g) {
      ga.setColor(bg1); ga.fillRect(0, 0, 392, 230);       //clear the image buffer area
      ga.setColor(bg2); ga.fillRect(X, Y-200, 366, 200);   //clear the image buffer area
      w = 0;                                               //set to first day of the year
      for(int i = 0; i < month.length; i++) {              //for each month shown
         ga.setColor(Color.black);                         //set pen (foreground) colour
         ga.drawString(month[i], X + 6 + w, Y + 20);       //month name
         ga.drawLine(X + w, Y + 5, X + w, Y + 10);         //month boundary mark
         ga.setColor(bg3);
         ga.drawLine(X + w, Y, X + w, Y - 200);            //vertical graticule lines
         w += days[i]; if(i == 1 && leap) w++;             //accumulated number of days
      }
      ga.drawLine(X + w, Y, X + w, Y - 200);               //final vertical graticule line
      ga.setColor(Color.black);
      ga.drawLine(X + w, Y + 5, X + w, Y + 10);            //final year boundary mark
      ga.drawLine(X, Y + 5, X + w, Y + 5);                 //horizontal axis

      int z = 0;                                           //vertical graticule increment
      for(int i = 0; i < hits.length; i++) {               //for each £1000 graduation
         ga.setColor(Color.black);
         ga.drawString(hits[i], X - n, Y + 5 - z);         //vertical annotation
         ga.drawLine(X - 10, Y - z, X - 5, Y - z);         //vertical annotation mark
         ga.setColor(bg3);                                 //make all others light grey
         ga.drawLine(X, Y - z, X + w, Y - z);              //final horizontal annotation mark
         z += 40;                                          //accumulated number of £s (scaled)
      }
      H = 300000;                                          //set penalty threshold
      if(inflate) H = infln.getInt(H, year);               //compute inflated penalty threshold
      H /= 2500;
      ga.setColor(Color.red);                              //set colour for penalty threshold
      ga.drawLine(X, Y - H, X + w, Y - H);                 //draw savings penalty threshold

      ga.setColor(Color.black);
      ga.drawLine(X - 5, Y, X - 5, Y - 200);               //vertical axis

      if(lp == 3) {                                        //provided appropriate data download completed
         try {                                             //try for IOException
            D.reset();                                     //reset byte array pointer to start of data
            w = X;                                         //initial x-bias for day number
            int prevy1 = 0, prevy2 = 0;                    //previous day's balance for each account
            int a = 0, max = 0, min = 2000000;             //prime total, maximum and minimum account balances (integral pence)
            try {                                          //try for EOFException
               while(true) {                               //infinite loop broken only by try failure
                  m = 0;                                   //today's balances
                  prevy1 = plot(prevy1, Color.blue);       //plot my account only
                  prevy2 = plot(prevy2, Color.cyan);       //plot mine + ruby's balance
                  a += H;                                  //accumulate total pence
                  if(max < H) max = H;                     //trap maximum balance
                  if(min > H) min = H;                     //trap minimum balance
                  w++;                                     //increment the day number
               }
            } catch(EOFException f) {                      //terminate loop when attempt is made to read past end of byte array
            } finally {
               gb.setColor(bg1); gb.fillRect(0,0,392,20);  //clear the average balance panel
               gb.setColor(Color.black);                   //display average balance for the year in black
               gb.drawString(
                  "Average £" + money((int)((double)a / (double)(w - X))) + 
                  "  Max £" + money(max) + 
                  "  Min £" + money(min) + 
                  "  Swing £" + money(max - min),
                  0, 17);
            }
         } catch(IOException e) {}                         //readByte() can throw an IOException also
      }

      g.drawImage(img1, 5, 30, null);                      //screen the completed graph
      g.drawImage(img2, 5, 0, null);                       //screen the completed average banalce message
      g.drawString("YEAR", 420, 18);
      showStatus(E);
   }


   private int plot(int prev, Color c) throws IOException {
      int x = (int)D.readInt();                            //read account's next day's balance from the input array
      if(x == 0) return x;                                 //don't plot zero balances
      m += x;
      if(inflate)                                          //if inflation radio button checked
         H = infln.getInt(m, year);                        //compute inflated balance
      else                                                 //otherwise, normal radio button is checked
         H = m;                                            //so use normal balance
      int h = H / 2500;                                    //scale to pixels
      if(h < 0) h = -1;                                    //don't plot below the zero line
      if(h > 200) h = 201;                                 //don't plot above top of graph
      int y = Y - h;                                       //off-set it from Y-axis zero
      if(w > X && (y != prev || (y == prev && h > -1 && h < 201))) {                                          //skip for first value
         ga.setColor(c);                                   //set trace colour to blue
         ga.drawLine(w - 1, prev, w, y);                   //join previous plot to this day's balance
      }
      return y;                                            //return balance for next pass
   }


   String money(int x) {                 //MONEY DISPLAY FORMATTER (takes integral pence)
      double X = (double)x; X /= 100;    //form into decimal pounds
      String s = "" + X;                 //form into a string
      int c = '.';                       //decimal point character
      int i = s.lastIndexOf(c);          //find position of decimal point in string
      if(i == -1) s += ".00";            //if no decimal point found
      else {
         int e = s.length() - i;         //how far decimal point is from end of string
         if(e == 1) s += "00";           //add two zeros after decimal point
         else if(e == 2) s += "0";       //add only one zero after decimal point
      }
      return s;                          //return the formatted string
   }


   public void start() {
      try {
         TH.resume();                           //resume the loading thread if suspended
      } catch(SecurityException e) {}           //fuck browser security
   }

   public void run() {                          //MANAGE INTERNET DATA TRANSFERS ON A SEPARATE THREAD
      while(TH.isAlive()) {                     //while this thread exists
         switch(lp) {                           //THE 2 DOWNLOADING PHASES
            case 1: fileConnect(); break;       //connect to index resource on server
            case 2: fileLoad(); break;          //manage the downloading of its content
            case 3:
            if(lp != LP) {                      //if loading of data has finished
               LP = lp;                         //latch the current load state
               repaint();                       //display the graph
            }
         }
         try {
            Thread.currentThread().sleep(250);  //sleep to allow other things to take place
         } catch (InterruptedException e) {}    //catch interrupt from GUI or browser
      }
   }

   public void stop() { 
      try {
         TH.suspend();                          //freeze execution of thread while away from host HTML page
      } catch(SecurityException e) {}           //fuck browser security
   }


   public void destroy() {                      //called after stop() before applet is removed from memory
      if(TH.isAlive()) {                        //if the Internet data transfer thread object still exists
         TH.stop();                             //stop it running
         TH = null;                             //and jettison its object into limbo
      }
   }


   void fileConnect() {                                    //CONNECT TO THE HITS DATA FILE ON THE SERVER
      if(ready) {                                          //if we're sure init thread finished (re Navigator lockups)
         try {
            url = new URL(cb + "sav" + yearFile + ".dat"); //form the url of the account balance data
            URLConnection u = url.openConnection();        //open a connection to the remote file
            L = u.getContentLength();                      //length of the remote file (bytes)
            l = 0;                                         //number of bytes so far successfully downloaded
            I = u.getInputStream();                        //create an input stream object to access the file
            B = new byte[L];                               //create the gigantic buffer for the data
            D = new DataInputStream(new ByteArrayInputStream(B));
            lp = 2;                                        //advance to the file loading phase
         } catch(Exception e) {
            lp = 0;                                        //set unrecoverable error status
            E = "fileConnect() " + e;                      //note the exception and where it occurred
         } 
      }
   }


   void fileLoad() {                                       //DOWNLOAD THE HITS DATA
      int k;                                               //current byte being read()
      try {                                                //if read() hasn't hit the current end of input stream
         while(l < L && (k = I.read()) != -1)              //and the entire file has not yet been downloaded
            B[l++] = (byte)k;                              //add its new byte to the array big byte array
         if(l >= L) {                                      //if the whole of the index has now been downloaded
            I.close();                                     //close the URL Connection's input stream
            lp = 3;                                        //indicate file loading completed successfully
            E = "Graph data loaded.";
         }
      } catch(Exception e) { 
         lp = 0;                                           //set error condition
         E = "fileLoad() " + e + L + " " + l;              //note the exception and where it occurred
      }
   }

   void get1991() {yearFile = "1991"; lp = 1; LP = 0; leap = false; year = 1991;}
   void get1992() {yearFile = "1992"; lp = 1; LP = 0; leap = true;  year = 1992;}
   void get1993() {yearFile = "1993"; lp = 1; LP = 0; leap = false; year = 1993;}
   void get1994() {yearFile = "1994"; lp = 1; LP = 0; leap = false; year = 1994;}
   void get1995() {yearFile = "1995"; lp = 1; LP = 0; leap = false; year = 1995;}
   void get1996() {yearFile = "1996"; lp = 1; LP = 0; leap = true;  year = 1996;}
   void get1997() {yearFile = "1997"; lp = 1; LP = 0; leap = false; year = 1997;}
   void get1998() {yearFile = "1998"; lp = 1; LP = 0; leap = false; year = 1998;}
   void get1999() {yearFile = "1999"; lp = 1; LP = 0; leap = false; year = 1999;}
   void get2000() {yearFile = "2000"; lp = 1; LP = 0; leap = true;  year = 2000;}
   void get2001() {yearFile = "2001"; lp = 1; LP = 0; leap = false; year = 2001;}

   void normal()  {inflate = false; repaint();}
   void inflate() {inflate = true;  repaint();}
}



class savlim2 implements ItemListener {   //LISTENS FOR EVENTS FROM THE YEAR SELECTOR CHECKBOXES
   static final int Y1991 = 0;            //an event from the '1991' checkbox
   static final int Y1992 = 1;            //an event from the '1992' checkbox
   static final int Y1993 = 2;            //an event from the '1993' checkbox
   static final int Y1994 = 3;            //an event from the '1994' checkbox
   static final int Y1995 = 4;            //an event from the '1995' checkbox
   static final int Y1996 = 5;            //an event from the '1996' checkbox
   static final int Y1997 = 6;            //an event from the '1997' checkbox
   static final int Y1998 = 7;            //an event from the '1998' checkbox
   static final int Y1999 = 8;            //an event from the '1999' checkbox
   static final int Y2000 = 9;            //an event from the '2000' checkbox
   static final int Y2001 =10;            //an event from the '2001' checkbox
   static final int no   = 11;            //an event from the 'no' checkbox
   static final int yes  = 12;            //an event from the 'yes' checkbox
   int id;                                //one of the above events
   savlim1 ap;                            //the application that called: always the above applet!

   public savlim2(int id, savlim1 ap) {   //constructor for a new checkbox event
      this.id = id;                       //set id number pertaining to this instance of 'savlim2'
      this.ap = ap;                       //set reference to the instance of the applet from
   }                                      //which it came. (there'll only be one instance anyway).

   public void itemStateChanged(ItemEvent e) {  //entered when a checkbox event has occurred
      switch(id) {
         case Y1991: ap.get1991(); break; //Execute the method in the above
         case Y1992: ap.get1992(); break; //applet which deals with the
         case Y1993: ap.get1993(); break; //checkbox on whose instance of
         case Y1994: ap.get1994(); break; //savlim2 this method was invoked.
         case Y1995: ap.get1995(); break;
         case Y1996: ap.get1996(); break;
         case Y1997: ap.get1997(); break;
         case Y1998: ap.get1998(); break;
         case Y1999: ap.get1999(); break;
         case Y2000: ap.get2000(); break;
         case Y2001: ap.get2001(); break;
         case no:    ap.normal();  break;
         case yes:   ap.inflate(); break;
      }
   }
}


/*  Robert J Morton, the author of this program, 
    is a poor but Right Honourable Member of the
    Ancient and Noble Order of the Long-term Unemployed.

    Offers of work please to: robmorton@clara.net  */