import 'dart:math';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';

import 'package:jeweled/config/default_global_settings.dart';
import 'package:jeweled/models/game.dart';
import 'package:jeweled/models/cell_location.dart';
import 'package:jeweled/utils/color_extensions.dart';
import 'package:jeweled/utils/color_theme.dart';

class GameBoardPainter extends CustomPainter {
  const GameBoardPainter({
    required this.game,
    required this.animations,
  });

  final Game game;
  final List<List<Animation<double>?>> animations;

  @override
  void paint(Canvas canvas, Size size) {
    int graphicTheme = game.globalSettings.graphicTheme;

    final double canvasSize = min(size.width, size.height);

    const double cellBorderWidth = 2;
    const double boardBorderWidth = 3;

    switch (graphicTheme) {
      case DefaultGlobalSettings.graphicThemeSolidBackground:
        drawBoard(
          canvas: canvas,
          canvasSize: canvasSize,
          game: game,
        );
        break;
      case DefaultGlobalSettings.graphicThemeGradientAndBorder:
        drawBoard(
          canvas: canvas,
          canvasSize: canvasSize,
          game: game,
          borderWidth: cellBorderWidth,
          gradientFrom: -10,
          gradientTo: 10,
        );
        break;
      case DefaultGlobalSettings.graphicThemeEmojis:
        drawBoard(
          canvas: canvas,
          canvasSize: canvasSize,
          game: game,
          contentStrings: DefaultGlobalSettings.graphicThemeContentEmojiStrings,
        );
        break;
      case DefaultGlobalSettings.graphicThemePatterns:
        drawBoard(
          canvas: canvas,
          canvasSize: canvasSize,
          game: game,
          contentStrings: DefaultGlobalSettings.graphicThemeContentPatternStrings,
        );
        break;

      default:
    }

    // board borders
    final boardPaintBorder = Paint();
    boardPaintBorder.color = ColorTheme.getBorderColor();
    boardPaintBorder.strokeWidth = boardBorderWidth;
    boardPaintBorder.strokeCap = StrokeCap.round;

    const Offset boardTopLeft = Offset(0, 0);
    final Offset boardTopRight = Offset(canvasSize, 0);
    final Offset boardBottomLeft = Offset(0, canvasSize);
    final Offset boardBottomRight = Offset(canvasSize, canvasSize);

    canvas.drawLine(boardTopLeft, boardTopRight, boardPaintBorder);
    canvas.drawLine(boardTopRight, boardBottomRight, boardPaintBorder);
    canvas.drawLine(boardBottomRight, boardBottomLeft, boardPaintBorder);
    canvas.drawLine(boardBottomLeft, boardTopLeft, boardPaintBorder);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

  void drawBoard({
    required Canvas canvas,
    required double canvasSize,
    required Game game,
    double overlapping = 1,
    int gradientFrom = 0,
    int gradientTo = 0,
    double borderWidth = 0,
    List<String>? contentStrings,
  }) {
    final int cellsCountHorizontal = game.gameSettings.boardSize;
    final int cellsCountVertical = game.gameSettings.boardSize;
    final int colorsTheme = game.globalSettings.colorsTheme;

    final double size = canvasSize / max(cellsCountHorizontal, cellsCountVertical);

    for (int row = 0; row < cellsCountVertical; row++) {
      final double yOrigin = size * row;
      for (int col = 0; col < cellsCountHorizontal; col++) {
        final double x = size * col;

        final CellLocation cellLocation = CellLocation.go(row, col);
        final int? cellValue = game.getCellValueShuffled(cellLocation);
        if (cellValue != null) {
          final Animation<double>? translation = animations[row][col];
          final double y = yOrigin + (translation?.value ?? 0) * size;

          drawCell(
            canvas: canvas,
            x: x,
            y: y,
            cellSize: size,
            cellValue: cellValue,
            colorsTheme: colorsTheme,
            overlapping: overlapping,
            gradientFrom: gradientFrom,
            gradientTo: gradientTo,
            borderWidth: borderWidth,
            contentStrings: contentStrings,
          );
        }
      }
    }
  }

  void drawCell({
    required Canvas canvas,
    required double x,
    required double y,
    required double cellSize,
    required int cellValue,
    required int colorsTheme,
    required double overlapping,
    required int gradientFrom,
    required int gradientTo,
    required double borderWidth,
    required List<String>? contentStrings,
  }) {
    final Color baseColor = ColorTheme.getColor(cellValue, colorsTheme);

    final paint = Paint();

    // draw background
    final Rect rect = Rect.fromLTWH(x, y, cellSize + overlapping, cellSize + overlapping);

    // solid or gradient
    if (gradientFrom == 0 && gradientTo == 0) {
      paint.color = baseColor;
    } else {
      paint.shader = ui.Gradient.linear(
        rect.topLeft,
        rect.bottomCenter,
        [
          (gradientFrom < 0)
              ? baseColor.lighten(-gradientFrom)
              : baseColor.darken(gradientFrom),
          (gradientTo < 0) ? baseColor.lighten(-gradientTo) : baseColor.darken(gradientTo),
        ],
      );
    }
    paint.style = PaintingStyle.fill;
    canvas.drawRect(rect, paint);

    // draw border
    if (borderWidth != 0) {
      final Rect border = Rect.fromLTWH(x + borderWidth, y + borderWidth,
          cellSize + overlapping - 2 * borderWidth, cellSize + overlapping - 2 * borderWidth);

      final paintBorder = Paint();
      paintBorder.color = baseColor.darken(20);
      paintBorder.style = PaintingStyle.stroke;

      paintBorder.strokeWidth = borderWidth;
      paintBorder.strokeJoin = StrokeJoin.round;
      canvas.drawRect(border, paintBorder);
    }

    // draw content
    if (contentStrings != null) {
      final int emojiIndex =
          cellValue - 1 + game.shuffledColors[0] + 2 * game.shuffledColors[1];
      final String text = contentStrings[emojiIndex % contentStrings.length];

      final textSpan = TextSpan(
        text: text,
        style: TextStyle(
          color: Colors.black,
          fontSize: 4 + cellSize / 2,
          fontWeight: FontWeight.bold,
        ),
      );
      final textPainter = TextPainter(
        text: textSpan,
        textDirection: TextDirection.ltr,
      );
      textPainter.layout();
      textPainter.paint(
        canvas,
        Offset(
          x + (cellSize - textPainter.width) * 0.5,
          y + (cellSize - textPainter.height) * 0.5,
        ),
      );
    }
  }
}