package helowars;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 * Main game.
 * @author Adarshkumar Pavani
 */

public class Game {
    
	//Object for random number generation
    Random random;
    
    // Specifically used for setting the mouse position
    Robot robot;
    
    // Player - chopper that is managed by player.
    PlayerChopper player;
    
    // Opponent choppers.
    private ArrayList<OpponentChopper> opponentChopperList = new ArrayList<OpponentChopper>();
    
    // Explosions
    private ArrayList<Animation> explosionsList;
    private BufferedImage explosionImage;
    
    //to keep track of all pellets, missiles and missile smoke
    private ArrayList<Pellet> pelletsList;
    private ArrayList<Missile> missilesList;
    private ArrayList<MissileSmoke> missilesSmokeList;
    
    //sky color image.
    private BufferedImage skyImage;
    
    //background images (clouds, mountains, grass)
    private BufferedImage clouds1Image;
    private BufferedImage clouds2Image;
    private BufferedImage mountainsImage;
    private BufferedImage grassImage;
    
    //Scrolling image objects
    private Background clouds1Scrolling;
    private Background clouds2Scrolling;
    private Background mountainsScrolling;
    private Background grassScrolling;
    
    //Mouse Pointer Image
    private BufferedImage mousePointerImage;
    
    // Font for showing stats in the end
    private Font font;
    
    // Stats (destroyed enemies, excaped enemies)
    private int escapedOpponents;
    private int destroyedOpponents;
    

    public Game()
    {
        Framework.gameState = Framework.GameplayState.GAME_LOADING;
        
        Thread threadForInitGame = new Thread() {
            @Override
            public void run(){
                // To set variables and objects for the game.
                initialize();
                // Load game files (images, sounds, ...)
                LoadGameContent();
                
                Framework.gameState = Framework.GameplayState.PLAYING;
            }
        };
        threadForInitGame.start();
    }
    
    
   /**
     * Setting variables and objects for the game.
     */
    private void initialize()
    {
        random = new Random();
        
        try {
            robot = new Robot();
        } catch (AWTException ex) {
            Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        player = new PlayerChopper(Framework.frameWidth / 4, Framework.frameHeight / 4);
        
        opponentChopperList = new ArrayList<OpponentChopper>();
        
        explosionsList = new ArrayList<Animation>();
        
        pelletsList = new ArrayList<Pellet>();
        
        missilesList = new ArrayList<Missile>();
        missilesSmokeList = new ArrayList<MissileSmoke>();
        
        
        // Moving images.
        clouds1Scrolling = new Background();
        clouds2Scrolling = new Background();
        mountainsScrolling = new Background();
        grassScrolling = new Background();
        
        font = new Font(Font.MONOSPACED, Font.BOLD, 18);
        
        escapedOpponents = 0;
        destroyedOpponents = 0;
    }
    
    /**
     * To load image files
     */
    private void LoadGameContent()
    {
        try 
        {
            // Background image objects
            URL skyPath = this.getClass().getResource("/helowars/resources/sky.jpg");
            skyImage = ImageIO.read(skyPath);
            URL clouds1Path = this.getClass().getResource("/helowars/resources/clouds1.png");
            clouds1Image = ImageIO.read(clouds1Path);
            URL clouds2Path = this.getClass().getResource("/helowars/resources/clouds2.png");
            clouds2Image = ImageIO.read(clouds2Path);
            URL mountainsPath = this.getClass().getResource("/helowars/resources/mountains.png");
            mountainsImage = ImageIO.read(mountainsPath);
            URL grassPath = this.getClass().getResource("/helowars/resources/grass.png");
            grassImage = ImageIO.read(grassPath);
            
            // parts of opponent chopper
            URL opponentChopperPath = this.getClass().getResource("/helowars/resources/opponent_chopper_body.png");
            OpponentChopper.opponentChopperImage = ImageIO.read(opponentChopperPath);
            URL topPropellerPath = this.getClass().getResource("/helowars/resources/opponent_top_propeller_animation.png");
            OpponentChopper.topPropellerImage = ImageIO.read(topPropellerPath);
            URL backPropellerPath = this.getClass().getResource("/helowars/resources/opponent_back_propeller_animation.png");
            OpponentChopper.backPropellerImage = ImageIO.read(backPropellerPath);
            
            // missile and smoke images
            URL missilePath = this.getClass().getResource("/helowars/resources/missile.png");
            Missile.missileImage = ImageIO.read(missilePath);
            URL missileSmokePath = this.getClass().getResource("/helowars/resources/missile_smoke.png");
            MissileSmoke.smokeImage = ImageIO.read(missileSmokePath);
            
            // image of explosion animation.
            URL explosionAnimImgUrl = this.getClass().getResource("/helowars/resources/explosion.png");
            explosionImage = ImageIO.read(explosionAnimImgUrl);
            
            //mouse pointer image.
            URL mousePointerPath = this.getClass().getResource("/helowars/resources/mouse_pointer.png");
            mousePointerImage = ImageIO.read(mousePointerPath);
            
            //Pellet image.
            URL pelletPath = this.getClass().getResource("/helowars/resources/pellet.png");
            Pellet.pelletImg = ImageIO.read(pelletPath);
        } 
        catch (IOException ex) 
        {
            Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        //initializing background images
        clouds1Scrolling.Initialize(clouds1Image, -6, 0);
        clouds2Scrolling.Initialize(clouds2Image, -2, 0);
        mountainsScrolling.Initialize(mountainsImage, -1, Framework.frameHeight - grassImage.getHeight() - mountainsImage.getHeight() + 40);
        grassScrolling.Initialize(grassImage, -1.2, Framework.frameHeight - grassImage.getHeight());
    }
    
    
    /**
     * To keep updating game logic.
     * 
     * @param gameTime The elapsed game time in nanoseconds.
     * @param mousePosition current mouse position.
     */
    public void UpdateGame(long gameTime, Point mousePosition)
    {
        // changing the game status one the player is dead and explosions are displayed
        if( !isPlayerAlive() && explosionsList.isEmpty() ){
            Framework.gameState = Framework.GameplayState.GAMEOVER;
            return;
        }
        
        
        //game over once all the ammo and missiles are exhausted
        if(player.numberOfAmmo <= 0 && 
           player.numberOfMissiles <= 0 && 
           pelletsList.isEmpty() && 
           missilesList.isEmpty() && 
           explosionsList.isEmpty())
        {
            Framework.gameState = Framework.GameplayState.GAMEOVER;
            return;
        }
        
        
        // update the player if he's alive
        if(isPlayerAlive()){
            isPlayerShooting(gameTime, mousePosition);
            isMissileFired(gameTime);
            player.isMoving();
            player.Update();
        }
        
        limitMousePosition(mousePosition);
        
        updatePellets();
        
        updateMissiles(gameTime);
        updateMissileSmoke(gameTime);
        
        createOpponentChopper(gameTime);
        updateOpponents();
        
        updateExplosions();
    }
    
    /**
     * Draw the game to the screen.
     * @param g2d Graphics2D
     * @param mousePosition current mouse position.
     */
    public void Draw(Graphics2D g2d, Point mousePosition, long gameTime)
    {
        // Image for background sky color.
        g2d.drawImage(skyImage, 0, 0, Framework.frameWidth, Framework.frameHeight, null);
        
        // Moving images.
        mountainsScrolling.Draw(g2d);
        grassScrolling.Draw(g2d);
        clouds2Scrolling.Draw(g2d);
        
        if(isPlayerAlive())
            player.Draw(g2d);
        
        // Draws all the opponents.
        for(int i = 0; i < opponentChopperList.size(); i++)
        {
            opponentChopperList.get(i).Draw(g2d);
        }
        
        // Draws all the pellets. 
        for(int i = 0; i < pelletsList.size(); i++)
        {
            pelletsList.get(i).Draw(g2d);
        }
        
        // Draws all the missiles. 
        for(int i = 0; i < missilesList.size(); i++)
        {
            missilesList.get(i).Draw(g2d);
        }
        // Draws smoke of all the missiles.
        for(int i = 0; i < missilesSmokeList.size(); i++)
        {
            missilesSmokeList.get(i).Draw(g2d);
        }
        
        // Draw all explosions.
        for(int i = 0; i < explosionsList.size(); i++)
        {
            explosionsList.get(i).Draw(g2d);
        }
        
        // Draw stats
        g2d.setFont(font);
        g2d.setColor(Color.darkGray);
        
        g2d.drawString(formatTime(gameTime), Framework.frameWidth/2 - 45, 21);
        g2d.drawString("DESTROYED: " + destroyedOpponents, 10, 21);
        g2d.drawString("ESCAPED: "   + escapedOpponents,   10, 41);
        g2d.drawString("MISSILES: "   + player.numberOfMissiles, 10, 81);
        g2d.drawString("AMMO: "      + player.numberOfAmmo, 10, 101);
        
        // drawing coulds above the chopper layer
        clouds1Scrolling.Draw(g2d);
        
        // Mouse pointer
        if(isPlayerAlive())
            drawRotatingMousePointer(g2d, mousePosition);
    }
    
    
    
    /**
     * Creating opponents based on time
     * @param gameTime Game time.
     */
    private void createOpponentChopper(long gameTime)
    {
        if(gameTime - OpponentChopper.timeOfLastCreatedOpponent >= OpponentChopper.timeBetweenNewOpponents)
        {
            OpponentChopper oh = new OpponentChopper();
            int xCoordinate = Framework.frameWidth;
            int yCoordinate = random.nextInt(Framework.frameHeight - OpponentChopper.opponentChopperImage.getHeight());
            oh.Initialize(xCoordinate, yCoordinate);
            // Add created opponenent to the list of enemies.
            opponentChopperList.add(oh);
            
            // Speed up opponent speed and aperence.
            OpponentChopper.speedUp();
            
            // Sets new time for last created opponent.
            OpponentChopper.timeOfLastCreatedOpponent = gameTime;
        }
    }
    
    
    
    
    /**
 	 * To draw a rotating mouse pointer
     * @param g2d Graphics2D
     * @param mousePosition Position of the mouse.
     */
    private void drawRotatingMousePointer(Graphics2D g2d, Point mousePosition)
    {
        double RIGHT_ANGLE_RADIANS = Math.PI / 2;
        
        // Positon of the player chopper machine gun.
        int pivotX = player.gunXcoordinate;
        int pivotY = player.gunYcoordinate;
        
        int a = pivotX - mousePosition.x;
        int b = pivotY - mousePosition.y;
        double ab = (double)a / (double)b;
        double alfaAngleRadians = Math.atan(ab);

        if(mousePosition.y < pivotY) // Above the chopper.
            alfaAngleRadians = RIGHT_ANGLE_RADIANS - alfaAngleRadians - RIGHT_ANGLE_RADIANS*2;
        else if (mousePosition.y > pivotY) // Under the chopper.
            alfaAngleRadians = RIGHT_ANGLE_RADIANS - alfaAngleRadians;
        else
            alfaAngleRadians = 0;

        AffineTransform origXform = g2d.getTransform();
        AffineTransform newXform = (AffineTransform)(origXform.clone());

        newXform.rotate(alfaAngleRadians, mousePosition.x, mousePosition.y);
        g2d.setTransform(newXform);
        
        g2d.drawImage(mousePointerImage, mousePosition.x, mousePosition.y - mousePointerImage.getHeight()/2, null); 
        
        g2d.setTransform(origXform);
    }
    
    /**
     * Formatting elapsed time into mm:ss format.
     * @param time Time that is in nanoseconds.
     * @return Time in mm:ss format.
     */
    private static String formatTime(long time){
            // Given time in seconds.
            int sec = (int)(time / Framework.milisecsInNanosecs / 1000);

            // Given time in minutes and seconds.
            int min = sec / 60;
            sec = sec - (min * 60);

            String minString, secString;

            if(min <= 9)
                minString = "0" + Integer.toString(min);
            else
                minString = "" + Integer.toString(min);

            if(sec <= 9)
                secString = "0" + Integer.toString(sec);
            else
                secString = "" + Integer.toString(sec);

            return minString + ":" + secString;
    }
    
    
    
    
    // Game updation methods
   
    /**
     * set game over status once the player is dead 
     * @return True if player is alive, false otherwise.
     */
    private boolean isPlayerAlive()
    {
        if(player.health <= 0)
            return false;
        
        return true;
    }
    
    /**
     * To check if the player is shooting with the gun and renders pellets.
     */
    private void isPlayerShooting(long gameTime, Point mousePosition)
    {
        if(player.isFiring(gameTime))
        {
            Pellet.timeOfLastCreatedPellet = gameTime;
            player.numberOfAmmo--;
            
            Pellet b = new Pellet(player.gunXcoordinate, player.gunYcoordinate, mousePosition);
            pelletsList.add(b);
        }
    }
    
    /**
     *creates missile on firing it
     */
    private void isMissileFired(long gameTime)
    {
        if(player.isFiredMissile(gameTime))
        {
            Missile.timeOfLastSpawnedMissile = gameTime;
            player.numberOfMissiles--;
            
            Missile r = new Missile();
            r.Initialize(player.missileHolderXcoordinate, player.missileHolderYcoordinate);
            missilesList.add(r);
        }
    }
    
   
    
    /**
     * Updates all opponents.
     * Move the chopper and checks if he left the screen.
     * Updates chopper animations.
     * Checks if opponent was destroyed.
     * Checks if any opponent collision with player.
     */
    private void updateOpponents()
    {
        for(int i = 0; i < opponentChopperList.size(); i++)
        {
            OpponentChopper oc = opponentChopperList.get(i);
            
            oc.Update();
            
            // check for player and opponent crash
            Rectangle playerRectangel = new Rectangle(player.xCoordinate, player.yCoordinate, player.playerChopperImage.getWidth(), player.playerChopperImage.getHeight());
            Rectangle opponentRectangel = new Rectangle(oc.xCoordinate, oc.yCoordinate, OpponentChopper.opponentChopperImage.getWidth(), OpponentChopper.opponentChopperImage.getHeight());
            if(playerRectangel.intersects(opponentRectangel)){
                player.health = 0;
                
                // Remove chopper from the list.
                opponentChopperList.remove(i);
                
                // Play animation of explosion for player chopper
                for(int exNum = 0; exNum < 3; exNum++){
                    Animation expAnimation = new Animation(explosionImage, 134, 134, 12, 45, false, player.xCoordinate + exNum*60, player.yCoordinate - random.nextInt(100), exNum * 200 +random.nextInt(100));
                    explosionsList.add(expAnimation);
                }
                // Play animation of explosion for opponent chopper
                for(int exNum = 0; exNum < 3; exNum++){
                    Animation expAnimation = new Animation(explosionImage, 134, 134, 12, 45, false, oc.xCoordinate + exNum*60, oc.yCoordinate - random.nextInt(100), exNum * 200 +random.nextInt(100));
                    explosionsList.add(expAnimation);
                }
                
                break;
            }
            
            // Check health.
            if(oc.health <= 0){
                // Add explosion of chopper.
                Animation expAnimation = new Animation(explosionImage, 134, 134, 12, 45, false, oc.xCoordinate, oc.yCoordinate - explosionImage.getHeight()/3, 0);
                explosionsList.add(expAnimation);

                destroyedOpponents++;
                opponentChopperList.remove(i);
                
                // Chopper was destroyed so moving to next chopper.
                continue;
            }
            
            // If the current opponent has left the screen its can be removed the list and update the escapedOpponents variable.
            if(oc.isOutOfScreen())
            {
                opponentChopperList.remove(i);
                escapedOpponents++;
            }
        }
    }
    
    /**
     * Update pellets. 
     * Move pellets.
     * Check if the pellets is left the screen.
     * Check if any pellets is hit any opponent.
     */
    private void updatePellets()
    {
        for(int i = 0; i < pelletsList.size(); i++)
        {
            Pellet pellet = pelletsList.get(i);
            
            // Move the pellet.
            pellet.Update();
            
            // check and update the list for pellets that have moved out of the screen
            if(pellet.isItOutOfScreen()){
                pelletsList.remove(i);

                continue;
            }
            
            //check if it hits opponent
            Rectangle pelletRectangle = new Rectangle((int)pellet.xCoordinate, (int)pellet.yCoordinate, Pellet.pelletImg.getWidth(), Pellet.pelletImg.getHeight());
            // Go trough all enemis.
            for(int j = 0; j < opponentChopperList.size(); j++)
            {
                OpponentChopper oc = opponentChopperList.get(j);

                Rectangle opponentRectangel = new Rectangle(oc.xCoordinate, oc.yCoordinate, OpponentChopper.opponentChopperImage.getWidth(), OpponentChopper.opponentChopperImage.getHeight());

                // When a pellet hits the opponent reduce his power, remove the pellet from the list
                if(pelletRectangle.intersects(opponentRectangel))
                {
                    oc.health -= Pellet.damagePower;
                    
                    pelletsList.remove(i);
                    
                    break;
                }
            }
        }
    }

    /**
     * Update missiles. 
     * Move missile and add smoke behind it.
     * Check if the missile is left the screen.
     * Check if any missile is hit any opponent.
     */
    private void updateMissiles(long gameTime)
    {
        for(int i = 0; i < missilesList.size(); i++)
        {
            Missile missile = missilesList.get(i);
            
            // Moves the missile.
            missile.Update();
            
            // remove the missile if if it is left the screen.
            if(missile.isItOutOfScreen())
            {
                missilesList.remove(i);
                continue;
            }
            
            // Creates a missile smoke.
            MissileSmoke ms = new MissileSmoke();
            int xCoordinate = missile.xCoordinate - MissileSmoke.smokeImage.getWidth();
            int yCoordinte = missile.yCoordinate - 5 + random.nextInt(6);
            ms.Initialize(xCoordinate, yCoordinte, gameTime, missile.currentSmokeLifeTime);
            missilesSmokeList.add(ms);
            
            // Adding more smoke to fill up space in the smoke trail
            int smokePositionX = 5 + random.nextInt(8); 
            ms = new MissileSmoke();
            xCoordinate = missile.xCoordinate - MissileSmoke.smokeImage.getWidth() + smokePositionX; 
            yCoordinte = missile.yCoordinate - 5 + random.nextInt(6);
            ms.Initialize(xCoordinate, yCoordinte, gameTime, missile.currentSmokeLifeTime);
            missilesSmokeList.add(ms);
            
            // Increase the life time for the next piece of missile smoke.
            missile.currentSmokeLifeTime *= 1.02;
            
            //remove missile from the list of it hits the opponent
            if( checkIfMissileHitOpponent(missile) )
                missilesList.remove(i);
        }
    }
    
    /**
     * Returns true if the missile hits the opponent
     */
    private boolean checkIfMissileHitOpponent(Missile missile)
    {
        boolean didItHitOpponent = false;
        
        Rectangle missileRectangle = new Rectangle(missile.xCoordinate, missile.yCoordinate, 2, Missile.missileImage.getHeight());
        
        // Go trough all enemis.
        for(int j = 0; j < opponentChopperList.size(); j++)
        {
            OpponentChopper oc = opponentChopperList.get(j);

            // Current opponent rectangle.
            Rectangle opponentRectangel = new Rectangle(oc.xCoordinate, oc.yCoordinate, OpponentChopper.opponentChopperImage.getWidth(), OpponentChopper.opponentChopperImage.getHeight());

            // if the missle hits the opponent, reduce health of the opponent
            if(missileRectangle.intersects(opponentRectangel))
            {
                didItHitOpponent = true;
                
                oc.health -= Missile.damagePower;
                
                break;
            }
        }
        
        return didItHitOpponent;
    }
    
    /**
     * Updates smoke of all the missiles.
     * If the life time of the smoke is over then it is deleted from list.
     * Changes a transparency of a smoke image, so that smoke slowly disappear.
     */
    private void updateMissileSmoke(long gameTime)
    {
        for(int i = 0; i < missilesSmokeList.size(); i++)
        {
            MissileSmoke ms = missilesSmokeList.get(i);
            
            // remove smoke
            if(ms.didSmokeDisapper(gameTime))
                missilesSmokeList.remove(i);
            
            ms.updateTransparency(gameTime);
        }
    }
    
    /**
     * For updating all the animations of an explosion and remove the animation when is over.
     */
    private void updateExplosions()
    {
        for(int i = 0; i < explosionsList.size(); i++)
        {
            // Removing animation from the list.
            if(!explosionsList.get(i).active)
                explosionsList.remove(i);
        }
    }
    
    /**
     * It limits the distance of the mouse from the player.
     */
    private void limitMousePosition(Point mousePosition)
    {
        int maxYcoordinateDistanceFromPlayer_top = 30;
        int maxYcoordinateDistanceFromPlayer_bottom = 120;
        
        // Mouse cursor will always be the same distance from the player chopper machine gun on the x coordinate.
        int mouseXcoordinate = player.gunXcoordinate + 250;
        
        // limiting the movement of pointer in y direction
        int mouseYcoordinate = mousePosition.y;
        if(mousePosition.y < player.gunYcoordinate){ // Above the chopper gun.
            if(mousePosition.y < player.gunYcoordinate - maxYcoordinateDistanceFromPlayer_top)
                mouseYcoordinate = player.gunYcoordinate - maxYcoordinateDistanceFromPlayer_top;
        } else {
            if(mousePosition.y > player.gunYcoordinate + maxYcoordinateDistanceFromPlayer_bottom)
                mouseYcoordinate = player.gunYcoordinate + maxYcoordinateDistanceFromPlayer_bottom;
        }
        
        // moving y co-ordinate with the chopper
        mouseYcoordinate += player.movingYspeed;
        
        // Move the mouse.
        robot.mouseMove(mouseXcoordinate, mouseYcoordinate);
    }
    
    /**
     * Draws game stats on game over
     * @param g2d Graphics2D
     * @param gameTime Elapsed game time.
     */
    public void drawStats(Graphics2D g2d, long gameTime){
        g2d.drawString("Time Elapsed: " + formatTime(gameTime),                Framework.frameWidth/2 - 100, Framework.frameHeight/3 + 120);
        g2d.drawString("Missiles left: "     + player.numberOfMissiles,Framework.frameWidth/2 - 75, Framework.frameHeight/3 + 160);
        g2d.drawString("Ammo left: "         + player.numberOfAmmo,    Framework.frameWidth/2 - 78, Framework.frameHeight/3 + 190);
        g2d.drawString("Opponents Destroyed: " + destroyedOpponents,       Framework.frameWidth/2 - 100, Framework.frameHeight/3 + 230);
        g2d.drawString("Opponents Escaped: "   + escapedOpponents,         Framework.frameWidth/2 - 95, Framework.frameHeight/3 + 260);
        g2d.setFont(new Font(Font.MONOSPACED, Font.BOLD, 30));
        g2d.drawString("STATS: ",                                 Framework.frameWidth/2 - 60, Framework.frameHeight/3 + 80);
    }
    
    /**
     * reset variables on restart
     */
    public void restartGame()
    {
        player.Reset(Framework.frameWidth / 4, Framework.frameHeight / 4);
        
        OpponentChopper.restartOpponent();
        
        Pellet.timeOfLastCreatedPellet = 0;
        Missile.timeOfLastSpawnedMissile = 0;
        
        // Empty all the lists.
        opponentChopperList.clear();
        pelletsList.clear();
        missilesList.clear();
        missilesSmokeList.clear();
        explosionsList.clear();
        
        // Statistics
        escapedOpponents = 0;
        destroyedOpponents = 0;
    }
}
