import 'dart:math' as math;

import 'package:ecosim/src/common/models/pvector.dart';
import 'package:ecosim/src/ecosim/models/boid.dart';
import 'package:ecosim/src/ecosim/models/boid_base.dart';
import 'package:ecosim/src/ecosim/models/predator_dna.dart';
import 'package:ecosim/src/ecosim/models/world_object.dart';
import 'package:ecosim/src/ecosim/utils.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

// Predator Boid for the Ecosystem Simulation
// extends the standard boid and only overrides
// the functionality needed for this Boid to act
// as predator instead of prey
class PredatorBoid extends Boid {
  PredatorDna predatorDna;
  double predatorBoidNutrition;

  PredatorBoid({
    required this.predatorDna,
    required this.predatorBoidNutrition,
    required super.position,
    required super.dna,
    required super.velocity,
    required super.tickDamage,
    required super.health,
    required super.maxHealth,
    required super.replicationProbability,
    required super.birthTimestamp,
    required super.healthyColor,
    required super.unhealthyColor,
    required super.mutationThreshold,
  });

  @override
  PredatorBoid? clone() {
    if (DateTime.now().millisecondsSinceEpoch - birthTimestamp < 10000 ||
        DateTime.now().millisecondsSinceEpoch - lastReplicationTimestamp <
            10000) {
      return null;
    }
    final rng = math.Random();
    final replicationThreshold = 1.0 - replicationProbability;
    if (rng.getRandomDoubleInRange(0.0, 1.0) <= replicationThreshold &&
        health > maxHealth / 2) {
      final newDna = BoidBaseDna(
        steeringForce: dna.steeringForce,
        speed: dna.speed,
        foodPull: dna.foodPull,
        poisonPull: dna.poisonPull,
        predatorPull: dna.predatorPull,
        foodPerceptionRadius: dna.foodPerceptionRadius,
        poisonPerceptionRadius: dna.poisonPerceptionRadius,
        predatorPerceptionRadius: dna.predatorPerceptionRadius,
        mutationThreshold: mutationThreshold,
      );
      newDna.mutate();
      final newPredatorDna = PredatorDna(
        preyPerception: predatorDna.preyPerception,
        preyPull: predatorDna.preyPull,
      );
      newPredatorDna.mutate();
      lastReplicationTimestamp = DateTime.now().millisecondsSinceEpoch;
      return PredatorBoid(
        position: position,
        predatorDna: newPredatorDna,
        predatorBoidNutrition: predatorBoidNutrition,
        velocity: velocity,
        dna: newDna,
        tickDamage: tickDamage,
        health: maxHealth,
        maxHealth: maxHealth,
        replicationProbability: replicationProbability,
        birthTimestamp: DateTime.now().millisecondsSinceEpoch,
        healthyColor: healthyColor,
        unhealthyColor: unhealthyColor,
        mutationThreshold: mutationThreshold,
      );
    } else {
      return null;
    }
  }

  @override
  PVector? getClosestItem(
    PVector origin,
    double range,
    List<Object>? items,
    Size worldSize,
  ) {
    double closestItemDist = double.infinity;
    WorldObject? closestItem;

    if (items == null) return null;
    for (int i = 0; i <= items.length - 1; i++) {
      final worldObject = items[i] as WorldObject;
      final dist = worldObject.position.distanceTo(origin);
      if (dist <= closestItemDist && dist <= range) {
        closestItemDist = dist;
        closestItem = worldObject;
      }
    }

    if (closestItemDist < 2) {
      if (closestItem is Boid) {
        health += predatorBoidNutrition;
      }
      items.remove(closestItem);
    }

    return closestItem?.position;
  }

  @override
  void updateBoidDirection(
    List<List<Object>> objects,
    Size worldSize,
  ) {
    final closestPrey = getClosestItem(
      position,
      predatorDna.preyPerception,
      objects.firstWhereOrNull((element) => element.firstOrNull is Boid),
      worldSize,
    );
    if (closestPrey != null) {
      PVector steer = getSteeringForce(closestPrey, position, velocity) *
          dna.steeringForce.abs() *
          predatorDna.preyPull;
      acceleration.add(steer);
    }
  }

  @override
  void renderDebug(Canvas canvas) {
    var angle = velocity.heading + math.pi / 2;
    canvas.save();
    canvas.translate(
      position.x.toDouble(),
      position.y.toDouble(),
    );

    const textStyle = TextStyle(
      color: Colors.white,
      fontSize: 12,
    );
    final textSpan = TextSpan(
      // render the health of the boid:
      text: '${health.roundToDouble()}',
      style: textStyle,
    );
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout(minWidth: 0, maxWidth: 100);
    textPainter.paint(canvas, const Offset(10, 10));

    canvas.rotate(angle);

    var circlePaintBoid = Paint()
      ..strokeWidth = 3
      ..color = const Color(0xFFFFFFFF)
      ..style = PaintingStyle.stroke;

    canvas.drawCircle(
      Offset.zero,
      predatorDna.preyPerception,
      circlePaintBoid,
    );
    canvas.drawLine(
      Offset.zero,
      Offset(0, predatorDna.preyPull * -20),
      circlePaintBoid,
    );
    canvas.restore();
  }
}
