import java.applet.*;
///////////////////////////////////////////////////////////

// "Flipper" Demo

// by Steve Crutchfield (stevec@jhu.edu)

///////////////////////////////////////////////////////////



import java.awt.*;

import java.awt.image.*;

import java.applet.*;

import java.util.Vector;

import java.net.*;
import java.io.*;

///////////////////////////////////////////////////////////

// The Flipper class is the applet itself.

///////////////////////////////////////////////////////////



public class Flipper extends Applet

{

	ConvPanel fConvPanel;

   boolean fSigChanged = false;



	public void init()

   {

//      setLayout(new GridLayout(5, 1, 5, 5));
     GridBagLayout gridBag = new GridBagLayout();
     setLayout(gridBag);

     GridBagConstraints c = new GridBagConstraints();
     c.fill = GridBagConstraints.BOTH;
     c.weightx = c.weighty = 1.0;
     c.insets = new Insets(3, 3, 3, 3);

     ControllableSignal signalX = new ControllableSignal("x(t)", "t", Color.blue, this);
     c.gridwidth = GridBagConstraints.REMAINDER;
     gridBag.setConstraints(signalX, c);

     fConvPanel = new ConvPanel(signalX.getSigPanel());
     gridBag.setConstraints(fConvPanel, c);

	 add(signalX);
	 add(fConvPanel);
   }

	ConvPanel getConvPanel()
	{
		return fConvPanel;
	}

   public void sigChanged()

   {

   	fConvPanel.sigChanged();

   }

}



///////////////////////////////////////////////////////////

// The SigPanel class handles the actual drawing

// operations, and deals with user mouse actions.

///////////////////////////////////////////////////////////



class SigPanel extends Panel

{

	Signal signal;

   boolean setup = false;
	boolean fDrawTicks;
   String fName;
	String fLabel;

   Color fColor;

	Image fImage;
	Graphics fGraphics;

   public SigPanel(String name, String label, Color color, boolean drawTicks)

   {

   	setBackground(Color.white);

      fName = name;
		fLabel = label;

      fColor = color;
	fDrawTicks = drawTicks;
   }



   public void paint(Graphics g)

   {

   	if (!setup)

      {
	fImage = createImage(bounds().width, bounds().height);
	fGraphics = fImage.getGraphics();
      	signal = new Signal(fName, fLabel, bounds().width, bounds().height, fColor, fDrawTicks);

         setup = true;

      }


	fGraphics.clearRect(0, 0, bounds().width, bounds().height);
      signal.Draw(fGraphics);
	g.drawImage(fImage, 0, 0, this);
   }

	public void update(Graphics g)
	{
		paint(g);
	}

   public Signal getSignal()

   {

   	return signal;

   }



   public void doPulse()

   {

   	signal.doPulse();

      repaint();

   }

	public void doDoublePulse()
	  {
	    signal.doDoublePulse();
	    repaint();
	  }

   public void doDecay()

   {

   	signal.doDecay();

      repaint();

   }



   public void setSignal(int[] newSig)

   {
   	if (!setup)

      {

      	signal = new Signal(fName, fLabel, bounds().width, bounds().height, fColor, true);

         setup = true;

      }

   	signal.set(newSig);

      repaint();

   }



   public void clear()

   {

      // the user clicked the "Clear" button

      signal.zero();

      repaint();

   }

	public Insets insets()
	  {
	    return new Insets(5, 5, 5, 5);
	  }

	public synchronized Dimension minimumSize()
	  {
	    return new Dimension(100, 100);
	  }
}



class ControllableSignal extends Panel

{

	MouseSigPanel fSigPanel;



	ControllableSignal(String name, String label, Color color, Flipper applet)

	{

   	setLayout(new BorderLayout());

      add("Center", fSigPanel = new MouseSigPanel(name, label, color, applet));

      add("South", new SigControls(fSigPanel, applet));

   }



   SigPanel getSigPanel()

	{

   	return fSigPanel;

   }

}



///////////////////////////////////////////////////////////

// The SigControls class deals with the buttons

// displayed by the applet.

///////////////////////////////////////////////////////////



class SigControls extends Panel

{

	boolean setup = false;

	SigPanel fSigPanel;

   SigButton fPulseButton, fDoublePulseButton, fDecayButton, fClearButton;



   public SigControls(SigPanel panel, Flipper applet)

   {

   	fSigPanel = panel;



   	setLayout(new GridLayout(1, 5, 5, 5));



  	 	// add the controls



      add(new Label("Select:"));

      

      fPulseButton = new SigButton("pulse", Color.blue, fSigPanel, applet);

   	add(fPulseButton);


	fDoublePulseButton = new SigButton("double pulse", Color.red, fSigPanel, applet);
	add(fDoublePulseButton);


      fDecayButton = new SigButton("decay", new Color(0, 128, 0), fSigPanel, applet);

      add(fDecayButton);



      fClearButton = new SigButton("clear", Color.black, fSigPanel, applet);

      add(fClearButton);

   }



   public Insets insets()

   {

   	return new Insets(3, 3, 3, 3);

   }

}



class SigButton extends SigPanel

{

	SigPanel fSigPanel;

   boolean buttonSetup = false;

	Flipper fApplet;



	public SigButton(String name, Color color, SigPanel panel, Flipper applet)

   {

   	super(name, "", color, false);

      fSigPanel = panel;

      fApplet = applet;

   }



	public void paint(Graphics g)

   {

      super.paint(g);



   	if (!buttonSetup)

      {

   		if (fName.equals("pulse"))

     	 		doPulse();

      	else if (fName.equals("decay"))

      		doDecay();

	else if (fName.equals("double pulse"))
	  doDoublePulse();

         buttonSetup = true;

		}

   }

	public boolean mouseDown(Event evt, int x, int y)

   {

   	if (fName.equals("pulse"))

      	fSigPanel.doPulse();

      else if (fName.equals("decay"))

      	fSigPanel.doDecay();

	else if (fName.equals("double pulse"))
	  fSigPanel.doDoublePulse();
      else if (fName.equals("clear"))

      	fSigPanel.clear();

      fApplet.sigChanged();



     	return true;

   }



   public synchronized Dimension minimumSize()

   {

   	return new Dimension(20, 20);

   }



   public synchronized Dimension preferredSize()

   {

   	return new Dimension(50, 20);

   }

}



class MouseSigPanel extends SigPanel

{

   int prevX = 0;

   int prevY = 0;






   Flipper fApplet;



   public MouseSigPanel(String name, String label, Color color, Flipper applet)

   {

   	super(name, label, color, true);

      fApplet = applet;

   }



   public void paint(Graphics g)

   {

   	if (!setup)

      {

      	signal = new Signal(fName, fLabel, bounds().width,

         	bounds().height, fColor, true);

         setup = true;

      }



      signal.Draw(g);

   }

	public void update(Graphics g)
	{
		g.clearRect(0, 0, bounds().width, bounds().height);
		paint(g);
	}

   public boolean mouseDown(Event evt, int x, int y)

   {

   	signal.Modify(x, y);

      prevX = x;

      prevY = y;

      repaint();

      fApplet.sigChanged();

      return true;

   }



	public boolean mouseDrag(Event evt, int x, int y)

   {

   	signal.Drag(prevX, prevY, x, y);

   	prevX = x;

   	prevY = y;

   	repaint();

      fApplet.sigChanged();

   	return true;

   }

   public synchronized Dimension minimumSize()
     {
       return new Dimension(100, 100);
     }


   public synchronized Dimension preferredSize()
     {
       return new Dimension(100, 100);
     }

}


class ConvPanel extends Panel

{

   SigPanel fXPanel;

   boolean setup = false;

   boolean fSigChanged = true;

   int fSigLength, fAmplitude, fOffset;

   double fScale;



   Image fXImage, fHImage, fSigImage, fTransXImage;

   Graphics fXGraphics, fHGraphics, fSigGraphics;



   int fT;

   int fPrevX;

   Signal fX;



   public ConvPanel(SigPanel xPanel)

   {

   	setBackground(Color.white);

      fXPanel = xPanel;

   }



   public void sigChanged()

   {

   	if (!fSigChanged)

      {

         clear();

		 fSigChanged = true;

         repaint();

      }

   }



   void clear()

   {

      // Clear both images

      fXGraphics.clearRect(0, 0, fSigLength, 2 * fAmplitude);

      fHGraphics.clearRect(0, 0, fSigLength, 2 * fAmplitude);



      // Draw axes in h image

    	fHGraphics.setColor(Color.lightGray);

      fHGraphics.drawLine(0, fAmplitude, fSigLength, fAmplitude);

      fHGraphics.drawLine(fSigLength / 2, 0, fSigLength / 2, fAmplitude * 2);

	fHGraphics.drawLine(fSigLength / 2 - 2, fAmplitude / 2,
			fSigLength / 2 + 2, fAmplitude / 2);
	fHGraphics.drawLine(fSigLength / 2 - 2, 3 * fAmplitude / 2,
			fSigLength / 2 + 2, 3 * fAmplitude / 2);

	fHGraphics.drawString("+1", fSigLength / 2 + 5, fAmplitude / 2 + 5);
	fHGraphics.drawString("-1", fSigLength / 2 + 5, fAmplitude * 3 / 2 + 5);

      fHGraphics.setColor(Color.black);

		fHGraphics.drawString("x(T - t)", 5, 15);

      fHGraphics.drawString("t", fSigLength - 10, fAmplitude + 10);

   }



   void updateSignal()

   {
	fX = fXPanel.getSignal();

   	clear();




	// Draw the signals

    fXGraphics.setColor(Color.blue);


      fOffset = (fSigLength - fX.getLength()) / 2;



      for (int i = 1; i < fSigLength; i++)

		{

        		fXGraphics.drawLine(-fOffset + fSigLength - (i - 1),

          		fAmplitude - fX.getValue(i - 1),

         		-fOffset + fSigLength - i, fAmplitude - fX.getValue(i));

      }


    // Draw t label in x image

	fXGraphics.setColor(Color.lightGray);
	fXGraphics.drawLine(fSigLength / 2, fAmplitude - 2, fSigLength / 2, fAmplitude + 2);

    fXGraphics.setColor(Color.black);
	fXGraphics.drawString("T", fSigLength / 2 + 4, fAmplitude + 10);




      // Make fTransXImage a transparent version of fXImage

      ImageFilter filter = new TransparentFilter();

      fTransXImage = createImage(new FilteredImageSource

      	(fXImage.getSource(), filter));

   }



   public void paint(Graphics g)

   {

   	if (!setup)

      {

      	fSigLength = bounds().width;

         fAmplitude = bounds().height / 2;

         fScale = 2.0 / fAmplitude;



         fXImage = createImage(fSigLength, 2 * fAmplitude);

         fXGraphics = fXImage.getGraphics();



      	fHImage = createImage(fSigLength, 2 * fAmplitude);

         fHGraphics = fHImage.getGraphics();



         fSigImage = createImage(fSigLength, 2 * fAmplitude);

         fSigGraphics = fSigImage.getGraphics();



	      clear();

         setup = true;

      }



		fSigGraphics.drawImage(fHImage, 0, 0, this);



      if (!fSigChanged)  // don't draw if signals are being modified
	  {
      	fSigGraphics.drawImage(fTransXImage, fT - fSigLength, 0, this);

		fSigGraphics.setColor(Color.blue);
		fSigGraphics.drawLine(0, fAmplitude, fT - fSigLength, fAmplitude);
		fSigGraphics.drawLine(fT, fAmplitude, fSigLength, fAmplitude);
	  }


   	g.drawImage(fSigImage, 0, 0, this);

   }

   void startConvolution()
   {
   	if (fSigChanged)

      {

      	updateSignal();

      	fSigChanged = false;

		int firstNonZeroH = 0, firstNonZeroX = 0;


		for (int i = 0; i < fX.getLength(); i++)
			if (fX.getValue(i) != 0)
			{
				firstNonZeroX = i;
				break;
			}

		int startT = fSigLength / 2 + firstNonZeroH - (fX.getLength() - firstNonZeroX);

		if (startT > 50)
			startT -= 50;
		else
			startT = 0;

		mouseDown(null, startT, 0);
      }
   }

	public boolean mouseDown(Event evt, int x, int y)

   {

   	if (fSigChanged)
	{
		updateSignal();
		fSigChanged = false;
    }


   	if (x < 0)

      	x = 0;



      if (x >= fSigLength)

      	x = fSigLength - 1;



      fT = x + fSigLength / 2;



      fPrevX = x;

      repaint();

      return true;

   }



	public boolean mouseDrag(Event evt, int x, int y)

   {

	int realX = x;

   	if (x < 0)

      	x = 0;



      if (x >= fSigLength)

      	x = fSigLength - 1;






      mouseDown(evt, x, y);
		return true;
   }



   public void update(Graphics g)

   {

   	paint(g);

   }


	public Insets insets()
	  {
	    return new Insets(5, 5, 5, 5);
	  }

	public synchronized Dimension minimumSize()
	  {
	    return new Dimension(100, 100);
	  }
}



class TransparentFilter extends RGBImageFilter

{

	public TransparentFilter()

   {

   	canFilterIndexColorModel = true;

   }



   public int filterRGB(int x, int y, int rgb)

   {

   	return (rgb == 0xFFFFFFFF) ? (rgb & 0x00FFFFFF) : rgb;

   }

}



class Signal

{

	int fSigLength, fAmplitude;

	int x[];

   String fName, fLabel;

   Color fColor;

	boolean fDrawTicks;


   public int getLength()

   {

   	return fSigLength;

   }



	public Signal(String name, String label, int width, int height, Color color,
			boolean drawTicks)

   {
		
      fName = name;
		fLabel = label;

   	fSigLength = width;

      fAmplitude = height / 2;

	fDrawTicks = drawTicks;

		x = new int[fSigLength];

      fColor = color;

   	zero();

   }



   public int getValue(int i)

   {

   	if (i < 0 || i >= fSigLength)

      	return 0;

      else

   		return x[i];

   }



   public int[] values()

   {

   	return x;

   }



   public void zero()

   {

   	for (int i = 0; i < fSigLength; i++)

      	x[i] = 0;

   }



   public void doDoublePulse()

   {

   	zero();


   	for (int i = fSigLength / 2; i < fSigLength * 3 / 4; i++)

      	x[i] = -fAmplitude / 2;


   	for (int i = fSigLength * 3 / 4; i < fSigLength - 1; i++)

      	x[i] = fAmplitude / 2;

   }

   public void doPulse()

   {

   	zero();



   	for (int i = fSigLength / 2; i < 3 * fSigLength / 4; i++)

      	x[i] = fAmplitude / 2;

   }


   public void doDecay()

   {

   	zero();



   	for (int i = fSigLength / 2; i < fSigLength; i++)

		x[i] = (int) (Math.exp(-6.0 * (i - fSigLength / 2) / fSigLength)

         	* fAmplitude / 2);

   }


public void Draw(Graphics g)
{
	DrawWithMovedTicks(g, fAmplitude / 2, fAmplitude);
}

   public void DrawWithMovedTicks(Graphics g, int tickOffset, int windowHeight)

   {

   	g.setColor(Color.lightGray);

      g.drawLine(0, fAmplitude, fSigLength, fAmplitude);

      g.drawLine(fSigLength / 2, 0, fSigLength / 2, fAmplitude * 2);

	if (fDrawTicks)
		for (int yOff = tickOffset, count = 1; yOff < windowHeight; yOff += tickOffset, count++)
		{
			g.drawLine(fSigLength / 2 - 2, fAmplitude - yOff,
				fSigLength / 2 + 2, fAmplitude - yOff);
			g.drawLine(fSigLength / 2 - 2, fAmplitude + yOff,
				fSigLength / 2 + 2, fAmplitude + yOff);

			g.drawString("+" + new Integer(count).toString(), fSigLength / 2 + 5, fAmplitude - yOff + 5);
			g.drawString("-" + new Integer(count).toString(), fSigLength / 2 + 5, fAmplitude + yOff + 5);
		}

//	else  // if !fDrawTicks, we're drawing a very small signal
//	  g.setFont(new Font("Times", Font.PLAIN, 9));

      g.setColor(Color.black);


	if (fDrawTicks)
	{
   		g.drawString(fName, 5, 15);
		g.drawString(fLabel, fSigLength - 10, fAmplitude + 10);
	}

		g.setColor(fColor);



      for (int i = 1; i < fSigLength; i++)

      	g.drawLine(i - 1, fAmplitude - x[i - 1], i, fAmplitude - x[i]);

  	}



   public void Modify(int i, int y)

   {
	if (i > 0 && i < fSigLength - 1)
   		x[i] = fAmplitude - y;

   }



   public void set(int[] newSig)

   {

   	x = newSig;

   }



   public void setValue(int i, int y)

   {
	if (i > 0 && i < fSigLength - 1)
   		x[i] = y;
   }



   public void Drag(int oldI, int oldY, int i, int y)

   {

   	if (i == oldI)

      	return;



   	if (oldI > i)

   	{

   		int temp = oldI;

      	oldI = i;

         i = temp;



         temp = oldY;

         oldY = y;

         y = temp;

      }



      for (int currI = oldI; currI <= i; currI++)

		if (currI > 0 && currI < fSigLength - 1)
	      	x[currI] = fAmplitude - (oldY + (y - oldY)
         					* (currI - oldI) / (i - oldI));

   }

}



