package com.brackeen.javagamebook.path;

import java.util.Iterator;
import com.brackeen.javagamebook.path.PathFinder;
import com.brackeen.javagamebook.math3D.*;
import com.brackeen.javagamebook.game.GameObject;

/**
    Robot PathBot jest obiektem gry GameObject, który ma podążać
    ścieżką znalezioną przez mechanizm PathFinder.
*/
public class PathBot extends GameObject {

    private static final float DEFAULT_TURN_SPEED = .005f;
    private static final float DEFAULT_SPEED = .25f;
    private static final long DEFAULT_PATH_RECALC_TIME = 4000;
    private static final float DEFAULT_FLY_HEIGHT = 64;

    protected PathFinder pathFinder;
    protected Iterator currentPath;
    private Vector3D nextPathLocation;
    protected long timeUntilPathRecalc;
    private long pathRecalcTime;
    private Vector3D facing;

    private float turnSpeed;
    private float speed;
    private float flyHeight;


    public PathBot(PolygonGroup polygonGroup) {
        super(polygonGroup);
        nextPathLocation = new Vector3D();

        // ustaw wartości domyślne
        setPathRecalcTime(DEFAULT_PATH_RECALC_TIME);
        setSpeed(DEFAULT_SPEED);
        setTurnSpeed(DEFAULT_TURN_SPEED);
        setFlyHeight(DEFAULT_FLY_HEIGHT);
        setState(STATE_ACTIVE);
    }


    /**
        Ustawia kierunek, w którym ten obiekt powinien być zwrócony, gdy
        podąża ścieżką. Wartość ta może się zmieniać. Gdy jest null, to
        obiekt zwraca się przodem w kierunku ruchu.
    */
    public void setFacing(Vector3D facing) {
        this.facing = facing;
    }


    /**
        Definiuje klasę PathFinder, wykorzystywaną do podążania ścieżką.
    */
    public void setPathFinder(PathFinder pathFinder) {
        if (this.pathFinder != pathFinder) {
            this.pathFinder = pathFinder;
            currentPath = null;

            // losowo ustalony odstęp czasu, do następnego obliczenia, 
            // by wszystkie roboty nie wyliczały ścieżek równocześnie
            timeUntilPathRecalc = (long)(Math.random() * 1000);
        }
    }

    public void setPathRecalcTime(long pathRecalcTime) {
        this.pathRecalcTime = pathRecalcTime;
    }

    public void setSpeed(float speed) {
        this.speed = speed;
    }

    public void setTurnSpeed(float turnSpeed) {
        this.turnSpeed = turnSpeed;
    }

    public void setFlyHeight(float flyHeight) {
        getTransform().getLocation().y+=flyHeight - this.flyHeight;
        this.flyHeight = flyHeight;
    }

    public float getFlyHeight() {
        return flyHeight;
    }

    public void update(GameObject player, long elapsedTime) {

        if (pathFinder == null) {
            super.update(player, elapsedTime);
            return;
        }

        timeUntilPathRecalc-=elapsedTime;

        // aktualizuj ścieżkę do gracza
        if (timeUntilPathRecalc <= 0) {
            currentPath = pathFinder.find(this, player);
            if (currentPath != null) {
                getTransform().stop();
            }
            timeUntilPathRecalc = pathRecalcTime;
        }

        // podążaj ścieżką
        if (currentPath != null &&
            !getTransform().isMovingIgnoreY())
        {
            if (currentPath.hasNext()) {
                nextPathLocation.setTo(
                    (Vector3D)currentPath.next());
                nextPathLocation.y+=flyHeight;
                getTransform().moveTo(nextPathLocation, speed);

                Vector3D faceLocation = facing;
                if (faceLocation == null) {
                    faceLocation = nextPathLocation;
                }
                getTransform().turnYTo(
                    faceLocation.x - getX(),
                    faceLocation.z - getZ(),
                    (float)-Math.PI/2, turnSpeed);
            }
            else {
                currentPath = null;
                notifyEndOfPath();
            }

        }

        super.update(player, elapsedTime);
    }


    /**
        W przypadku jakiejś kolizji, cofnij robota o 200 milisekund 
        i odczekaj kilka sekund przed ponownym wyliczeniem ścieżki.
    */
    protected void backupAndRecomputePath() {
        // cofnij robota o drogę przebywaną przez niego w ciągu 200 ms
        nextPathLocation.setTo(getTransform().getVelocity());
        if (!isFlying()) {
            nextPathLocation.y = 0;
        }
        nextPathLocation.multiply(-1);
        getTransform().setVelocity(nextPathLocation, 200);

        // odczekaj przed ponownym wyliczeniem ścieżki
        currentPath = null;
        timeUntilPathRecalc = (long)(Math.random() * 1000);
    }

    public boolean isFlying() {
        return (flyHeight > 0);
    }

    public void notifyEndOfPath() {
        // nic nie rób
    }

    public void notifyWallCollision() {
        backupAndRecomputePath();
    }

    public void notifyObjectCollision(GameObject object) {
        backupAndRecomputePath();
    }

}
