import java.awt.*;
import java.applet.*;
import java.awt.image.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Event;
import java.awt.Image;
import java.awt.Font;
import java.lang.System;



//---- ZGO STRUCTURES --------------------------------------------------------//

//---- ZGoDATA

class ZGoDATA
{
  protected ZGo zgo;
  protected ZGoFn input_of, output_of;
  protected int frame;

  public ZGoDATA()
  {
  }

  public ZGoDATA(ZGo zgo)
  {
    this.zgo = zgo;
    input_of = null;
    output_of = null;
    frame = 0;
  }
  
  public ZGoDATA setInput(ZGoFn fn)
  {
    input_of = fn;
    return this;
  }

  public ZGoDATA setOutput(ZGoFn fn)
  {
    output_of = fn;
    return this;
  }
  
  public void process()
  {
    if(frame == zgo.frame) return;

    frame = zgo.frame;
    if(output_of != null) output_of.process();
  }
}



//---- ZGoPARAMS

class ZGoPARAMS
{
  protected ZGo zgo;
  protected ZGoDATA[] data;
  protected String[] name;
  
  protected int num;

  public ZGoPARAMS(ZGo zgo)
  {
    this.zgo = zgo;
    data = new ZGoDATA[16];
    name = new String[16];
    num = 0;
  }
  
  public ZGoPARAMS setInput(ZGoFn fn)
  {
    for(int i=0; i<num; i++) data[i].setInput(fn);
    return this;
  }
  
  public ZGoPARAMS setOutput(ZGoFn fn)
  {
    for(int i=0; i<num; i++) data[i].setOutput(fn);
    return this;
  }
  
  public ZGoPARAMS add(String n, ZGoDATA d)
  {
    data[num] = d;
    name[num] = new String(n);
    num ++;
    return this;
  }
  
  public ZGoDATA get(String n)
  {
    for(int i=0; i<num; i++) if(name[i].equals(n))
    {
      return data[i];
    }
    return null;
  }
}



//---- ZGoFn

class ZGoFn
{
  protected ZGo zgo;
  protected ZGoPARAMS input, output;

  public ZGoFn()
  {
  
  }

  public ZGoFn(ZGo zgo, ZGoPARAMS input, ZGoPARAMS output)
  {
    this.zgo = zgo;
    this.input = input.setInput(this);
    this.output = output.setOutput(this);
  }

  public void process()
  {
  }

}



//---- ZGO DATA TYPES --------------------------------------------------------//

//---- ZGoREAL

class ZGoREAL extends ZGoDATA
{
  public double value;
  
  public ZGoREAL(ZGo zgo)
  {
    this.zgo = zgo;
    value = 0;
  }
}



//---- ZGoIMAGE

class ZGoIMAGE extends ZGoDATA
{
  public int w, h;
  public int[] buf;
  public int size;

  public ZGoIMAGE(ZGo zgo, int w, int h)
  {
    this.zgo = zgo;
    this.w = w;
    this.h = h;

    size = w*h;
    buf = new int[size];
  }

  public ZGoIMAGE(ZGo zgo, String fname, int w, int h)
  {
    this.zgo = zgo;
    this.w = w;
    this.h = h;

    size = w*h;
    buf = new int[size];

	  Image im = zgo.applet.getImage(zgo.applet.getDocumentBase(), fname);
	  
		zgo.applet.showStatus("Loading image "+fname);

		PixelGrabber pg = new PixelGrabber(im, 0, 0, w, h, buf, 0, w);
		try { pg.grabPixels();	} catch (InterruptedException e) { }
  }

}



//---- ZGO PROCESS TYPES -----------------------------------------------------//

//---- ZGoFader

class ZGoFader extends ZGoFn
{
  private ZGoIMAGE im1, im2, result;
  private ZGoREAL alpha;

  ZGoFader(ZGo zgo, ZGoPARAMS input, ZGoPARAMS output)
  {
    this.zgo = zgo;
    this.input = input.setInput((ZGoFn)this);
    this.output = output.setOutput((ZGoFn)this);
    //  Parameters
    im1 = (ZGoIMAGE)input.get("image1");
    im2 = (ZGoIMAGE)input.get("image2");
    alpha = (ZGoREAL)input.get("alpha");
    result = (ZGoIMAGE)output.get("result");
  }

  public void process()
  {
    int[] buf1, buf2, rbuf;

    int r1, g1, b1, a1;
    int r2, g2, b2, a2;
    
    alpha.process();
    
    int a = (int)(255*alpha.value), aa = 255-a;

    if(a <= 0)
    {
      im1.process();

      System.arraycopy(im1.buf, 0, result.buf, 0, im1.size);
    }
    else if(a >= 255)
    {
      im2.process();

      System.arraycopy(im2.buf, 0, result.buf, 0, im2.size);
    }
    else
    {
      im1.process();
      im2.process();
      
      int i, j, w, h;

      w = im1.w; if(w > im2.w) w = im2.w; if(w > result.w) w = result.w;
      h = im1.h; if(h > im2.h) w = im2.h; if(h > result.h) h = result.h;

      int o1 = 0, o2 = 0, o3 = 0;
      int c;
      
      buf1 = im1.buf;
      buf2 = im2.buf;
      rbuf = result.buf;

      for(j=0; j<h; j++)
      {
        for(i=0; i<w; i++)
        {
          c = buf1[o1++]; a1 = (c>>24)&255; r1 = (c>>16)&255; g1 = (c>>8)&255; b1 = c&255;
          c = buf2[o2++]; a2 = (c>>24)&255; r2 = (c>>16)&255; g2 = (c>>8)&255; b2 = c&255;
          
          a1 = (a1*aa + a2*a)>>8;
          r1 = (r1*aa + r2*a)>>8;
          g1 = (g1*aa + g2*a)>>8;
          b1 = (b1*aa + b2*a)>>8;
          
          rbuf[o3++] = (a1<<24) + (r1<<16) + (g1<<8) + (b1);
        }

        o1 += im1.w - w;
        o2 += im2.w - w;
        o3 += result.w - w;
      }

    }
  }

}



//---- ZGoSine

class ZGoSine extends ZGoFn
{
  ZGoREAL time, result;

  public ZGoSine(ZGo zgo, ZGoPARAMS input, ZGoPARAMS output)
  {
    this.zgo = zgo;
    this.input = input.setInput(this);
    this.output = output.setOutput(this);
    //  Parameters
    time = (ZGoREAL)input.get("time");
    result = (ZGoREAL)output.get("result");
  }
  
  public void process()
  {
    time.process();
    result.value = 0.5 + 0.4*Math.sin(time.value*3.14159/1.4);
  }
}



//---- ZGoMain

class ZGoMain extends ZGoFn
{
  ZGoIMAGE im1, im2;
  ZGoREAL alpha;

  ZGoSine sine;
  ZGoFader fader;

  ZGoMain(ZGo zgo, ZGoPARAMS input, ZGoPARAMS output)
  {
    this.zgo = zgo;
    this.input = input.setInput(this);
    this.output = output.setOutput(this);
    //  Create internal data
    im1 = new ZGoIMAGE(zgo, "image1.jpg", 512, 240);
    im2 = new ZGoIMAGE(zgo, "image2.jpg", 512, 240);
    alpha = new ZGoREAL(zgo);
    //  Create internal functions
    sine = new ZGoSine(zgo,
                 new ZGoPARAMS(zgo).add("time", input.get("time")),
                 new ZGoPARAMS(zgo).add("result", alpha)
               );
    fader = new ZGoFader(zgo,
                 new ZGoPARAMS(zgo).add("image1", im1).add("image2", im2).add("alpha", alpha),
                 new ZGoPARAMS(zgo).add("result", output.get("result"))
               );
  }

  public void process()
  {
  }

}



//---- ZGO SYSTEM CLASS ------------------------------------------------------//

class ZGo
{
  private ZGoREAL time;
  private ZGoIMAGE result;
  private ZGoPARAMS input, output;

  private ZGoMain main;

  public Applet applet;

  public int frame;

  //---- ZGo

  public ZGo(Applet applet, int w, int h)
  {
    this.applet = applet;

    time = new ZGoREAL(this);
    result = new ZGoIMAGE(this, w, h);

    input = new ZGoPARAMS(this).add("time", time);
    output = new ZGoPARAMS(this).add("result", result);

    main = new ZGoMain(this, input, output);
    
    frame = 0;
  }

  //---- render

  public int[] render(double t)
  {
    frame ++;
    time.value = t;
    result.process();
    return result.buf;
  }

}



//---- ZGoTest ---------------------------------------------------------------//

public class ZGoTest extends tinyptc
{
  //- Mouse data

  int mouse_x = 0, mouse_y = 0;
  int mouse_oldx = 0, mouse_oldy = 0;
  boolean mouse_drag = true;
  int mouse_changex = 0, mouse_changey = 0;

  //- ZGo Data

  ZGo zgo;

  //- Main code

  public void main(int width, int height)
  {
    zgo = new ZGo(this, width, height);

    double t1 = System.currentTimeMillis()*0.001;
    double t = 0;

    while (true)
    {
      t = System.currentTimeMillis()*0.001 - t1;
      update( zgo.render(t) );
    }
  }

  //- mouseDown

  public boolean mouseDown(Event evt, int x, int y)
  {
    mouse_oldx = x;
    mouse_oldy = y;
    mouse_x = x;
    mouse_y = y;
    mouse_drag = true;

    return true;
  }

  //- mouseDrag
  
  public boolean mouseDrag(Event e, int x, int y)
  {
    mouse_x = x;
    mouse_y = y;

    mouse_changex += mouse_x-mouse_oldx;    
    mouse_changey += mouse_y-mouse_oldy;    

    mouse_oldx = x;
    mouse_oldy = y;

    return true;
  }

  //- mouseUp

  public boolean mouseUp(Event evt, int x, int y)
  {
    mouse_drag = false;

    return true;
  }
  

}

