import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.MemoryImageSource;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileNotFoundException;
import java.lang.Math;

import Coordinate;
import SommerCampWrap;

// Canvas mit Animation
class AnimCanvas extends Canvas implements Runnable
{
  private int w, h, pix[];
  private Image img;
  // for the thread
  private int sPixels[], tPixels[], number;
  private Coordinate[][] sCoord, tCoord;
  private boolean doSave;
  private SommerCampWrap applet;
  public Thread AnimThread;

  public AnimCanvas(int w, int h, SommerCampWrap applet)
  {
    // Initialisieren
    int i;

    this.w = w;
    this.h = h;
    this.applet = applet;
    pix = new int[w*h];
    for (i=0; i<w*h; i++)
      pix[i] = 0xff000000;
    showImage();
  }

  public void StartThread(int[] sPixels, int[] tPixels, Coordinate[][] sCoord,
			  Coordinate[][] tCoord, int number, boolean doSave)
  {
    this.sPixels = sPixels;
    this.tPixels = tPixels;
    this.sCoord = sCoord;
    this.tCoord = tCoord;
    this.number = number;
    this.doSave = doSave;
    AnimThread = new Thread(this);
    AnimThread.start(); // Startet die run() Methode
  }

  public void run()
  {
    if (tPixels==null)
      // Es existiert kein Zielbild  =>  Deformations-Aktion
      deform();
    else
      // Es existiert ein Zielbild   =>  Animations-Aktion
      animate();
  }

  public Dimension getPreferredSize()
  {
    return getMinimumSize();
  }

  public synchronized Dimension getMinimumSize()
  {
    // Am oberen Rand eine Bildzeile Platz lassen
    return new Dimension(w, h+1);
  }

  public void paint(Graphics g)
  {
    update(g);
  }
  
  public void update(Graphics g)
  {
    // Am oberen Rand eine Bildzeile Platz lassen
    g.drawImage(img, 0, 1, this);
  }

  private void showImage()
  {
    // Neues Bild aus Pixel-Array aufbauen und zeichnen
    img = createImage(new MemoryImageSource(w, h, pix, 0, w));
    Graphics g = getGraphics();
    if (g != null)
      {
	g.drawImage(img, 0, 1, this);
        // Zum Anzeigen 5 ms warten
	try { Thread.sleep(5); }
	catch (InterruptedException e) {}
      }
  }

  public void saveImage(String filename, int ImageNumber)
  {
    if (pix==null)
      return;

    try
      {
        FileOutputStream fos;
	DataOutputStream dos;
	int i, j, pixel;
	byte rgb[];

	if (ImageNumber<10)
	  filename = filename + ".00" + ImageNumber + ".ppm" ;
	else if (ImageNumber<100)
	  filename = filename + ".0" + ImageNumber + ".ppm" ;
	else
	  filename = filename + "." + ImageNumber + ".ppm" ;
	fos = new FileOutputStream(filename);
	dos = new DataOutputStream(fos);
	dos.writeBytes("P6\n" + w + " " + h + "\n255\n");
	rgb = new byte[3*w*h+1];
	for (i = w*h-1, j = 3*w*h-1; i >= 0; )
	  {
	    pixel = pix[i--];
	    rgb[j--] = (byte)( 0xFF &  pixel );
	    rgb[j--] = (byte)(0xFF & (pixel>>8));
	    rgb[j--] = (byte)(0xFF & (pixel>>16));
	  }
	rgb[3*w*h] = '\n';
	dos.write(rgb, 0, 3*w*h+1);
	fos.close();
      }
    catch (FileNotFoundException e) {}
    catch (IOException e) {}
  }

  private int getRed(int[] p, int x, int y)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      return (p[y*w+x]>>16)&0xff;
    return 0;
  }

  private int getGreen(int[] p, int x, int y)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      return (p[y*w+x]>>8)&0xff;
    return 0;
  }

  private int getBlue(int[] p, int x, int y)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      return (p[y*w+x])&0xff;
    return 0;
  }

  private int getPix(int[] p, int x, int y)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      return (p[y*w+x])&0xffffff;
    return 0;
  }

  private void setRGB(int[] p, int x, int y, int r, int g, int b)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      p[y*w+x] = 0xff000000 + ((r&0xff)<<16) + ((g&0xff)<<8) + (b&0xff);
  }

  private void setPix(int[] p, int x, int y, int value)
  {
    if ( (x>=0) && (x<w) && (y>=0) && (y<h) )
      p[y*w+x] = 0xff000000 + (value&0xffffff);
  }



  // Hier beginnen die Animations-Prozeduren

  public void deform()
       /* Eingabewerte: BildBreite   w
			BildHoehe    h
			QuellPixel   sPixels
	                QuellGitter  sCoord
			ZielGitter   tCoord
			Speichern    doSave
	  Ausgabe:	Zielbild     pix      */
  {
    applet.showStatus("Deformation gestartet");
    
    int breite = sCoord.length;
    int hoehe = sCoord[0].length;
    int laengstesQuellx, laengstesQuelly, Quellex, Quelley, Zielx, Ziely;
    float lamda, my;
    int laengste;

    for(int x=0;x<breite-1; x++)
      {
      for(int y=0; y<hoehe-1; y++)
	{	
        
	laengstesQuellx=(int) (Math.sqrt((tCoord[x][y].x - tCoord[x+1][y+1].x)*
					 (tCoord[x][y].x - tCoord[x+1][y+1].x)+
					 (tCoord[x][y].y - tCoord[x+1][y+1].y)*
					 (tCoord[x][y].y - tCoord[x+1][y+1].y)));
        laengstesQuelly=(int) (Math.sqrt((tCoord[x+1][y].x - tCoord[x][y+1].x)*
					 (tCoord[x+1][y].x - tCoord[x][y+1].x)+
					 (tCoord[x+1][y].y - tCoord[x][y+1].y)*
					 (tCoord[x+1][y].y - tCoord[x][y+1].y)));
	laengste=Math.max(laengstesQuellx,laengstesQuelly);
	for (int i=0; i<laengste; i++) 
	  {
	  for (int j=0; j<laengste; j++) 
	    {
	    lamda=(float) i/laengste;
	    my=(float) j/laengste;
	    Quellex = (int) ((1-lamda)*((1-my)*sCoord[x][y].x + my*sCoord[x+1][y].x)+lamda*((1-my)* sCoord[x][y+1].x + my*sCoord[x+1][y+1].x));
	    Quelley = (int) ((1-lamda)*((1-my)*sCoord[x][y].y + my*sCoord[x+1][y].y)+lamda*((1-my)* sCoord[x][y+1].y + my*sCoord[x+1][y+1].y));
	    Zielx = (int) ((1-lamda)*((1-my)*tCoord[x][y].x + my*tCoord[x+1][y].x)+lamda*((1-my)* tCoord[x][y+1].x + my*tCoord[x+1][y+1].x));
	    Ziely = (int) ((1-lamda)*((1-my)*tCoord[x][y].y + my*tCoord[x+1][y].y)+lamda*((1-my)* tCoord[x][y+1].y + my*tCoord[x+1][y+1].y));
	    setPix(pix,Zielx,Ziely,getPix(sPixels,Quellex,Quelley));
	    
	    }
	  }
    showImage();      }
     } 
    
    showImage();
    if (doSave)
      saveImage("Deform", 0);
    applet.showStatus("Deformation beendet");
  }


  public void deform(Coordinate sCoord[][], Coordinate tCoord[][], int step)
       /* Eingabewerte: BildBreite   w
			BildHoehe    h
			QuellPixel   sPixels
	                QuellGitter  sCoord
			ZielGitter   tCoord
			Speichern    doSave
	  Ausgabe:	Zielbild     pix      */
  {
    applet.showStatus("Deformation gestartet ; berechne Bild "+step);
    
    int breite = sCoord.length;
    int hoehe = sCoord[0].length;
    int laengstesQuellx, laengstesQuelly, Quellex, Quelley, Zielx, Ziely;
    float lamda, my;
    int laengste,srot, sgruen, sblau, trot, tgruen, tblau;
    int temp =1;

    for(int x=0;x<breite-1; x++)
      {
      for(int y=0; y<hoehe-1; y++)
	{	
        temp+=1;
	laengstesQuellx=(int) (Math.sqrt((tCoord[x][y].x - tCoord[x+1][y+1].x)*
					 (tCoord[x][y].x - tCoord[x+1][y+1].x)+
					 (tCoord[x][y].y - tCoord[x+1][y+1].y)*
					 (tCoord[x][y].y - tCoord[x+1][y+1].y)));
        laengstesQuelly=(int) (Math.sqrt((tCoord[x+1][y].x - tCoord[x][y+1].x)*
					 (tCoord[x+1][y].x - tCoord[x][y+1].x)+
					 (tCoord[x+1][y].y - tCoord[x][y+1].y)*
					 (tCoord[x+1][y].y - tCoord[x][y+1].y)));
	laengste=Math.max(laengstesQuellx,laengstesQuelly);
	for (int i=0; i<laengste; i++) 
	  {
	  for (int j=0; j<laengste; j++) 
	    {
	    lamda=(float) i/laengste;
	    my=(float) j/laengste;
	    Quellex = (int) ((1-lamda)*((1-my)*sCoord[x][y].x + my*sCoord[x+1][y].x)+lamda*((1-my)* sCoord[x][y+1].x + my*sCoord[x+1][y+1].x));
	    Quelley = (int) ((1-lamda)*((1-my)*sCoord[x][y].y + my*sCoord[x+1][y].y)+lamda*((1-my)* sCoord[x][y+1].y + my*sCoord[x+1][y+1].y));
	    Zielx = (int) ((1-lamda)*((1-my)*tCoord[x][y].x + my*tCoord[x+1][y].x)+lamda*((1-my)* tCoord[x][y+1].x + my*tCoord[x+1][y+1].x));
	    Ziely = (int) ((1-lamda)*((1-my)*tCoord[x][y].y + my*tCoord[x+1][y].y)+lamda*((1-my)* tCoord[x][y+1].y + my*tCoord[x+1][y+1].y));
	    srot=getRed(sPixels,Quellex,Quelley);
	    sgruen=getGreen(sPixels,Quellex,Quelley);
	    sblau=getBlue(sPixels,Quellex,Quelley); 
	    trot=getRed(tPixels,Zielx,Ziely);
	    tgruen=getGreen(tPixels,Zielx,Ziely);
	    tblau=getBlue(tPixels,Zielx,Ziely); 
            setRGB(pix,Zielx,Ziely,
		   Math.round(srot+((trot-srot)/number*step)),
		   Math.round(sgruen+((tgruen-sgruen)/number*step)),
		   Math.round(sblau+((tblau-sblau)/number*step)));
            }
	  }
	
	applet.showStatus("Deformation gestartet ; berechne Bild "+step+", "+(Math.round(100/((hoehe-1)*(breite-1)))*temp)+" %");
	}
     } 
    
    showImage();
    if (doSave)
      saveImage("Deform", step);
    applet.showStatus("Deformation beendet");
  }

  public void animate()
       /* Eingabewerte: BildBreite   w
			BildHoehe    h
			QuellPixel   sPixels
			ZielPixel    tPixels
	                QuellGitter  sCoord
			ZielGitter   tCoord
			Bildanzahl   number
			Speichern    doSave

	  Ausgabe:	Zielbilder   pix      */
  {
    applet.showStatus("Animation gestartet");

    int hoehe = sCoord[0].length;    
    int breite = sCoord.length;  
    Coordinate ZwischenGitter[][] = new Coordinate[breite][hoehe];
    for(int i=0; i<breite; i++)
      {
	for(int j=0; j<hoehe; j++)
	  {
	    ZwischenGitter[i][j] = new Coordinate();
	  }
      }
    for(int anim=0; anim<=number; anim++)
      {
	for(int anzGitterX=0; anzGitterX<breite; anzGitterX++)
	  {
	    for(int anzGitterY=0; anzGitterY<hoehe; anzGitterY++)
	      {
		ZwischenGitter[anzGitterX][anzGitterY].x =
		  Math.round((tCoord[anzGitterX][anzGitterY].x -
		   sCoord[anzGitterX][anzGitterY].x) /
		   number *anim)+ sCoord[anzGitterX][anzGitterY].x;
		ZwischenGitter[anzGitterX][anzGitterY].y =
		  Math.round((tCoord[anzGitterX][anzGitterY].y -
		   sCoord[anzGitterX][anzGitterY].y) /
		   number *anim)+ sCoord[anzGitterX][anzGitterY].y;
		
	      }
	  
	  }
	deform(sCoord,ZwischenGitter,anim);
     

	/* showImage();
    if(doSave)
      saveImage("Animate", number);*/
      }
    applet.showStatus("Animation beendet");
      
  }
}
