package de.miaurizius.jgame2d.core;

import de.miaurizius.jgame2d.aStar.PathFinder;
import de.miaurizius.jgame2d.core.enums.GameState;
import de.miaurizius.jgame2d.core.enums.Map;
import de.miaurizius.jgame2d.core.handlers.*;
import de.miaurizius.jgame2d.data.Config;
import de.miaurizius.jgame2d.data.SaveLoad;
import de.miaurizius.jgame2d.entity.Entity;
import de.miaurizius.jgame2d.entity.Player;
import de.miaurizius.jgame2d.environment.EnvironmentManager;
import de.miaurizius.jgame2d.tile.MiniMap;
import de.miaurizius.jgame2d.tile.TileManager;
import de.miaurizius.jgame2d.tile.interactive.InteractiveTile;

import javax.sound.sampled.Clip;
import javax.sound.sampled.FloatControl;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.logging.Level;

public class GamePanel extends JPanel implements Runnable {

    // SCREEN SETTINGS
    final int originalTileSize = 16; //16x16 tile
    final int scale = 3;
    public final int tileSize = originalTileSize * scale; //48x48 tile
    public final int maxScreenCol = 20;
    public final int maxScreenRow = 12;
    public final int screenWidth = tileSize * maxScreenCol; // 960 pixels
    public final int screenHeight = tileSize * maxScreenRow; // 576 pixels

    // FULLSCREEN SETTINGS
    public boolean fullscreen;
    int fScreenWidth = screenWidth;
    int fScreenHeight = screenHeight;
    BufferedImage tempScreen;
    public Graphics2D fg2;

    // WORLD SETTINGS
    public int maxWorldCol = 50;
    public int maxWorldRow = 50;
    public Map currentMap = Map.OVERWORLD;

    //FPS
    final int FPS = 60;
    int fpsMeasure;

    // SYSTEM
    public TileManager tileM = new TileManager(this);
    public KeyHandler keyH = new KeyHandler(this);
    public CollisionHandler collisionH = new CollisionHandler(this);
    public AssetSetter assetSetter = new AssetSetter(this);
    public UI ui = new UI(this);
    public EventHandler eventH = new EventHandler(this);
    public Sound sfx = new Sound();
    public Sound music = new Sound();
    public Config config = new Config(this);
    public PathFinder pFinder = new PathFinder(this);
    public EnvironmentManager eManager = new EnvironmentManager(this);
    public MiniMap mapMan = new MiniMap(this);
    public SaveLoad saveLoad = new SaveLoad(this);
    Thread gameThread;

    // ENTITY AND OBJECT
    public Player player = new Player(this, keyH);
    public Entity[][] obj = new Entity[Map.values().length][20];
    public Entity[][] npc = new Entity[Map.values().length][10];
    public Entity[][] monster = new Entity[Map.values().length][20];
    public InteractiveTile[][] iTile = new InteractiveTile[Map.values().length][50];
    public ArrayList<Entity> projectileList = new ArrayList<>();
    public ArrayList<Entity> particleList = new ArrayList<>();
    ArrayList<Entity> entityList = new ArrayList<>();

    // GAME STATE
    public GameState gameState;

    public GamePanel() throws IOException {
        this.setPreferredSize(new Dimension(screenWidth, screenHeight));
        this.setBackground(Color.black);
        this.setDoubleBuffered(true);
        this.addKeyListener(keyH);
        this.setFocusable(true);
    }

    // GAME THREAD / CORE
    public void startGameThread() {
        gameThread = new Thread(this);
        gameThread.start();
    }
    @Override
    public void run() {
        double drawInterval = (double) 1000000000 / FPS;
        double delta = 0;
        double lastTime = System.nanoTime();
        long currentTime;
        long timer = 0;
        int drawCount = 0;

        while(gameThread != null) {
            currentTime = System.nanoTime();
            delta += (currentTime - lastTime) / drawInterval;
            timer += (long) (currentTime - lastTime);
            lastTime = currentTime;

            if(delta >= 1) {
                update();
                //repaint(); //since the new drawing method
                drawTempScreen();
                drawScreen();
                delta--;
                drawCount++;
            }
            if(timer >= 1000000000) {
                fpsMeasure = drawCount;
                drawCount = 0;
                timer = 0;
            }
        }
    }

    // FRAME GENERATION
    public void update() {
        switch(gameState) {
            case PLAY:
                player.update();
                for(Entity entity : npc[currentMap.getIndex()]) if(entity != null) entity.update();
                for(int i = 0; i < monster[currentMap.getIndex()].length; i++) {
                    Entity m = monster[currentMap.getIndex()][i];
                    if(m != null) {
                        if(m.alive && !m.dying) m.update();
                        if(!m.alive) {
                            monster[currentMap.getIndex()][i].checkDrop();
                            monster[currentMap.getIndex()][i] = null;
                        }
                    }
                }
                for(int i = 0; i < projectileList.size(); i++) {
                    Entity m = projectileList.get(i);
                    if(m != null) {
                        if(m.alive) m.update();
                        else projectileList.remove(i);
                    }
                }
                for(int i = 0; i < particleList.size(); i++) {
                    Entity m = particleList.get(i);
                    if(m != null) {
                        if(m.alive) m.update();
                        else particleList.remove(i);
                    }
                }
                for(Entity entity : iTile[currentMap.getIndex()]) if(entity != null) entity.update();
                eManager.update();
                break;
            case PAUSE:
                break;
        }
    }
    public void drawTempScreen() {
        // DEBUG
        long drawStart;
        drawStart = System.nanoTime();

        // TITLE SCREEN
        if(gameState == GameState.TITLE) {
            ui.draw(fg2);
            return;
        }

        if(gameState == GameState.MAP) {
            mapMan.drawFullMapScreen(fg2);
            return;
        }

        // GAME
        tileM.draw(fg2);
        for(Entity entity : iTile[currentMap.getIndex()]) if(entity != null) entity.draw(fg2);

        // ENTITY RENDER SYSTEM
        entityList.add(player);
        for(Entity entity : npc[currentMap.getIndex()]) if(entity != null) entityList.add(entity);
        for(Entity entity : obj[currentMap.getIndex()]) if(entity != null) entityList.add(entity);
        for(Entity entity : monster[currentMap.getIndex()]) if(entity != null) entityList.add(entity);
        for(Entity entity : projectileList) if(entity != null) entityList.add(entity);
        for(Entity entity : particleList) if(entity != null) entityList.add(entity);
        entityList.sort(Comparator.comparingInt(o -> o.worldY));
        for(Entity entity : entityList) entity.draw(fg2);
        entityList.clear();

        // ENVIRONMENT
        eManager.draw(fg2);

        // MINI MAP
        mapMan.drawMiniMap(fg2);

        // UI
        ui.draw(fg2);

        long drawEnd = System.nanoTime();
        long passed = drawEnd - drawStart;

        // DEBUG
        if(keyH.debug) {
            int start = 350;
            fg2.setColor(Color.white);
            fg2.drawString("Draw Time: " + passed, 10, start);
            fg2.drawString("FPS: " + fpsMeasure, 10, start+tileSize);
            fg2.drawString("Invincible: " + player.invincibleCount, 10, start+tileSize*2);
            fg2.drawString("X, Y: " + player.worldX+", "+player.worldY, 10, start+tileSize*3);
            fg2.drawString("Col, Row: " + (player.worldX+player.solidArea.x)/tileSize+", "+(player.worldY+player.solidArea.y)/tileSize, 10, start+tileSize*4);
            Boot.logger.log(Level.FINE, "Draw Time: " + passed);
        }
    }
    public void drawScreen() {
        Graphics fg = getGraphics();
        fg.drawImage(tempScreen, 0, 0, fScreenWidth, fScreenHeight, null);
        fg.dispose();
    }

    // MUSIC
    public void playMusic(int i) {
        Clip c = music.clips[i];
        music.clip = c;
        music.fc = (FloatControl) c.getControl(FloatControl.Type.MASTER_GAIN);
        music.checkVolume();
        if(c.isRunning()) c.stop();
        c.setFramePosition(0);
        c.start();
        c.loop(Clip.LOOP_CONTINUOUSLY);
    }
    public void stopMusic() {
        music.stop();
    }
    public void playSE(int i) {
        Clip c = sfx.clips[i];
        sfx.fc = (FloatControl) c.getControl(FloatControl.Type.MASTER_GAIN);
        sfx.checkVolume();
        if(c.isRunning()) c.stop();
        c.setFramePosition(0);
        c.start();
    }

    // SETTING THINGS UP
    public void setupGame() {
        assetSetter.setObject();
        assetSetter.setNPC();
        assetSetter.setMonster();
        assetSetter.setITiles();
        eManager.setup();
        gameState = GameState.TITLE;

        tempScreen = new BufferedImage(screenWidth, screenHeight, BufferedImage.TYPE_INT_RGB);
        fg2 = (Graphics2D) tempScreen.getGraphics();

        if(fullscreen) setFullscreen();
    }
    public void setFullscreen() {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice gd = ge.getDefaultScreenDevice();
        gd.setFullScreenWindow(Boot.window);

        fScreenWidth = Boot.window.getWidth();
        fScreenHeight = Boot.window.getHeight();
    }
    public void resetGame(boolean restart) {
        player.setDefaultPositions();
        player.restoreStatus();
        player.resetCounter();
        assetSetter.setNPC();
        assetSetter.setMonster();

        if(!restart) return;
        player.setDefaultValues();
        assetSetter.setObject();
        assetSetter.setITiles();
        eManager.lighting.resetDay();
    }
}
