import java.awt.*;
import java.applet.Applet;
import java.awt.event.*;
import java.awt.image.*;

public class Keplerpfeile extends Applet implements ActionListener , ImageObserver{
		private Welt welt;		    // Objekt, das das Koordinatensystem und die Planeten zeichnet
		private Iteration iteration;	    // Objekt, das für die physikalischen Berechnungen zuständig ist
		private Button startstopB, zuruecksetzenB, kpfeilB, gpfeilB, bahnenB;	// Objekte der Buttons
		public Label zeit;		    // Objekt zum Schreiben der Zeit auf den Bildschirm
		public Image i;			    // Offscreen-Image (Hierin wird gezeichnet => Flimmerfreie Animation!)
		Graphics g,og;			    // Grafikkontexte für das Offscreen-Image und den Bildschirm
		
// Die Methode init wird automatisch aufgerufen, wenn das Applet gestartet wird
    public void init() {
    
	i = createImage(600,500);		    // Offscreen-Image initialisieren
	// Buttons initialisieren
	startstopB = new Button("Start/Stop");
    	add(startstopB);
	startstopB.addActionListener(this);
	
	zuruecksetzenB = new Button("Zuruecksetzen");
    	add(zuruecksetzenB);
	zuruecksetzenB.addActionListener(this);
	
	kpfeilB = new Button("Kraftpfeile ein/aus");
	add(kpfeilB);
	kpfeilB.addActionListener(this);
	
	gpfeilB = new Button("Gesamtkraftpfeil ein/aus");
	add(gpfeilB);
	gpfeilB.addActionListener(this);

	bahnenB = new Button("Bahnen ein/aus");
	add(bahnenB);
	bahnenB.addActionListener(this);

	
	// Label initialisieren
	zeit = new Label("Zeit steht.");
	add(zeit);
	
	// g ist der Handler für den Grafikkontext. Mit seiner Hilfe kann auf der
	// dem Applet zugewiesenen Fläche gezeichnet werden.	
	g = getGraphics();
	og = i.getGraphics();	//Offscreen
	
	// Das Applet besitzt ein Welt-Objekt. Hier wird es konstruiert!
	welt = new Welt(og);
	// Das Applet beseitzt ein Iteration-Objekt. Hier wird es konstruiert!
	iteration = new Iteration(welt,this);
	// iteration ist eine Tochter von Thread. Hier wird der Thread gestartet, d.h.
	// iteration.run() wird als neuer Thread aufgerufen.
	iteration.start();
    }
    
    // paint wird vom Betriebssystem immer dann aufgerufen, wenn die ganze Fläche des
    // Applets neu gezeichnet werden muss.
    public void paint(Graphics g) {
	welt.ZeichneAlles ();
	g.drawImage(i,0,0,this);
    }
    
    // Event-Handler für die Buttons
    public void actionPerformed(ActionEvent ev) {
	if (ev.getSource()==startstopB) {
	    iteration.startstop();
	}
	if (ev.getSource()==zuruecksetzenB) {
	    iteration.zuruecksetzen = true;
	}
	if (ev.getSource()==kpfeilB) {
	    welt.KPfeilButton();
	    paint(g);
	}
	if (ev.getSource()==gpfeilB) {
	    welt.GPfeilButton();
	    paint(g);
	}

	if (ev.getSource()==bahnenB) {
	    welt.BahnenButton();
	    paint(g);
	}


    }
    // Leerer Handler zur Fehlerbehandlung beim Kopieren des Offscreen-Images
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } 

}


// Diese Klasse implementiert einen Himmelskörpter. Er besteht aus seinem Ort, seiner Geschwindigkeit, 
// Beschleunigung, Masse und Farbe. Er ist in der Lage, seine aktuelle Position auf dem Bildschirm 
// einzuzeichnen (Methode zeichne) und merkt sich die letzten 10000 Positionen, um sie bei 
// Bedarf (überdecken durch anderes Fenster und wieder aufdecken!) erneut zu zeichnen.
class Himmelskoerper {
	public double x,y,vx,vy,ax,ay,m;
	public int Xs,Ys;
	public Color farbe;
	public boolean neu = false;
	
	public int xs[],ys[];		// Arrays zum speichern der letzten 10000 Positionen
	
	private Welt w;			// Jeder Himmelskoerper kennt die Welt, in der er sich befindet
	private final int merkanzahlmax = 3000;	// so viele alte Positionen merkt sich der Himmelskoerper
	private int merkposition = 0;	// An dieser Stelle in den Arrays xs[},ys[] wird die nächste Position abgelegt
	private int merkanzahl = 0;	// So viele alte Positionen wurden bis jetzt gespeichert
	
	public Himmelskoerper (double X, double Y, double Vx, double Vy, double M, Color Farbe, Welt W)
	{
	    x = X; y = Y; vx = Vx; vy = Vy; m = M; farbe = Farbe;
	    w = W;			// Jeder Himmelskoerper bekommt von iterate die Welt mitgeteilt
	    xs = new int[merkanzahlmax];    // Arrays zum Speichern der alten Positionen reservieren
	    ys = new int[merkanzahlmax];
	    xs[0] = -1; ys[0] = -1;	    // Dummy-Werte erleichtern die Programmierung einer Schleife unten
	    zeichne();
	}
	
	// Die Methode schaut nach, ob sich die Position des Himmelskoerpers am Bildschirm seit
	// dem letzten Zeichnen verändert hat. Falls ja, wird der Himmelskoerper neu gezeichnet und
	// die aktuelle Position den Arrays xs[] und ys[] hinzugefügt    
	public void zeichne ()
	{
	  neu = false;
	  Xs = w.WeltToScreenx(x);	    // Umwandeln der Weltkoordinaten in Bildschirmkoordinaten
	  Ys = w.WeltToScreeny(y);
	  if (Xs != xs[merkposition] || Ys != ys[merkposition])	    // Hat sich die Position seit dem letzten Zeichnen verändert?
	     {
		if (merkposition < merkanzahlmax -1) merkposition++;	// aktuellen Eintrag in xs[], ys[] finden
		else merkposition = 0;
		if (merkanzahl < merkposition) merkanzahl = merkposition;   // evtl. merkanzahl erhöhen
		xs[merkposition] = Xs; ys[merkposition] = Ys;		    // aktuellen Wert speichern
		neu = true;
    	     }
	}
	
	// redraw zeichnet den Himmelskoerper an allen gespeicherten Positionen auf den Bildschirm
	// Die Methode wird aufgerufen, wenn das Ausgabefenster vollständig neu gezeichnet werden muss
	public void redraw ()
	{
	    for(int i = 0; i <= merkanzahl; i++) w.ZeichnePunkt(xs[i],ys[i],farbe);
	    w.ZeichneHimmelskoerper(xs[merkposition],ys[merkposition],farbe);
	}
}


// Die Klasse Welt implementiert die Welt, in der sich die Planeten bewegen. Sie ist durch ihre Grenzen in Weltkoordinaten festgelegt und 
// kennt zusätzlich ihre Ausdehnung auf dem Bildschirm. Sie stellt Methoden zum Zeichnen des Koordinatensystems und zur Umrechnung von Welt-
// koordinaten in Bildschirmkoordinaten zur Verfügung. Außerdem bekommt sie von iterate über die Methode registriere Referenzen auf alle
// Planeten mitgeteilt und ist so in der Lage, alle Planeten aufzufordern, ihre alten Positionen einzuzeichnen. 
class Welt {
		private double xmin = -1e11, xmax= 1e11,ymin = -1e11,ymax = 1e11;   // Grenzen des Weltkoordinatensystems
		private boolean unverzerrt = true;		    // true, wenn für x- und y-Achse der selbe Abbildungsmaßstab benutzt werden soll
		private double xfaktor, yfaktor;		    // Abbildungsfaktoren vom Weltkoordinatensystem ins Bildschirmkoordinatensystem

		private int xsmin = 10;				    // linke obere Ecke des Bildschirmkoordinatensystems
		private int ysmin = 40;
		private int width = 600;			    // Ausdehnung des Bildschirmkoordinatensystems
		private int height = 500;
	
		private Himmelskoerper h[];			    // Array mit den Himmelskörpern
		private int n = -1;				    // Anzahl der Himmelskörper, vorerst mit Dummywert belegt
			
		private Graphics g;				    // Handler für den Grafikkontext, wird von der Klasse Kepler mitgeteilt
		private boolean kpfeil = false;			    // Sollen die Kraftpfeile gezeichnet werden?
		private boolean gpfeil = false;			    // Sollen die Gesamtkraftpfeile gezeichnet werden?
		private boolean bahnen = true;			    // Sollen die Bahnen der Planeten gezeichnet werden?
		
		// Konstruktor
		public Welt (Graphics gE){
			g = gE;
			g.setClip(xsmin,ysmin,width,height);
			zeichneKoSy ();
		}
		
		// Mit dieser Methode werden die grenzen des Koordinatensystems festgelegt
		public void Koordinatensystem (double Xmin, double Xmax, double Ymin, double Ymax, boolean Unverzerrt) {
			xmin = Xmin; xmax = Xmax; ymin = Ymin; ymax = Ymax; unverzerrt = Unverzerrt;
			if (Xmax < Xmin)	// Sicherheitschecks, ob die Reihenfolge der Grenzen stimmt
				{
				xmax = Xmin;
				xmin = Xmax;
				}
			if (Ymax < Ymin)
				{
				ymax = Ymin;
				ymin = Ymax;
				}
			
		}
		
		// Rechnet Weltkoordinate x in Bildschirmkoordinate um
		public int WeltToScreenx(double x) {
			int xs = (int)Math.round( (x-xmin)*xfaktor ) + xsmin;
			return xs;
		}
		
		// Rechnet Weltkoordinate y in Bildschirmkoordinate um
		public int WeltToScreeny(double y) {
			int ys = (int)Math.round( (ymax-y)*yfaktor ) + ysmin;
			return ys;
		}
		
		// korrigiert die y-Grenzen, falls unverzerrt == true und zeichnet die Achsen ein
		public void zeichneKoSy (){
			double ymaxz, yminz;
			
			if (unverzerrt)
				{
				yminz = (ymax+ymin)/2.0-(5e-1)*height*(xmax-xmin)/width;
				ymaxz = (ymax+ymin)/2.0+(5e-1)*height*(xmax-xmin)/width;
				ymax = ymaxz; ymin = yminz;
				}	
			xfaktor = width/(xmax-xmin);		// Die Faktoren werden auch von WeltToScreenx und WeltToScreeny gebraucht
			yfaktor = height/(ymax-ymin);
			g.setColor(Color.black);		// Hintergrund schwarz färben
			g.fillRect(xsmin,ysmin,width, height);
			g.setColor(Color.white);		// Achsen einzeichnen
			g.drawLine(WeltToScreenx(0),ysmin,WeltToScreenx(0),height+ysmin);
			g.drawLine(xsmin,WeltToScreeny(0),width+xsmin,WeltToScreeny(0));
		}
		
		// Zeichnet einen Planeten mit den Bildschirmkoordinaten xs,ys und der Farbe farbe auf den Bildschirm	
		public void ZeichnePunkt( int xs, int ys, Color farbe ) {
			if (bahnen)
			{
			    g.setColor(farbe);
			    g.drawRect(xs,ys,1,1);
			}
		}
			
		public void ZeichneHimmelskoerper (int xs,int ys,Color farbe) {
			g.setColor(farbe);
			g.fillOval(xs-3,ys-3,7,7);
		}
		// Mit dieser Methode teilt iterate der Klasse Welt mit, welche und wie viele Himmelskoerper es gibt
		public void registriere(Himmelskoerper H[], int N) {
		    h = H;
		    n = N;
		}
		
		// Methoden, die beim Drücken der Buttons aufgerufen werden
		public void KPfeilButton() {
		if(kpfeil) kpfeil = false;
		else kpfeil = true;
		}
		
		public void GPfeilButton() {
		if (gpfeil) gpfeil = false;
		else gpfeil = true;
		}
		
		public void BahnenButton() {
		if (bahnen) bahnen = false;
		else bahnen = true;
		}

		// Zeichnet die Welt mit allen Planeten und ihren gespeicherten alten Positionen auf den Bildschirm    
		public void ZeichneAlles() {
		    zeichneKoSy();
		    for (int i = 0; i < n; i++)
		    if (h[i] != null)
		    h[i].redraw();	    // Die Himmelskoerper zeichnen sich selbst!
		    if(h[0]!= null)
		    if (kpfeil || gpfeil) Zeichnepfeile();  // Kraftpfeile oder Gesamtkraftpfeile mitzeichnen?
		}
		
		// Zeichnet Kraftpfeile und Gesamtkraftpfeile
		public void Zeichnepfeile () 
		{
		    double gfx,gfy;	// Gesamtkraft
		    for(int i = 0; i < n; i++)
		    {
			gfx = 0; gfy = 0;
			for(int j = 0; j < n; j++)
			{
			    if (i != j)
			    {	
				// Berechnung der Einzelkräfte
				double G = 6673e-14;
				double d = Math.sqrt( (h[i].x-h[j].x)*(h[i].x-h[j].x)+(h[i].y-h[j].y)*(h[i].y-h[j].y) );
				double z = G*h[i].m*h[j].m/(d*d*d);
				double fx = z*(h[j].x-h[i].x);
				double fy = z*(h[j].y-h[i].y);

				gfx += fx; gfy += fy;	    // Aufaddieren zur Gesamtkraft, die auf den Himmelskoerper i wirkt
				
				if(kpfeil)		    // Einzelkraftpfeile einzeichnen?
				{
				g.setColor(h[j].farbe);	    // In der Farbe des jeweils anderen Himmelskoerpers zeichnen
				Pfeildraw(h[i].Xs, h[i].Ys,fx,fy);
				}
			    }
			}
			if (gpfeil)			    // Gesamtkraft zeichnen?
			{
			    g.setColor(Color.red);
			    Pfeildraw(h[i].Xs,h[i].Ys,gfx,gfy);
			}
		    }
		}
		
		// Zeichnet einen Kraftpfeil. Übergebene Parameter: Pfeilanfang x,y, danach Kraft (in N), x-und y-Komponente	
		private void Pfeildraw (int sx, int sy, double fx, double fy)
		{
		    double faktor = 5e-26;	    // Umrechnungsfaktor von N in Pixel auf dem Bildschirm (gefühlsmäßig gewählt)
		    int fxi = (int)(fx*faktor);	    // Komponenten der Kraft in Bildschirmpixeln
		    int fyi = (int)(fy*faktor);
		    
		    thickline(sx,sy,sx+fxi,sy-fyi); // "Mittellinie" des Kraftpfeils
		    double l = Math.sqrt(fx*fx+fy*fy);
		    int laenge = 6;		    // Länge der Linien, die die Spitze bilden
		    fx /= l; fy /= l;		    // Normieren der Kraftkomponenten
		    int hx = sx+fxi; int hy = sy-fyi;	// Koordinaten der Pfeilspitze
		    thickline(hx,hy,hx-(int)(fx*laenge+fy*laenge),hy+(int)(fy*laenge-fx*laenge));   //Spitze zeichnen
		    thickline(hx,hy,hx-(int)(fx*laenge-fy*laenge),hy+(int)(fy*laenge+fx*laenge));
		}
		    
		// Setzt eine dicke Linie aus mehereren dünnen zusammen
		private void thickline (int x1, int y1, int x2, int y2) {
		    g.drawLine(x1,y1,x2,y2); g.drawLine (x1+1,y1+1,x2+1,y2+1);
		    g.drawLine(x1-1,y1-1,x2-1,y2-1);
		    g.drawLine(x1+1,y1,x2+1,y2); g.drawLine(x1,y1+1,x2,y2+1);
		    g.drawLine(x1,y1-1,x2,y2-1);g.drawLine(x1-1,y1,x2-1,y2);
		    }
		
		
		    
}   // Ende class Welt

class Iteration extends Thread  {
		public boolean zuruecksetzen = true;	// wird gesetzt, wenn wieder mit den Startwerten begonnen werden soll
		private double fx, fy ;			// Komponenten der Kraft 
		private double t;			// Zeit
		private double e = 18e2;		// Epsilon!
		private int n=3;			// Anzahl der Planeten. Vorsicht: In Java hat ein Feld mit 3 Einträgen die Indizes 0,1,2!
		private Welt w;				// Hier wird die Welt gemerkt!
		private Himmelskoerper h[];		// Array mit den Himmelskörpern
	    	private boolean weitermachen = false;	// Flag, das anzeigt, ob der Programmlauf gerade mit dem Button Stop unterbrochen wurde
		private Keplerpfeile k;			// Hier steht das Haup-Keplercl-Objekt drin
		private boolean neu = false;		// hat sich die Position eines Himmelskoerpers verändert?
		private int zaehler = 0;		// zählt, wie oft sich die Pos. eines Himmelsk. verändert hat. Nur alle 4-mal wird
							// ein neues Bild gezeichnet!
		
		// Dem Konstruktor wird von der Klasse Kepler das Welt-Objekt mitgeteilt
		public Iteration(Welt We, Keplerpfeile K)
		{
			w = We;				 // Merken!
			k = K;
			h = new Himmelskoerper[n];	 // Speicher für die Himmelskörper reservieren
			w.registriere(h,n);		 // der Welt mitteilen, welche Himmelskörper und wie viele es gibt
		}
		
		// Die Methoeden anhalten und starten werden aufgerufen, wenn die Buttons Start bzw. Stop gedrückt wurden
		public void startstop() {
			if(weitermachen) weitermachen = false;
			else weitermachen = true;
			}
		
		// Startwerte setzen		
		private void setzevariablen() {
		w.Koordinatensystem(-2e11,12e11,-95e10,7e11,true);	// Weltkoordinatensystem setzen
		w.zeichneKoSy();
		t = 0; k.zeit.setText(" 0 Tage");
		// Reihenfolge der Parameter bei der Konstruktion eines Himmelskoerper-Objekts:
		// x,y,vx,vy,m,farbe,Welt
		h[0] = new Himmelskoerper(0,0,0,0,200e28,Color.yellow,w); 
		h[1] = new Himmelskoerper(6e11,0,0,9e3,150e28,Color.green,w);
		h[2] = new Himmelskoerper(0,-6e11,1e4,0,150e28,Color.white,w);
		}

				
		// Diese Methode wird aufgerufen, wenn der Thread gestartet wird		
		public void run() {
			double faktor = 5e-1;	// Faktor hat Anfangs den Wert 0.5
			double G = 6673e-14;	// Gravitationskonstante
			
			while(1==1)		// Endlosschleife !! (Ungefährlich, da sie keinen anderen Thread des Betriebssystems blockiert!)
			{
				if (zuruecksetzen)
				{
				    zuruecksetzen = false;
				    setzevariablen();	// Startwerte für die Koordinaten, Geschwindigkeiten, .. setzen
				    k.paint(k.g);
				}
    				if (weitermachen)   // nur weiterrechnen, falls nicht gerade stop gedrückt wurde
				{
					for (int i = 0; i < n; i++)
					{
						fx = 0; fy = 0;
						for (int j = 0; j < n; j++)
							if (i != j)
							{
								double d = Math.sqrt( (h[i].x-h[j].x)*(h[i].x-h[j].x)+(h[i].y-h[j].y)*(h[i].y-h[j].y) );
								double z = G*h[i].m*h[j].m/(d*d*d);
								fx += z*(h[j].x-h[i].x);
								fy += z*(h[j].y-h[i].y);
							}
						h[i].ax = fx/h[i].m;
						h[i].ay = fy/h[i].m;
					}
					for (int i = 0; i < n; i++)
					{
						h[i].vx += h[i].ax*e*faktor;
						h[i].vy += h[i].ay*e*faktor;
						
						h[i].x += h[i].vx*e;
						h[i].y += h[i].vy*e;
						h[i].zeichne();		    // Der aktuelle Planet zeichnet sich selbst
					}
					 faktor = 1e0;				    // Nach dem ersten Durchlauf: Faktor = 1
					 t = t + e;				    // Zeit erhöhen
					 Long l = new Long(0);				    // Zeit ausgeben
					 k.zeit.setText( l.toString((long)(t/(3600*24)) )+" Tage");

					 neu = false;			    // Herausfinden, ob sich die Position eines der Himmelsk. verändert hat
					 for (int i = 0; i < n; i++) neu = neu || h[i].neu;
					 if (neu)
					 {   
					     zaehler ++;
					     if (zaehler == 4)		    // nur alle 4-Mal Bildschirm neu aufbauen!
					     {
						zaehler = 0;
						w.ZeichneAlles();	    // In das Offscreen-Image zeichnen
						k.g.drawImage(k.i,0,0,k);   // das Image auf den Bildschirm kopieren
					     }
					 }
				}
// Damit kann der Programmlauf verlangsamt werden, falls nötig (darf auch auskommentiert werden,
// falls der Programmlauf zu langsam ist!)
				try {
					Thread.sleep(1);	    // Schlafzeit in ms
				}
				catch (InterruptedException e) {
					System.err.println("Ausnahmefehler beim Befehl sleep");
				}
				
			}  // Ende der "Endlos"-schleife
		} // Ende der Methode run	
} // Ende der Klasse interation	
