package com.brackeen.javagamebook.game;

import com.brackeen.javagamebook.math3D.Vector3D;

/**
    Klasa Physics reprezentuje różne atrybuty fizyczne (np. wpływ grawitacji)
    i udostępnia funkcje umożliwiające modyfikowanie obiektów w oparciu o te
    atrybuty. Klasa obsługuje obecnie jedynie wpływ grawitacji i przyspieszenie
    podczas wchodzenia po schodach.
*/
public class Physics {

    /**
        Domyślne przyspieszenie ziemskie w jednostkach na milisekundę kwadratową
    */
    public static final float DEFAULT_GRAVITY_ACCEL = -.002f;

    /**
        Domyślne przyspieszenie dla ruchu w górę schodów w jednostkach na
        milisekundę kwadratową.
    */
    public static final float DEFAULT_SCOOT_ACCEL = .006f;

    private static Physics instance;

    private float gravityAccel;
    private float scootAccel;
    private Vector3D velocity = new Vector3D();

    /**
        Zwraca instancję klasy Phisics. Jeśli taka instancja jeszcze
        nie istnieje, jest tworzona z domyślnymi wartościami atrybutów.
    */
    public static synchronized Physics getInstance() {
        if (instance == null) {
            instance = new Physics();
        }
        return instance;
    }


    protected Physics() {
        gravityAccel = DEFAULT_GRAVITY_ACCEL;
        scootAccel = DEFAULT_SCOOT_ACCEL;
    }


    /**
        Zwraca przyspieszenie ziemskie w jednostkach na milisekundę kwadratową.
    */
    public float getGravityAccel() {
        return gravityAccel;
    }


    /**
        Ustawia przyspieszenie ziemskie w jednostkach na milisekundę kwadratową.
    */
    public void setGravityAccel(float gravityAccel) {
        this.gravityAccel = gravityAccel;
    }


    /**
        Zwraca przyspieszenie dla ruchu w górę schodów w jednostkach na
        milisekundę kwadratową. Przyspieszenie to jest wykorzystywane do
        płynnego pokonywania schodów.
    */
    public float getScootAccel() {
        return scootAccel;
    }


    /**
        Ustawia przyspieszenie dla ruchu w górę schodów w jednostkach na
        milisekundę kwadratową. Przyspieszenie to jest wykorzystywane do
        płynnego pokonywania schodów.
    */
    public void setScootAccel(float scootAccel) {
        this.scootAccel = scootAccel;
    }


    /**
        Stosuje przyspieszenie grawitacyjne dla określonego obiektu klasy
        GameObject według czasu, który minął od ostatniej aktualizacji.
    */
    public void applyGravity(GameObject object, long elapsedTime) {
        velocity.setTo(0, gravityAccel * elapsedTime, 0);
        object.getTransform().addVelocity(velocity);
    }


    /**
        Stosuje przyspieszenie dla ruchu po schodach w górę dla określonego
        obiektu klasy GameObject według czasu, który minął od ostatniej
        aktualizacji.
    */
    public void scootUp(GameObject object, long elapsedTime) {
        velocity.setTo(0, scootAccel * elapsedTime, 0);
        object.getTransform().addVelocity(velocity);
    }


    /**
        Stosuje odwrotne przyspieszenie dla ruchu po schodach w górę dla
        określonego obiektu klasy GameObject według czasu, który minął od
        ostatniej aktualizacji.
    */
    public void scootDown(GameObject object, long elapsedTime) {
        velocity.setTo(0, -scootAccel * elapsedTime, 0);
        object.getTransform().addVelocity(velocity);
    }


    /**
        Ustawia dla danego obiektu GameObject wektor ruchu w górę, który
        umożliwi jego skok na określoną wysokość. Wywołuje metodę
        getJumpVelocity(), która oblicza szybkość ruchu za pomocą funkcji
        Math.sqrt().
    */
    public void jumpToHeight(GameObject object, float jumpHeight) {
        jump(object, getJumpVelocity(jumpHeight));
    }


    /**
        Ustawia określoną szybkość ruchu pionowego obiektu GameObject dla
        określonej szybkości skoku.
    */
    public void jump(GameObject object, float jumpVelocity) {
        velocity.setTo(0, jumpVelocity, 0);
        object.getTransform().getVelocity().y = 0;
        object.getTransform().addVelocity(velocity);
    }


    /**
        Zwraca początkową szybkość skoku potrzebną do osiągnięcia
        określonej wysokości (po uwzględnieniu bieżącej grawitacji).
        Wykorzystuje w obliczeniach funkcję Math.sqrt().
    */
    public float getJumpVelocity(float jumpHeight) {
        // użyj wzoru dla szybkości i przyspieszenia: v*v = -2 * a(y-y0)
        // (v jest szybkością skoku, a to przysp., y-y0 to maksymalna wys.)
        return (float)Math.sqrt(-2*gravityAccel*jumpHeight);
    }
}
