import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/models/game_settings.dart';

part 'game_state.dart';

class GameCubit extends HydratedCubit<GameState> {
  GameCubit()
      : super(GameState(
          currentGame: Game.createNull(),
        ));

  void updateState(Game game) {
    emit(GameState(
      currentGame: game,
    ));
  }

  void refresh() {
    final Game game = Game(
      board: state.currentGame.board,
      settings: state.currentGame.settings,
      isRunning: state.currentGame.isRunning,
      isFinished: state.currentGame.isFinished,
      availableBlocksCount: state.currentGame.availableBlocksCount,
      movesCount: state.currentGame.movesCount,
      score: state.currentGame.score,
    );
    // game.dump();

    updateState(game);
  }

  void quitGame() {
    state.currentGame.updateGameIsRunning(false);
    refresh();
  }

  void updateCellValue(CellLocation locationToUpdate, int? value) {
    state.currentGame.updateCellValue(locationToUpdate, value);
    refresh();
  }

  void increaseMovesCount() {
    this.state.currentGame.increaseMovesCount();
    refresh();
  }

  void increaseScore(int increment) {
    this.state.currentGame.increaseScore(increment);
    refresh();
  }

  void updateAvailableBlocksCount() {
    this.state.currentGame.updateAvailableBlocksCount();
    refresh();
  }

  void updateGameIsFinished(bool gameIsFinished) {
    this.state.currentGame.updateGameIsFinished(gameIsFinished);
    refresh();
  }

  moveCellsDown() {
    final Game currentGame = this.state.currentGame;

    final int boardSizeHorizontal = currentGame.settings.boardSize;
    final int boardSizeVertical = currentGame.settings.boardSize;

    for (int row = 0; row < boardSizeVertical; row++) {
      for (int col = 0; col < boardSizeHorizontal; col++) {
        // empty cell?
        if (currentGame.getCellValue(CellLocation.go(row, col)) == null) {
          // move cells down
          for (int r = row; r > 0; r--) {
            this.updateCellValue(CellLocation.go(r, col),
                currentGame.getCellValue(CellLocation.go(r - 1, col)));
          }
          // fill top empty cell
          this.updateCellValue(
              CellLocation.go(0, col), currentGame.getFillValue(CellLocation.go(row, col)));
        }
      }
    }
  }

  void deleteBlock(List<CellLocation> block) {
    // Sort cells from top to bottom
    block.sort((cell1, cell2) => cell1.row.compareTo(cell2.row));
    // Delete all cells
    block.forEach((CellLocation blockItemToDelete) {
      this.updateCellValue(blockItemToDelete, null);
    });
  }

  int getScoreFromBlock(int blockSize) {
    return 3 * (blockSize - 2);
  }

  List<CellLocation> tapOnCell(CellLocation tappedCellLocation) {
    final Game currentGame = this.state.currentGame;

    final int? cellValue = currentGame.getCellValue(tappedCellLocation);

    if (cellValue != null) {
      List<CellLocation> blockCells = currentGame.getSiblingCells(tappedCellLocation, []);
      if (blockCells.length >= 3) {
        this.deleteBlock(blockCells);
        this.increaseMovesCount();
        this.increaseScore(getScoreFromBlock(blockCells.length));
        return blockCells;
      }
    }

    return [];
  }

  void postAnimate() {
    this.moveCellsDown();
    this.updateAvailableBlocksCount();
    refresh();

    if (!this.state.currentGame.hasAtLeastOneAvailableBlock()) {
      print('no more block found. finish game.');
      this.updateGameIsFinished(true);
    }
  }

  void startNewGame(GameSettings settings) {
    Game newGame = Game.createNew(
      gameSettings: settings,
    );

    newGame.dump();

    updateState(newGame);
    this.postAnimate();
  }

  @override
  GameState? fromJson(Map<String, dynamic> json) {
    Game currentGame = json['currentGame'] as Game;

    return GameState(
      currentGame: currentGame,
    );
  }

  @override
  Map<String, dynamic>? toJson(GameState state) {
    return <String, dynamic>{
      'currentGame': state.currentGame.toJson(),
    };
  }
}