import 'dart:async';

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

import 'package:awale/models/game/game.dart';
import 'package:awale/models/settings/settings_game.dart';
import 'package:awale/models/settings/settings_global.dart';
import 'package:awale/robot/robot_player.dart';
import 'package:awale/utils/tools.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(
      // Settings
      gameSettings: state.currentGame.gameSettings,
      globalSettings: state.currentGame.globalSettings,
      // State
      isRunning: state.currentGame.isRunning,
      isStarted: state.currentGame.isStarted,
      isFinished: state.currentGame.isFinished,
      animationInProgress: state.currentGame.animationInProgress,
      // Base data
      board: state.currentGame.board,
      // Game data
      currentPlayer: state.currentGame.currentPlayer,
      scores: state.currentGame.scores,
      currentHand: state.currentGame.currentHand,
    );
    // game.dump();

    updateState(game);
  }

  void startNewGame({
    required GameSettings gameSettings,
    required GlobalSettings globalSettings,
  }) {
    final Game newGame = Game.createNew(
      // Settings
      gameSettings: gameSettings,
      globalSettings: globalSettings,
    );

    newGame.dump();

    updateState(newGame);
    refresh();

    robotPlay();
  }

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

  void resumeSavedGame() {
    state.currentGame.isRunning = true;
    refresh();
  }

  void deleteSavedGame() {
    state.currentGame.isRunning = false;
    state.currentGame.isFinished = true;
    refresh();
  }

  void toggleCurrentPlayer() {
    state.currentGame.currentPlayer = 1 - state.currentGame.currentPlayer;
    refresh();

    robotPlay();
  }

  void robotPlay() async {
    if (!state.currentGame.isFinished && !state.currentGame.isCurrentPlayerHuman()) {
      final int pickedCell = RobotPlayer.pickCell(state.currentGame);
      await Future.delayed(const Duration(milliseconds: 500));

      tapOnCell(pickedCell);
    }
  }

  void tapOnCell(int cellIndex) async {
    printlog('tapOnCell: $cellIndex');

    if (!state.currentGame.isCurrentPlayerHouse(cellIndex)) {
      printlog('not allowed');

      return;
    }

    if (state.currentGame.board.cells[cellIndex] == 0) {
      printlog('empty cell');

      return;
    }

    if (!state.currentGame.isMoveAllowed(cellIndex)) {
      printlog('not allowed (need to give at least one seed to other player)');

      return;
    }

    state.currentGame.animationInProgress = true;
    refresh();

    final int lastCellIndex = await animateSeedsDistribution(cellIndex);
    await animateSeedsEarning(lastCellIndex);

    toggleCurrentPlayer();

    if (!state.currentGame.canPlay()) {
      printlog('user has no more move to play');
      state.currentGame.isFinished = true;
      refresh();
    }

    state.currentGame.animationInProgress = false;
    refresh();
  }

  Future<int> animateSeedsDistribution(int sourceCellIndex) async {
    printlog('animateSeedsDistribution / sourceCellIndex: $sourceCellIndex');

    final int seedsCount = state.currentGame.board.cells[sourceCellIndex];

    // empty source cell
    printlog('animateSeedsDistribution / empty source cell');
    state.currentGame.board.cells[sourceCellIndex] = 0;
    state.currentGame.currentHand = seedsCount;
    refresh();

    await Future.delayed(const Duration(milliseconds: 200));

    int cellIndex = sourceCellIndex;
    for (int i = 0; i < seedsCount; i++) {
      cellIndex = state.currentGame.getNextCellIndex(cellIndex, sourceCellIndex);
      state.currentGame.currentHand--;
      state.currentGame.board.cells[cellIndex] += 1;
      refresh();
      await Future.delayed(const Duration(milliseconds: 300));
    }

    refresh();

    return cellIndex;
  }

  Future<int> animateSeedsEarning(int lastCellIndex) async {
    printlog('animateSeedsEarning / lastCellIndex: $lastCellIndex');

    int earnedSeedsCount = 0;

    if (state.currentGame.isOpponentHouse(lastCellIndex)) {
      final int seedsCount = state.currentGame.board.cells[lastCellIndex];
      printlog('found $seedsCount seed(s) on final house');

      if ([2, 3].contains(seedsCount)) {
        printlog('-> ok will earn these seeds');

        state.currentGame.board.cells[lastCellIndex] = 0;
        state.currentGame.scores[state.currentGame.currentPlayer] += seedsCount;
        earnedSeedsCount += seedsCount;
        refresh();

        await Future.delayed(const Duration(milliseconds: 500));

        // (recursively) check previous cells
        printlog('-> dispatch to previous cell');
        final int previousCellIndex = state.currentGame.getPreviousCellIndex(lastCellIndex);
        earnedSeedsCount += await animateSeedsEarning(previousCellIndex);
      } else {
        printlog('-> nothing to do');
      }
    }

    return earnedSeedsCount;
  }

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

    return GameState(
      currentGame: currentGame,
    );
  }

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