<?php

namespace App\Http\Controllers;

use App\DelegationOneVote;
use App\Election;
use App\ExtensionDelegationElection;
use App\MajorityVote;
use App\Population;
use App\Voter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;

class APIController extends Controller
{
    public function generatePopulationTemplate(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',
                'forgetting_factor' => 'nullable|int|min:0|max:100',
                'follower_factor' => 'nullable|min:1'
            ]);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Incorrect payload',
                'val_errors' => $e->errors()
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        $start = microtime(true);

        $population = new Population();

        $population->forgetting_factor = isset($attributes['forgetting_factor']) ? $attributes['forgetting_factor'] : Config::get('app.forgetting_factor', 1);

        $population->follower_factor = isset($attributes['follower_factor']) ? $attributes['follower_factor'] : Config::get('app.follower_factor', 100);

        $population->save();
        $population->name = isset($attributes['name']) ? $attributes['name'] : 'Population Template ' . $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 getChildPopulations(Population $population) {
        $data = Population::with('voters', 'elections')
            ->where('parent_id', '=', $population->id)
            ->orderBy('id', 'asc')
            ->get()
            ->makeHidden(['elections', 'voters']);

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

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

    public function generateChildPopulation(Population $template, Request $request) {
        try {
            $attributes = $request->validate([
                'election_type' => 'required|string|in:m,d1,d2,d3'
            ]);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Incorrect payload',
                'val_errors' => $e->errors()
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        $nextChildId = $template->childPopulations()->where('election_type', '=', $attributes['election_type'])->count() + 1;

        $newPopulation = new Population();
        if (!isset($template->parent_id)) {
            $newPopulationName = "[". $template->name . "].(" . $attributes['election_type'] . "-" . $nextChildId . ")";
        } else {
            //TEMP
            $newPopulationName = $template->name . ".(g2-" . $nextChildId . ")";
            //$newPopulationName = $template->name . ".(" . $attributes['election_type'] . "-" . $nextChildId . ")";
        }
        $newPopulation->election_type = $attributes['election_type'];
        $newPopulation->name = $newPopulationName;
        $newPopulation->parent_id = $template->id;
        $newPopulation->forgetting_factor = $template->forgetting_factor;
        $newPopulation->follower_factor = $template->follower_factor;

        // 'performance'/'learning' default stage for child population
        if($newPopulation->election_type === "d1" || $newPopulation->election_type === "m") {
            $newPopulation->stage = 'p';
        } else {
            $newPopulation->stage = 'l';
        }
        $newPopulation->save();

        foreach ($template->voters as $parentPopulationVoter) {
            Voter::create([
                'population_id' => $newPopulation->id,
                'expertise'     => $parentPopulationVoter->expertise,
                'confidence'    => $parentPopulationVoter->confidence,
                'following'     => $parentPopulationVoter->following,
                'leadership'    => $parentPopulationVoter->leadership,
                'reputation'    => $parentPopulationVoter->reputation,
                'group'         => $parentPopulationVoter->group
            ]);
        }
    }

    public function getPopulationTemplates(Request $request)
    {
        $attributes = $request->validate([
            'top_level_only' => 'nullable|boolean'
        ]);

        $topLevelOnly = isset($attributes["top_level_only"]) ? $attributes["top_level_only"] : true;

        $data = Population::with('voters', 'elections', 'childPopulations')
            ->whereNull('parent_id')
            ->when(!$topLevelOnly,function($q) {
                $q->orWhere(function($q) {
                    $q->where('stage','=','p')
                        ->where('election_type', '<>', 'd1');
                });
            })
            //->whereNull('parent_id')
            ->orderBy('id', 'desc')
            ->get()
            ->makeHidden(['elections', 'voters', 'childPopulations']);

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

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

    public function getPopulationTemplate(int $template, Request $request) {
        $data = Population::where('id', '=', $template)
            ->where(function($q) {
                $q->whereNull('parent_id')
                    ->orWhere(function($q) {
                        $q->where('stage','=','p')
                            ->where('election_type', '<>', 'd1');
                    });
            })
            ->with('elections', 'voters', 'childPopulations')
            ->firstOrFail()
            ->append(['elections_stats', 'voters_stats', 'children_count', 'child_populations_stats'])
            ->makeHidden('elections', 'voters');

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

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

    public function getPopulation(int $template, int $population, Request $request)
    {
        $attributes = $request->validate([
            'omit_voters' => 'nullable|boolean'
        ]);

        $omitVoters = isset($attributes["omit_voters"]) ? $attributes["omit_voters"] : false;

        $toAppend = $omitVoters ? ['elections_stats'] : ['elections_stats', 'voters_stats'];

        $data = Population::where('id', '=', $population)
            ->where('parent_id', '=', $template)
            ->with('elections')
            ->when(!$omitVoters, function($q) {
                $q->with('voters');
            })
            ->firstOrFail()
            ->append($toAppend)
            ->makeHidden('elections', 'voters');

       //$data->forgetting_percent = 100 - (100 * Config::get('app.forgetting_factor', 0));
        //$data->follower_factor = Config::get('app.follower_factor');

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

    public function changeToPerformanceStage(int $template, int $population, Request $request)
    {
        $childPopulation = Population::where('id', '=', $population)
            ->where('parent_id', '=', $template)
            ->firstOrFail();

        $childPopulation->stage = 'p';

        if ($childPopulation->save()) {
            return response()->json(null, Response::HTTP_NO_CONTENT);
        }

        return response()->json(['error' => 'Stage not saved'], Response::HTTP_INTERNAL_SERVER_ERROR);
    }

    public function getMajorityElectionsDistribution(int $template, int $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();
        $distributionRounded10 = array();
        //$raw = array();
        for($i=0;$i<101;$i++){
            $distribution[$i] = 0;
        }
        for($i=0;$i<11;$i++){
            $distributionRounded10[$i] = 0;
        }
        foreach ($populationData->elections as $election) {
            $sum = $election->total_correct + $election->total_incorrect;
            if ($sum > 0) {
                $percentCorrect = $election->total_correct / $sum;
                $distribution[floor(100 * $percentCorrect)]++;
                $distributionRounded10[floor(10 * $percentCorrect)]++;
                //$raw[] = round($percentCorrect, 2);
            }
        }
        $distributionRounded10[9] = $distributionRounded10[9] + $distributionRounded10[10];
        unset($distributionRounded10[10]);
        //$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,
            'distribution_r_10' => $distributionRounded10
        ], Response::HTTP_OK);
    }

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

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

    /**
     * @deprecated
     *
     * @param $population
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     * @throws \Exception
     */
    public function runMajorityElections(int $template, int $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 $population
     * @param null $forgettingFactor
     * @param null $followerFactor
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleMajorityElection($population, $forgettingFactor = null, $followerFactor = null): \stdClass
    {
        $startTime = microtime(true);
        $election = Election::create([
            'population_id' => $population->id,
            'type' => 'm'
        ]);

        $minValue = 1;
        $maxValue = 100;
        $votes = array();
        $electionStats = new \stdClass();
        $electionStats->type = "m";
        $correctChoices = 0;
        $incorrectChoices = 0;

        foreach ($population->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;
    }

    public function runElections(int $template, int $population, Request $request) {

        $startTime = microtime(true);

        $data = new \stdClass();
        //$data->elections_stats = array();

        $attributes = $request->validate([
            'type'      => 'required|string',
            'number'    => 'nullable|integer|min:1',
            'omit_details' => 'nullable|boolean'
        ]);

        $omitElectionDetails = isset($attributes['omit_details']) && $attributes['omit_details'];

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

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

        switch ($attributes['type']) {
            case 'm' :
                $electionMethod = 'runSingleMajorityElection';
                break;
            case 'd1' :
                $electionMethod = 'runSingleDelegationElectionVersion1';
                break;
            case 'd2' :
                $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion2' : 'runSingleDelegationElectionVersion1';
                break;
            case 'd3' :
                $electionMethod =  $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion3' : 'runSingleDelegationElectionVersion1';
                break;
            default :
                return response()->json('unknown election type', Response::HTTP_UNPROCESSABLE_ENTITY);
        }


        $data->population_name = $existingPopulation->name;

        $data->forgetting_factor = $existingPopulation->forgetting_factor;
        $forgettingModifier = 1 - (0.01 * $existingPopulation->forgetting_factor);
        $data->follower_factor = $existingPopulation->follower_factor;
        $data->number_of_elections = $numberOfElections;
        $data->elections_type = $attributes['type'];

        $elections = array();
        for( $i = 0; $i < $numberOfElections; $i++) {
            $singleElectionStats = $this->$electionMethod($existingPopulation, $forgettingModifier, $data->follower_factor);
            $elections[] = $singleElectionStats;
        }

        if(!$omitElectionDetails) {
            $data->elections = $elections;
        }

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

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

    /**
     * Performance stage
     * No changes to voters' attributes
     *
     * @param Population $population
     * @param $forgettingModifier
     * @param $followerFactor
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleDelegationElectionVersion1(Population $population, $forgettingModifier, $followerFactor) {
        return $this->runSingleDelegationElection($population, false, 'd1', false, $forgettingModifier, $followerFactor);
    }

    /**
     * Learning stage.
     * Modify Reputation only after each election
     *
     * @param Population $population
     * @param $forgettingModifier
     * @param $followerFactor
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleDelegationElectionVersion2 (Population $population, $forgettingModifier, $followerFactor) {
        return $this->runSingleDelegationElection($population, true, 'd2', false, $forgettingModifier, $followerFactor);
    }

    /**
     * Learning stage.
     * Modify Reputation, Following and Leadership after each election
     *
     * @param Population $population
     * @param $forgettingModifier
     * @param $followerFactor
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleDelegationElectionVersion3 (Population $population, $forgettingModifier, $followerFactor) {
        return $this->runSingleDelegationElection($population, true, 'd3', true, $forgettingModifier, $followerFactor);
    }

    /**
     * Run delegation election, default in performance stage (d1)
     * @param Population $population
     * @param bool $modifyReputation
     * @param string $type
     * @param bool $modifyAttributes
     * @param $forgettingModifier
     * @param $followerFactor
     * @return \stdClass
     * @throws \Exception
     */
    private function runSingleDelegationElection(
        Population $population,
        $modifyReputation = false,
        $type = 'd1',
        $modifyAttributes = false,
        $forgettingModifier,
        $followerFactor
    ) {
        $startTime = microtime(true);
        $election = Election::create([
            'population_id' => $population->id,
            'type' => $type
        ]);
        $votes = array();
        $electionStats = new \stdClass();
        $electionStats->type = "d2"; // for chart
        $asDelegate = 0;
        $asFollower = 0;
        $asIndependent = 0;

        $weightedDelegatesIDs = array();

        $lastInsertedWeight = 0;
        $followersDistribution = array();

        $minValue = 1;
        $maxValue = 100;
        $correctChoices = 0;
        $incorrectChoices = 0;

        $noOfVotes = $population->voters->count();
        $electionStats->no_of_votes = $noOfVotes;

        $followersVotes = [];

        $votingWeightA = 0;
        $votingWeightB = 0;
        $sumReputationA = 0;
        $sumReputationB = 0;

        foreach ($population->voters as $voter) {
            $voterWeight = $voter->reputation > 0 ? $voter->reputation : 1;
            if ($voter->group == "A") {
                $votingWeightA += $voterWeight;
                $sumReputationA += $voter->reputation;
            } else {
                $votingWeightB += $voterWeight;
                $sumReputationB+= $voter->reputation;
            }
            $tresholdFollowing = $voter->following;
            $tresholdIndependent = $tresholdFollowing + $voter->confidence;
            $tresholdLeadership = $tresholdIndependent + $voter->leadership;

            $randomBehaviour = random_int(0, $tresholdLeadership);

            $randomExpertise = random_int($minValue, $maxValue);
            $voteDirect = $randomExpertise <= $voter->expertise;
            $voteDirect ? $correctChoices++ : $incorrectChoices++;

            $newVote = [
                'election_id' => $election->id,
                'voter_id' => $voter->id,
                'vote_direct' => $voteDirect,
                'vote_delegation' => null,
                'vote_weight' => 1,
                'vote_final' => $voteDirect
            ];

            if ($randomBehaviour <= $tresholdFollowing) {
                $newVote['voter_mark'] = 'f';
                $asFollower++;
                $followersVotes[$voter->id] = $newVote;
            } elseif (($randomBehaviour <= $tresholdIndependent)) {
                $newVote['voter_mark'] =  'i';
                $asIndependent++;
                $votes[$voter->id] = $newVote;
            } else {
                $newVote['voter_mark'] =  'd';
                $asDelegate++;
                $reputationWeight = $voter->reputation > 0 ? $voter->reputation : 1;    // minimum weight = 1
                $weightedDelegatesIDs[$voter->id] = $lastInsertedWeight + $reputationWeight;
                $lastInsertedWeight = $weightedDelegatesIDs[$voter->id];
                $followersDistribution[$voter->id] = 0;
                $votes[$voter->id] = $newVote;
            }
        }

        $electionStats->initial_correct = $correctChoices;
        $electionStats->initial_incorrect = $incorrectChoices;

        $prepareTime = microtime(true);

        DelegationOneVote::insert($votes);

        $delegatesSavedVotes = array();

        // replace own expertise test if follower and there are delegates
        if ($asDelegate > 0) {

            $savedVotes = DelegationOneVote::where('election_id', '=', $election->id)
                ->where('voter_mark', '=', 'd')
                ->get();

            foreach ($savedVotes as $savedVote) {
                $delegatesSavedVotes[$savedVote->voter_id] = $savedVote;
            }

            foreach ($followersVotes as $key => $item) {
                if($item['voter_mark'] == 'f' ) {
                    // choose delegate
                    $randomDelegation = random_int(1, $lastInsertedWeight);
                    $delegateID = $this->findDelegateID($weightedDelegatesIDs, $randomDelegation);

                    $followersVotes[$key]['vote_delegation'] = $delegatesSavedVotes[$delegateID]->id;//$delegateID;
                    $followersDistribution[$delegateID]++;
                    // revert expertise choice
                    $item['vote_direct'] ? $correctChoices-- : $incorrectChoices--;
                    //$followersVotes[$key]['vote_direct'] = null; // keep track of own expertise choice
                    $followersVotes[$key]['vote_weight'] = 0;
                    // add delegate choice
                    $delegatesSavedVotes[$delegateID]->vote_weight++;
                    $delegatesSavedVotes[$delegateID]->vote_direct ? $correctChoices++ : $incorrectChoices++;
                    $followersVotes[$key]['vote_final'] = $delegatesSavedVotes[$delegateID]->vote_final;
                }
            }

        }

        // Adjust attributes of all voters (skip leadership/following adjustments)
        // if in learning stage
        if ($population->stage === 'l') {
            foreach ($population->voters as $voter) {
                $previousReputation = $voter->reputation;
                $voterID = $voter->id;
                if (isset($delegatesSavedVotes[$voterID])) {
                    // is delegate
                    $noOfFollowers = $followersDistribution[$delegateID];
                    if ($noOfFollowers > 0) {
                        // save weight adjustments for delegate's vote
                        $delegatesSavedVotes[$voterID]->save();
                        // adjust voter reputation
                        if ($delegatesSavedVotes[$voterID]->vote_final > 0) {
                            $voter->reputation += ($followerFactor * $noOfFollowers);
                            if($modifyAttributes && $voter->leadership < 100)
                                $voter->leadership++;
                        } else {
                            $voter->reputation -= ($followerFactor * $noOfFollowers);
                            if($modifyAttributes && $voter->leadership > 1)
                                $voter->leadership--;
                        }
                    } else {
                        if($modifyAttributes && $voter->leadership > 1)
                            $voter->leadership--;
                    }
                }  elseif ($modifyAttributes && isset($followersVotes[$voterID])) {
                    // is follower
                    if ($followersVotes[$voterID]['vote_final']) {
                        if (!$followersVotes[$voterID]['vote_direct']) {
                            if($voter->following < 100)
                                $voter->following++;
                        }
                    } elseif ($followersVotes[$voterID]['vote_direct']) {
                        if($voter->following > 1)
                            $voter->following--;
                    }
                }

                // balance voter reputation over time between elections
                $voter->reputation = round($voter->reputation * $forgettingModifier);
                /*if ($voter->reputation < 0) {
                    $voter->reputation++;
                } elseif ($voter->reputation > 0) {
                    $voter->reputation--;
                }*/

                if ($previousReputation != $voter->reputation) {
                    $voter->save();
                }
            }
        }


        $election->total_correct = $correctChoices;
        $election->total_incorrect = $incorrectChoices;

        $electionStats->total_correct_choices = $correctChoices;
        $electionStats->total_incorrect_choices = $incorrectChoices;

        if ($noOfVotes > 0) {
            $electionStats->percent_initial_correct_choices = 100 * $electionStats->initial_correct / $noOfVotes;
            $electionStats->percent_correct_choices = 100 * $election->total_correct / $noOfVotes;
        } else {
            $electionStats->percent_initial_correct_choices = null;
            $electionStats->percent_correct_choices = null;
        }

        $votesTime = microtime(true);

        $election->save();

        $electionExtension = ExtensionDelegationElection::create([
            'election_id'       => $election->id,
            'initial_correct'   => $electionStats->initial_correct,
            'initial_incorrect' => $electionStats->initial_incorrect,
            'as_delegate'       => $asDelegate,
            'as_follower'       => $asFollower,
            'as_independent'    => $asIndependent,
            'weight_a'          => $votingWeightA,
            'weight_b'          => $votingWeightB,
            'reputation_a'      => $sumReputationA,
            'reputation_b'      => $sumReputationB
        ]);

        DelegationOneVote::insert($followersVotes);

        $databaseTime = microtime(true);

        $electionStats->votes_time = round($votesTime - $startTime, 5);
        $electionStats->votes_db_time = round($databaseTime - $votesTime, 5);

        $electionStats->as_delegate = $asDelegate;
        $electionStats->as_follower = $asFollower;
        $electionStats->as_independent = $asIndependent;

        $electionStats->delegates = array_keys($weightedDelegatesIDs);
        $electionStats->cumulative_delegates_reputation = $weightedDelegatesIDs;
        $electionStats->followers_distribution = $followersDistribution;
        $electionStats->data = array_values($votes);

        return $electionStats;
    }



    private function findDelegateID(array $weightedDelegatesIDs, int $randomDelegation) {
        foreach ($weightedDelegatesIDs as $id => $cumulativeReputation) {
            if ($randomDelegation <= $cumulativeReputation) {
                return $id;
            }
        }
        return null;
    }

    public function getVoterStats(int $template, int $population, $voter) {
        $data = Voter::where('id', '=', $voter)
            ->with('delegationOneVotes.parentDelegationOneVote')
            ->firstOrFail()
            ->makeHidden('delegationOneVotes')
        ;

        return response()->json($data,200);
    }

    public function getVotersStats(int $template, int $population) {

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

        $population->voters->makeHidden('delegationOneVotes');

        return response()->json($population->voters,200);
    }

    public function getElectionsTimeline (int $template, Population $population, Request $request) {
        $data = new \stdClass();

        try {
            $attributes = $request->validate([
                'type' => 'required|string|in:m,d1,d2,d3',
                'moving_average' => 'nullable|integer'
            ]);
        } catch (ValidationException $e) {
            return response()->json(['error' => 'Unknown election type'], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        $movingAverage = isset($attributes['moving_average']) ? $attributes['moving_average'] : 0;

        $data->elections_type = $attributes['type'];
        $data->no_of_voters = $population->noOfVoters;

        if ($data->no_of_voters < 1) {
            return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
        }

        $elections = Election::where('population_id', '=', $population->id)
            ->where('type', '=', $attributes['type'])
            ->select(
                DB::raw(sprintf('100 * total_correct / %d AS percent', $data->no_of_voters))
            )
            ->pluck('percent');

        $data->no_of_elections = $elections->count();
        $data->moving_average = $movingAverage;

        if($movingAverage > 0) {
            $flattenData = [];
            if ($data->no_of_elections >= $movingAverage) {
                $asArray = $elections->toArray();
                for ($i = $movingAverage - 1; $i < $data->no_of_elections; $i++) {
                    $sumOfValues = 0;
                    for ($j = 0; $j < $movingAverage; $j++) {
                        $sumOfValues += $asArray[$i - $j];
                    }
                    $flattenData[] = $sumOfValues / $movingAverage;
                }
            }
            $data->elections = $flattenData;
        } else {
            $data->elections = $elections;
        }

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

    public function getWeightsTimeline (int $template, Population $population, Request $request) {
        $data = new \stdClass();

        try {
            $attributes = $request->validate([
                'type' => 'required|string|in:d2,d3',
                'moving_average' => 'nullable|integer'
            ]);
        } catch (ValidationException $e) {
            return response()->json(['error' => 'Unknown election type'], Response::HTTP_UNPROCESSABLE_ENTITY);
        }

        $movingAverage = isset($attributes['moving_average']) ? $attributes['moving_average'] : 0;

        $data->elections_type = $attributes['type'];

        $countA = $population->voters()->where('group', '=', 'A')->count();
        $countB = $population->voters()->where('group', '=', 'B')->count();

        $data->no_of_voters = $countA + $countB;

        if ($data->no_of_voters < 1) {
            return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
        }

        $data->no_of_voters_a = $countA;
        $data->no_of_voters_b = $countB;

        $elections = Election::where('population_id', '=', $population->id)
            ->where('type', '=', $attributes['type'])
            ->with('extension')->get();

        $data->no_of_elections = $elections->count();
        $data->moving_average = $movingAverage;

        $weightsTimeline = array();
        foreach ($elections as $election) {
            $weightSum = $election->extension->weight_a + $election->extension->weight_b;
            $newWeight = new \stdClass();
            if ($countA > 0) {
                $newWeight->reputation_a = $election->extension->reputation_a;
                $newWeight->avg_weight_a = $election->extension->weight_a / $countA;
            } else {
                $newWeight->reputation_a = 0;
                $newWeight->avg_weight_a = 0;
            }
            if ($countB > 0) {
                $newWeight->reputation_b = $election->extension->reputation_b;
                $newWeight->avg_weight_b = $election->extension->weight_b / $countB;
                //$newWeight->weight_share_b = 100 * $newWeight->avg_weight_b / ($newWeight->avg_weight_a + $newWeight->avg_weight_b);
                $newWeight->weight_share_b = 100 * $election->extension->weight_b / ($election->extension->weight_a + $election->extension->weight_b);
            } else {
                $newWeight->reputation_b = 0;
                $newWeight->avg_weight_b = 0;
                $newWeight->weight_share_b = 0;
            }
            array_push($weightsTimeline, $newWeight);
        }
        $data->weights = $weightsTimeline;
/*
        if($movingAverage > 0) {
            $flattenData = [];
            if ($data->no_of_elections >= $movingAverage) {
                $asArray = $elections->toArray();
                for ($i = $movingAverage - 1; $i < $data->no_of_elections; $i++) {
                    $sumOfValues = 0;
                    for ($j = 0; $j < $movingAverage; $j++) {
                        $sumOfValues += $asArray[$i - $j];
                    }
                    $flattenData[] = $sumOfValues / $movingAverage;
                }
            }
            $data->elections = $flattenData;
        } else {
            $data->elections = $elections;
        }*/

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

    public function getTemplateWeightsStats(int $template, Request $request) {
        $startTime = microtime(true);
        $metadata = new \stdClass();

        try {
            $attributes = $request->validate([
                'election_type' => 'required|string|in:d2,d3'
            ]);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Incorrect payload',
                'val_errors' => $e->errors()
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        $type = $attributes['election_type'];
        $metadata->election_type = $type;
        $templatePopulation = Population::where('id', '=', $template)
            //->whereNull('parent_id')
            ->with(['childPopulations' => function ($q) use ($type) {
                $q->where('election_type', '=', $type)
                    ->with('elections.extension');
            }])
            ->firstOrFail()
            ->makeHidden('childPopulations');

        $templatePopulation->report_metadata = $metadata;

        $countA = $templatePopulation->voters()->where('group', '=', 'A')->count();
        $countB = $templatePopulation->voters()->where('group', '=', 'B')->count();

        $templatePopulation->no_of_voters = $countA + $countB;

        if ($templatePopulation->no_of_voters < 1) {
            return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
        }

        $templatePopulation->no_of_voters_a = $countA;
        $templatePopulation->no_of_voters_b = $countB;
        $templatePopulation->no_of_child_populations = $templatePopulation->childPopulations->count();

        $first = true;
        $minElectionCount = 0;
        $maxElectionCount = 0;
        $childPopulationCount = 0;

        foreach ($templatePopulation->childPopulations as $childPopulation) {
            $childPopulationCount++;
            $electionCount = $childPopulation->elections()->count();
            if ($first) {
                $minElectionCount = $electionCount;
                $maxElectionCount = $electionCount;
                $first = false;
            } else {
                if ($electionCount > $maxElectionCount) {
                    $maxElectionCount = $electionCount;
                }
                if ($electionCount < $minElectionCount) {
                    $minElectionCount = $electionCount;
                }
            }
            $childPopulation->elections_count = $electionCount;
            $childPopulation->makeHidden('elections');
        }
        $templatePopulation->min_election_count = $minElectionCount;
        $templatePopulation->max_election_count = $maxElectionCount;

        // init weight counters
        $weightsSequencesA = array();
        $weightsSequencesB = array();
        $weightShareSequenceB = array();
        $minWeightShareSequenceB = array();
        $maxWeightShareSequenceB = array();

        $minWeightsSequencesA = array();
        $minWeightsSequencesB = array();
        $maxWeightsSequencesA = array();
        $maxWeightsSequencesB = array();

        for ($i = 0; $i < $minElectionCount; $i++) {
            $weightsSequencesA[$i] = 0;
            $weightsSequencesB[$i] = 0;
        }

        $first = true;
        foreach ($templatePopulation->childPopulations as $childPopulation) {
            $childPopulation->exceeding_elections = $childPopulation->elections_count - $minElectionCount;

            if ($first) {
                for ($i = 0; $i < $minElectionCount; $i++) {
                    $weightA = $childPopulation->elections[$i]->extension->weight_a;
                    $weightB = $childPopulation->elections[$i]->extension->weight_b;
                    $weightShareB = $weightB / ($weightA + $weightB);
                    $weightsSequencesA[$i] = $weightA;
                    $weightsSequencesB[$i] = $weightB;
                    $minWeightsSequencesA[$i] = $weightA;
                    $minWeightsSequencesB[$i] = $weightB;
                    $maxWeightsSequencesA[$i] = $weightA;
                    $maxWeightsSequencesB[$i] = $weightB;
                    $weightShareSequenceB[$i] = $weightShareB;
                    $minWeightShareSequenceB[$i] = $weightShareB;
                    $maxWeightShareSequenceB[$i] = $weightShareB;
                }
                $first = false;
                continue;
            }
            for ($i = 0; $i < $minElectionCount; $i++) {
                $weightA = $childPopulation->elections[$i]->extension->weight_a;
                $weightB = $childPopulation->elections[$i]->extension->weight_b;
                $weightShareB = $weightB / ($weightA + $weightB);
                $weightsSequencesA[$i] += $weightA;
                $weightsSequencesB[$i] += $weightB;
                $weightShareSequenceB[$i] += $weightShareB;
                if ($minWeightsSequencesA[$i] > $weightA) {
                    $minWeightsSequencesA[$i] = $weightA;
                }
                if ($minWeightsSequencesB[$i] > $weightB) {
                    $minWeightsSequencesB[$i] = $weightB;
                }
                if ($weightA > $maxWeightsSequencesA[$i]) {
                    $maxWeightsSequencesA[$i] = $weightA;
                }
                if ($weightB > $maxWeightsSequencesB[$i]) {
                    $maxWeightsSequencesB[$i] = $weightB;
                }
                if ($weightShareB > $maxWeightShareSequenceB[$i]) {
                    $maxWeightShareSequenceB[$i] = $weightShareB;
                }
                if ($minWeightShareSequenceB[$i] > $weightShareB) {
                    $minWeightShareSequenceB[$i] = $weightShareB;
                }
            }
        }

        $weightsAvgTimeline = array();
        //$votersFactorA = $templatePopulation->no_of_child_populations * $countA;
        //$votersFactorB = $templatePopulation->no_of_child_populations * $countB;

        $previousWeightShareB = 0;

        for ($i = 0; $i < $minElectionCount; $i++) {
            $newWeight = new \stdClass();
            $newWeight->min_sum_weight_a = $minWeightsSequencesA[$i];
            $newWeight->max_sum_weight_a = $maxWeightsSequencesA[$i];
            $newWeight->min_sum_weight_b = $minWeightsSequencesB[$i];
            $newWeight->max_sum_weight_b = $maxWeightsSequencesB[$i];
            //$newWeight->avg_voter_weight_a = $weightsSequencesA[$i] / $votersFactorA;
            //$newWeight->avg_voter_weight_b = $weightsSequencesB[$i] / $votersFactorB;
            $newWeight->avg_sum_weight_a = $weightsSequencesA[$i] / $templatePopulation->no_of_child_populations;
            $newWeight->avg_sum_weight_b = $weightsSequencesB[$i] / $templatePopulation->no_of_child_populations;
            $newWeight->share_sum_weight_b = $weightShareSequenceB[$i] / $templatePopulation->no_of_child_populations;
            $newWeight->min_share_sum_weight_b = $minWeightShareSequenceB[$i];
            $newWeight->max_share_sum_weight_b = $maxWeightShareSequenceB[$i];
            $shareDiff = $newWeight->share_sum_weight_b - $previousWeightShareB;
            $newWeight->diff_share_sum_weight_b = $shareDiff > 0 ? $shareDiff : -$shareDiff;
            $previousWeightShareB = $newWeight->share_sum_weight_b;

            array_push($weightsAvgTimeline, $newWeight);
        }
        $templatePopulation->weights = $weightsAvgTimeline;

        $endTime = microtime(true);
        $metadata->process_time = round($endTime - $startTime, 5);

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


    public function fetchAllPerformanceModeElections(int $template, Request $request) {
        $startTime = microtime(true);
        $metadata = new \stdClass();

        $templatePopulation = Population::where('id', '=', $template)
            //->whereNull('parent_id')
            ->with(['childPopulations' => function ($q){
                $q->where('stage', '=', 'p')
                    ->with(['elections' => function ($q1) {
                        $q1->where('type', '=', 'd1')
                            ->with('extension');
                    }]);
            }])
            ->firstOrFail()
            ->makeHidden('childPopulations');

        $d2TypesCount = 0;
        $d2Correct = 0;
        $d2Incorrect = 0;
        $d3TypesCount = 0;
        $d3Correct = 0;
        $d3Incorrect = 0;

        foreach ($templatePopulation->childPopulations as $childPopulation) {
            $electionCount = $childPopulation->elections->count();
            $totalCorrect = $childPopulation->elections->sum('total_correct');
            $totalIncorrect = $childPopulation->elections->sum('total_incorrect');
            if ($childPopulation->election_type == 'd2') {
                $d2TypesCount += $electionCount;
                $d2Correct += $totalCorrect;
                $d2Incorrect += $totalIncorrect;
            } elseif($childPopulation->election_type == 'd2') {
                $d3TypesCount += $electionCount;
                $d3Correct += $totalCorrect;
                $d3Incorrect += $totalIncorrect;
            }
        }

        $response = new \stdClass();
        $response->report_metadata = $metadata;

        $response->results = array();

        $d2Votes = $d2Correct + $d2Incorrect;

        if ($d2Votes > 0) {
            $d2Stats = new \stdClass();
            $d2Stats->election_type = 'd2';
            $d2Stats->election_count = $d2TypesCount;
            $d2Stats->correct = $d2Correct;
            $d2Stats->incorrect = $d2Incorrect;
            $d2Stats->percent_correct = 100 * $d2Correct / $d2Votes;
            $response->results['d2'] = $d2Stats;
        } else {
            $response->results['d2'] = null;
        }

        $d3Votes = $d3Correct + $d3Incorrect;

        if ($d3Votes > 0) {
            $d3Stats = new \stdClass();
            $d3Stats->election_type = 'd3';
            $d3Stats->election_count = $d3TypesCount;
            $d3Stats->correct = $d3Correct;
            $d3Stats->incorrect = $d3Incorrect;
            $d3Stats->percent_correct = 100 * $d2Correct / $d3Votes;
            $response->results['d3'] = $d3Stats;
        } else {
            $response->results['d3'] = null;
        }

        $endTime = microtime(true);
        $metadata->process_time = round($endTime - $startTime, 5);

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

    /**
     * Gets all performance mode results for d1,d2,d3
     *
     * @param int $template
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function getTemplateElectionResultStats(int $template, Request $request) {
        $startTime = microtime(true);
        $metadata = new \stdClass();

        try {
            $attributes = $request->validate([
                'election_type' => 'required|string|in:d1,d2,d3'
            ]);
        } catch (ValidationException $e) {
            return response()->json([
                'message' => 'Incorrect payload',
                'val_errors' => $e->errors()
            ], Response::HTTP_UNPROCESSABLE_ENTITY);
        }
        $type = $attributes['election_type'];
        $metadata->election_type = $type;

        $templatePopulation = Population::where('id', '=', $template)
            //->whereNull('parent_id')
            ->with(['childPopulations' => function ($q) use ($type) {
                $q->where('election_type', '=', $type)
                    ->where('stage', '=', 'p')
                    ->with(['elections' => function ($q1) {
                        $q1->where('type', '=', 'd1')
                            ->with('extension');
                    }]);
            }])
            ->firstOrFail()
            ->makeHidden('childPopulations');

        $templatePopulation->report_metadata = $metadata;

        $countA = $templatePopulation->voters()->where('group', '=', 'A')->count();
        $countB = $templatePopulation->voters()->where('group', '=', 'B')->count();

        $templatePopulation->no_of_voters = $countA + $countB;

        $templatePopulation->avg_expertise = $templatePopulation->voters()->average('expertise');
        $templatePopulation->max_expertise = $templatePopulation->voters()->max('expertise');

        if ($templatePopulation->no_of_voters < 1) {
            return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
        }

        $templatePopulation->no_of_voters_a = $countA;
        $templatePopulation->no_of_voters_b = $countB;
        $templatePopulation->no_of_child_populations = $templatePopulation->childPopulations->count();

        $first = true;
        $minElectionCount = 0;
        $maxElectionCount = 0;
        $childPopulationCount = 0;

        $noumberOfVoters = $templatePopulation->no_of_voters;
        $electionResultsGathered = array();

        foreach ($templatePopulation->childPopulations as $childPopulation) {
            $childPopulationCount++;
            $electionCount = $childPopulation->elections->count();

            if ($first) {
                $minElectionCount = $electionCount;
                $maxElectionCount = $electionCount;
                $first = false;
            } else {
                if ($electionCount > $maxElectionCount) {
                    $maxElectionCount = $electionCount;
                }
                if ($electionCount < $minElectionCount) {
                    $minElectionCount = $electionCount;
                }
            }
            $childPopulation->elections_count = $electionCount;
            $childPopulation->makeHidden('elections');

            foreach ($childPopulation->elections as $election) {
                $correct = $election->total_correct;
                $correctShare = $correct / $noumberOfVoters;
                $correctPercent = 100 * $correctShare;
                array_push($electionResultsGathered, $correctPercent);
            }

        }
        $templatePopulation->min_election_count = $minElectionCount;
        $templatePopulation->max_election_count = $maxElectionCount;

        sort($electionResultsGathered);
        $templatePopulation->all_elections_gathered = $electionResultsGathered;

        $logPath = 'election_logs/' . $templatePopulation->id  . '_' . time() . '.json';

        Storage::disk('local')->put($logPath, json_encode($electionResultsGathered));

        $endTime = microtime(true);
        $metadata->process_time = round($endTime - $startTime, 5);

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


        $electionResults = array();
        $electionResultsDetails = array();
        $electionResultsGathered = array();

        for ($i = 0; $i < $minElectionCount; $i++) {
            $electionResults[$i] = 0;
        }

        $distributionGathered = array();
        $distributionGatheredRounded5 = array();
        $distributionGatheredRounded10 = array();

        for($i=0;$i<101;$i++){
            $distributionGathered[$i] = 0;
        }
        for($i=0;$i<21;$i++){
            $distributionGatheredRounded5[$i] = 0;
        }
        for($i=0;$i<11;$i++){
            $distributionGatheredRounded10[$i] = 0;
        }

        foreach ($templatePopulation->childPopulations as $childPopulation) {
            $childPopulation->exceeding_elections = $childPopulation->elections_count - $minElectionCount;
            $childResults = array();

            for ($i = 0; $i < $minElectionCount; $i++) {
                $correct = $childPopulation->elections[$i]->total_correct;
                $correctShare = $correct / $templatePopulation->no_of_voters;
                $correctPercent = 100 * $correctShare;
                $electionResults[$i] += $correctPercent;
                array_push($childResults, $correctPercent);
                array_push($electionResultsGathered, $correctPercent);
                $distributionGathered[floor($correctPercent)]++;
                $distributionGatheredRounded5[floor(20 * $correctShare)]++;
                $distributionGatheredRounded10[floor(10 * $correctShare)]++;
            }
            array_push($electionResultsDetails, $childResults);
        }
/*
        $electionsAvgTimeline = array();
        for ($i = 0; $i < $minElectionCount; $i++) {
            $newResult = $electionResults[$i] / $templatePopulation->no_of_child_populations;
            array_push($electionsAvgTimeline, $newResult);
        }
*/
        //sort($electionsAvgTimeline);
        //$templatePopulation->elections_avg = $electionsAvgTimeline;
        //$templatePopulation->elections_detailed = $electionResultsDetails;
        sort($electionResultsGathered);
        $templatePopulation->all_elections_gathered = $electionResultsGathered;
        //$templatePopulation->elections_gathered_distribution = $distributionGathered;
        //$templatePopulation->elections_gathered_distribution_5 = $distributionGatheredRounded5;
        //$templatePopulation->elections_gathered_distribution_10 = $distributionGatheredRounded10;





        $endTime = microtime(true);
        $metadata->process_time = round($endTime - $startTime, 5);

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