import 'dart:math';

import 'package:flutter_custom_toolbox/flutter_toolbox.dart';

import 'package:suguru/config/application_config.dart';
import 'package:suguru/cubit/activity/activity_cubit.dart';
import 'package:suguru/data/game_data.dart';
import 'package:suguru/models/activity/board.dart';
import 'package:suguru/models/activity/cell.dart';
import 'package:suguru/models/activity/cell_location.dart';
import 'package:suguru/models/activity/move.dart';
import 'package:suguru/models/activity/types.dart';
import 'package:suguru/utils/suguru_solver.dart';

class Activity {
  Activity({
    // Settings
    required this.activitySettings,

    // State
    this.isRunning = false,
    this.isStarted = false,
    this.isFinished = false,
    this.animationInProgress = false,
    this.boardAnimated = const [],

    // Base data
    required this.board,
    required this.boardSizeHorizontal,
    required this.boardSizeVertical,

    // Game data
    this.boardConflicts = const [],
    this.selectedCell,
    this.showConflicts = false,
    this.givenTipsCount = 0,
    this.buttonTipsCountdown = 0,
  });

  // Settings
  final ActivitySettings activitySettings;

  // State
  bool isRunning;
  bool isStarted;
  bool isFinished;
  bool animationInProgress;
  AnimatedBoard boardAnimated;

  // Base data
  final Board board;
  final int boardSizeHorizontal;
  final int boardSizeVertical;

  // Game data
  ConflictsCount boardConflicts;
  Cell? selectedCell;
  bool showConflicts;
  int givenTipsCount;
  int buttonTipsCountdown;

  factory Activity.createEmpty() {
    return Activity(
      // Settings
      activitySettings: ActivitySettings.createDefault(appConfig: ApplicationConfig.config),
      // Base data
      board: Board.createEmpty(),
      boardSizeHorizontal: 0,
      boardSizeVertical: 0,
      // Game data
      givenTipsCount: 0,
    );
  }

  factory Activity.createNew({
    ActivitySettings? activitySettings,
  }) {
    final ActivitySettings newActivitySettings = activitySettings ??
        ActivitySettings.createDefault(appConfig: ApplicationConfig.config);

    final List<String> templates =
        GameData.templates[newActivitySettings.get(ApplicationConfig.parameterCodeBoardSize)]
                ?[newActivitySettings.get(ApplicationConfig.parameterCodeDifficultyLevel)] ??
            [];
    final String template = templates.elementAt(Random().nextInt(templates.length)).toString();

    final List<String> templateParts = template.split(';');
    if (templateParts.length != 3) {
      printlog('Failed to get grid template (wrong format)...');
      return Activity.createEmpty();
    }

    final String boardSizeAsString = templateParts[0];
    final int boardSizeHorizontal = int.parse(boardSizeAsString.split('x')[0]);
    final int boardSizeVertical = int.parse(boardSizeAsString.split('x')[1]);

    // Build empty conflicts board
    ConflictsCount nonConflictedBoard = [];
    for (int row = 0; row < boardSizeVertical; row++) {
      List<int> line = [];
      for (int col = 0; col < boardSizeHorizontal; col++) {
        line.add(0);
      }
      nonConflictedBoard.add(line);
    }

    // Build empty animated background
    AnimatedBoard notAnimatedBoard = [];
    for (int row = 0; row < boardSizeVertical; row++) {
      List<bool> line = [];
      for (int col = 0; col < boardSizeHorizontal; col++) {
        line.add(false);
      }
      notAnimatedBoard.add(line);
    }

    // Build main game board
    final Board board = Board.createEmpty();
    board.createFromTemplate(template: template);

    return Activity(
      // Settings
      activitySettings: newActivitySettings,
      // State
      isRunning: true,
      boardAnimated: notAnimatedBoard,
      // Base data
      board: board,
      boardSizeHorizontal: boardSizeHorizontal,
      boardSizeVertical: boardSizeVertical,
      // Game data
      boardConflicts: nonConflictedBoard,
      selectedCell: null,
    );
  }

  bool get canBeResumed => isStarted && !isFinished;

  bool get gameWon => isRunning && isStarted && isFinished;

  bool get canGiveTip => (buttonTipsCountdown == 0);

  bool checkBoardIsSolved() {
    if (board.isSolved()) {
      printlog('-> ok suguru solved!');
      return true;
    }

    return false;
  }

  ConflictsCount computeConflictsInBoard() {
    final BoardCells cells = board.cells;
    final ConflictsCount conflicts = boardConflicts;

    // reset conflict states
    for (int row = 0; row < boardSizeVertical; row++) {
      for (int col = 0; col < boardSizeHorizontal; col++) {
        conflicts[row][col] = 0;
      }
    }

    // check siblings
    for (int row = 0; row < boardSizeVertical; row++) {
      for (int col = 0; col < boardSizeHorizontal; col++) {
        final int value = cells[row][col].value;
        if (value != 0) {
          for (int deltaRow = -1; deltaRow <= 1; deltaRow++) {
            for (int deltaCol = -1; deltaCol <= 1; deltaCol++) {
              if (row + deltaRow >= 0 &&
                  row + deltaRow < boardSizeHorizontal &&
                  col + deltaCol >= 0 &&
                  col + deltaCol < boardSizeVertical &&
                  (deltaRow * deltaCol != 0)) {
                final int siblingValue = cells[row + deltaRow][col + deltaCol].value;

                if (siblingValue == value) {
                  conflicts[row][col]++;
                }
              }
            }
          }
        }
      }
    }

    // check blocks does not contain duplicates
    for (String blockId in board.getBlockIds()) {
      List<int> values = [];
      List<int> duplicateValues = [];
      for (int row = 0; row < boardSizeVertical; row++) {
        for (int col = 0; col < boardSizeHorizontal; col++) {
          if (cells[row][col].blockId == blockId) {
            final int value = cells[row][col].value;
            if (value != 0) {
              if (!values.contains(value)) {
                values.add(value);
              } else {
                duplicateValues.add(value);
              }
            }
          }
        }
      }
      for (int duplicateValue in duplicateValues) {
        for (int row = 0; row < boardSizeVertical; row++) {
          for (int col = 0; col < boardSizeHorizontal; col++) {
            if (cells[row][col].blockId == blockId &&
                cells[row][col].value == duplicateValue) {
              conflicts[row][col]++;
            }
          }
        }
      }
    }

    return conflicts;
  }

  void showTip(ActivityCubit activityCubit) {
    bool tipGiven = false;

    if (selectedCell == null) {
      // no selected cell -> pick one
      tipGiven = helpSelectCell(activityCubit);
    } else {
      // currently selected cell -> set value
      tipGiven = helpFillCell(activityCubit, selectedCell?.location);
    }

    if (tipGiven) {
      activityCubit.increaseGivenTipsCount();
    }
  }

  bool helpSelectCell(ActivityCubit activityCubit) {
    CellLocation? cellLocationToFix;

    // pick one of wrong value cells, if found
    final List<CellLocation> wrongValueCells = getCellsWithWrongValue();
    if (wrongValueCells.isNotEmpty) {
      printlog('pick from wrongValueCells');
      wrongValueCells.shuffle();
      cellLocationToFix = wrongValueCells[0];
    }

    // pick one of conflicting cells, if found
    final List<CellLocation> conflictingCells = getCellsWithConflicts();
    if (conflictingCells.isNotEmpty) {
      printlog('pick from conflictingCells');
      conflictingCells.shuffle();
      cellLocationToFix = conflictingCells[0];
    }

    if (cellLocationToFix != null) {
      printlog('picked cell with wrong or conflicting value...');
      activityCubit.selectCell(cellLocationToFix);
      return true;
    }

    final Move? nextMove = SuguruSolver.pickNextMove(board);

    if (nextMove == null) {
      printlog('no easy cell to select...');
      return false;
    }

    activityCubit.selectCell(nextMove.location);
    return true;
  }

  bool helpFillCell(ActivityCubit activityCubit, CellLocation? location) {
    final Move? nextMove = SuguruSolver.pickNextMove(board, location);

    if (nextMove == null) {
      printlog('unable to compute cell value for $location');
      activityCubit.unselectCell();
      return false;
    }

    activityCubit.updateCellValue(nextMove.location, nextMove.value);
    activityCubit.unselectCell();
    return true;
  }

  List<CellLocation> getCellsWithWrongValue() {
    final BoardCells cells = board.cells;
    final BoardCells cellsSolved = board.solvedCells;

    List<CellLocation> cellsWithWrongValue = [];

    for (int row = 0; row < boardSizeVertical; row++) {
      for (int col = 0; col < boardSizeHorizontal; col++) {
        if (cells[row][col].value != 0 &&
            cells[row][col].value != cellsSolved[row][col].value) {
          cellsWithWrongValue.add(CellLocation.go(row, col));
        }
      }
    }

    return cellsWithWrongValue;
  }

  List<CellLocation> getCellsWithConflicts() {
    List<CellLocation> cellsWithConflict = [];

    for (int row = 0; row < boardSizeVertical; row++) {
      for (int col = 0; col < boardSizeHorizontal; col++) {
        if (boardConflicts[row][col] != 0) {
          cellsWithConflict.add(CellLocation.go(row, col));
        }
      }
    }

    return cellsWithConflict;
  }

  void dump() {
    printlog('');
    printlog('## Current game dump:');
    printlog('');
    printlog('$Activity:');
    printlog('  Settings');
    activitySettings.dump();
    printlog('  State');
    printlog('    isRunning: $isRunning');
    printlog('    isStarted: $isStarted');
    printlog('    isFinished: $isFinished');
    printlog('    animationInProgress: $animationInProgress');
    printlog('  Base data');
    printlog('    boardSizeHorizontal: $boardSizeHorizontal');
    printlog('    boardSizeVertical: $boardSizeVertical');
    printlog('  Game data');
    printlog('    selectedCell: ${selectedCell?.toString() ?? ''}');
    printlog('    showConflicts: $showConflicts');
    printlog('    givenTipsCount: $givenTipsCount');
    printlog('    buttonTipsCountdown: $buttonTipsCountdown');
    printlog('');

    board.dump();
    printlog('');
  }

  @override
  String toString() {
    return '$Activity(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      // Settings
      'activitySettings': activitySettings.toJson(),
      // State
      'isRunning': isRunning,
      'isStarted': isStarted,
      'isFinished': isFinished,
      'animationInProgress': animationInProgress,
      'boardAnimated': boardAnimated,
      // Base data
      'board': board.toJson(),
      'boardSizeHorizontal': boardSizeHorizontal,
      'boardSizeVertical': boardSizeVertical,
      // Game data
      'boardConflicts': boardConflicts,
      'selectedCell': selectedCell?.toJson(),
      'showConflicts': showConflicts,
      'givenTipsCount': givenTipsCount,
      'buttonTipsCountdown': buttonTipsCountdown,
    };
  }
}