import 'package:ecosim/src/common/models/pvector.dart';
import 'package:ecosim/src/ecosim/models/boid.dart';
import 'package:ecosim/src/ecosim/models/predator.dart';
import 'dart:math' as math;

// return a random PVector in the set range
PVector getRandUnitVec2() {
  final rng = math.Random();
  final x = rng.getRandomDoubleInRange(-100.0, 100.0);
  final y = rng.getRandomDoubleInRange(-100.0, 100.0);
  final vec2 = PVector(x, y);
  vec2.normalize();
  return vec2;
}

// A method that calculates a steering force towards a target
// STEER = DESIRED - VELOCITY
PVector getSteeringForce(PVector target, PVector pos, PVector velocity) {
  final desired = target - pos;
  return desired - velocity;
}

// extension function on Random
// to get a random number in
// a defined start and end range
extension RandomNumberInRange on math.Random {
  double getRandomDoubleInRange(num start, num end) =>
      nextDouble() * (end - start) + start;
}

// get the stat values
// multiple named stat values are returned in a record
({
  int numBoids,
  int numPredators,
  double foodPerceptionRadius,
  double poisonPerceptionRadius,
  double predatorPerceptionRadius,
  double foodAffinity,
  double poisonAffinity,
  double predatorAversion,
  double speed,
  double steeringForce,
  double avgLifespan,
}) getStats(
  List<Boid> boids,
  List<PredatorBoid> predators,
) {
  double avgFoodPerceptionRadius = 0.0;
  double avgPoisonPerceptionRadius = 0.0;
  double avgPredatorPerceptionRadius = 0.0;

  double avgFoodAffinity = 0.0;
  double avgPoisonAffinity = 0.0;
  double avgPredatorAffinity = 0.0;

  double avgSpeed = 0.0;

  double avgSteeringForce = 0.0;

  double avgLifespan = 0.0;

  for (int i = 0; i <= boids.length - 1; i++) {
    // perception
    avgFoodPerceptionRadius += boids[i].dna.foodPerceptionRadius;
    avgPoisonPerceptionRadius += boids[i].dna.poisonPerceptionRadius;
    avgPredatorPerceptionRadius += boids[i].dna.predatorPerceptionRadius;

    // affinity
    avgFoodAffinity += boids[i].dna.foodPull;
    avgPoisonAffinity += boids[i].dna.poisonPull;
    avgPredatorAffinity += boids[i].dna.predatorPull;

    // speed
    avgSpeed += boids[i].dna.speed;

    // steering force
    avgSteeringForce += boids[i].dna.steeringForce;

    // lifespan
    avgLifespan += (DateTime.timestamp().millisecondsSinceEpoch -
            boids[i].birthTimestamp) /
        1000;
  }
  // perception
  avgFoodPerceptionRadius =
      (avgFoodPerceptionRadius / boids.length).roundToDouble();
  avgPoisonPerceptionRadius =
      (avgPoisonPerceptionRadius / boids.length).roundToDouble();
  avgPredatorPerceptionRadius =
      (avgPredatorPerceptionRadius / boids.length).roundToDouble();

  // affinity
  avgFoodAffinity =
      double.parse((avgFoodAffinity / boids.length).toStringAsPrecision(4));
  avgPoisonAffinity =
      double.parse((avgPoisonAffinity / boids.length).toStringAsPrecision(4));
  avgPredatorAffinity =
      double.parse((avgPredatorAffinity / boids.length).toStringAsPrecision(4));

  // speed
  avgSpeed = double.parse((avgSpeed / boids.length).toStringAsPrecision(4));

  // steering force
  avgSteeringForce =
      double.parse((avgSteeringForce / boids.length).toStringAsPrecision(4));

  // lifespan
  avgLifespan = (avgLifespan / boids.length).roundToDouble();

  var record = (
    numBoids: boids.length,
    numPredators: predators.length,
    foodPerceptionRadius: avgFoodPerceptionRadius,
    poisonPerceptionRadius: avgPoisonPerceptionRadius,
    predatorPerceptionRadius: avgPredatorPerceptionRadius,
    foodAffinity: avgFoodAffinity,
    poisonAffinity: avgPoisonAffinity,
    predatorAversion: avgPredatorAffinity,
    speed: avgSpeed,
    steeringForce: avgSteeringForce,
    avgLifespan: avgLifespan,
  );
  return record;
}

// calculate the milliseconds from a double representation in seconds
// 1.5 seconds => 1500 milliseconds
int getTimerMillisecondsFromSeconds({required double interval}) {
  final int milliseconds = (interval * 1000).toInt();
  return milliseconds;
}
