<?php

namespace App\Http\Controllers;

use App\Election;
use App\MajorityElection;
use App\MajorityVote;
use App\Population;
use App\Voter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;

class APIController extends Controller
{
    public function generatePopulation(Request $request)
    {
        try {
            $attributes = $request->validate([
                'name' => 'nullable|string',
                'size_a' => 'required|integer|min:0|max:1000',
                'size_b' => 'required|integer|min:0|max:1000',
                'init_expertise_a' => 'required|integer|min:0|max:100',
                'init_expertise_b' => 'required|integer|min:0|max:100',
                'spread_expertise_a' => 'required|integer|min:0|max:100',
                'spread_expertise_b' => 'required|integer|min:0|max:100',
                'init_confidence_a' => 'required|integer|min:0|max:100',
                'init_confidence_b' => 'required|integer|min:0|max:100',
                'spread_confidence_a' => 'required|integer|min:0|max:100',
                'spread_confidence_b' => 'required|integer|min:0|max:100',
                'init_following_a' => 'required|integer|min:0|max:100',
                'init_following_b' => 'required|integer|min:0|max:100',
                'spread_following_a' => 'required|integer|min:0|max:100',
                'spread_following_b' => 'required|integer|min:0|max:100',
                'init_leadership_a' => 'required|integer|min:0|max:100',
                'init_leadership_b' => 'required|integer|min:0|max:100',
                'spread_leadership_a' => 'required|integer|min:0|max:100',
                'spread_leadership_b' => 'required|integer|min:0|max:100'
            ]);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Incorrect payload',
                'val_errors' => $e->errors()
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        $start = microtime(true);

        $population = new Population();
        $population->save();
        $population->name = isset($attributes['name']) ? $attributes['name'] : 'Population ' . $population->id;
        $population->update();

        $minValue = 1;
        $maxValue = 100;

        $minExpertiseValueA = $attributes['init_expertise_a'] - $attributes['spread_expertise_a'];
        $minExpertiseValueA = $minExpertiseValueA < $minValue ? $minValue : $minExpertiseValueA;
        $maxExpertiseValueA = $attributes['init_expertise_a'] + $attributes['spread_expertise_a'];
        $maxExpertiseValueA = $maxExpertiseValueA > $maxValue ? $maxValue : $maxExpertiseValueA;
        $minExpertiseValueB = $attributes['init_expertise_b'] - $attributes['spread_expertise_b'];
        $minExpertiseValueB = $minExpertiseValueB < $minValue ? $minValue : $minExpertiseValueB;
        $maxExpertiseValueB = $attributes['init_expertise_b'] + $attributes['spread_expertise_b'];
        $maxExpertiseValueB = $maxExpertiseValueB > $maxValue ? $maxValue : $maxExpertiseValueB;

        $minConfidenceValueA = $attributes['init_confidence_a'] - $attributes['spread_confidence_a'];
        $minConfidenceValueA = $minConfidenceValueA < $minValue ? $minValue : $minConfidenceValueA;
        $maxConfidenceValueA = $attributes['init_confidence_a'] + $attributes['spread_confidence_a'];
        $maxConfidenceValueA = $maxConfidenceValueA > $maxValue ? $maxValue : $maxConfidenceValueA;
        $minConfidenceValueB = $attributes['init_confidence_b'] - $attributes['spread_confidence_b'];
        $minConfidenceValueB = $minConfidenceValueB < $minValue ? $minValue : $minConfidenceValueB;
        $maxConfidenceValueB = $attributes['init_confidence_b'] + $attributes['spread_confidence_b'];
        $maxConfidenceValueB = $maxConfidenceValueB > $maxValue ? $maxValue : $maxConfidenceValueB;

        $minFollowingValueA = $attributes['init_following_a'] - $attributes['spread_following_a'];
        $minFollowingValueA = $minFollowingValueA < $minValue ? $minValue : $minFollowingValueA;
        $maxFollowingValueA = $attributes['init_following_a'] + $attributes['spread_following_a'];
        $maxFollowingValueA = $maxFollowingValueA > $maxValue ? $maxValue : $maxFollowingValueA;
        $minFollowingValueB = $attributes['init_following_b'] - $attributes['spread_following_b'];
        $minFollowingValueB = $minFollowingValueB < $minValue ? $minValue : $minFollowingValueB;
        $maxFollowingValueB = $attributes['init_following_b'] + $attributes['spread_following_b'];
        $maxFollowingValueB = $maxFollowingValueB > $maxValue ? $maxValue : $maxFollowingValueB;

        $minLeadershipValueA = $attributes['init_leadership_a'] - $attributes['spread_leadership_a'];
        $minLeadershipValueA = $minLeadershipValueA < $minValue ? $minValue : $minLeadershipValueA;
        $maxLeadershipValueA = $attributes['init_leadership_a'] + $attributes['spread_leadership_a'];
        $maxLeadershipValueA = $maxLeadershipValueA > $maxValue ? $maxValue : $maxLeadershipValueA;
        $minLeadershipValueB = $attributes['init_leadership_b'] - $attributes['spread_leadership_b'];
        $minLeadershipValueB = $minLeadershipValueB < $minValue ? $minValue : $minLeadershipValueB;
        $maxLeadershipValueB = $attributes['init_leadership_b'] + $attributes['spread_leadership_b'];
        $maxLeadershipValueB = $maxLeadershipValueB > $maxValue ? $maxValue : $maxLeadershipValueB;

        $data = new \stdClass();
        $data->meta = new \stdClass();
        $data->meta->size_a = $attributes['size_a'];
        $data->meta->min_expertise_a = $minExpertiseValueA;
        $data->meta->max_expertise_a = $maxExpertiseValueA;
        $data->meta->min_confidence_a = $minConfidenceValueA;
        $data->meta->max_confidence_a = $maxConfidenceValueA;
        $data->meta->min_following_a = $minFollowingValueA;
        $data->meta->max_following_a = $maxFollowingValueA;
        $data->meta->min_leadership_a = $minLeadershipValueA;
        $data->meta->max_leadership_a = $maxLeadershipValueA;
        $data->meta->size_b = $attributes['size_b'];
        $data->meta->min_expertise_b = $minExpertiseValueB;
        $data->meta->max_expertise_b = $maxExpertiseValueB;
        $data->meta->min_confidence_b = $minConfidenceValueB;
        $data->meta->max_confidence_b = $maxConfidenceValueB;
        $data->meta->min_following_b = $minFollowingValueB;
        $data->meta->max_following_b = $maxFollowingValueB;
        $data->meta->min_leadership_b = $minLeadershipValueB;
        $data->meta->max_leadership_b = $maxLeadershipValueB;
/*
        $data->random_expertise_array_a = array();
        $data->random_expertise_array_b = array();
        $data->random_confidence_array_a = array();
        $data->random_confidence_array_b = array();
        $data->random_following_array_a = array();
        $data->random_following_array_b = array();


        for ($i = $minValue; $i <= $maxValue; $i++) {
            $data->random_expertise_array_a[$i] = 0;
            $data->random_expertise_array_b[$i] = 0;
            $data->random_confidence_array_a[$i] = 0;
            $data->random_confidence_array_b[$i] = 0;
            $data->random_following_array_a[$i] = 0;
            $data->random_following_array_b[$i] = 0;
        }
*/
        $data->meta->init_vars = microtime(true) - $start;

        for ($i = 0; $i < $attributes['size_a']; $i++) {
            $randomExpertiseValue = random_int($minExpertiseValueA, $maxExpertiseValueA);
            //$data->random_expertise_array_a[$randomExpertiseValue]++;
            $randomConfidenceValue = random_int($minConfidenceValueA, $maxConfidenceValueA);
            //$data->random_confidence_array_a[$randomConfidenceValue]++;
            $randomFollowingValue = random_int($minFollowingValueA, $maxFollowingValueA);
            //$data->random_following_array_a[$randomFollowingValue]++;
            $randomLeadershipValue = random_int($minLeadershipValueA, $maxLeadershipValueA);
            Voter::create([
                'population_id' => $population->id,
                'expertise'     => $randomExpertiseValue,
                'confidence'    => $randomConfidenceValue,
                'following'     => $randomFollowingValue,
                'leadership'    => $randomLeadershipValue,
                'group'         => 'A'
            ]);
        }

        $data->meta->done_group_a = microtime(true) - $data->meta->init_vars;

        for ($i = 0; $i < $attributes['size_b']; $i++) {
            $randomExpertiseValue = random_int($minExpertiseValueB, $maxExpertiseValueB);
            //$data->random_expertise_array_b[$randomExpertiseValue]++;
            $randomConfidenceValue = random_int($minConfidenceValueB, $maxConfidenceValueB);
            //$data->random_confidence_array_b[$randomConfidenceValue]++;
            $randomFollowingValue = random_int($minFollowingValueB, $maxFollowingValueB);
            //$data->random_following_array_b[$randomFollowingValue]++;
            $randomLeadershipValue = random_int($minLeadershipValueB, $maxLeadershipValueB);
            Voter::create([
                'population_id' => $population->id,
                'expertise'     => $randomExpertiseValue,
                'confidence'    => $randomConfidenceValue,
                'following'     => $randomFollowingValue,
                'leadership'    => $randomLeadershipValue,
                'group'         => 'B'

            ]);
        }

        $data->meta->done_group_b = microtime(true) - $data->meta->done_group_a;

        $data->meta->total_time = round(microtime(true) - $start, 3);

        return response()->json($data, Response::HTTP_OK);
    }

    public function getPopulations(Request $request)
    {
        $data = Population::with('voters', 'elections')
            ->orderBy('id', 'desc')
            ->get()
            ->makeHidden(['elections', 'voters']);

        $data->each(function($item) {
           $item->append(['elections_stats', 'voters_stats']);
        });

        return response()->json($data, Response::HTTP_OK);
    }

    public function getPopulation($population, Request $request)
    {
        $data = Population::where('id', '=', $population)
            ->with('elections', 'voters')
            ->firstOrFail()
            ->append(['elections_stats', 'voters_stats'])
            ->makeHidden('elections', 'voters');

        return response()->json($data, Response::HTTP_OK);
    }

    public function getMajorityElectionsDistribution($population, Request $request)
    {
        $metadata = new \stdClass();
        $startTime = microtime(true);

        $populationData = Population::where('id', '=', $population)
            ->with(['elections' => function($q) {
                $q->where('type', '=', 'm');
            }])
            ->firstOrFail();

        $distribution = array();
        $raw = array();
        for($i=0;$i<100;$i++){
            $distribution[$i] = 0;
        }
        foreach ($populationData->elections as $election) {
            $sum = $election->total_correct + $election->total_incorrect;
            if ($sum > 0) {
                $percentCorrect = 100 * $election->total_correct / $sum;
                $distribution[floor($percentCorrect)]++;
                $raw[] = round($percentCorrect, 2);
            }
        }
        $sortedRaw = sort($raw);
        $metadata->total_time = round(microtime(true) - $startTime, 3);
        $metadata->number_of_elections = $populationData->elections->count();
        return response()->json([
            'metadata' => $metadata,
            'sorted_raw' => $raw,
            'distribution' => $distribution
        ], Response::HTTP_OK);
    }

    public function getVoters($population, Request $request)
    {
        $data = Population::where('id', '=', $population)
            ->with('voters')
            ->firstOrFail();

        return response()->json($data->voters, Response::HTTP_OK);
    }

    public function runMajorityElections($population, Request $request) {
        $data = new \stdClass();
        $data->elections_stats = array();

        $attributes = $request->validate([
            'number' => 'nullable|integer|min:1'
        ]);

        $numberOfElections = isset($attributes['number']) ? $attributes['number'] : 1;


        $existingPopulation = Population::where('id', '=', $population)
            ->with('voters')
            ->firstOrFail();

        $startTime = microtime(true);

        for( $i = 0; $i < $numberOfElections; $i++) {
            $singleElectionStats = $this->runSingleMajorityElection($existingPopulation);
            $data->elections[] = $singleElectionStats;
        }

        $data->total_time = round(microtime(true) - $startTime, 3);
        $data->number_of_elections = $numberOfElections;

        return response()->json($data, Response::HTTP_OK);
    }

    /**
     * @param $populationID
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleMajorityElection($populationID): \stdClass
    {
        $startTime = microtime(true);
        $election = Election::create([
            'population_id' => $populationID->id,
            'type' => 'm'
        ]);

        $minValue = 1;
        $maxValue = 100;
        $votes = array();
        $electionStats = new \stdClass();
        $correctChoices = 0;
        $incorrectChoices = 0;

        foreach ($populationID->voters as $voter) {
            $random = random_int($minValue, $maxValue);
            $vote = $random <= $voter->expertise;
            $vote ? $correctChoices++ : $incorrectChoices++;
            $votes[] = [
                'election_id' => $election->id,
                'voter_id' => $voter->id,
                'vote' => $vote
            ];
        }

        $votesTime = microtime(true);
        $electionStats->votes_time = round($votesTime - $startTime, 3);

        MajorityVote::insert($votes);

        $electionStats->total_correct_choices = round($correctChoices, 2);
        $electionStats->total_incorrect_choices = round($incorrectChoices, 2);

        $sum = $correctChoices + $incorrectChoices;
        if ($sum > 0) {
            $percentCorrect = 100 * $correctChoices / $sum;
        } else {
            $percentCorrect = null;
        }

        $electionStats->percent_correct_choices = round($percentCorrect, 2);

        $election->total_correct = $correctChoices;
        $election->total_incorrect = $incorrectChoices;
        $election->save();

        $dbTime = microtime(true);
        $electionStats->votes_db_time = round($dbTime - $votesTime,3);
        return $electionStats;
    }
}