import 'package:flutter_custom_toolbox/flutter_toolbox.dart';

import 'package:snake/models/activity/board.dart';
import 'package:snake/models/activity/cell.dart';
import 'package:snake/models/activity/cell_location.dart';
import 'package:snake/models/activity/snake.dart';
import 'package:snake/models/settings/settings_activity.dart';
import 'package:snake/models/settings/settings_global.dart';

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

    // State
    this.isRunning = false,
    this.isStarted = false,
    this.isFinished = false,
    this.animationInProgress = false,

    // Base data
    required this.snake,
    required this.board,
    required this.boardSize,

    // Game data
    this.score = 0,
    this.gameWon = false,
  });

  // Settings
  final ActivitySettings activitySettings;
  final GlobalSettings globalSettings;

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

  // Base data
  Snake snake;
  Board board;
  final int boardSize;

  // Game data
  int score;
  bool gameWon;

  factory Activity.createEmpty() {
    final ActivitySettings defaultActivitySettings = ActivitySettings.createDefault();
    final GlobalSettings defaultGlobalSettings = GlobalSettings.createDefault();

    return Activity(
      // Settings
      activitySettings: defaultActivitySettings,
      globalSettings: defaultGlobalSettings,
      // Base data
      snake: Snake.create(defaultActivitySettings),
      board: Board.createEmpty(defaultActivitySettings),
      boardSize: defaultActivitySettings.boardSize,
    );
  }

  factory Activity.createNew({
    ActivitySettings? activitySettings,
    GlobalSettings? globalSettings,
  }) {
    final ActivitySettings newActivitySettings =
        activitySettings ?? ActivitySettings.createDefault();
    final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();

    Activity newActivity = Activity(
      // Settings
      activitySettings: newActivitySettings,
      globalSettings: newGlobalSettings,
      // State
      isRunning: true,
      // Base data
      snake: Snake.create(newActivitySettings),
      board: Board.createEmpty(newActivitySettings),
      boardSize: newActivitySettings.boardSize,
      // Game data
      score: 0,
    );
    newActivity.computeBoard();

    return newActivity;
  }

  bool get canBeResumed => isStarted && !isFinished;

  void computeBoard() {
    Board newBoard = Board.createEmpty(activitySettings);

    // Add snake
    for (int i = 0; i < snake.cells.length - 1; i++) {
      newBoard.setCellValue(snake.cells[i], CellValue.body);
    }
    newBoard.setCellValue(snake.cells[snake.cells.length - 1], CellValue.head);

    board = newBoard;
  }

  void turnLeft() {
    switch (snake.direction) {
      case SnakeDirection.left:
        snake.direction = SnakeDirection.bottom;
      case SnakeDirection.right:
        snake.direction = SnakeDirection.top;
      case SnakeDirection.top:
        snake.direction = SnakeDirection.left;
      case SnakeDirection.bottom:
        snake.direction = SnakeDirection.right;
    }
  }

  void turnRight() {
    switch (snake.direction) {
      case SnakeDirection.left:
        snake.direction = SnakeDirection.top;
      case SnakeDirection.right:
        snake.direction = SnakeDirection.bottom;
      case SnakeDirection.top:
        snake.direction = SnakeDirection.right;
      case SnakeDirection.bottom:
        snake.direction = SnakeDirection.left;
    }
  }

  CellLocation nextHeadLocation() {
    CellLocation head = CellLocation(col: snake.head.col, row: snake.head.row);

    switch (snake.direction) {
      case SnakeDirection.left:
        head.moveLeft();
      case SnakeDirection.right:
        head.moveRight();
      case SnakeDirection.top:
        head.moveTop();
      case SnakeDirection.bottom:
        head.moveBottom();
    }

    return head;
  }

  bool canMove() {
    // compute head's next cell
    CellLocation head = nextHeadLocation();

    // check head is inside board
    if (head.col < 0 ||
        head.col > (boardSize - 1) ||
        head.row < 0 ||
        head.row > (boardSize - 1)) {
      return false;
    }

    // check head is not looped on snake body
    for (int i = 0; i < snake.cells.length - 1; i++) {
      if (head.col == snake.cells[i].col && head.row == snake.cells[i].row) {
        return false;
      }
    }

    return true;
  }

  void updateSnakeSize(int delta) {
    snake.size += delta;
    if (snake.size < 3) {
      snake.size = 3;
    }
  }

  void moveSnake() {
    if (!canMove()) {
      printlog('boom');
      isFinished = true;
      return;
    }

    // compute head's next cell
    CellLocation head = nextHeadLocation();

    // Append new head to snake cells
    snake.cells.add(head);

    // Drop tail
    while (snake.cells.length > snake.size) {
      snake.cells.removeAt(0);
    }

    computeBoard();
  }

  void drawBoard() {
    String horizontalRule = '----';
    for (int i = 0; i < board.cells[0].length; i++) {
      horizontalRule += '-';
    }

    printlog('Board:');
    printlog(horizontalRule);

    for (int rowIndex = 0; rowIndex < board.cells.length; rowIndex++) {
      String row = '| ';
      for (int colIndex = 0; colIndex < board.cells[rowIndex].length; colIndex++) {
        String cellValue = ' ';
        switch (board.getCellValue(CellLocation.go(rowIndex, colIndex))) {
          case CellValue.empty:
            cellValue = ' ';
          case CellValue.head:
            cellValue = 'O';
          case CellValue.body:
            cellValue = 'o';
          case CellValue.fruit:
            cellValue = 'X';
        }
        row += cellValue;
      }
      row += ' |';

      printlog(row);
    }

    printlog(horizontalRule);
  }

  void dump() {
    printlog('');
    printlog('## Current game dump:');
    printlog('');
    printlog('$Activity:');
    printlog('  Settings');
    activitySettings.dump();
    globalSettings.dump();
    printlog('  State');
    printlog('    isRunning: $isRunning');
    printlog('    isStarted: $isStarted');
    printlog('    isFinished: $isFinished');
    printlog('    animationInProgress: $animationInProgress');
    printlog('  Base data');
    drawBoard();
    printlog('  Game data');
    printlog('    score: $score');
    printlog('    gameWon: $gameWon');
    printlog('');
  }

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

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      // Settings
      'activitySettings': activitySettings.toJson(),
      'globalSettings': globalSettings.toJson(),
      // State
      'isRunning': isRunning,
      'isStarted': isStarted,
      'isFinished': isFinished,
      'animationInProgress': animationInProgress,
      // Base data
      'snake': snake.toJson(),
      'board': board.toJson(),
      // Game data
      'score': score,
      'gameWon': gameWon,
    };
  }
}