import greenfoot.*;  // (World, Actor, GreenfootImage, Greenfoot and MouseInfo)

/**
 * Stuff that flies through the sky. Base class for both AI-controlled ships and manually controlled ones.
 * 
 * @author Jannis Andrija Schnitzer, Martin Schend
 * @version 2011-01-22
 */
public class Spaceship  extends QuantumObject
{
    protected int side;
    protected int level;
    protected int hitpoints = 0;
    protected int max_hitpoints = 0;
    
    /**
     * Variable containing the state of the blink animation that happens when the spaceship is damaged.
     * 1: start blink animation
     * 2..MAX_BLINK: continue blinking
     * MAX_BLINK+1: restore default sprite
     * 
     * @see #doBlink()
     */
    protected int blink = 0;
    
    /**
     * How many turns the blinking animations should last.
     */
    protected int max_blink = 2;
    
    /**
     * Filename of the sprite to be used while blinking, i. e. of the spaceship's "blink" state.
     */
    protected String blink_file = "";
    
    /**
     * X distance between guns on one spaceship.
     */
    public static final int GUN_DISTANCE = 10;
    
    /**
     * Determines the angular distance between guns when angled_guns is true. The angle between the first and the
     * last gun equals 2 * n_guns * GUN_ANGLE. The value is expressed in radians.
     * 
     * @see #angled_guns
     * @see #n_guns
     */
    public static final double GUN_ANGLE = .05;
    public static final int N_GUNS = 6;
    protected Gun[] guns;
    protected int n_guns;
    
    /**
     * Determines if guns should shoot in a straight way (angled_guns = false) or rather in a widespread way
     * (angled_guns = true).
     */
    protected boolean angled_guns;
    
    public void act() {
        super.act();
        for (int i = 0; i < N_GUNS; i++)
        {
            if (guns[i] != null)
                guns[i].tick();
        }
        doBlink();
    }
    
    public Spaceship(int the_level)
    {
        super();
        guns = new Gun[N_GUNS];
        for(int i = 0; i < N_GUNS; i++)
            guns[i] = null;
        n_guns = 0;
        angled_guns = false;
        level = the_level;  // influences HP and such. Setting HP etc. should be done in a subclass's constructor.
    }
    
    /**
     * Add a gun in the given position. Doesn't check if position exceeds array bounds. This is a feature.
     * (Seriously: The check wasn't implemented because that way programming mistakes in MoreGuns etc. became
     * obvious more quickly.)
     * 
     * @see MoreGuns
     */
    public void addGun(int position, Gun new_gun)
    {
        if (guns[position] == null)
        {
            guns[position] = new_gun;
            guns[position].setSide(side);
            guns[position].setLevel(level);
            n_guns++;
            
            setAngledGuns(angled_guns);
        }
    }
    
    /**
     * Remove the gun in the given position. Doesn't check if position exceeds array bounds.
     * 
     * @see #addGun()
     */
    public void removeGun(int position)
    {
        if (guns[position] != null)
        {
            guns[position] = null;
            n_guns--;
            
            setAngledGuns(angled_guns);
        }
    }
    
    public int getNumberOfGuns()
    {
        return n_guns;
    }
    
    /**
     * Set the guns' dx values so that the missiles are launched from appear in an neat, aligned fashion.
     * That is, make them launch from positions that are GUN_DISTANCE pixels apart from each other, and
     * aligned to the center of the spaceship.
     */
    public void reorderGuns()
    {
        // needed because there *could* be holes in the guns array.
        int counter = 0;
        
        /* We *could* add i < N_GUNS in the loop test condition, but once again, programming errors are more
         * obvious if we don't.
         */
        for (int i = 0; counter < n_guns; i++)
        {
            if (guns[i] == null)
                continue;
            
            if (n_guns % 2 == 0)
                guns[i].set_dx(GUN_DISTANCE*i - GUN_DISTANCE*(n_guns/2 - 1) - GUN_DISTANCE/2);
            else
                guns[i].set_dx(GUN_DISTANCE*i - GUN_DISTANCE*((n_guns-1)/2));
            
            counter++;
        }
    }
    
    /**
     * Return an angle that lies between -max_angle and max_angle for x between 0 and 1
     * 
     * @param x: number between 0.0 and 1.0
     */
    public double angle(double x, double max_angle)
    {
        double result = Math.asin(x-.5);
        result = max_angle/(Math.PI/6) * result;
        
        return result;
    }
    
    /**
     * Sets the ship to shoot straight or widespread (aka angled). Recalculate the guns' speed vectors based
     * on that value.
     */
    public void setAngledGuns(boolean angled)
    {
        angled_guns = angled;
        if (angled)
        {
            for (int i = 0; i < n_guns; i++)
            {
                double abs_speed = guns[i].getAbsSpeed();
                double angle;
                int x = 0; // used to get the correct order with both white and black ships.
                if (side == InvadersWorld.WHITE)
                    x = i;
                else if (side == InvadersWorld.BLACK)
                    x = n_guns-(i+1);
                
                if (n_guns == 1)
                    angle = 0.0;
                else
                    angle = angle((double)x/(double)(n_guns-1), n_guns * GUN_ANGLE);
                guns[i].setSpeed(abs_speed, angle);
            }
        }
        else
        {
            for (int i = 0; i < n_guns; i++)
            {
                double abs_speed = guns[i].getAbsSpeed();
                guns[i].setSpeed(abs_speed, 0.0); // angle of 0 -> straight
            }
        }
    }
    
    public void setSide(int new_side)
    {
        side = new_side;
    }
    public int getSide()
    {
        return side;
    }
    
    public void setLevel(int new_level)
    {
        level = new_level;
        for (int i = 0; i < n_guns; i++)
        {
            guns[i].setLevel(level);
        }
    }
    public int getLevel()
    {
        return level;
    }
    
    /**
     * Try to fire (all) the guns. That is, call each gun's missile() method and check if something came out yet.
     * If yes, place it into the world.
     */
    protected void fire() {
        int x = getX();
        int y = getY();
        World world = getWorld();
        
        for (int i = 0; i < N_GUNS; i++)
        {
            if (guns[i] != null)
            {
                Missile m = guns[i].missile();
                if (m != null)
                    world.addObject(m, x + guns[i].get_dx(), y - guns[i].get_dy());
                    // subtracting dy here because greenfoot has 0,0 at the top left corner
            }
        }
    }
    
    public void addHitpoints(int n_hitpoints)
    {
        if (hitpoints + n_hitpoints <= max_hitpoints)
            hitpoints += n_hitpoints;
        else
            hitpoints = max_hitpoints;
    }
    public void removeHitpoints(int n_hitpoints)
    {
        blink = 1;
        hitpoints -= n_hitpoints;
        if (hitpoints <= 0)
        {
            this.beforeDestruction();
            destroysOnNextTurn = true; // shouldn't survive
        }
    }
    public int getHitpoints()
    {
        return hitpoints;
    }
    public void setHitpoints(int hit)
    {
        hitpoints = hit;
    }
    public int getMaxHitpoints()
    {
        return max_hitpoints;
    }
    public void setMaxHitpoints(int max)
    {
        max_hitpoints = max;
    }
    
    /**
     * This method is called right before the spaceship is destroyed by (and only by) lack of hitpoints.
     * Empty here, to be overridden in subclasses that e. g. want to leave power-ups behind.
     */
    public void beforeDestruction()
    {
        
    }
    
    /**
     * If the missile was shot by an enemy, subtract hitpoints.
     */
    public boolean hit(Missile m)
    {
        if (m.getSide() == side) // the missile is ours
            return true;
        else
        {
            this.removeHitpoints(m.getDamage());
            return true;
        }
    }
    
    /**
     * Sort-of abstract method available for overriddance [sic]. Subclasses can implement this to set their sprite
     * (back) to its default value. It's called whenever a reset to the default sprite is needed, e. g. after
     * the blinking animation.
     * 
     * @see #doBlink()
     */
    protected void setupImages()
    {
        
    }
    
    public void setBlinkFile(String the_blink_file)
    {
        blink_file = the_blink_file;
    }
    public String getBlinkFile()
    {
        return blink_file;
    }
    
    /**
     * Do the blinking animation. This is called whenever the ship has hitpoints subtracted. It evaluates and
     * changes the "blink" variable, and changes sprites if necessary.
     */
    public void doBlink()
    {
        int max = max_blink + 1; // This way max_blink actually contains the number of turns the animation lasts.
        
        if (blink == 1)
        {
            try
            {
                this.setImage(blink_file);
            }
            catch (IllegalArgumentException ex)
            {
                // The image could not be found -- don't blink then.
            }
        }
        
        if (blink > 0 && blink < max_blink)
        {
            blink++;
        }
        else if (blink >= max_blink) // >= because you never know.
        {
             setupImages();
             blink = 0;
        }
    }
    
    public void setMaxBlink(int max)
    {
        max_blink = max;
    }
    public int getMaxBlink()
    {
        return max_blink;
    }
}
