package de.thdeg.greiner.superpangworld.graphics.base;

import de.thdeg.greiner.superpangworld.gameview.GameView;
import de.thdeg.greiner.superpangworld.graphics.helper.HelperValues;
import de.thdeg.greiner.superpangworld.graphics.moveable.Harpoon;
import de.thdeg.greiner.superpangworld.graphics.moveable.PlayerObject;
import de.thdeg.greiner.superpangworld.graphics.moveable.SpecialBubble;

import java.awt.geom.Point2D;
import java.util.*;

/**
 * A Bubble, which moves around on the screen and can be shot by the player.
 */
public abstract class Bubble extends CollidingGameObject implements MovingGameObject{

    /** The random generator */
    protected final Random random;
    /** Flag, if the bubble is in the spawning phase */
    private boolean spawning;
    /** Spawning speed */
    private int spawnSpeed;
    /** Unique id for the spawn event timer */
    protected final long spawnID;
    /** The current bubble velocity */
    protected Point2D velocity;

    /**
     * Create a bubble with default values.
     *
     * @param gameView the {@link GameView} to display the bubble
     * @param objectsToCollideWith the list of {@link CollidableGameObject} the bubble can collide with
     */
    public Bubble(GameView gameView, List<CollidableGameObject> objectsToCollideWith){
        super(gameView,objectsToCollideWith);
        random = new Random();
        rotation = 90;
        size = 10;
        width = (int) (12 * size);
        height = (int) (12 * size);
        speedInPixel = 1;

        spawnID = UUID.randomUUID().getLeastSignificantBits();
        position.x = 2*HelperValues.FRAME_BORDER_WIDTH + random.nextInt((int)(GameView.WIDTH-width - (2*HelperValues.FRAME_BORDER_WIDTH))-(int)(2*HelperValues.FRAME_BORDER_WIDTH));
        position.y = -width;
        spawning = true;
        spawnSpeed = 500;
        gameView.setTimer("spawn"+spawnID,"bubble"+spawnID,spawnSpeed);

        if(random.nextBoolean()){
            velocity = new Point2D.Double(1 + random.nextInt(501)/100.0, 1 + random.nextInt(10));
        }else{
            velocity = new Point2D.Double((1 + random.nextInt(501)/100.0) * -1.0, 1+ random.nextInt(10));
        }
    }

    /**
     * Create a bubble.
     *
     * @param gameView the {@link GameView} to display the bubble
     * @param objectsToCollideWith the list of {@link CollidableGameObject} the bubble can collide with
     * @param size the object size
     * @param position the position
     * @param spawning flag, if the bubble is in the spawning phase
     * @param velocity the initial velocity of the bubble
     */
    public Bubble(GameView gameView, List<CollidableGameObject> objectsToCollideWith, double size, Position position, boolean spawning,  Point2D velocity){
        super(gameView,objectsToCollideWith);
        random = new Random();
        rotation = 90;
        this.size = size;
        setPosition(position.x, position.y);
        this.spawning = spawning;
        this.velocity = velocity;
        spawnID = UUID.randomUUID().getLeastSignificantBits();

        width = (int) (12 * size);
        height = (int) (12 * size);

        if(spawning){
            spawnSpeed = 500;
            gameView.setTimer("spawn"+spawnID,"bubble"+spawnID,spawnSpeed);
        }


    }

    @Override
    public void updatePosition(){
        if(spawning){
            if(position.y >= HelperValues.FRAME_BORDER_WIDTH){
                spawning = false;
            } else if(gameView.timerExpired("spawn"+spawnID,"bubble"+spawnID)){
                position.down((width + HelperValues.FRAME_BORDER_WIDTH)/4.0);
                gameView.setTimer("spawn"+spawnID,"bubble"+spawnID,spawnSpeed);
            }
        }else{
            Point2D newLocation = new Point2D.Double(position.x,position.y);
            double speedFactor = 0.2;
            Point2D bubbleGravity = new Point2D.Double(0,0.1);

            newLocation = new Point2D.Double(newLocation.getX() + (velocity.getX() * speedFactor), newLocation.getY() + (velocity.getY() * speedFactor));
            velocity = new Point2D.Double(velocity.getX() + bubbleGravity.getX(), velocity.getY() + bubbleGravity.getY());

            // Wall bounce
            if((newLocation.getX() >= GameView.WIDTH - width - HelperValues.FRAME_BORDER_WIDTH) || newLocation.getX() <= HelperValues.FRAME_BORDER_WIDTH){
                velocity = new Point2D.Double(velocity.getX() * -1.0, velocity.getY());

            }
            // Floor/roof bounce
            if(newLocation.getY() >= HelperValues.FRAME_WINDOW_HEIGHT + HelperValues.FRAME_BORDER_WIDTH - width){
                velocity = new Point2D.Double(velocity.getX(), velocity.getY() * -0.95);
                newLocation = new Point2D.Double(newLocation.getX(), HelperValues.FRAME_WINDOW_HEIGHT + HelperValues.FRAME_BORDER_WIDTH - width);
                if(this instanceof SpecialBubble){
                    SpecialBubble specialBubble = (SpecialBubble) this;
                    specialBubble.freezeState = !specialBubble.freezeState;
                    gameView.playSound("specialBounce.wav",false);
                }
            }else if(newLocation.getY() <= HelperValues.FRAME_BORDER_WIDTH){
                velocity = new Point2D.Double(velocity.getX(), velocity.getY() * -0.95);
                newLocation = new Point2D.Double(newLocation.getX(), HelperValues.FRAME_BORDER_WIDTH);
            }

            setPosition(newLocation.getX(),newLocation.getY());
        }
    }

    /**
     * Get the score to add when bursting the bubble.
     *
     * @return the score to add
     */
    public abstract int getBurstScore();

    /**
     * Get the size of the bubble.
     *
     * @return the size
     */
    public double getSize(){
        return this.size;
    }

    @Override
    public void reactToCollision(CollidableGameObject otherObject) {
        if (otherObject instanceof PlayerObject && !gamePlayManager.movementFroze) {
            gamePlayManager.looseLife();
        } else if (otherObject instanceof Harpoon && !spawning) {
            gamePlayManager.destroy((Harpoon) otherObject,this);
        }
    }

    /**
     * Destroys the bubble.
     */
    public abstract void destroy();
}
