import 'dart:async';
import 'dart:math';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_custom_toolbox/flutter_toolbox.dart';
import 'package:image/image.dart' as imglib;

import 'package:puzzlegame/models/game/game.dart';
import 'package:puzzlegame/models/game/moving_tile.dart';
import 'package:puzzlegame/models/settings/settings_game.dart';
import 'package:puzzlegame/models/settings/settings_global.dart';

part 'game_state.dart';

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

  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,
      shufflingInProgress: state.currentGame.shufflingInProgress,
      // Base data
      image: state.currentGame.image,
      tiles: state.currentGame.tiles,
      // Game data
      movesCount: state.currentGame.movesCount,
      displayTip: state.currentGame.displayTip,
      tileSize: state.currentGame.tileSize,
    );
    // 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();

    state.currentGame.isRunning = true;
    state.currentGame.shufflingInProgress = true;
    refresh();

    Timer(const Duration(seconds: 1), () {
      splitImageInTiles();
    });
  }

  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 updateTileSize(double tileSize) {
    if (tileSize != state.currentGame.tileSize) {
      state.currentGame.tileSize = tileSize;
      for (var i = 0; i < state.currentGame.tiles.length; i++) {
        state.currentGame.tiles[i].size = tileSize;
      }
      refresh();
    }
  }

  void toggleDisplayTipImage() {
    state.currentGame.displayTip = !state.currentGame.displayTip;
    refresh();
  }

  void incrementMovesCount() {
    state.currentGame.movesCount++;
    refresh();
  }

  bool checkTilesetIsCleared() {
    for (MovingTile tile in state.currentGame.tiles) {
      if (!tile.isCorrect()) {
        return false;
      }
    }
    return true;
  }

  void swapTiles(List<int> tile1, List<int> tile2) {
    state.currentGame.isStarted = true;

    final int indexTile1 = state.currentGame.tiles.indexWhere(
        (tile) => ((tile.currentCol == tile1[0]) && (tile.currentRow == tile1[1])));
    final int indexTile2 = state.currentGame.tiles.indexWhere(
        (tile) => ((tile.currentCol == tile2[0]) && (tile.currentRow == tile2[1])));

    final MovingTile swap = state.currentGame.tiles[indexTile1];
    state.currentGame.tiles[indexTile1] = state.currentGame.tiles[indexTile2];
    state.currentGame.tiles[indexTile2] = swap;

    final int swapCol = state.currentGame.tiles[indexTile1].currentCol;
    state.currentGame.tiles[indexTile1].currentCol =
        state.currentGame.tiles[indexTile2].currentCol;
    state.currentGame.tiles[indexTile2].currentCol = swapCol;

    final int swapRow = state.currentGame.tiles[indexTile1].currentRow;
    state.currentGame.tiles[indexTile1].currentRow =
        state.currentGame.tiles[indexTile2].currentRow;
    state.currentGame.tiles[indexTile2].currentRow = swapRow;

    incrementMovesCount();
    if (checkTilesetIsCleared()) {
      state.currentGame.isFinished = true;
    }

    refresh();
  }

  Future<void> splitImageInTiles() async {
    final String imageAsset = 'assets/images/${state.currentGame.image}.png';
    final Uint8List imageData = (await rootBundle.load(imageAsset)).buffer.asUint8List();

    final int tilesCount = state.currentGame.gameSettings.tilesetSizeValue;

    final imglib.Image image = imglib.decodeImage(imageData) ??
        imglib.Image.fromBytes(
          height: 1,
          width: 1,
          bytes: Uint8List.fromList([]).buffer,
        );

    int x = 0, y = 0;
    final int width = (image.width / tilesCount).round();
    final int height = (image.height / tilesCount).round();

    final List<MovingTile> tiles = [];
    for (int i = 0; i < tilesCount; i++) {
      for (int j = 0; j < tilesCount; j++) {
        final Uint8List tileData = Uint8List.fromList(imglib.encodeJpg(imglib.copyCrop(
          image,
          x: x,
          y: y,
          width: width,
          height: height,
        )));

        tiles.add(MovingTile(
          currentCol: j,
          currentRow: i,
          image: Image.memory(tileData),
          size: state.currentGame.tileSize,
          originalCol: j,
          originalRow: i,
        ));

        x += width;
      }
      x = 0;
      y += height;
    }

    state.currentGame.tiles = tiles;
    shuffleTiles();

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

  void shuffleTiles() {
    final Random random = Random();

    final List<MovingTile> tiles = state.currentGame.tiles;
    final int tilesCount = tiles.length;

    for (int i = 0; i < (10 * tilesCount); i++) {
      final int indexTile1 = random.nextInt(tilesCount);
      final int indexTile2 = random.nextInt(tilesCount);

      final MovingTile swap = tiles[indexTile1];
      tiles[indexTile1] = tiles[indexTile2];
      tiles[indexTile2] = swap;

      final int swapCol = tiles[indexTile1].currentCol;
      tiles[indexTile1].currentCol = tiles[indexTile2].currentCol;
      tiles[indexTile2].currentCol = swapCol;

      final int swapRow = tiles[indexTile1].currentRow;
      tiles[indexTile1].currentRow = tiles[indexTile2].currentRow;
      tiles[indexTile2].currentRow = swapRow;
    }

    state.currentGame.tiles = tiles;
  }

  @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(),
    };
  }
}