package com.brackeen.javagamebook.test;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.ArrayList;

import com.brackeen.javagamebook.input.*;
import com.brackeen.javagamebook.math3D.*;
import com.brackeen.javagamebook.graphics3D.*;

public abstract class GameCore3D extends GameCore {

    private static final long INSTRUCTIONS_TIME = 4000;

    protected PolygonRenderer polygonRenderer;
    protected ViewWindow viewWindow;
    protected List polygons;

    private List overlays = new ArrayList();

    protected boolean drawFrameRate = false;
    protected boolean drawInstructions = true;
    private long drawInstructionsTime = 0;

    // do obliczania szybkości wyświetlania klatek
    private int numFrames;
    private long startTime;
    private float frameRate;

    protected InputManager inputManager;
    private GameAction exit = new GameAction("exit");
    private GameAction smallerView = new GameAction("smallerView",
        GameAction.DETECT_INITAL_PRESS_ONLY);
    private GameAction largerView = new GameAction("largerView",
        GameAction.DETECT_INITAL_PRESS_ONLY);
    private GameAction frameRateToggle = new GameAction(
        "frameRateToggle", GameAction.DETECT_INITAL_PRESS_ONLY);
    protected GameAction goForward = new GameAction("goForward");
    protected GameAction goBackward = new GameAction("goBackward");
    protected GameAction goUp = new GameAction("goUp");
    protected GameAction goDown = new GameAction("goDown");
    protected GameAction goLeft = new GameAction("goLeft");
    protected GameAction goRight = new GameAction("goRight");
    protected GameAction turnLeft = new GameAction("turnLeft");
    protected GameAction turnRight = new GameAction("turnRight");
    protected GameAction tiltUp = new GameAction("tiltUp");
    protected GameAction tiltDown = new GameAction("tiltDown");
    protected GameAction tiltLeft = new GameAction("tiltLeft");
    protected GameAction tiltRight = new GameAction("tiltRight");

    public void init(DisplayMode[] modes) {
        super.init(modes);

        inputManager = new InputManager(
            screen.getFullScreenWindow());
        inputManager.setRelativeMouseMode(true);
        inputManager.setCursor(InputManager.INVISIBLE_CURSOR);

        inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);
        inputManager.mapToKey(goForward, KeyEvent.VK_W);
        inputManager.mapToKey(goForward, KeyEvent.VK_UP);
        inputManager.mapToKey(goBackward, KeyEvent.VK_S);
        inputManager.mapToKey(goBackward, KeyEvent.VK_DOWN);
        inputManager.mapToKey(goLeft, KeyEvent.VK_A);
        inputManager.mapToKey(goLeft, KeyEvent.VK_LEFT);
        inputManager.mapToKey(goRight, KeyEvent.VK_D);
        inputManager.mapToKey(goRight, KeyEvent.VK_RIGHT);
        inputManager.mapToKey(goUp, KeyEvent.VK_PAGE_UP);
        inputManager.mapToKey(goDown, KeyEvent.VK_PAGE_DOWN);
        inputManager.mapToMouse(turnLeft,
            InputManager.MOUSE_MOVE_LEFT);
        inputManager.mapToMouse(turnRight,
            InputManager.MOUSE_MOVE_RIGHT);
        inputManager.mapToMouse(tiltUp,
            InputManager.MOUSE_MOVE_UP);
        inputManager.mapToMouse(tiltDown,
            InputManager.MOUSE_MOVE_DOWN);

        inputManager.mapToKey(tiltLeft, KeyEvent.VK_INSERT);
        inputManager.mapToKey(tiltRight, KeyEvent.VK_DELETE);

        inputManager.mapToKey(smallerView, KeyEvent.VK_SUBTRACT);
        inputManager.mapToKey(smallerView, KeyEvent.VK_MINUS);
        inputManager.mapToKey(largerView, KeyEvent.VK_ADD);
        inputManager.mapToKey(largerView, KeyEvent.VK_PLUS);
        inputManager.mapToKey(largerView, KeyEvent.VK_EQUALS);
        inputManager.mapToKey(frameRateToggle, KeyEvent.VK_R);

        // tworzy obiekt renderujący wielokąt
        createPolygonRenderer();

        // tworzy wielokąty
        polygons = new ArrayList();
        createPolygons();
    }


    public abstract void createPolygons();


    public void createPolygonRenderer() {
        // włącza tryb pełnoekranowy
        viewWindow = new ViewWindow(0, 0,
            screen.getWidth(), screen.getHeight(),
            (float)Math.toRadians(75));

        Transform3D camera = new Transform3D(0,100,0);
        polygonRenderer = new SolidPolygonRenderer(
            camera, viewWindow);
    }


    /**
        Ustawia granice widoku, wyśrodkowuje kierunek patrzenia na ekranie.
    */
    public void setViewBounds(int width, int height) {
        width = Math.min(width, screen.getWidth());
        height = Math.min(height, screen.getHeight());
        width = Math.max(64, width);
        height = Math.max(48, height);
        viewWindow.setBounds((screen.getWidth() - width) /2,
            (screen.getHeight() - height) /2, width, height);

        // czyści ekran, jeśli jego rozmiary uległy zmianie
        // (czyści oba bufory)
        for (int i=0; i<2; i++) {
            Graphics2D g = screen.getGraphics();
            g.setColor(Color.BLACK);
            g.fillRect(0,0, screen.getWidth(), screen.getHeight());
            screen.update();
        }

    }


    public void update(long elapsedTime) {

        elapsedTime = Math.min(elapsedTime, 100);

        if (exit.isPressed()) {
            stop();
            return;
        }
        if (largerView.isPressed()) {
            setViewBounds(viewWindow.getWidth() + 64,
                viewWindow.getHeight() + 48);
        }
        else if (smallerView.isPressed()) {
            setViewBounds(viewWindow.getWidth() - 64,
                viewWindow.getHeight() - 48);
        }
        if (frameRateToggle.isPressed()) {
            drawFrameRate = !drawFrameRate;
        }

        drawInstructionsTime+=elapsedTime;
        if (drawInstructionsTime >= INSTRUCTIONS_TIME) {
            drawInstructions = false;
        }
        updateWorld(elapsedTime);

        // aktualizacja nakładek
        for (int i=0; i<overlays.size(); i++) {
            Overlay overlay = (Overlay)overlays.get(i);
            if (overlay.isEnabled()) {
                overlay.update(elapsedTime);
            }
        }
    }

    public void updateWorld(long elapsedTime) {

        // ogranicza elapsedTime
        elapsedTime = Math.min(elapsedTime, 100);

        float angleChange = 0.0002f*elapsedTime;
        float distanceChange = .5f*elapsedTime;

        Transform3D camera = polygonRenderer.getCamera();
        Vector3D cameraLoc = camera.getLocation();

        // uwzględnij ruch
        if (goForward.isPressed()) {
            cameraLoc.x -= distanceChange * camera.getSinAngleY();
            cameraLoc.z -= distanceChange * camera.getCosAngleY();
        }
        if (goBackward.isPressed()) {
            cameraLoc.x += distanceChange * camera.getSinAngleY();
            cameraLoc.z += distanceChange * camera.getCosAngleY();
        }
        if (goLeft.isPressed()) {
            cameraLoc.x -= distanceChange * camera.getCosAngleY();
            cameraLoc.z += distanceChange * camera.getSinAngleY();
        }
        if (goRight.isPressed()) {
            cameraLoc.x += distanceChange * camera.getCosAngleY();
            cameraLoc.z -= distanceChange * camera.getSinAngleY();
        }
        if (goUp.isPressed()) {
            cameraLoc.y += distanceChange;
        }
        if (goDown.isPressed()) {
            cameraLoc.y -= distanceChange;
        }

        // patrzenie w górę lub w dół (obrót wokół osi x)
        int tilt = tiltUp.getAmount() - tiltDown.getAmount();
        tilt = Math.min(tilt, 200);
        tilt = Math.max(tilt, -200);

        // ogranicza kąt widzenia w górę i w dół
        float newAngleX = camera.getAngleX() + tilt * angleChange;
        newAngleX = Math.max(newAngleX, (float)-Math.PI/2);
        newAngleX = Math.min(newAngleX, (float)Math.PI/2);
        camera.setAngleX(newAngleX);

        // obracanie się (obrót wokół osi y)
        int turn = turnLeft.getAmount() - turnRight.getAmount();
        turn = Math.min(turn, 200);
        turn = Math.max(turn, -200);
        camera.rotateAngleY(turn * angleChange);

        // przechylanie głowy w lewo i w prawo (obrót wokół osi z)
        if (tiltLeft.isPressed()) {
            camera.rotateAngleZ(10*angleChange);
        }
        if (tiltRight.isPressed()) {
            camera.rotateAngleZ(-10*angleChange);
        }
    }


    public void draw(Graphics2D g) {
        int viewX1 = viewWindow.getLeftOffset();
        int viewY1 = viewWindow.getTopOffset();
        int viewX2 = viewX1 + viewWindow.getWidth();
        int viewY2 = viewY1 + viewWindow.getHeight();
        if (viewX1 != 0 || viewY1 != 0) {
            g.setColor(Color.BLACK);
            g.fillRect(0,0, viewX1, screen.getHeight());
            g.fillRect(viewX2, 0, screen.getWidth() - viewX2,
                screen.getHeight());
            g.fillRect(viewX1,0, viewWindow.getWidth(), viewY1);
            g.fillRect(viewX1,viewY2, viewWindow.getWidth(),
                screen.getHeight() - viewY2);
        }

        drawPolygons(g);
        drawOverlays(g);
    }

    public void drawPolygons(Graphics2D g) {
        polygonRenderer.startFrame(g);
        for (int i=0; i<polygons.size(); i++) {
            polygonRenderer.draw(g, (Polygon3D)polygons.get(i));
        }
        polygonRenderer.endFrame(g);
    }


    public void drawOverlays(Graphics2D g) {

        // rysuje tekst
        if (drawInstructions) {
            // wygaszanie tekstu przez 500 ms
            long fade = INSTRUCTIONS_TIME - drawInstructionsTime;
            if (fade < 500) {
                fade = fade * 255 / 500;
                g.setColor(
                    new Color(0xffffff | ((int)fade << 24), true));
            }
            else {
                g.setColor(Color.WHITE);
            }

            g.drawString("Użyj myszy i klawiszy strzełek do poruszania się. " +
                "Naciśnij Esc, aby wyjść.", 5, fontSize);
        }
        // (wykonanie bardziej precyzyjnych testów może wymagać wyłącznia
        // BufferStrategy w obiekcie ScreenManager)
        if (drawFrameRate) {
            g.setColor(Color.WHITE);
            calcFrameRate();
            g.drawString(frameRate + " klatek/sekundę", 5,
                screen.getHeight() - 5);
        }

        // rysuje nakładki
        for (int i=0; i<overlays.size(); i++) {
            Overlay overlay = (Overlay)overlays.get(i);
            if (overlay.isEnabled()) {
                Font prevFont = g.getFont();
                Color prevColor = g.getColor();
                overlay.draw(g, viewWindow);
                g.setColor(prevColor);
                g.setFont(prevFont);
            }
        }
    }


    public void addOverlay(Overlay overlay) {
        overlays.add(overlay);
    }


    public void removeOverlay(Overlay overlay) {
        overlays.remove(overlay);
    }

    public void calcFrameRate() {
        numFrames++;
        long currTime = System.currentTimeMillis();

        // oblicza szybkość wyświetlania klatek co 500 milisekund
        if (currTime > startTime + 500) {
            frameRate = (float)numFrames * 1000 /
                (currTime - startTime);
            startTime = currTime;
            numFrames = 0;
        }
    }

}