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

/**
 * InvadersWorld: A styled and fun Space Invaders clone. Special feature: multiplayer mode: two humans
 * can play with one keyboard, sending waves of spaceships into battle against each other.
 * 
 * @author Martin Schend, Jannis Andrija Schnitzer
 * @version 2011-01-21
 */
public class InvadersWorld  extends World
{
    /**
     * Magic variable altering the game's behavior. If set to true, multi-player mode is enabled!
     */
    protected static final boolean MULTIPLAYER = true;
    
    /**
     * Game field width.
     */
    protected static final int WIDTH = 800;
    
    /**
     * Game field height.
     */
    protected static final int HEIGHT = 600;
    
    /**
     * Health bars' width.
     */
    protected static final int BAR_WIDTH = 183;
    
    /**
     * Health bars' height.
     */
    protected static final int BAR_HEIGHT = 25;
    
    /**
     * Distance of health bars to game field edges.
     */
    protected static final int DISTANCE = 13;
    
    /**
     * Size of the zone at the upper and lower edge of the playfield where new spaceships can be
     * created.
     */
    protected static final int STARTING_ZONE = 30;
    
    // constants for sides
    public static final int WHITE = 0;
    public static final int BLACK = 1;
    
    /**
     * Number of sides participating in the game. Usually 2, still written as a constant for clearer
     * code.
     */
    public static final int N_SIDES = 2;
    
    protected SpaceshipFactory[] factory;
    protected CollectibleFactory[] collFactory;
    
    /**
     * Array[N_SIDES]: true if the corresponding side is a human player, false if they're AI.
     */
    public boolean[] player;
    
    /**
     * Array[N_SIDES]: true if the corresponding side should generate & send out autonomous spaceships.
     * Usually true for the computer enemy or for both sides in 2-player mode.
     * 
     * @see #act()
     */
    public boolean[] player_generates_spaceships;
    
    /**
     * Array[N_SIDES]: Denominator for probability P for spaceship creation. I. e., chance for a
     * spaceship being generated each turn is 1/chance[side].
     * 
     * @see #act()
     */
    public int[] chance;
    
    /**
     * Array[N_SIDES]: Denominator for probability P for collectible creation. I. e., chance for a
     * collectible being generated for each destroyed spaceship is 1/coll_chance[side].
     * 
     * @see #maybeCreateCollectible()
     * @see #createCollectible()
     */
    public int[] coll_chance;
    
    /**
     * List containing objects that need destruction at the start of the next turn.
     * (Currently unused, if I am informed correctly.)
     */
    protected ArrayList destroyList;
    
    protected HealthBar blackBar;
    protected HealthBar whiteBar;
    
    /**
     * if it is clear that one side wins or loses (within a Greenfoot turn), this variable is set to
     * the side of the winner. InvadersWorld.act() will act thereafter.
     * 
     * @see #act()
     */
    protected int next_turn_winner = -1;
    
    public InvadersWorld()
    {
        this(WIDTH, HEIGHT, 1);
    }
    
    /**
     * InvadersWorld constructor. Creates and initializes all objects that are necessary for
     * gameplay. The section that creates the two manually controlled spaceships might be of special
     * interest. They're marked in the source code.
     */
    public InvadersWorld(int worldWidth, int worldHeight, int cellSize)
    {
        super(worldWidth, worldHeight, 1);
        
        factory = new SpaceshipFactory[N_SIDES];
        factory[WHITE] = new SpaceshipFactory(WHITE, 1); // TODO: do we always start at level 1?
        factory[BLACK] = new SpaceshipFactory(BLACK, 1);
        factory[WHITE].setDirection(-1.0);
        factory[BLACK].setDirection(+1.0);
        
        collFactory = new CollectibleFactory[N_SIDES];
        collFactory[WHITE] = new CollectibleFactory(WHITE);
        collFactory[BLACK] = new CollectibleFactory(BLACK);
        
        player = new boolean[N_SIDES];
        player_generates_spaceships = new boolean[N_SIDES];
        chance = new int[N_SIDES];
        coll_chance = new int[N_SIDES];
        player[WHITE] = true;
        player[BLACK] = MULTIPLAYER;
        player_generates_spaceships[WHITE] = MULTIPLAYER;
        player_generates_spaceships[BLACK] = true;
        
        // setting of probabilities
        chance[WHITE] = 100;
        chance[BLACK] = 100;
        coll_chance[WHITE] = 4;
        coll_chance[BLACK] = 4;
        
        setBackground("background.png");

        // White manually controlled spaceship creation. The comments below are solely for readability.
        ManuallyControlledSpaceship white_ship = new ManuallyControlledSpaceship(1); // TODO: level, s.a.
        white_ship.setSide(WHITE);
        this.addObject(white_ship, getWidth()/2, getHeight()-65);
        
        // gun of white ship
        LaserGun white_gun = new LaserGun(20.0, 0.0);
        white_ship.addGun(0, white_gun);
        
        // white health bar
        whiteBar = new HealthBar();
        whiteBar.setSide(WHITE);
        whiteBar.setMaxHealth(white_ship.getMaxHitpoints());
        this.addObject(whiteBar, DISTANCE+(BAR_WIDTH/2), getHeight() - DISTANCE - BAR_HEIGHT/2);
        white_ship.updateHealth();
        
        // you may only move up & down if you're in single player mode.
        white_ship.setMayMoveUpAndDown(player[WHITE] && !player[BLACK]);
        
        // two-player mode: creation of black spaceship
        if (player[BLACK])
        {
            ManuallyControlledSpaceship black_ship = new ManuallyControlledSpaceship(1); // TODO: level
            black_ship.setSide(BLACK);
            black_ship.setRotation(180);
            this.addObject(black_ship, getWidth()/2, 65);
            
            // gun of black ship
            LaserGun black_gun = new LaserGun(-20.0, 0.0);
            black_ship.addGun(0, black_gun);
            
            // black health bar
            blackBar = new HealthBar();
            blackBar.setSide(BLACK);
            blackBar.setMaxHealth(black_ship.getMaxHitpoints());
            this.addObject(blackBar, getWidth() - ((BAR_WIDTH/2)) - DISTANCE, (BAR_HEIGHT/2)+DISTANCE);
            black_ship.updateHealth();
            
            // see above
            black_ship.setMayMoveUpAndDown(player[BLACK] && !player[WHITE]);
        }
        
        destroyList = new ArrayList();
    }
    
    public void setHealthBar(int side, int value)
    {
        if (side == WHITE)
            whiteBar.setHealth(value);
        else if (side == BLACK)
            blackBar.setHealth(value);
    }
    
    public void setHealthBar(int side, int value, int max_value)
    {
        if (side == WHITE)
        {
            whiteBar.setMaxHealth(max_value);
            whiteBar.setHealth(value);
        }
        else if (side == BLACK)
        {
            blackBar.setMaxHealth(max_value);
            blackBar.setHealth(value);
        }
    }
    
    /**
     * Actions that a world just has to perform. Main things:
     *  1. Create random spaceships.
     *  2. Delete objects in the deletion list.
     *  3. End the game if necessary.
     *  
     *  TODO: Increase difficulty (i. e. ship levels, probabilities) over time.
     */
    public void act()
    {
        // randomly add random spaceships, huh?
        for (int side = 0; side < N_SIDES; side++)
        {
            if (player_generates_spaceships[side])
            {
                if (Greenfoot.getRandomNumber(chance[side]) != 0) // no ship if the RNG doesn't want
                    continue;
                
                int x, y;
                AIControlledSpaceship ship;
                y = 1 + Greenfoot.getRandomNumber(STARTING_ZONE);
                if (side == WHITE) // sorry, no abstract logic possible here anymore.
                    y = getHeight()-y;
                x = 1 + Greenfoot.getRandomNumber(getWidth()-1);
                ship = factory[side].ship();
                this.addObject(ship, x, y);
            }
        }
        
        for (int i = 0; i < destroyList.size(); i++)
            this.removeObject((QuantumObject) destroyList.get(i));
        
        if (next_turn_winner > -1)
            gameOver(next_turn_winner);
    }
    
    /**
     * Usually called by dying spaceships, this rolls a dice and maybe creates a collectible
     * at that very place.
     */
    public void maybeCreateCollectible(int x, int y, int c_side)
    {
        if (Greenfoot.getRandomNumber(coll_chance[c_side]) == 0)
            createCollectible(x, y, c_side);
    }
    
    /**
     * Uses the side's collectible factory to get a random collectible.
     * 
     * @see CollectibleFactory
     */
    public void createCollectible(int x, int y, int c_side)
    {
        Collectible coll;
        coll = collFactory[c_side].collectible();
        this.addObject(coll, x, y);
    }
    
    public void addObjectToDestroy(QuantumObject obj) {
        destroyList.add(obj);
    }
    
    public void gameOverNextTurn(int winner)
    {
        next_turn_winner = winner;
    }
    
    /**
     * Called when the game is totally over, this method displays an end screen.
     * 
     * TODO: scoring.
     */
    public void gameOver(int winner)
    {
        ArrayList EVERYTHING = (ArrayList) this.getObjects(Actor.class);
        
        for (int i = 0; i < EVERYTHING.size(); i++)
            this.removeObject((Actor) EVERYTHING.get(i));
        
        Label gameOverLabel;
        
        if (winner == WHITE)
            gameOverLabel = new Label("Game Over. White wins."); 
        else
            gameOverLabel = new Label("Game Over. Black wins."); 
        
        addObject(gameOverLabel, 550, 310);
        Greenfoot.stop();
    }
}
