| 
<?php/*
 * Copyright (c) 2017 @meet-aleksey <https://github.com/meet-aleksey>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
 
 if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
 Error('POST request method is expected.');
 return;
 }
 
 // get request body:
 $requestBody = file_get_contents('php://input');
 
 // check type of content:
 if (strrpos($_SERVER['HTTP_CONTENT_TYPE'], '/json') !== FALSE || strrpos($_SERVER['CONTENT_TYPE'], '/json') !== FALSE) {
 // json:
 $query = json_decode($requestBody, true);
 } else {
 // file:
 $query = array('method' => 'upload');
 }
 
 if (empty($query['method'])) {
 Error('The "method" is required. Value cannot be empty.');
 return;
 }
 
 require_once 'team.php';
 require_once 'match.php';
 require_once 'stage.php';
 
 switch (strtolower($query['method'])) {
 case 'example':
 // return example data:
 OutputExampleData();
 break;
 
 case 'simulate':
 // return results of simulation:
 OutputSimulationResult($query['teams']);
 break;
 
 case 'upload':
 OutputData($requestBody);
 break;
 
 default:
 Error('Unknown method.');
 break;
 }
 
 /**
 * Gets data from data.csv.
 *
 * @return void
 */
 function OutputExampleData() {
 if (($data = file_get_contents('data.csv')) !== FALSE) {
 OutputData($data);
 }
 else {
 Error('Cannot open file data.csv. Check the existence of "data.csv" and the permission to read it.');
 }
 }
 
 /**
 * Outputs the specified data.
 *
 * @param string $data
 *   CSV data to parse and output.
 *
 * @return void
 */
 function OutputData($data) {
 if (empty($data)) {
 Error('Data is required.');
 return;
 }
 
 $skip = TRUE;
 $teams = array();
 
 $total = new Team();
 $total->Name = 'Total';
 $total->Games = $total->Goals = $total->Losses =
 $total->Missed = $total->Draw = $total->Wins = 0;
 
 $rows = split("\n", preg_replace('~\R~u', "\n", $data));
 
 foreach ($rows as $row) {
 if (empty($row)) {
 continue;
 }
 
 $row = str_getcsv($row, ';');
 
 if (count($row) < 6) {
 Error('Wrong data. Please check the file format.');
 return;
 }
 
 if ($skip) {
 $skip = FALSE;
 continue;
 }
 
 $team = new Team();
 
 $team->Name = trim($row[0]);
 $total->Games += ($team->Games = (int)$row[1]);
 $total->Wins += ($team->Wins = (int)$row[2]);
 $total->Losses += ($team->Losses = (int)$row[3]);
 $total->Draw += ($team->Draw = (int)$row[4]);
 
 $balls = split('-', $row[5]);
 
 $total->Goals += ($team->Goals = (int)trim($balls[0]));
 $total->Missed += ($team->Missed = (int)trim($balls[1]));
 
 // add team to list
 $teams[] = $team;
 }
 
 // add total results
 $teams[] = $total;
 
 Output($teams);
 }
 
 /**
 * Outputs simulations result.
 *
 * @param array $teams
 *   List of teams.
 *
 * @return void
 */
 function OutputSimulationResult($teams) {
 $result = array();
 
 Play($result, $teams);
 Output($result);
 }
 
 /**
 * Simulates a stage of football matches.
 *
 * @param Stage[] &$writer
 *   List of stages for output.
 *
 * @param array $teams
 *   List of teams.
 *
 * @param int $stageNumber
 *   Stage number. Default: 1.
 *
 * @return void
 */
 function Play(&$writer, $teams, $stageNumber = 1) {
 if (!isset($stageNumber)) {
 $stageNumber = 1;
 }
 
 // create new stage:
 $writer[] = $stage = new Stage();
 $stage->Matches = array();
 
 // teams count:
 $totalTeams = count($teams);
 
 // mix the array:
 shuffle($teams);
 
 // losers to remove:
 $losers = array();
 
 // simulate
 for ($i = 0; $i < $totalTeams; $i += 2) {
 $goalsAvg = array(
 $teams[$i]['Goals'] / $teams[$i]['Games'],
 $teams[$i + 1]['Goals'] / $teams[$i + 1]['Games']
 );
 $missedAvg = array(
 $teams[$i]['Missed'] / $teams[$i]['Games'],
 $teams[$i + 1]['Missed'] / $teams[$i + 1]['Games']
 );
 
 $stage->Matches[] = $match = new Match();
 
 $match->Teams = array($teams[$i]['Name'], $teams[$i + 1]['Name']);
 $match->Results = array(
 Poisson(
 ($goalsAvg[0] / $missedAvg[0]) * ($missedAvg[0] / $goalsAvg[0]) * $goalsAvg[0],
 ($goalsAvg[1] / $missedAvg[1]) * ($missedAvg[1] / $goalsAvg[1]) * $goalsAvg[1]
 ),
 Poisson(
 ($goalsAvg[1] / $missedAvg[1]) * ($missedAvg[1] / $goalsAvg[1]) * $goalsAvg[1],
 ($goalsAvg[0] / $missedAvg[0]) * ($missedAvg[0] / $goalsAvg[0]) * $goalsAvg[0]
 )
 );
 
 if ($match->Results[0] > $match->Results[1]) {
 $losers[] = $teams[$i + 1]['Name'];
 }
 else if ($match->Results[1] > $match->Results[0]) {
 $losers[] = $teams[$i]['Name'];
 }
 else {
 $losers[] = $teams[rand($i, $i + 1)]['Name'];
 }
 }
 
 // names of all teams to search index by name:
 $namesOfAllTeams = array_map(function($itm) { return $itm['Name']; }, $teams);
 
 // remove losers:
 foreach ($losers as $teamName) {
 if (($index = array_search($teamName, $namesOfAllTeams)) !== FALSE) {
 unset($teams[$index]);
 }
 }
 
 // next stage:
 if (count($teams) >= 2) {
 Play($writer, $teams, $stageNumber + 1);
 }
 }
 
 /**
 * Calculates factorial.
 *
 * @param double|integer $number
 *
 * @return double|integer
 */
 function Factorial($number)
 {
 if ($number < 2) {
 return 1;
 } else {
 return ($number * Factorial($number - 1));
 }
 }
 
 /**
 * Poisson Distribution.
 *
 * @param mixed $chance
 *   The probability value.
 *
 * @param mixed $occurrence
 *   The number of occurances.
 *
 * @return double
 */
 function Poisson($chance, $occurrence)
 {
 return round((exp(-$chance) * pow($chance, $occurrence) / Factorial($occurrence)) * 100, 2);
 }
 
 /**
 * Outputs error message.
 *
 * @param string $message
 *   Text of error message.
 *
 * @return void
 */
 function Error($message) {
 Output(array('Error' => array('Text' => $message)), 500);
 }
 
 /**
 * Outputs response.
 *
 * @param mixed $data
 *   Data to output.
 *
 * @param int $status
 *   The HTTP status code. Default: 200 (OK).
 *
 * @return void
 */
 function Output($data, $status = 200) {
 header('Content-Type: application/json');
 http_response_code($status);
 
 $result = json_encode($data);
 
 if ($result === FALSE)
 {
 throw new ErrorException('JSON encode error #' . json_last_error() . ': ' . json_last_error_msg());
 }
 
 echo $result;
 }
 
 |