package com.brackeen.javagamebook.tilegame;

import java.awt.*;
import java.util.Iterator;

import com.brackeen.javagamebook.graphics.Sprite;
import com.brackeen.javagamebook.tilegame.sprites.Creature;

/**
    Klasa TileMapRenderer realizuje rysowanie TileMap na ekranie.
    Rysuje wszystkie kafelki oraz opcjonalny rysunek tła
    wyśrodkowany względem biżącego położenia bohatera.

    <p>Jeżeli szerokość tła jest mniejsza niż szerokość mapy kafelek,
    będzie się wydawąło, że rysunek tła porusza się wolniej,
    tworząc efekt paralaksy.

    <p>Dodatkowo, udostępnione są również trzy metody statyczne do konwersji
    pikseli na współrzędne kafelków i odwrotnie.

    <p>Ten obiekt TileMapRender korzysta z kafelków o szerokości 64.
*/
public class TileMapRenderer {

    private static final int TILE_SIZE = 64;
    // rozmiar kafelka w bitach
    // Math.pow(2, TILE_SIZE_BITS) == TILE_SIZE
    private static final int TILE_SIZE_BITS = 6;

    private Image background;

    /**
        Konwersja współrzędnych pikselowych na współrzędne kafelek.
    */
    public static int pixelsToTiles(float pixels) {
        return pixelsToTiles(Math.round(pixels));
    }

    /**
        Konwersja współrzędnych pikselowych na współrzędne kafelek.
    */
    public static int pixelsToTiles(int pixels) {
        // wykorzystanie przesuwania do korygowania ujemnych współrzędnych
        return pixels >> TILE_SIZE_BITS;

        // w przypadku kafelków o rozmiarach nie będących potęgą dwójki
        // użycie funkcji floor:
        //return (int)Math.floor((float)pixels / TILE_SIZE);
    }

    /**
        Konwersja współrzędnych kafelek na współprzedne pikselowe.
    */
    public static int tilesToPixels(int numTiles) {
        // Użycie przesuwania nie jest konieczne.
        // Jest nieco szybsze, ale nie daje zbyt dużo
        // przyspieszenia na nowoczesnych procesorach.
        return numTiles << TILE_SIZE_BITS;

        // jeżeli rozmiar kafelków nie jest potęgą dwóch:
        //return numTiles * TILE_SIZE;
    }

    /**
        Ustawienie tła do narysowania.
    */
    public void setBackground(Image background) {
        this.background = background;
    }

    /**
        Rysowanie obiektu TileMap.
    */
    public void draw(Graphics2D g, TileMap map,
        int screenWidth, int screenHeight)
    {
        Sprite player = map.getPlayer();
        int mapWidth = tilesToPixels(map.getWidth());

        // ustawienie pozycji przewijania mapy
        // korzystając z położenia bohatera
        int offsetX = screenWidth / 2 -
            Math.round(player.getX()) - TILE_SIZE;
        offsetX = Math.min(offsetX, 0);
        offsetX = Math.max(offsetX, screenWidth - mapWidth);

        // pobranie przesunięcia współrzędnej y do narysowania wszystkich 
        // duszków i kafelek
        int offsetY = screenHeight -
            tilesToPixels(map.getHeight());

        // w razie potrzeby rysowanie czarnego tła
        if (background == null ||
            screenHeight > background.getHeight(null))
        {
            g.setColor(Color.black);
            g.fillRect(0, 0, screenWidth, screenHeight);
        }

        // rysowanie przesuniętego rysunku tła
        if (background != null) {
            int x = offsetX *
                (screenWidth - background.getWidth(null)) /
                (screenWidth - mapWidth);
            int y = screenHeight - background.getHeight(null);

            g.drawImage(background, x, y, null);
        }

        // rysowanie widocznych kafelków
        int firstTileX = pixelsToTiles(-offsetX);
        int lastTileX = firstTileX +
            pixelsToTiles(screenWidth) + 1;
        for (int y=0; y<map.getHeight(); y++) {
            for (int x=firstTileX; x <= lastTileX; x++) {
                Image image = map.getTile(x, y);
                if (image != null) {
                    g.drawImage(image,
                        tilesToPixels(x) + offsetX,
                        tilesToPixels(y) + offsetY,
                        null);
                }
            }
        }
        // rysowanie bohatera
        g.drawImage(player.getImage(),
            Math.round(player.getX()) + offsetX,
            Math.round(player.getY()) + offsetY,
            null);

        // rysowanie duszków
        Iterator i = map.getSprites();
        while (i.hasNext()) {
            Sprite sprite = (Sprite)i.next();
            int x = Math.round(sprite.getX()) + offsetX;
            int y = Math.round(sprite.getY()) + offsetY;
            g.drawImage(sprite.getImage(), x, y, null);

            // obudzenie przeciwnika gdy znajdzie się na ekranie
            if (sprite instanceof Creature &&
                x >= 0 && x < screenWidth)
            {
                ((Creature)sprite).wakeUp();
            }
        }
    }
}
