package eu.midnightdust.yaytris;

import eu.midnightdust.yaytris.game.Space;
import eu.midnightdust.yaytris.ui.ScoreMenu;
import eu.midnightdust.yaytris.ui.TetrisUI;
import eu.midnightdust.yaytris.util.SoundUtil;

import javax.swing.*;
import java.time.LocalTime;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

public class Tetris {
    public static final Random random = new Random();
    private static Space space;
    private static Timer timer;
    private static TetrisUI ui;
    private static TimerTask gravityTask;
    private static LocalTime startTime;

    /**
     * The application's entry point.
     * Initializes the UI library to match the system style.
     * Also loads saved settings, translations, and highscores from JSON.
     *
     * @param args   command line arguments – will be ignored
     */
    public static void main(String[] args) {
        try {
            System.setProperty("java.awt.headless", "false");
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception | Error e) { System.out.printf("%s: %s\n", "Error setting system look and feel", e); }
        Settings.load();
        Translation.load(Settings.language.locale);
        HighScores.load();
        timer = new Timer("Tetris falling pieces");
        space = new Space();
        ui = new TetrisUI();
    }

    /**
     * Get the active game space
     *
     * @see          Space
     */
    public static Space getSpace() {
        return space;
    }

    /**
     * Resets the game space, preparing it for a new game.
     *
     * @see          Space
     */
    public static void resetSpace() {
        SoundUtil.stopMusic("/music/theme.wav");
        if (gravityTask != null) gravityTask.cancel();
        timer.purge();
        space = new Space();
    }

    /**
     * Starts a new game of Tetris :D
     * This involves starting our gravity task, playing music and spawning the first {@link eu.midnightdust.yaytris.game.Tetromino}
     *
     * @see          Space#spawnTetromino()
     */
    public static void startGame() {
        SoundUtil.playMusic("/music/theme.wav", true);
        space.spawnTetromino();
        startTime = LocalTime.now();
        gravityTask = new GravityTimerTask();
        timer.scheduleAtFixedRate(gravityTask, 1, Settings.difficulty.getTimerPeriod());
    }

    /**
     * Stops the current game.
     * Disables falling, fades out music and handles saving of high scores.
     *
     * @see         ScoreMenu
     * @see         HighScores
     */
    public static void stopGame() {
        SoundUtil.stopMusic("/music/theme.wav");
        if (gravityTask != null) gravityTask.cancel();
        timer.purge();
        if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).gameOver();
        ui.transferFocus();
        if (HighScores.scores.values().stream().noneMatch(hs -> hs > space.getScore())) ui.showHighscoreDialog(space.getScore());
    }

    /**
     * Updates the displayed score
     *
     * @param score  the new score
     * @see          ScoreMenu
     */
    public static void updateScore(int score) {
        if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateScore(score);
        updateLevel(score);
    }
    /**
     * Updates the elapsed time
     *
     * @see          ScoreMenu
     */
    public static void updateTime() {
        if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateTime(startTime);
    }
    /**
     * Updates the displayed level
     *
     * @param score  the new score, from which the level will be calculated
     * @see          ScoreMenu
     */
    public static void updateLevel(int score) {
        int newLevel = Math.max(0, (int) (score / 1000f));
        if (newLevel != space.level) {
            if (gravityTask != null && Settings.shouldScaleSpeed) {
                gravityTask.cancel();
                gravityTask = new GravityTimerTask();
                timer.scheduleAtFixedRate(gravityTask, 0, Math.max(10, Settings.difficulty.getTimerPeriod() - (Settings.difficulty.getTimerPeriod() / 8) * newLevel));
            }
            space.level = newLevel;
            if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateLevel(newLevel);
        }
    }

    /**
     * Defines our custom timer task that handles falling pieces.
     */
    public static class GravityTimerTask extends TimerTask {
        @Override
        public void run() {
            if (space.getCurrentTetromino() != null) {
                updateTime();
                space.getCurrentTetromino().fall();
                ui.getGamePanel().repaint();
            }
        }
    }
}
