package eu.mhsl.minenet.minigames.instance.game.stateless.types.tetris.game;

import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.scoreboard.Sidebar;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;

import java.util.*;

public class TetrisGame {
    private final StatelessGame instance;
    private final Playfield playfield;
    private final boolean isFast;
    private int level = 1;
    private int lines = 0;
    private int score = 0;
    private int combo = 0;
    private int attackingLines = 0;
    public boolean lost = false;
    public boolean paused = true;
    private final boolean hasCombat;
    public Tetromino currentTetromino;
    private final List<Tetromino> nextTetrominoes = new ArrayList<>();
    private Tetromino holdTetromino;
    private final List<Tetromino> tetrominoBag = new ArrayList<>();
    private boolean holdPossible = true;
    private final Pos nextPosition;
    private final Pos holdPosition;
    private final Pos tetrominoSpawnPosition;
    public Sidebar sidebar = new Sidebar(Component.text("Info:"));
    private final Map<Button, Long> lastPresses = new HashMap<>();
    private final List<TetrisGame> otherTetrisGames = new ArrayList<>();
    private final Random random;

    public enum Button {
        W,
        A,
        S,
        D,
        mouseLeft,
        mouseRight,
        space
    }

    public TetrisGame(StatelessGame instance, Pos lowerLeftCorner, Tetromino.Shape startTetrominoShape, int nextTetrominoesCount, boolean isfast, boolean hasCombat, long randomSeed) {
        this.isFast = isfast;
        this.hasCombat = hasCombat;
        this.instance = instance;
        this.playfield = new Playfield(lowerLeftCorner, this.instance, nextTetrominoesCount);
        this.random = new Random(randomSeed);

        this.holdPosition = this.playfield.getHoldPosition();
        this.nextPosition = this.playfield.getNextPosition();
        this.tetrominoSpawnPosition = this.playfield.getTetrominoSpawnPosition();
        this.buildSidebar();

        this.currentTetromino = new Tetromino(this.instance, startTetrominoShape);
        for (int i = 0; i < nextTetrominoesCount; i++) {
            this.updateNextTetrominoes();
        }
    }

    public void pressedButton(Button button) {
        final int standardButtonDelay = 100;
        final int buttonDebounce = 70;

        if(this.lastPresses.getOrDefault(button, 0L) >= System.currentTimeMillis()-standardButtonDelay) return;

        this.lastPresses.put(button, System.currentTimeMillis());
        if(button == Button.W) this.lastPresses.put(button, System.currentTimeMillis()+buttonDebounce);
        if(button == Button.S) this.lastPresses.put(button, System.currentTimeMillis()-buttonDebounce);

        if(this.lost || this.paused) return;

        switch (button) {
            case A -> this.currentTetromino.moveLeft();
            case S -> this.moveDown();
            case D -> this.currentTetromino.moveRight();
            case W -> this.hardDrop();
            case mouseLeft -> this.currentTetromino.rotate(false);
            case mouseRight -> this.currentTetromino.rotate(true);
            case space -> this.switchHold();
        }
    }

    public Pos getPlayerSpawnPosition() {
        return this.playfield.getPlayerSpawnPosition();
    }

    public void start() {
        this.paused = false;
        Scheduler scheduler = MinecraftServer.getSchedulerManager();
        scheduler.submitTask(() -> {
            if(this.lost) return TaskSchedule.stop();
            int standardTickDelay = 40;
            if(this.isFast) standardTickDelay = 20;

            TaskSchedule nextTick = TaskSchedule.tick(Math.round((float) standardTickDelay /this.level));
            if(this.paused) return nextTick;
            this.tick();
            return nextTick;
        });

        this.updateInfo();
        this.nextTetrominoes.forEach(tetromino -> {
            double xChange = -tetromino.getXChange();
            tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0));
            tetromino.drawAsEntities();
        });
    }

    public void generate() {
        this.playfield.generate();

        this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
        this.currentTetromino.draw(false);
    }

    public void tick() {
        if(this.lost || this.paused) return;
        if(!currentTetromino.moveDown()) {
            setActiveTetrominoDown();
        }
    }

    public int getScore() {
        return this.score;
    }

    public void updateOtherTetrisGames(Collection<TetrisGame> tetrisGames) {
        List<TetrisGame> games = new ArrayList<>(tetrisGames);
        games.remove(this);
        games.removeIf(tetrisGame -> tetrisGame.lost);
        this.otherTetrisGames.clear();
        this.otherTetrisGames.addAll(games);
    }

    public void getAttacked(int lines) {
        if(this.hasCombat && !this.lost) {
            this.attackingLines += lines;
            this.playfield.updateAttackingLines(this.attackingLines);
        }
    }

    public void loose() {
        this.lost = true;
    }


    private boolean moveDown() {
        if(!this.currentTetromino.moveDown()) {
            this.setActiveTetrominoDown();
            return false;
        }
        this.score += 1;
        this.updateInfo();
        return true;
    }

    private boolean hardDrop() {
        if(!this.currentTetromino.moveDown()) {
            this.setActiveTetrominoDown();
            return false;
        }
        this.score += 2;
        this.updateInfo();
        while(this.currentTetromino.moveDown()) {
            this.score += 2;
            this.updateInfo();
        }
        this.setActiveTetrominoDown();
        return true;
    }

    private boolean switchHold() {
        if(!holdPossible) return false;

        Tetromino newCurrentTetromino;
        if(this.holdTetromino == null) {
            newCurrentTetromino = this.nextTetrominoes.removeFirst();
            newCurrentTetromino.remove();
            this.updateNextTetrominoes();
        } else {
            newCurrentTetromino = this.holdTetromino;
            this.holdTetromino.remove();
        }

        this.currentTetromino.remove();
        this.holdTetromino = new Tetromino(this.instance, this.currentTetromino.getShape());
        this.currentTetromino = newCurrentTetromino;

        this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
        this.currentTetromino.draw();
        if(!this.currentTetromino.moveDown()) loose();

        double xChange = this.holdTetromino.getXChange();
        this.holdTetromino.setPosition(this.holdPosition.add(xChange, 0, 0));
        this.holdTetromino.drawAsEntities();
        this.holdPossible = false;
        return true;
    }


    private void updateNextTetrominoes() {
        if(this.tetrominoBag.isEmpty()) {
            for(Tetromino.Shape shape : Tetromino.Shape.values()) {
                this.tetrominoBag.add(new Tetromino(this.instance, shape));
            }
            Collections.shuffle(this.tetrominoBag, this.random);
        }

        if(!this.nextTetrominoes.isEmpty()) this.nextTetrominoes.forEach(Tetromino::remove);
        Tetromino newTetromino = this.tetrominoBag.removeFirst();
        this.nextTetrominoes.add(newTetromino);
        this.nextTetrominoes.forEach(tetromino -> {
            double xChange = -tetromino.getXChange();
            tetromino.setPosition(this.nextPosition.sub(xChange, 4*this.nextTetrominoes.indexOf(tetromino), 0));
            tetromino.drawAsEntities();
        });
    }

    private void buildSidebar() {
        this.sidebar.createLine(new Sidebar.ScoreboardLine(
            "0",
            Component.text("Score: "),
            0
        ));
        this.sidebar.createLine(new Sidebar.ScoreboardLine(
            "1",
            Component.text("Lines: "),
            0
        ));
        this.sidebar.createLine(new Sidebar.ScoreboardLine(
            "2",
            Component.text("Level: "),
            1
        ));
    }

    private void updateInfo() {
        this.playfield.updateScore(this.score);
        this.sidebar.updateLineScore("0",  this.score);
        this.sidebar.updateLineScore("1", this.lines);
        this.sidebar.updateLineScore("2", this.level);
    }

    private void setActiveTetrominoDown() {
        this.currentTetromino.removeOwnEntities();
        this.currentTetromino = this.nextTetrominoes.removeFirst();
        this.currentTetromino.remove();
        this.updateNextTetrominoes();

        int removedLines = this.playfield.removeFullLines();
        int combatLines = 0;
        this.combo += 1;
        switch (removedLines) {
            case 0 -> this.combo = 0;
            case 1 -> {
                this.lines += 1;
                this.score += 40 * this.level;
            }
            case 2 -> {
                combatLines = 1;
                this.lines += 2;
                this.score += 100 * this.level;
            }
            case 3 -> {
                combatLines = 2;
                this.lines += 3;
                this.score += 300 * this.level;
            }
            case 4 -> {
                combatLines = 4;
                this.lines += 4;
                this.score += 1200 * this.level;
            }
        }

        this.score += 50 * this.combo * this.level;
        if(this.combo >= 2) {
            combatLines += (int) Math.floor((double) this.combo /2);
        }

        if(this.hasCombat && this.attackingLines > 0) {
            if(combatLines > 0 && this.attackingLines >= combatLines) {
                this.attackingLines -= combatLines;
                combatLines = 0;
            } else if(combatLines > 0) {
                combatLines -= this.attackingLines;
                this.attackingLines = 0;
            } else {
                this.playfield.addLines(this.attackingLines);
                this.attackingLines = 0;
            }

            this.playfield.updateAttackingLines(this.attackingLines);
        }

        if(this.hasCombat && !this.otherTetrisGames.isEmpty()) {
            Collections.shuffle(this.otherTetrisGames);
            this.otherTetrisGames.getFirst().getAttacked(combatLines);
        }

        this.level = (int) Math.floor((double) this.lines / 10) + 1;
        this.holdPossible = true;

        this.updateInfo();

        this.currentTetromino.setPosition(this.tetrominoSpawnPosition);
        this.currentTetromino.draw();
        if(!this.currentTetromino.moveDown()) {
            loose();
        }
    }
}
