<?php

namespace Drupal\ufc\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\node\NodeInterface;
use Drupal\node\Entity\Node;
use Drupal\media\Entity\Media;
use Drupal\file\Entity\File;
use Drupal\Core\Cache\CacheableJsonResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;

class FightTrainingController extends ControllerBase {

  /*
   * Fields holding fighter data.
   */
  protected $fields = [
      'age',
      'height',
      'reach',
      'leg_reach',
      'knockouts',
      'striking_accuracy',
      'strikes_per_minute',
      'sig_strike_defense',
      'absorbed_per_min',
      'standing_strikes',
      'clinch_strikes',
      'ground_strikes',
      'grappling_accuracy',
      'strikes_to_head',
      'strikes_to_body',
      'strikes_to_leg',
      'knockdown_ratio',
      'takedowns_per_15',
      'takedown_defense',
      'average_fight_time',
      'first_round_finishes',
  ];

  /**
   *  The entity type manager service.
   *
   *  @var Drupal\Core\Entity\EntityTypeManager
   *    The entity type manager service.
   */
  protected $entityTypeManager;

  /**
   *  Public constructor.
   *
   *  @var \Drupal\Core\Entity\EntityTypeManager $entityTypeManager
   *    The entity type manager service.
   *
   */
  public function __construct(
    EntityTypeManager $entityTypeManager
  ) {
    $this->entityTypeManager = $entityTypeManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    // Instantiates this form class.
    return new static(
      // Load the service required to construct this class.
      $container->get('entity_type.manager'),
    );
  }

  /**
   * Generates all fighting data for training NN.
   */
  public function generateTrainingData(): CacheableJsonResponse {
    //  Go get all fights.
    $all_fights = $this->entityTypeManager->getStorage('node')->loadByProperties(['type' => 'fight']);
    $training_data = [];
    foreach ($all_fights as $fight) {
      $train_array = [
        'input' => [],
        'output' => [],
      ];
      // Ensure we have a winner.
      if (!$fight->field_result->target_id) {
        continue;
      }
      // Extract fighters.
      $fighter_one_id = $fight->field_fighter_one->target_id;
      $fighter_two_id = $fight->field_fighter_two->target_id;

      if (!$fighter_one_id || !$fighter_two_id) {
        continue;
      }

      $fighter_one_data = $this->getFighterData($fighter_one_id);
      $fighter_two_data = $this->getFighterData($fighter_two_id, FALSE);

      if (empty($fighter_one_data) || empty($fighter_two_data)) {
        continue;
      }

      $train_array['input'] = array_merge($fighter_one_data, $fighter_two_data);

      if ($fight->field_result->target_id == $fighter_one_id) {
        $train_array['output'] = [
          'fighter_one' => 1,
          'fighter_two' => 0,
        ];
        $training_data[] = $train_array;
      }
      else if ($fight->field_result->target_id == $fighter_two_id) {
        $train_array['output'] = [
          'fighter_one' => 0,
          'fighter_two' => 1,
        ];
        $training_data[] = $train_array;
      }
      else {
        continue;
      }
    }

    return new CacheableJsonResponse($training_data);
  }

  /**
   * Retrieves data about a specific fight for predictions.
   */
  public function getFightData(NodeInterface $fight): CacheableJsonResponse {
    $fighter_1_id = $fight->field_fighter_one->target_id;
    $fighter_2_id = $fight->field_fighter_two->target_id;

    $fight_data = array_merge(
      $this->getFighterData($fighter_1_id),
      $this->getFighterData($fighter_2_id, FALSE),
    );

    return new CacheableJsonResponse($fight_data);
  }

  /**
   * Gets the fighter data.
   *
   * @return array<string,mixed>
   */
  private function getFighterData(int $id, bool $is_f1 = TRUE): array {
    if ($is_f1) {
      $prefix = 'fighter_one_';
    }
    else {
      $prefix = 'fighter_two_';
    }

    $extracted_values = $this->extractValuesFromFields($id, $this->fields, $prefix);

    return $this->normalizeData($extracted_values);
  }

  /**
   * Extracts a value from a given field (cannot be ent reference)
   */
  private function extractValuesFromFields(int $id, array $field_names, string $prefix): mixed {
    $fighter = Node::load($id);
    $values = [];

    foreach ($field_names as $field) {
      $value_key = $prefix . $field;
      $field_machine_name = 'field_' . $field;
      $values[$value_key] = $fighter->{$field_machine_name}->value ?? 0;
    }

    return $values;
  }

  /**
   * Normalize the field value.
   *
   *  This needs to be between 0-1
   */
  private function normalizeData(array $data): array {
    // @todo - this creates an issue where duration of fight is the largest number which isnt really correct
    // and smaller duration would be better
    $min = min(array_values($data));
    $max = max(array_values($data));
    $normalized = [];
    foreach ($data as $key => $value) {

      $value = (float) $value;
      $normalized[$key] = $this->convertToDecimal($value);
      /* $norm_val = 0; */
      /* if ($max - $min == 0) { */
      /*   $normalized[$key] = $norm_val; */
      /* } */
      /* else { */
      /*   $norm_val = ($value - $min) / ($max - $min); */
      /*   $normalized[$key] = $norm_val; */
      /* } */
    }

    return $normalized;
  }

  public function convertToDecimal(float $input) {
    $output = 0;
    if ($input > 0 && $input <= 1) {
       $output = $input / 100;
    }
    elseif ($input > 1 && $input < 10) {
      $output = $input / 100;
    }
    elseif ($input > 10 && $input < 100) {
      $output = $input / 1000;
    }
    elseif ($input > 100 && $input < 1000) {
      $output = $input / 10000;
    }

    return $output;
  }

  /**
   * Gets the trained neural network.
   */
  public function getNeuralNetwork(): CacheableJsonResponse {
    $build = [];
    $cur_network = \Drupal::state()->get('neuralNetwork') ?? FALSE;
    if (!$cur_network) {
      $build['ERROR'] = "There is no spoon.";
    }
    else {
      $build['data'] = base64_decode($cur_network);
    }

    return new CacheableJsonResponse($build);
  }

}
