/////////////////////////////////////////////////////////////////////
// SlideScript.java           -- part of SlideViewer Applet v2.1   //
//                                                                 //
// This class is used by the SlideViewer applet to read in the     //
// script that specifies the images to use, and what trasitions    //
// to perform to change from one image to another.  For more       //
// information, check the Java link off of my homepage,            //
//       http://www.cs.hope.edu/~crider/                           //
//                                                                 //
//*****************************************************************//
// Revisions:                                                      //
//    v1.0:     Written July 18 - August 7, 1995                   //
//    v2.0beta: Released January 2, 1996                           //
//    v2.0:     Released February 18, 1996                         //
//    v2.1:     Released March 18, 1996                            //
//              Added ability to target frames.                    //
//              Improved fast-forward control.                     //
//                                                                 //
//*****************************************************************//
// By Mike Crider                                                  //
//       crider@cs.hope.edu                                        //
//       http://www.cs.hope.edu/~crider/                           //
//                                                                 //
// © Copyright 1995 by Mike Crider                                 //
/////////////////////////////////////////////////////////////////////

import java.net.*;
import java.applet.Applet;
import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;

/////////////////////////////////////////////////////////////////////
// Here is what SlideInfo is like (see SlideViewer.java for the
// actual definition):
//class SlideInfo
//{
//   int type;    // Type of transition
//   int dir;     // Direction for the transition
//   int pic;     // Number of the pic for this transition
//   int delay;   // Delay time to use
//   int size;    // jumpSize or pixelSize, depending on type
//   int speed;   // the speed of the transition (only some transitions obey)
//   int amp;     // amplitude -- used by wavy transitions
//   int num;     // some value number--meaning depends on type
//}
/////////////////////////////////////////////////////////////////////

class SlideScript
{
   SlideViewer parent;
   Hashtable TransNames, TransDirections;
   Hashtable picst;
   Vector picnames, piccolors, picurls, pictargets, script;
   Stack repeatstk;
   SlidePic pics[];
   boolean inSkip;
   int picwidth, picheight;
   int numtrans;

   SlideScript(SlideViewer parent)
   {
      this.parent = parent;
   }

   void init_stuff()
   {
      TransNames = new Hashtable(18,1);
      TransNames.put("appear",          new Integer(1));
      TransNames.put("pixelin",         new Integer(2));
      TransNames.put("slide",           new Integer(3));
      TransNames.put("slideOver",       new Integer(4));
      TransNames.put("rollIn",          new Integer(5));
      TransNames.put("rollOut",         new Integer(6));
      TransNames.put("rotate",          new Integer(7));
      TransNames.put("wavyMorph",       new Integer(8));
      TransNames.put("fadeIn",          new Integer(9));
      TransNames.put("fadeOut",         new Integer(10));
      TransNames.put("explode",         new Integer(11));
      TransNames.put("implode",         new Integer(12));
      TransNames.put("wipe",            new Integer(13));
      TransNames.put("stripsOver",      new Integer(14));
      TransNames.put("stripsSlide",     new Integer(15));
      TransNames.put("wavyDelay",       new Integer(97));
      TransNames.put("decayDelay",      new Integer(98));
      TransNames.put("delay",           new Integer(99));
      TransNames.put("repeat",          new Integer(100));
      // The parsing-only commands
      TransNames.put("pic",             new Integer(101));
      TransNames.put("do",              new Integer(102));
      TransNames.put("SKIP",            new Integer(103));
      TransNames.put("ENDSKIP",         new Integer(104));

      TransDirections = new Hashtable(19,1);
      TransDirections.put("left",       new Integer(0));
      TransDirections.put("right",      new Integer(1));
      TransDirections.put("up",         new Integer(2));
      TransDirections.put("top",        new Integer(2));
      TransDirections.put("down",       new Integer(3));
      TransDirections.put("bottom",     new Integer(3));
      TransDirections.put("leftright",  new Integer(4));
      TransDirections.put("updown",     new Integer(5));
      TransDirections.put("topleft",    new Integer(6));
      TransDirections.put("topright",   new Integer(7));
      TransDirections.put("botleft",    new Integer(8));
      TransDirections.put("bottomleft", new Integer(8));
      TransDirections.put("botright",   new Integer(9));
      TransDirections.put("bottomright",new Integer(9));
      TransDirections.put("center",     new Integer(10));

      // These are for 'rotate'
      TransDirections.put("horiz",      new Integer(0));
      TransDirections.put("horizontal", new Integer(0));
      TransDirections.put("vert",       new Integer(1));
      TransDirections.put("vertical",   new Integer(1));

      picst = new Hashtable(5,2);
      picnames = new Vector(5,2);
      piccolors = new Vector(5,2);
      picurls = new Vector(5,2);
      pictargets = new Vector(5,2);

      repeatstk = new Stack();

      script = new Vector(10,2);
      numtrans = 0;

      // Not in a skip section
      inSkip = false;

      // No picture sizes yet
      picwidth = -1;
      picheight = -1;
   }

   void parseLine(String s)
   {
      try
      {
         SlideInfo t;
         Integer ti;
         int i;
         boolean accept;        // Whether or not to accept the transition
         String tmp, name, val;

         // Remove leading/trailing spaces
         s = s.trim();

         // Ignore comments
         if (s.charAt(0) == '!')
            return;

         t = new SlideInfo();

         // Get the transition type
         tmp = getWord(s,1);
         ti = (Integer)TransNames.get(tmp);
         if (ti == null)
         {
            System.out.println("Unknown transition '" + tmp + "' on line '" + s + "'.");
            return;
         }
         t.type = ti.intValue();

         // If we are in a SKIP block and this isn't an ENDSKIP command,
         // then just forget about parsing the line
         if (inSkip && t.type != 104)
            return;

         t.pic = -1;
         t.delay = 100;
         t.size = 4;
         t.num = 1;

         accept = true;         // Assume transition is good
         switch (t.type)
         {
            case 1:  // appear
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;
                     break;
            case 2:  // pixelin
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",100);
                     t.size = getParamPosInt(s,"pixelSize",4);

                     break;
            // cases 3 through 6 are the same parameter-wise
            case 3:  // slide
            case 4:  // slideOver
            case 5:  // rollIn
            case 6:  // rollOut
                     t.dir = getDirNum(getParam(s,"dir"));
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",50);
                     t.speed = getParamPosInt(s,"speed",4);

                     break;
            case 7:  // rotate
                     t.dir = getDirNum(getParam(s,"dir"));
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",50);
                     t.size = getParamPosInt(s,"strip",2);
                     t.speed = getParamPosInt(s,"speed",4);

                     break;
            case 8:  // wavyMorph
                     t.dir = getDirNum(getParam(s,"dir"));
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",50);
                     t.size = getParamPosInt(s,"strip",4);
                     t.amp = getParamInt(s,"amplitude",20);
                     t.speed = getParamPosInt(s,"speed",5);
                     t.num = getParamPosInt(s,"replace",5);
                     if (t.num < 1)
                        t.num = 1;

                     break;
            case 9:  // fadeIn
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",50);
                     t.speed = getParamPosInt(s,"speed",4);

                     break;
            case 10: // fadeOut
                     t.delay = getParamInt(s,"delay",50);
                     t.speed = getParamPosInt(s,"speed",4);

                     break;
            case 11: // explode
            case 12: // implode
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.delay = getParamInt(s,"delay",100);
                     t.speed = getParamPosInt(s,"speed",3);

                     break;
            case 13: // wipe
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.dir = getDirNum(getParam(s,"pivot"));
                     t.delay = getParamInt(s,"delay",100);
                     t.speed = getParamPosInt(s,"speed",5);
                     t.size = getParamPosInt(s,"strip",3);
                     t.num = getParamInt(s,"clockwise",1);
                     if (t.num != -1 && t.num != 1)
                        t.num = 1;

                     break;
            case 14: // stripsOver
            case 15: // stripsSlide
                     t.pic = getPicNum(getParam(s,"pic"));
                     if (t.pic == -1) accept = false;

                     t.dir = getDirNum(getParam(s,"dir"));
                     t.delay = getParamInt(s,"delay",100);
                     t.speed = getParamPosInt(s,"speed",10);
                     t.size = getParamPosInt(s,"strip",3);
                     t.amp = getParamPosInt(s,"step",6);
                     t.num = getParamInt(s,"type",0);

                     break;
            case 97: // wavyDelay
                     tmp = getWord(s,2);        // The second word is the delay time
                     if (tmp != null)
                        t.num = Integer.parseInt(tmp);
                     else
                        t.num = 1000;
                     t.dir = getDirNum(getParam(s,"dir"));
                     t.delay = getParamInt(s,"delay",50);
                     t.size = getParamPosInt(s,"strip",5);
                     t.speed = getParamPosInt(s,"theta",t.size*8);
                     t.amp = getParamInt(s,"amplitude",20);
                     t.pic = -1;
                     break;
            case 98: // decayDelay
                     tmp = getWord(s,2);        // The second word is the delay time
                     if (tmp != null)
                        t.num = Integer.parseInt(tmp);
                     else
                        t.num = 1000;
                     t.delay = getParamInt(s,"delay",50);
                     t.speed = getParamPosInt(s,"speed",10);
                     t.size = getParamPosInt(s,"size",10);
                     t.amp = getParamInt(s,"gravity",3);
                     t.pic = -1;
                     break;
            case 99: // delay
                     tmp = getWord(s,2);        // The second word is the delay time
                     if (tmp != null)
                        t.delay = Integer.parseInt(tmp);
                     else
                        t.delay = 1000;
                     t.pic = -1;
                     break;

            case 100: // repeat
                     if (repeatstk.empty())
                     {
                        System.out.println("Error:  No matching 'do' command for 'repeat'");
                        accept = false;
                     }
                     else
                     {
                        t.num = ((Integer)repeatstk.pop()).intValue();
                        t.amp = getParamInt(s,"times",1);
                        t.size = t.amp;         // Set # of times next time to full times
                     }
                     break;

            //////////////////////////////////////////////////////////////////
            // The rest are used ONLY for parsing                           //
            //////////////////////////////////////////////////////////////////
            case 101: // pic
                     {
                        String key, url, target, col, pgurl;
                        key = getParam(s,"key");    // Get the key used to reference the pic
                        url = getParam(s,"url");    // Get the url for the pic, if any
                        col = getParam(s,"rgb");    // Get the color for the pic, if any
                        pgurl = getParam(s,"page"); // Get the page url for clicking, if any
                        target = getParam(s,"target");// Get the target for the page, if any
                        if (target == null)
                           target = "_self";
                        // Get rid of "" in target, if any:
                        target = (target.replace('"',' ')).trim();

                        if (key == null)
                           System.out.println("Invalid pic line.  Must specify 'key'");
                        if (url == null && col == null)
                           System.out.println("Invalid pic line.  Must specify 'url' or 'rgb'");
                        else
                        {
                           picnames.addElement(url);
                           piccolors.addElement(col);
                           picurls.addElement(pgurl);
                           pictargets.addElement(target);
                           picst.put(key,new Integer(picnames.size()-1));
                        }
                     }
                     break;
            case 102: // do
                     repeatstk.push(new Integer(script.size()));
                     break;
            case 103: // SKIP
                     inSkip = true;
                     break;
            case 104: // ENDSKIP
                     if (!inSkip)
                        System.out.println("Error:  No matching 'SKIP' command for 'ENDSKIP'");
                     inSkip = false;
                     break;
         }
         
         // Ensure that the delay is o.k.
         // I have 5 milliseconds as a minimum because java, Alpha3 at least,
         // sometimes becomes overly intent on letting the run() proc in the
         // applet take control forever without ever calling the update()
         // procedure (despite the repaint() call).
         if (t.delay < 5)
            t.delay = 5;

         if (t.type > 0 && t.type <= 100 && accept)
         {
            script.addElement(t);
            numtrans++;
         }

         if (accept == false)
            System.out.println("Improper script line:  '" + s + "'");
      }
      catch (Exception e)
      {
         System.out.println("Invalid script line:  '" + s + "'");
      }
   }

   String getWord(String s, int wordnum)
   {
      int i;

      if (s == null)
         return null;

      i = 0;

      // Skip to the start of the word
      while (wordnum > 1)
      {
         i = s.indexOf(' ');
         if (i == -1)
            return null;

         s = s.substring(i+1);
         wordnum--;
      }

      // Find the end of the word
      i = s.indexOf(' ');
      if (i == -1)
         i = s.length();

      return s.substring(0,i);
   }

   String getParam(String s, String param)
   {
      int i;
      String name, val;

      // Parameters must have an '=' after them.
      param = param.concat("=");

      i = s.indexOf(param);
      if (i == -1)      // Then it isn't in there!
         return null;

      // Set 'name' equal to the param=val string there
      s = s.substring(i);
      name = getWord(s,1);

      // Default value, so we know param was in there with no val
      val = "";

      // Set val if it has a val
      if (name.indexOf('=') != -1)
         val = name.substring(name.indexOf('=')+1);

      return val;
   }

   int getParamInt(String s, String param, int dflt)
   {
      String val = getParam(s,param);
      if (val == null || val.equals(""))
         return dflt;
      else
         return Integer.parseInt(val);
   }

   int getParamPosInt(String s, String param, int dflt)
   {
      int i = getParamInt(s,param,dflt);
      if (i < 1)
         i = 1;
      return i;
   }

   //////////////////////////////////////////////////////////////////
   // Get all of the raw images and find out the maximum width and //
   // height.  processImages() makes the real ones, padded as      //
   // necessary.                                                   //
   //////////////////////////////////////////////////////////////////
   void getImages()
   {
      Graphics g;
      String name;
      int i;

      g = parent.getGraphics();
      g.clipRect(1,1,parent.size().width-2,parent.size().height-2);

      pics = new SlidePic[picnames.size()];
      for (i = 0; i < picnames.size(); i++)
      {
         parent.setStatus("Loading image "+i+" of "+picnames.size());

         name = (String) picnames.elementAt(i);

         pics[i] = new SlidePic();

         // Set the url to go to when clicked, if any
         pics[i].url = (String)picurls.elementAt(i);
         pics[i].target = (String)pictargets.elementAt(i);

         if (name == null)  // Then this one will be a solid color
         {
            pics[i].gif = null;
            continue;
         }

         pics[i].gif = parent.getImage(parent.getDocumentBase(),name);

         if (pics[i].gif == null)
         {
            // This one will be a solid color
            System.out.println("Image not found:  '" + name + "'");
            continue;
         }

         name = (String) picnames.elementAt(i);

//         System.out.println("Preparing: " + name);
         // Ask it to get me the image now!
         parent.prepareImage(pics[i].gif, parent);
         if ((parent.checkImage(pics[i].gif, parent) & ImageObserver.ERROR) != 0)
         {
            // This one will be a solid color
            System.out.println("Bad Image, or Image not found:  '" + name + "'");
            continue;
         }

         if (i == 0)
         {
            g.drawImage(pics[i].gif,1,1,parent);
            parent.setCurpic(pics[i].gif);
         }

         while((parent.checkImage(pics[i].gif, parent) & ImageObserver.ALLBITS) == 0)
         {
            if ((parent.checkImage(pics[i].gif, parent) & ImageObserver.ERROR) != 0)
            {
               // This one will be a solid color
               System.out.println("Bad Image, or Image not found:  '" + name + "'");
               break;
            }
            if (pics[i].gif == null)
               break;
            if (i == 0)
            {
               g.drawImage(pics[i].gif,1,1,parent);
               parent.setCurpic(pics[i].gif);
            }

//            System.out.println("Waiting for image: " + name);
            try { Thread.sleep(600); } catch (Exception e) { }
         }
         if (i == 0)
         {
            g.drawImage(pics[i].gif,1,1,parent);
            parent.setCurpic(pics[i].gif);
         }
//         System.out.println("Got it: " + name);
      }
      parent.setStatus("");

      // Use the applet size for the image sizes
      picwidth = parent.size().width;
      picheight = parent.size().height;
   }

   //////////////////////////////////////////////////////////////////
   // Process the Images taken in getImages().  Processing         //
   // includes padding the image if it is smaller than the maximum //
   // size, as well as creating a full image of one color if there //
   // was not a url supplied (or an invalid one).                  //
   //////////////////////////////////////////////////////////////////
   void processImages()
   {
      Graphics pixmap;
      String name;
      int i;

      for (i = 0; i < pics.length; i++)
      {
         int x, y;
         Color c;

         // If gif is null, then this is a solid color image
         if (pics[i].gif == null)
         {
            try
            {
               pics[i].gif = parent.createImage(picwidth,picheight);
            }
            catch(Exception e)
            {
               System.out.println("Error: Could not create image in SlideScript");
               continue;
            }
            pixmap = pics[i].gif.getGraphics();
            c = setcolor((String) piccolors.elementAt(i),Color.black);
            pixmap.setColor(c);
            pixmap.fillRect(0,0,picwidth,picheight);
         }

         // If the picture isn't big enough, we need to pad it out
         if (pics[i].gif.getWidth(parent) != -1 && pics[i].gif.getHeight(parent) != -1)
            if (pics[i].gif.getWidth(parent) != picwidth || pics[i].gif.getHeight(parent) != picheight)
            {
               Image tmpimg;

               try
               {
                  tmpimg = parent.createImage(picwidth,picheight);
               }
               catch(Exception e)
               {
                  System.out.println("Could not create image for padding in SlideScript");
                  continue;
               }
               pixmap = tmpimg.getGraphics();
               c = setcolor((String) piccolors.elementAt(i),Color.black);
               pixmap.setColor(c);
               pixmap.fillRect(0,0,picwidth,picheight);

               x = (picwidth - pics[i].gif.getWidth(parent))/2;
               y = (picheight - pics[i].gif.getHeight(parent))/2;

               pixmap.drawImage(pics[i].gif,x,y,parent);

               pics[i].gif = tmpimg;
            }
      }

      // Don't need these anymore
      picnames.removeAllElements();
      piccolors.removeAllElements();
      System.out.println("Processed Images.");
   }

   public Color setcolor(String s, Color dflt)
   {
      if (s == null)
      {
         return dflt;
      }
      else
      {
         int i, j;
         int r,g,b;
         try
         {
            i = s.indexOf(',');
            j = s.indexOf(',',i+1);
            r = Integer.parseInt(s.substring(0,i));
            g = Integer.parseInt(s.substring(i+1,j));
            b = Integer.parseInt(s.substring(j+1));
         }
         catch (Exception e)
         {
            System.out.println("Invalid 'rgb' specification.");
            return dflt;
         }
         return new Color(r,g,b);
      }
   }

   int getPicNum(String picname)
   {
      Integer i;

      if (picname == null)
         return -1;

      i = (Integer)picst.get(picname);
      if (i != null)
         return i.intValue();
      else
         return -1;
   }

   int getDirNum(String dirname)
   {
      Integer i;

      if (dirname == null)
         return 0;              // Default to 0

      i = (Integer)TransDirections.get(dirname.toLowerCase());
      if (i != null)
         return i.intValue();
      else
         return 0;              // Default to 0
   }

   void readScript(URL scripturl)
   {
      String s;
      DataInputStream f;

      init_stuff();

      try
      {
         f = new DataInputStream(scripturl.openStream());
      }
      catch (Exception e)
      {
         System.out.println("SlideViewer Error:  Could not open script file.");
         return;
      }

      try
      {
         s = f.readLine();
         while (s != null)
         {
            if (s.length() > 0)
               parseLine(s);
            System.out.println("Finished Parsing: " + s);
            s = f.readLine();
            System.out.println("Did another read");
         }
         f.close();
      }
      catch (Exception e)
      {
         System.out.println("SlideViewer Error: Problems reading/closing script file.");
         return;
      }

      // Print out an error message if we are in a SKIP section and there
      // was no ENDSKIP command.
      if (inSkip)
         System.out.println("Error:  No matching 'ENDSKIP' command for 'SKIP'");

      System.out.println("getting images");
      try
      {
         getImages();
      } catch (Exception e)
      {
         System.out.println("Error getting images");
         return;
      }
      System.out.println("got images");
      processImages();
      parent.pics = pics;
      parent.picwidth = picwidth;
      parent.picheight = picheight;
   }

   SlideInfo elementAt(int num)
   {
      return (SlideInfo) script.elementAt(num);
   }
}
