// no package, stand alone Applet

/*
 * (C) ELRIDEV SOFTWARE, 1999. ALL RIGHTS RESERVED.
 * THIS SOFTWARE IS THE PROPERTY OF ELRIDEV SOFTWARE, YOU CAN USE THIS SOFTWARE
 * ON YOUR WEBPAGE (OR USE THE SOURCE FOR LEARNING JAVA), IT PROVIDED AS AN EXAMPLE,
 * WITH NO WARRANTIES WHATSOEVER, SHOULD ANY DAMAGE OCCUR FROM THE USE OF THIS
 * PRODUCT ELRIDEV WILL BE HELD IN NO WAY LIABLE FOR DAMAGES.
 * YOU CAN ALSO MODIFY THIS CODE FOR YOU OWN PURPOSES, HOWEVER ONCE THIS IS DONE
 * YOU MUST INDICATE THAT THESE CHANGES HAVE BEEN MADE, SHOULD YOU WISH IT REMAIN
 * FREELY AVAILABLE.
 */

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.net.*;
import java.io.*;

/**
 * Scrolling Applet, you can put a top scroll object directly onto a web
 * page, its a standalone object. It will scroll text from right to left
 * and add a distortion effect to the page. The speed is adjustable from
 * 1 very slow thru 10 very fast (4 is default) using the SPEED parameter in the HTML file.
 * Set the Text filename and Image Filename  parameters from your HTML page.
 * The Text parmeter is TEXTFILE and should be set to the file name relative to
 * the html file's path. The Image parameter is IMAGENAME and should be a .gif
 * file of 256 colours or less, again this is relative to the html file. The
 * font must be fixed width and height. If the font width and height are not 20 pixels per
 * letter, you must set these too, the parameters are FONTWIDTH and FONTHEIGHT.
 * ALWAYS make the width divisible by the font width, and the height is
 * twice the size of the letters.
 *
 * @author Dave Cherry
 * @version Thor
 */
public class TopScroll extends Applet implements Runnable
{
    // the thread class of this instance.
	Thread	 m_TopScroll = null;

    // the maximum number of letters in the font
	private final int		finMaxLetters = 50;
    // the default scroll text name
	private final String	finDefImgName = "scrollfont.gif";
    // the maximum size of the text file containing scroller.
    private final int   fiMaxScrollTextBuffer = 10000;

	// scroll sizing specific data
	private String	m_strLetters;
	private int		m_iSmoothScroll = 0;
	private int		m_iNextCharPos = 0;
	private int		m_iTotalLen;
	private int		m_iTotalWidth;
	private int		m_iLetterWidth = 20, m_iLetterHeight = 20, m_iLetterSize;
    private int     m_iDistortX, m_iDistortY, m_iDistortSize;

	// the font image & bits.
	private IndexColorModel m_palColour;
	private byte[][]  m_byPixels;

	// the actual double buffered screens, letter storage and distortion store
	private Image	m_imgBuffer;
	private byte[]  m_byScroller;
	private int[]   m_distortion;
	private int[]		imgData;
	private int[]		destData;
	private Image totalBuffer;
	private Graphics gfxBuffer;

    // these are the scroll factors
	private int posscroller = 400;
	private int posscrolleradj=10;
    private int smoothadjust=4;


   /**
    * TopScroll constructor, empty, actually uses start for thread creation.
    */
	public TopScroll()
	{
	}

   /**
    * Returns the applet information
    * @return Applet information string
    */
	public String getAppletInfo()
	{
		return "Name: TopScroll V1.0\r\n" +
		       "Author: ElriDEV Software";
	}

    protected String readTextFile(String name) throws Exception
    {
        if(name==null || name.length()==0) return "";
        StringBuffer strOutput = new StringBuffer(fiMaxScrollTextBuffer);
        URL url = new URL(getDocumentBase(),name);
        URLConnection urlCon = url.openConnection();
        InputStreamReader file = new InputStreamReader(new BufferedInputStream(urlCon.getInputStream()));
        int i=0;
        while(file.ready()&&i<fiMaxScrollTextBuffer)
        {
            char chRead = (char)file.read();
            if(chRead!=-1&&chRead!='\n'&&chRead!='\r')
            {
                strOutput.append(chRead);
                i++;
            }
        }
        return strOutput.toString();
    }

  /**
   * initialises the scroller and sets up the instance data.
   */
	public void init()
	{
		try
		{
            // make our window the right size, and size up the variables.
	    	int width = getSize().width;
		    m_iTotalWidth=width + m_iLetterWidth;

            // get the image file name, if its not found, use the default.
            String strImage = getParameter("IMAGENAME");
            if(strImage==null) strImage = finDefImgName;

            m_strLetters = readTextFile(getParameter("TEXTFILE"));
	    	if(m_strLetters.length()==0)
            {
                m_strLetters = "ELRIDEV SCROLLER V1.0. SET TEXTFILE AND "+
                    "IMAGENAME PARAMETERS BEFORE USE.        ";
            }

            // load image file and wait for it to complete
			showStatus("One moment please...");
			Image imgFontSource = getImage(getDocumentBase(),strImage);
			MediaTracker tracker = new MediaTracker(this);
			tracker.addImage(imgFontSource,0);
			tracker.waitForAll();
            if(tracker.isErrorAny())
            {
                showStatus("Unable to load the image file");
                stop();
                return;
            }

            // get the width and height of the font.
            String strDim = getParameter("FONTWIDTH");
            if(strDim!=null) m_iLetterWidth=Integer.parseInt(strDim);
            strDim = getParameter("FONTHEIGHT");
            if(strDim!=null) m_iLetterHeight=Integer.parseInt(strDim);
            strDim = getParameter("SPEED");
            if(strDim!=null)
            {
                smoothadjust=Integer.parseInt(strDim);
                posscrolleradj=smoothadjust*2;
            }
            // calculate total letter sizes and distortion sizes.
            m_iLetterSize = m_iLetterWidth*m_iLetterHeight;
            m_iDistortX   = m_iLetterWidth*8;
            m_iDistortY   = m_iLetterHeight*2;
            m_iDistortSize= (m_iDistortX)*(m_iDistortY);
            m_distortion  = new int[m_iDistortSize];
            destData    = new int[m_iDistortSize];
            imgData     = new int[m_iDistortSize];

            // generate the 256 color mapped verison of the scroll font
            byte[] reds = new byte[256];
            byte[] greens = new byte[256];
            byte[] blues = new byte[256];
            m_byPixels = new byte[finMaxLetters][m_iLetterSize];
            getIndexColourData(imgFontSource,m_byPixels,reds,greens,blues);
            m_palColour = new IndexColorModel(8,256,reds,greens,blues);

            // make our double buffer space and distortion buffers
            m_imgBuffer = createImage(m_iTotalWidth,m_iLetterHeight*2);
            Graphics gb=m_imgBuffer.getGraphics();
            gb.setColor(Color.white);
            gb.fillRect(0,0,m_iTotalWidth,m_iLetterHeight*2);
            generateDistortion();
            totalBuffer = createImage(m_iTotalWidth,m_iLetterHeight*2);
            gfxBuffer = totalBuffer.getGraphics();
            m_iTotalLen = m_strLetters.length();
        }
        catch(Exception e)
        {
            // something went wrong
            showStatus("Error occurred during startup, stopped applet.");
            e.printStackTrace();
            stop();
        }
	}

    /**
     * We handle update so that drawing is smoother and doesnt flash,
     * this function directly calls paint(Graphics g);
     * @param g the awt graphics device
     */
    public void update(Graphics g)
    {
        paint(g);
    }

    /**
     * draws the current buffer that was created by the last call
     * to the scrollit() function.
     * @param g the awt graphics device.
     */
	public void paint(Graphics g)
	{
		g.drawImage(totalBuffer,0,0,Color.white,this);
	}

    /**
     * after scrolling the contents by calling StepScroll the scrollit function
     * places the distortion effect onto the current image.
     */
	protected void scrollit()
	{
        // do the scroll
		StepScroll();

		try
		{
            // get the current bits in the area to be distorted, and distort them
			PixelGrabber pg	= new PixelGrabber(m_imgBuffer, posscroller+m_iSmoothScroll, 0, m_iDistortX, m_iDistortY, imgData, 0, m_iDistortX);
			pg.grabPixels();
			for(int i=0;i<m_iDistortSize;i++)
			{
				destData[i]=imgData[m_distortion[i]];
			}

            // create a memory image of the distored bits
			ColorModel realModel = new DirectColorModel(24,255,255,255);
			Image imgDt = createImage(new MemoryImageSource(m_iDistortX,
															m_iDistortY,
															realModel,
															destData,
															0,
															m_iDistortX));

            // onto our buffer, draw the scroller, then the distortion.
			gfxBuffer.drawImage(m_imgBuffer,-m_iSmoothScroll,0,Color.white,this);
			gfxBuffer.drawImage(imgDt,posscroller,0,Color.white,this);

            // increment the position of the distortion if its past the end or begining
            // then reverse the direction
			posscroller += posscrolleradj;
			if(posscroller<0||posscroller>=m_iTotalWidth-m_iDistortX) posscrolleradj=-posscrolleradj;
		}
		catch(InterruptedException e)
		{
            // something odd happened here!!
			showStatus("Couldnt draw the scroll text!");
		}
	}

    /**
     * create a new thread for our Applet to run in
     */
	public void start()
	{
		if (m_TopScroll == null)
		{
			m_TopScroll = new Thread(this);
			m_TopScroll.start();

		}
	}

    /**
     * stop our applets thread, its done!
     */
	public void stop()
	{
		if (m_TopScroll != null)
		{
			m_TopScroll.stop();
			m_TopScroll = null;
		}
	}

    /**
     * just stays in a loop moving the scroller to its new location, then doing
     * a repaint and waiting 1/60th of a second before repeating.
     */
	public void run()
	{
		while (true)
		{
			try
			{
				scrollit();
				repaint();
				// wait 1/40th of a second.
				Thread.sleep(25);
			}
			catch (InterruptedException e)
			{
				stop();
			}
		}
	}

    /**
     * Given a character value, this function will convert this value into an
     * index position into the image file containing the chars
     * @param letter the letter to be converted
     * @return the index into the image file.
     */
	protected int GetNextCharIndex(char letter)
	{
		if(letter>='A'&&letter<='Z') return letter-'A';
		else if(letter>='0'&&letter<='9') return letter-17;
		else if(letter=='.') return 26;
		else if(letter==',') return 27;
		else if(letter=='\'') return 28; // quotation mark
		else if(letter=='`') return 29; // quote close.
		else if(letter=='{') return 30; // smiley face.
		else if(letter=='!') return 41;
		else if(letter=='-') return 42;
		else if(letter=='+') return 43;
		else if(letter=='?') return 44;
		else if(letter=='}') return 45; // (c)
		else if(letter=='@') return 46; // @
		else if(letter==':') return 47; // :
		else if(letter=='/') return 48; // /
		else if(letter=='\\') return 49; // \
		else return -1; // -1==space in string.
	}

    /**
     * move the scroller one position, if the scroller has moved by one
     * full letter position, then a new letter is added and the smooth
     * scroll offset is reset.
     */
	protected void StepScroll()
	{
		if(m_iSmoothScroll>=m_iLetterWidth) // at the end of a scroll.
		{
			m_iSmoothScroll=0;
			int letter = GetNextCharIndex(m_strLetters.charAt(m_iNextCharPos++));
			if(m_iNextCharPos>=m_iTotalLen) m_iNextCharPos=0;

			// coarse scroll 1 letter.
			Graphics g = m_imgBuffer.getGraphics();
			g.copyArea(m_iLetterWidth,0,m_iTotalWidth-m_iLetterWidth,m_iLetterHeight*2,-m_iLetterWidth,0);
			g.setColor(Color.white);
			g.fillRect(m_iTotalWidth-m_iLetterWidth,0,
						m_iLetterWidth,m_iLetterHeight*2);
			if(letter!=-1)
			{
				Image imgFontSource = createImage(new MemoryImageSource(m_iLetterWidth,
																		m_iLetterHeight,
																		m_palColour,
																		m_byPixels[letter],
																		0,
																		m_iLetterWidth));

				g.drawImage(imgFontSource,m_iTotalWidth-m_iLetterWidth,m_iLetterHeight/2,Color.white,this);
			}
		}
		else	// need to scroll again.
		{
			m_iSmoothScroll+=smoothadjust;
		}
	}

    /**
     * converts an image containing the letters into a 256 colour version, thats split
     * up by letter rather than by line.
     * @param img the image file to be converted
     * @param byPixels the pixel array where each image will be stored
     * @param byReds the color index for reds
     * @param byGreens the color index for greens
     * @param byBlues the color index for blues
     */
    protected void getIndexColourData(Image img, byte[][] byPixels, byte[] byReds, byte[] byGreens, byte[] byBlues)
	{
		int nWidth			= img.getWidth(this);
		int nHeight			= img.getHeight(this);
		int nPaletteIndex = 0;
		int[] nPixels		= new int[nWidth * nHeight];
		int[] nPalette		= new int[256];

        // get the pixels into memory.
		PixelGrabber pg	= new PixelGrabber(img, 0, 0, nWidth, nHeight, nPixels, 0, nWidth);
		try
		{
			pg.grabPixels();
		}
		catch (InterruptedException e)
		{
		}

		// Loop through each pixel in the image...
		for (int letter = 0; letter < finMaxLetters; letter++)
		{
			int strpos=0;
			for (int y=0;y<m_iLetterHeight;y++)
			{
				for (int dx=0;dx<m_iLetterWidth;dx++)
				{
					int nIndex = -1;
					int Rx = dx+(m_iLetterWidth*letter);
					// Search for the palette index of the current pixel's color...
					for (int i = 0; i <= nPaletteIndex; i++)
					{
						if ( nPalette[i] == nPixels[y * nWidth + Rx] )
						{
							nIndex = i;
							break;
						}
					}

					// If the current pixel's color is not in the palette, add it
					// to the palette.
					if ( nIndex == -1 )
					{
						nPalette[nPaletteIndex] = nPixels[y * nWidth + Rx];
						nIndex = nPaletteIndex;
						nPaletteIndex++;
					}

					// Change the pixel from a color value to an index value.
                    if((strpos+1)>m_iLetterSize) System.out.println("letter:"+letter+" index error");
					m_byPixels[letter][strpos++] = (byte) nIndex;
				}
			}
		}

		for (int i = 0; i < 256; i++)
		{
			int r = (nPalette[i] >> 16) & 0xff;
			int g = (nPalette[i] >> 8 ) & 0xff;
			int b = (nPalette[i]	  ) & 0xff;

			byReds[i]	= (byte)r;
			byGreens[i] = (byte)g;
			byBlues[i]	= (byte)b;
		}
	}

    /**
     * generates the distortion effect that is rolled across the scrolling
     * text, this is calculated once at the start, and held in memory.
     * the distortion is stored as an array of offsets into a memory
     * representation of an image.
     */
    protected void generateDistortion()
    {
		// now correct them to the screen
		int yCenter=m_iDistortY/2;

		double multiplier = 360. / (double)m_iDistortX;
		int distsize = m_iDistortY;
		for(int x=0;x<m_iDistortX;x++)
		{
			double degRads=((x*multiplier)/180.)*Math.PI;
			double scale = (Math.sin(degRads) + 1.3);
			if(scale==0) scale=0.001;
			double lineskip = (double)m_iLetterHeight/((double)m_iLetterHeight*scale);
			double startpos;
			if(scale>=1.)
				startpos = ((double)(m_iLetterHeight/4.)*scale)-2.;
			else
				startpos = ((double)20-(10*lineskip))/2.;
			for(int y=0;y<distsize;y++)
			{
					if(startpos<0.||startpos>39.)
						m_distortion[x+(y*m_iDistortX)]=0;
					else
						m_distortion[x+(y*m_iDistortX)]=x+((int)startpos*m_iDistortX);
					startpos+=lineskip;
			}
		}
	}

}
