import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)
import java.util.List;
import java.awt.Color; 
/**
 * Eigenschaften der Tower werden hier gesteuert, verwaltet und initialisiert
 * 
 * @author Marek Parucha
 * @version 2.0_24.02.2014
 */
public class Towers extends Actor
{
    
   
    /**
     * Stärke der Munition
     */
    protected int towerShotPower;
    /**
     * Bild der Munition
     */
    protected GreenfootImage munitionImage;
    /**
     * Radius der Munition
     */
    protected int munitionRadius;
    /**
     * Upgrade kosten
     */
    protected int upgradekosten;
    /**
     * Größe des Intervalls bis der nächste Schuss erfolgt
     */
    protected int towerShotSpeed; 
    
    /**
     * Zeit Intervall bis nächste Munition abgefeuert wird [in millisekunden]
     */
    protected int towerShotIntervall; 
    
    /**Distanz der Schussweite
     */
    protected int towerDistance;
    
   
    
    /**
     * Kosten des Turms
     */
    protected int towerMoney;
    
    /**
     * Auf welchem Level der Tower geupgradet ist
     */
    public int towerLevel;
    
    /**
     * Visueller Radius wird initialisier
     */
    public Drawing drawingOval;
    
    /**
     * das nächst gelegene Monster Objekt
     */
    Monsters nearestActor = null;
    
    /**
     * clicked == true falls auf den Tower gedrückt wird
     */
    private boolean clicked;
    /**
     * towerClicked == true falls auf den Tower gedrückt wurde
     */
    public static boolean towerClicked = false;
    /**
     * neuer Counter wird initialisiert
     */
    Clock intervallCountdown= new Clock(false,false,0,"");
    
    /**
     * erster Durchlauf der act() Methode?
     */
    protected boolean firstRun = true; 
    
    /**
     * true falls Monster ausgewählt wurde
     */
    static boolean isMonsterSelected = false;
    
    /**
     * GEspeichertes OBjekt der Klasse Monster
     */
    static Monsters selectedMonster = null;
    
    /**Gibt die Höhe des Turms an
     */
    protected int turmhöhe;
    protected int turmhöheAbs;
    
    /**Gibt den Faktor an um wieviel das Maximums 
     * der Geschosslaufbahn über der Turmhöhe liegt
     */
    protected double f; 
    
    /**Bild des Turmes von vorne, hinten, rechts und links
     */
    protected GreenfootImage turmImage_Gerade;
    
    /**Bild des Turmes von schräg vorne, hinten, rechts und links
     */
    protected GreenfootImage turmImage_Schräg;
    
    /** a * die Turmhöhe --> Relative Höhe des Maximums
     *  der Geschosslaufbahn zur Turmspize
     */
    private int max_Geschosshöhe_relativ = (int)(turmhöhe*f);
    
    /**Punkt an dem das Geschoss auftrifft
     */
    private Point virtuelEndPoint = new Point(true);
    
    /**Punkt an dem das Geschoss startet
     */
    private Point startPoint = new Point(true);
    
    /**Distanz zwischen Mittelpunkt der Turmgrundfläche und Monster
     */
    private double distanz = 0.0;
    
    /**Winkel den der Mittelpunkt der Turmgrundfläche mit dem Monster einschließt
     */
    private double winkel = 0.0;
    
    /** a nach Parabelform a*x² + b*x + c
     */
    private double a = 0.0;
    /** b nach Parabelform a*x² + b*x + c
     */
    private double b = 0.0;
    
    /** c nach Parabelform a*x² + b*x + c
     */
    private double c = 0.0;
    
    
    /**LGS zum errechnen von a , b und c
     */
    private double[][] LGS = new double[4][3];
    
    /** der Sektor in den die Munition abgfeuert wird:
     *      1 = oben    /   links
     *      2 = oben    /   rechts
     *      3 = unten   /   links
     *      4 = unten   /   rechts
     */
    protected int sektor = 0;
    
    /**Bilder der einzelnen Animationen
     */
    protected Animation aniNord;
    protected Animation aniSued;
    protected Animation aniOst;
    protected Animation aniWest;
    protected Animation aniMun;
    

    
    
    /**
     * act() Methode regelt die einzelnen Methoden
     */
    public void act() 
    {
        
        if(firstRun)
        {
            
            
            drawingOval = new Radius(towerDistance,this);
            setImage(turmImage_Gerade);
            
            if( TeD_World.SPIELEVERWALTUNG.buyProzess[(TeD_World.pixToCell(this.getY()+53))-1][(TeD_World.pixToCell(this.getX()))-1] == true )
            {
                Spieleverwaltung.removeMoney(towerMoney); 
                TeD_World.SPIELEVERWALTUNG.buyProzess[(TeD_World.pixToCell(this.getY()+53))-1][(TeD_World.pixToCell(this.getX()))-1] = false;
            }
             
            
            max_Geschosshöhe_relativ = (int)(turmhöhe*f);
            
            clicked = false;  
            
            
            getWorld().addObject( intervallCountdown ,0,0);
                
                
            firstRun = false;
        }
        
        
        aniOst.act();
        aniSued.act();
        aniNord.act();
        aniWest.act();
        
        if( this.getImage() != aniOst.getImage())
        {
            if( getImageSektor() == 1 )
            {
                this.setImage(aniNord.getImage());
            }
            else if( getImageSektor() == 2 )
            {
                this.setImage(aniOst.getImage());
            }
            else if( getImageSektor() == 3 )
            {
                this.setImage(aniSued.getImage());
            }
            else
            {
                this.setImage(aniWest.getImage());
            }
        }   
        
        
       
        
        
       
        max_Geschosshöhe_relativ = (int)(turmhöhe*f);
        getWorld().addObject( intervallCountdown ,0,0);
        
        
        if(Greenfoot.mouseClicked(this))
        {
            Spieleverwaltung.getTowerInfo(this,towerShotPower,towerShotSpeed,towerShotIntervall,towerDistance,towerMoney,towerLevel,upgradekosten, clicked);
            if(!clicked && !towerClicked) //falls auf den Tower geklickt wurde und noch kein Radius initialisiert wurde --> zeichne einen Radius der Klasse Drawing
            {
                drawingOval = new Radius(towerDistance,this);
                getWorld().addObject(drawingOval,getX(),getY());
                clicked=true;
                towerClicked = true;
            }
            else if (clicked && towerClicked)//falls auf den Tower geklickt wurde und ein Radius initialisiert wurde --> Lösche diesen Radius der Klasse Drawing
            {
                getWorld().removeObject(drawingOval);
                clicked=false;
                towerClicked = false;
            }
        }
        
        
        
        
        
        if(isMonsterInDistance())
        {
            
            shot();
            
            if( getImageSektor() == 1 )
            {
                aniNord.start();
            }
            else if( getImageSektor() == 2 )
            {
                aniOst.start();
            }
            else if( getImageSektor() == 3 )
            {
                aniSued.start();
            }
            else
            {
                aniWest.start();
            }
            
            
            
                
            
            
            
            
        }
        else
        {
            intervallCountdown.setClock();
        }
    }
        
        
        
        
     /**
      * Dieser Boolean wird wahr falls ein Monster in der gegebenen Distanz ist.
      */
    public boolean isMonsterInDistance()
    {
        double minDistance = towerDistance+1; //die Distanz des Monsters dass am nähesten liegt
        double nearestDistance = towerDistance; // die maximale Distanz des Towers wird hier übergeben
        int actorNumber =0; //das Array das den Wert übergeben soll z.B. nearActors.get(actorNumber)
        List nearActors = getObjectsInRange((int)nearestDistance, Monsters.class);   //eine liste der Monster die in der Distanz liegen 
           
               
            
            
            
        double[] distance =new double[nearActors.size()]; //double Array mit der Größe der enthaltenen Monster in der liste  'nearActors' wird hier initialisiert
        double actorDistance; //Distanz eines Monsters
        //for schleife die den nähesten Actor (Monster) seiner 'actorNumber' speicherst sowie seine Distanz in 'minDistance'
            
        if (isMonsterSelected && selectedMonster!=null)
        {
            minDistance = getDistance(getX(),getY(),selectedMonster);
            
            // überprüft ob die näheste Distanz in der maximalen Distanz des Monsters liegt falls ja --> wird in Richtung dieses Monsters ausgerichtet--> return true
            if(minDistance < nearestDistance)
            {
                if (isMonsterSelected && selectedMonster!=null)
                {
                    nearestActor = selectedMonster;
                }
                else 
                {   
                    nearestActor =((Monsters) nearActors.get(actorNumber));
                }
                
                
                return true;
            
            }   
        }
        
        
        
        
        
        for (int i = 0; i < nearActors.size() ; i++)    
        {    
            distance[i] = getDistance(getX(),getY(),(Monsters)nearActors.get(i));
            if (i>0)
            {
                if(minDistance>distance[i])
                {
                    minDistance = distance[i];
                    actorNumber = i;
                }
                else
                {
                    ;
                }
            }
            else
            {
                minDistance = distance[i];
            }
        }
        
        
        // überprüft ob die näheste Distanz in der maximalen Distanz des Monsters liegt falls ja --> wird in Richtung dieses Monsters ausgerichtet--> return true
        if(minDistance < nearestDistance)
        {
            nearestActor = ((Monsters)nearActors.get(actorNumber));
            return true;
        }   
       
  
    
        return false;
        
    }
    
      /**
      * erfasst das Ziel des Towers
      */
    private void zielErfassen()
    {
          
           startPoint.setLocation( this.getX() , this.getY()-turmhöhe );
           
           virtuelEndPoint.setLocation( nearestActor.getX() , nearestActor.getY()  );
           this.distanz = getDistanz();
           
           int monsterWeg = (int)((this.distanz*nearestActor.getSpeed())/this.towerShotSpeed);
           
           if( nearestActor.getRotation() == 0 )
           {
               virtuelEndPoint.setLocation( (nearestActor.getX()+monsterWeg) , nearestActor.getY()  );
           }
           else if( nearestActor.getRotation() == 90 )
           {
               virtuelEndPoint.setLocation( nearestActor.getX() , (nearestActor.getY()+monsterWeg)  );
           }
           else if( nearestActor.getRotation() == 180 )
           {
               virtuelEndPoint.setLocation( (nearestActor.getX()-monsterWeg) , nearestActor.getY()  );
           }
           else if( nearestActor.getRotation() == 90 )
           {
               virtuelEndPoint.setLocation( nearestActor.getX() , (nearestActor.getY()-monsterWeg)  );
           }
    }
    /**
     * Diese Methode wird in act() aufgerufen falls isMonsterInDistance() == true
     * 
     * Sie schießt in die Richtung des nähesten Monsters --> Object der Klass Munition wird erzeugt
     * 
     * @param 'X': X-Koordinate des Towers wird übergeben
     * @param 'Y': Y-Koordinate des Towers wird übergeben
     * @param 'munitionClass': String der Munitions Klasse wird übergeben
     */
    private void shot()
    {
        
          int intervallInFrames = (towerShotIntervall*29)/1000;//1000ms = 1s
           
          if (intervallCountdown.getFrames() == intervallInFrames)   
          {  
               getWorld().removeObject( startPoint );
               getWorld().removeObject( virtuelEndPoint );
                
               getWorld().addObject( startPoint , 1 , 1 );
               getWorld().addObject( virtuelEndPoint , 1 , 1 );
               
               zielErfassen();
               this.winkel = Math.atan((double)Math.abs( this.getY()- virtuelEndPoint.getY() ) / (double)Math.abs( this.getX() - virtuelEndPoint.getX() ) );
               
               berechneFlugbahn();
               
               this.distanz = getFlugLaenge(  this.a , this.b);
               
               zielErfassen();
               berechneFlugbahn();
               getWorld().addObject( new Munition(a,b,c,virtuelEndPoint,winkel, startPoint.getX(), startPoint.getY(),sektor,nearestActor,towerShotPower,towerShotSpeed,munitionRadius,aniMun) , startPoint.getX() , startPoint.getY() );
               
               intervallCountdown.setClock();
               
          }  
    }
    
    
    /**
     * Errechnet die Distance von einem Actor zu einem Punkt
     * 
     * @param 'x': X-Koordinate des Punktes
     * @param 'y':Y-Koordinate des Punktes
     * @param 'actor': Der Actor dessen Distanz von dem Punkt errechnet werden soll
     */
    public double getDistance(int x, int y,Actor actor) 
    {  
        return Math.hypot(actor.getX() - x, actor.getY() - y);  
    }  
      /**
      * Wählt ein Monster aus
      */
    static void selectMonster(Monsters monster,boolean isSelected)
    {
        if (!isSelected)
        {
            selectedMonster = monster;
            isMonsterSelected = true;
        }
        else if (isSelected)
        {
            selectedMonster = null;
            isMonsterSelected = false;
        }
    }
    
    /**
     * speichert die richtigen werte in a , b , c , distanz und winkel ab
     */
    private void berechneFlugbahn()
    {
        
        
        this.winkel  = getWinkel();
        getSektor(startPoint,virtuelEndPoint);
        setzeWerteInLGS();
        
        berechneLGS();
        
    }
    
    /**
     * brechnet die distanz mit dem Satz des Pytagoras
     */
    private double getDistanz()
    {
       return Math.sqrt( Math.pow( Math.abs( this.getX()-virtuelEndPoint.getX() ) , 2.0 ) + Math.pow( Math.abs( this.getY()-virtuelEndPoint.getY() ) , 2.0 ) );
    }
      /**
      * setzt Werte in LGS ein
      */
    private void setzeWerteInLGS()
    {
        if( this.sektor == 1 || this.sektor == 2 )
        {
            this.LGS[0][0] = Math.pow( ((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)   -   getVerschiebung_X((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz))) ,2.0 );
            this.LGS[0][1] = ((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)   -   getVerschiebung_X((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)));
            this.LGS[0][2] = ((turmhöhe + max_Geschosshöhe_relativ) +  getVerschiebung_Y((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)))-turmhöhe;
            
            this.LGS[1][0] = Math.pow( Math.abs(this.getX() - this.virtuelEndPoint.getX()),2.0);
            this.LGS[1][1] = Math.abs(this.getX() - this.virtuelEndPoint.getX());
            this.LGS[1][2] = Math.abs((double)(this.getY() - this.virtuelEndPoint.getY()))-turmhöhe;
        }
        else if( this.sektor == 3 || this.sektor == 4 )
        {
            this.LGS[0][0] = Math.pow( ((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)   -   getVerschiebung_X((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz))) ,2.0 );
            this.LGS[0][1] = ((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)   -   getVerschiebung_X((-1.0)*getE(turmhöhe , max_Geschosshöhe_relativ , distanz)));
            this.LGS[0][2] = ((turmhöhe + max_Geschosshöhe_relativ) +  getVerschiebung_Y(getE(turmhöhe , max_Geschosshöhe_relativ , distanz)))-turmhöhe;
            
            
            this.LGS[1][0] = Math.pow( Math.abs(this.getX() - this.virtuelEndPoint.getX()),2.0);
            this.LGS[1][1] = Math.abs(this.getX() - this.virtuelEndPoint.getX());
            this.LGS[1][2] = -Math.abs((double)(this.getY() - this.virtuelEndPoint.getY()))-turmhöhe;
        }
    }
      /**
      * Berechnet das LGS
      */
    private void berechneLGS()
    {
         double hilf_faktor = (-1)*(this.LGS[1][0]/this.LGS[0][0]);
         
         for(int i = 0 ; i < 3 ; ++i)
         {
            this.LGS[2][i] = this.LGS[0][i]*hilf_faktor;
         }
         
         for(int i = 0 ; i < 3 ; ++i)
         {
            this.LGS[3][i] = this.LGS[1][i]+this.LGS[2][i];
         }
         
         this.b = this.LGS[3][2]/this.LGS[3][1];
         this.c = turmhöhe;
         this.a = ((this.LGS[0][2]-(this.LGS[0][1]*this.b))/(this.LGS[0][0]));
         
    }
      /**
      * Erweitert Ziele
      */
    private void zeileErweiten( int nummer , double faktor )
    {
        for(int i = 0 ; i < 3 ; ++i)
        {
            this.LGS[nummer-1][i] *= faktor;
        }
    }
    /**
     * Errechnet den Sektor w das Monster ist
     * 
     * @param 'Start': Startpunkt
     * @param 'Ziel': Zielpunkt
     * 
     */
    private void getSektor(Point Start,Point Ziel)
    {
        if(Ziel.getX() <= Start.getX())
        {
            if(Ziel.getY() <= (Start.getY()+turmhöhe))
            {
                this.sektor = 1;
            }
            else if(Ziel.getY() >= (Start.getY()+turmhöhe))
            {
                this.sektor = 3;
            }
        }
        else if(Ziel.getX() >= Start.getX())
        {
            if(Ziel.getY() <= (Start.getY()+turmhöhe))
            {
                this.sektor = 2;
            }
            else if(Ziel.getY() >= (Start.getY()+turmhöhe))
            {
                this.sektor = 4;
            }
        }
    }
    
    private double getVerschiebung_X( double D )
    {
        return Math.tan( 0.5 * this.winkel ) * Math.sin( this.winkel ) * D;
    }
    
    private double getVerschiebung_Y( double D )
    {
        return Math.sin( this.winkel ) * D;
    }
   
    /**
     * brechnet e indem die Gleichung (max_Geschosshöhe_relativ / e²) = ( (Turmhöhe + max_Geschosshöhe_relativ) / (distanz-e)² ) nach 0 gesetz wird.
     * danach wird nach e aufgelöst.
     * 
     * @param 'turmhöhe':                   Turmhöhe
     * @param 'max_Geschosshöhe_relativ':   max_Geschosshöhe_relativ
     * @param 'distanz':                    distanz
     */
    private double getE( double turmhöhe , double max_Geschosshöhe_relativ , double distanz)
    {
        double e  = 0.0;
        
        final int D = (int)distanz;
        double[] l   = new double[D-1];
        double[] r   = new double[D-1];
        double[] erg = new double[D-1];
        double hilf = 100;
        
        
        for(int x = 1 ; x < D ; ++x )
        {
            l[x-1] = max_Geschosshöhe_relativ / Math.pow( x , 2.0 );
        }
        
        for(int x = 1 ; x < D ; ++x )
        {
            r[x-1] = ( max_Geschosshöhe_relativ + turmhöhe ) / Math.pow( (distanz-x) , 2.0 );
        }
        
        for(int i = 1 ; i < D ; ++i )
        {
            erg[i-1] = Math.abs(l[i-1]-r[i-1]);
        }
        
        for(int i = 0 ; i < D-1 ; ++i)
        {
            if(hilf > erg[i])
            {
                hilf = erg[i];
            }
        }
        
        for(int i = 0 ; i < D-1 ; ++i)
        {
            if(hilf == erg[i])
            {
                e = (double)i+1;
            }
        }
        
        return (double)(( -1 ) * e);
    }
    
    private int getImageSektor()
    {
        if( Math.toDegrees(winkel) < 45 )
        {
            if(sektor == 1 || sektor == 3)
            {
                return 4;
            }
            else
            {
                return 2;
            }
        }
        else
        {
            if(sektor == 1 || sektor == 2)
            {
                return 1;
            }
            else
            {
                return 3;
            }
        }
    }
    
    /**
     * brechnet den Winkel den der Mittelpunkt der Turmgrundfläche mit dem Monster einschließt.
     */
    private double getWinkel()
    {
        double i;
        i = Math.atan((double)Math.abs( this.getY()- virtuelEndPoint.getY() ) / (double)Math.abs( this.getX() - virtuelEndPoint.getX() ) );
        return i;
    }
    
    private int getFlugLaenge( double a , double b)
    {
        double dx = (double)Math.abs( this.getX() - virtuelEndPoint.getX() );
        double hilf1 = (Math.sqrt( Math.pow( 2*a*0 , 2 ) + 1 )*( 2*a*0 + b ) + Math.sinh( 2*a*0 + b ))/(4*a);
        double hilf2 = (Math.sqrt( Math.pow( 2*a*dx , 2 ) + 1 )*( 2*a*dx + b ) + Math.sinh( 2*a*dx + b ))/(4*a);
        
        
        
        double erg = hilf2-hilf1;
        
        
        
        return (int)erg;
    }
     /**
      * gibt Tower Kosten aus
      */
    public int getKosten()
    {
        return towerMoney;
    }
    /**
      * gibt Tower Level aus
      */
    public int getTowerLevel()
    {
        return towerLevel;
    }
    /**
      * setzt Bild fest
      */
    public void setI( GreenfootImage i) 
    {
        this.setImage(i);
    }
    /**
      * levelt den Tower auf
      */
    public void upgrade ()
    {

    }
}
