/** 
  * Applet to generate a graph of my 'as paid' and my inflation-corrected income
  * @author Robert J Morton <robmorton@clara.net>
  * @version 18 July 2000
  * @copyright July 2000 Robert J Morton (all rights reserved) */

/* I am a fully paid-up Fellow of the Ancient and Noble Order of the Long Term Unemployed 
   so if you know any employer who will employ me as a Java developer please email me.

   This applet require a specialised data files called income.dat located in the same directory
   as income.class and infln.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 income_txtdat.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 graphs 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 income for each year
   int w = 0;                                              //year counter used on horizontal axis of graph
   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 = 45;                                             //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 Yb = 0;                                             //possible upward zero bias
   int n = 40;                                             //off-set from start mark of year to start of its name eg 1999
   int FYB = 6;                                            //number of year from start of graph which is first one for which there is data
   int H = 5;                                              //number of horizontal pixels per year
   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;                                             //reference for off-screen image buffers
   Graphics ga;                                            //graphics context for the off-screen image
   CheckboxGroup CBG, CBH;                                 //checkbox group for the following
   Checkbox CB[];                                          //array in which to put check box reference
   graphbut IL[];                                          //array in which to put item listener references
   int NB = 10;                                            //total number of buttons (max is 10)
   String decade[] = {"1960s", "1970s", "1980s", "1990s"}; //decade labels
   String income[][] = {                                   //income £ scale labels
      {" £0k", "£10k", "£20k", "£30k", "£40k", "£50k"},          //<param name="scale" value="0" />
      {" £0k", " £5k", "£10k", "£15k", "£20k", "£25k"},          //<param name="scale" value="1" />
      {" £0k", " £2k", " £4k", " £6k", " £8k", "£10k"},          //<param name="scale" value="2" />
      {" £0k", " £1k", " £2k", " £3k", " £4k", " £5k"},          //<param name="scale" value="3" />
      {" £0k", " £½k", " £1k", "£1½k", " £2k", "£2½k"},          //<param name="scale" value="4" />
      {" £0k", "£200", "£400", "£600", "£800", " £1k"},          //<param name="scale" value="5" />
      {"£10k", " £0k", "£10k", "£20k", "£30k", "£40k"},          //<param name="scale" value="6" />
      {"£30k", "£20k", "£10k", " £0k", "£10k", "£20k"},          //<param name="scale" value="7" />
      {"  £0", " £50", "£100", "£150", "£200", "£250"},          //<param name="scale" value="8" />
      {"£10", "  £0", "£10", "£20", "£30", "£40"},               //<param name="scale" value="9" />
      {"0M", "1M", "2M", "3M", "4M", "5M"},                      //<param name="scale" value="10" />
      {"00M", "10M", "20M", "30M", "40M", "50M"}                 //<param name="scale" value="11" />
   };
   int SD[] = {25000, 12500, 5000, 2500, 1250, 500, 25000, 25000, 125, 25, 2500, 25000};     //scaling divisors
   int YB[] = {0, 0, 0, 0, 0, 0, 40, 120, 0, 40, 0, 0};    //Y-bias for when Y-scale starts at less than zero
   int sd;                                                 //scaling divisor in use
   int sf = 0;                                             //scaling factor
   String FN;                                              //name of current data file
   boolean showac = false, showic = true;                  //switches to show actual and inflation-corrected traces
   boolean ready = false;                                  //set true when initialization thread completed (re Navigator lock-ups)
   String DF[] = {
      "file0", "file1", "file2", "file3", 
      "file4", "file5", "file6"
   };
   String BL[] = {
      "label0", "label1", "label2", "label3", 
      "label4", "label5", "label6",
      "show original amount only",
      "show inflation-corrected",
      "show both together"
   };
   int nf = 6;                                             //number of dat files used by this instance of this applet

   public void init() {                                    //INITIALISE THE APPLET
      TH = new Thread(this);                               //create a thread for downloading data from server
      TH.start();                                          //start the loading thread
      TH.suspend();                                        //suspend it until underlying applet initialization completes
      bg1 = getBackground();                               //get background colour of host document
      setBackground(bg1);                                  //set the applet's background colour
      setLayout(null);                                     //allow the control panels to be laid out manually
      img1 = createImage(260, 250);                        //Create image in which to build the graph
      ga = img1.getGraphics(); 		                   //capture its graphics context
      ac = getAppletContext();                             //get details of HTML document this applet is running in

      nf = Integer.valueOf(getParameter("files"))          //get number of dat files from HTML parameter
           .intValue();
      if(nf > 7) nf = 7;                                   //not room to accommodate more than 7 graph buttons
      sf = Integer.valueOf(getParameter("scale"))          //get scale indicator parameter
           .intValue();
      sd = SD[sf];                                         //set up the appropriate scaling divisor
      Yb = YB[sf];
      boolean DB[] = {
         false, false, false, false,                       //default button states
         false, false, false,
         false, true,  false                               //show inflation-corrected trace by default
      };
      boolean BR[] = {                                     //which buttons are required
         false, false, false, false, 
         false, false, false,
         true,  true,  true                                //all 3 buttons in lower group always required
      };
      if(sf > 9) {                                         //if a non-money scale were specified
         BR[7] = false; BR[8] = false; BR[9] = false;      //don't create the 3 inflation radio buttons
         showic = false; showac = true;                    //show only the main (non-inflation-corrected) trace
      } else {
         showic = true; showac = false;                    //show only the inflation-corrected trace by default
      }
      for(int i = 0; i < nf; i++) {
         DF[i] = getParameter(DF[i]);                      //get name of data file for each trace
         BL[i] = getParameter(BL[i]);                      //substitute the required button labels
         BR[i] = true;                                     //set up which buttons are required in upper group
      }
      try {
         int df = Integer.valueOf(getParameter("default")) //specified default radio button
                  .intValue();
         DB[df] = true;                                    //set specified default radio button
         FN = DF[df];                                      //set the default data file name
      } catch(Exception e) {                               //or if parameter not present
         DB[nf - 1] = true;                                //set last radio button as default
         FN = DF[nf - 1];                                  //set the default data file name
      }
      CBG = new CheckboxGroup();                           //create checkbox group for radio buttons
      CBH = new CheckboxGroup();                           //create checkbox group for radio buttons
      CheckboxGroup G = CBG; 
      CB = new Checkbox[NB];                               //array in which to put check box reference
      IL = new graphbut[NB];                               //array in which to put item listener references
      int y = 15;                                          //vertical position of first button in group
      for(int i = 0; i < NB; i++) {
         if(i > 6) {                                       //if now beyond max of 7 upper group buttons
            y = 30;                                        //vertical position of first button in lower group
            G = CBH;                                       //add rest of buttons to lower group
         }   
         if(BR[i]) {                                       //if this button is required
            CB[i] = new Checkbox(BL[i], G, DB[i]);         //create next button 
            add(CB[i]);                                    //add it to the applet panel
            CB[i].setBounds(255, y + i * 20, 165, 15);     //set its size and position
            IL[i] = new graphbut(i, this);                 //create an event listener for it
            CB[i].addItemListener(IL[i]);                  //add listener to event handler list
         }
      }
      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;                                        //thread latch = initialization thread finished
   }


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


   public void update(Graphics g) {                        //DISPLAY GRATICULE, SCALES AND TRACES
      ga.setColor(bg1); ga.fillRect(0, 0, 260, 250);       //clear the graph area
      ga.setColor(bg2); ga.fillRect(X, Y-200, 200, 200);   //clear the graph area
      w = 0;                                               //set to first year
      for(int i = 0; i < decade.length; i++) {             //for each year in current decade
         ga.setColor(Color.black);                         //set pen (foreground) colour
         ga.drawString(decade[i], X + 8 + w, Y + 20);      //decade label
         ga.drawLine(X + w, Y + 5, X + w, Y + 10);         //decade boundary mark
         ga.setColor(bg3);
         ga.drawLine(X + w, Y, X + w, Y - 200);            //draw vertical graticule lines
         w += 10 * H;                                      //jump across 10 years
      }
      ga.drawLine(X + w, Y, X + w, Y - 200);               //draw final vertical graticule line
      ga.setColor(Color.black);
      ga.drawLine(X + w, Y + 5, X + w, Y + 10);            //draw final year boundary mark
      ga.drawLine(X, Y + 5, X + w, Y + 5);                 //draw horizontal axis

      int z = 0;                                           //vertical graticule increment
      for(int i = 0; i < 6; i++) {                         //for each £10,000 graduation
         if(sf == 6 && i < 1 || sf == 7 && i < 4 || sf == 9 && i < 1) 
            ga.setColor(Color.red);                        //show negative money as red
         else
            ga.setColor(Color.black);
         ga.drawString(income[sf][i], X - n, Y + 5 - z);   //vertical annotation
         ga.setColor(Color.black);
         ga.drawLine(X - 10, Y - z, X - 5, Y - z);         //vertical annotation mark
         ga.setColor(bg3);
         ga.drawLine(X, Y - z, X + w, Y - z);              //final horizontal annotation mark
         z += 40;                                          //accumulated number of £s (scaled)
      }
      ga.setColor(Color.black);
      ga.drawLine(X - 5, Y, X - 5, Y - 200);               //vertical axis

      if(lp == 3) {                                        //provided data download completed
         try {                                             //try for IOException
            D.reset();                                     //reset byte array pointer to start of data
            int py = 0, piy = 0;                           //y-values of previous real and inflated plots
            boolean pr = false, pi = false;                //logic latch for start of valid plots
            int p = X + FYB * H;                           //horizontal pixel position of start of graph
            w = 0;                                         //initial year number
            try {                                          //try for EOFException
               while(true) {                               //infinite loop broken only by try failure
                  double x = (double)D.readInt();          //read next input integer into a floating-point double
                  int r = (int)(x / sd);                   //real current value scaled to pixels
                  int y = Y - Yb - r;                      //convert to reverse vertical pixel position
                  int ph = p - H;                          //previous value of p
                  if(w > 0 && pr && x != 0 && showac) {    //miss out first value because we are drawing from it
                     ga.setColor(Color.blue);              //set trace colour to blue
                     ga.drawLine(ph - H, py, ph, py);      //draw horizontal part of previous year's value
                     ga.drawLine(ph, py, ph, y);           //draw vertical join to this year's value
                     ga.drawLine(ph, y, p, y);             //draw horizontal part of this year's value
                  }
                  py = y;                                  //note income for next pass
                  if(r != 0) pr = true;                    //logic latch for start of valid plots
                  int i = (int)(infln.getValue(x, w) / sd);//find amount in £2K and scale to pixels
                  int iy = Y - Yb - i;                     //convert to reverse vertical pixel position
                  if(w > 0 && pi && x != 0 && showic) {    //skip first pass
                     ga.setColor(Color.cyan);              //set trace colour to cyan
                     ga.drawLine(ph - H, piy, ph, piy);    //draw horizontal part of previous year's value
                     ga.drawLine(ph, piy, ph, iy);         //draw vertical join to this year's value
                     ga.drawLine(ph, iy, p, iy);           //draw horizontal part of this year's value
                  }
                  piy = iy;                                //note inflation-corrected income for next pass
                  if(i != 0) pi = true;                    //logic latch for start of valid plots
                  w++;                                     //increment the year number
                  p += H;                                  //advance to next year's horizontal pixel position
               }
            } catch(EOFException f) {}                     //terminate loop when attempt is made to read past end of byte array
         } catch(Exception e) {}                           //readByte() can throw an IOException or ArrayOutOfBoundsException
      }
      g.drawImage(img1, 0, 5, null);                       //screen the completed graph
      showStatus(E);                                       //to confirm that data was loaded from server OK
   }


   public void start() {
         TH.resume();                                      //resume the loading thread if suspended
   }

   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() { 
         TH.suspend();                                     //freeze execution of thread while away from host HTML page
   }


   public void destroy() {                                 //called after stop() before applet is removed from memory
      if(TH != null) {                                     //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) {                                          //provided we are absolutely sure initialization is complete
         try {
            url = new URL(cb + FN + ".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 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 getData(int i) {
      if(i < nf) {
         FN = DF[i];                                       //get the name of the required data file
         lp = 1;                                           //initiate the loading sequence
         LP = 0;                                           //prime the loading sequence latch
      }
   }

   void setActual()   {showac = true;  showic = false; repaint();}
   void setInflated() {showac = false; showic = true;  repaint();}
   void setBoth()     {showac = true;  showic = true;  repaint();}

}



class graphbut implements ItemListener {                   //LISTENS FOR EVENTS FROM THE YEAR SELECTOR CHECKBOXES

   int id;                                                 //one of the above events
   graphs ap;                                              //the application that called: always the above applet!

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

   public void itemStateChanged(ItemEvent e) {             //a checkbox event has occurred from checkbox 'id'
      switch(id) {
         case 0: ap.getData(0); break;
         case 1: ap.getData(1); break;
         case 2: ap.getData(2); break;                     //checkbox on whose instance of 'graphbut' this method was invoked.
         case 3: ap.getData(3); break;                     //Execute the method in the above applet which deals with the
         case 4: ap.getData(4); break;
         case 5: ap.getData(5); break;
         case 6: ap.getData(6); break;

         case 7: ap.setActual(); break;
         case 8: ap.setInflated(); break;
         case 9: ap.setBoth(); 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  */
}