<?php

namespace App\Http\Controllers;

use DateTime;
use App\Mail\WeeklyDigest;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ConnectException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;

class TrackerController extends Controller
{
    /**
     * Show the list of past trips.
     * TODO: Get these from the Wayward API rather than hard-coding them.
     *
     * @return \Illuminate\View\View
     */
    public function show_past_trips_list()
    {
        return view('past-trips');
    }

    /**
     * Show a trip in tracker app.
     *
     * @param Request $request
     * @param string? $tripId
     * @return \Illuminate\View\View
     */
    public function show_trip(Request $request, string $tripId = null)
    {
        $tripId = $tripId ?? config('app.current_trip_id');
        if (!$tripId) {
            return view('no-trip');
        }

        $viewMode = $request->input('show', null);
        $fromCheckin = $request->input('from', null);
        $toCheckin = $request->input('to', null);
        $forceDownload = $request->input('force', false);

        try {
            $tripData = $this->get_trip_data($tripId, $forceDownload);
        } catch (ConnectException) {
            return view(
                'error',
                [
                    'message' => "App timed out whilst downloading trip data" .
                    " from Wayward's servers. They may be down currently."
                ]
            );
        }

        if ($fromCheckin) {
            $tripData->checkins = array_filter(
                $tripData->checkins,
                function ($checkin) use ($fromCheckin) {
                    return $checkin->id >= $fromCheckin;
                }
            );
        }

        if ($toCheckin) {
            $tripData->checkins = array_filter(
                $tripData->checkins,
                function ($checkin) use ($toCheckin) {
                    return $checkin->id <= $toCheckin;
                }
            );
        }

        return view(
            'tracker',
            [
                'trip' => $tripData,
                'showAllCheckins' => ($viewMode === 'all'),
                'fromCheckin' => $fromCheckin,
                'toCheckin' => $toCheckin
            ]
        );
    }

    /**
     * Retrieves a trip from the local cache, or from the Wayward API.
     *
     * The retrieval algorithm is as follows:
     * 1. If no local cache exists, download the trip data
     * 2. If a local cache exists:
     *   1. If the trip is inactive (i.e., finished), use the cache
     *   2. If the trip is active:
     *     1. If the trip was actively tracking at last check, use the
     *        cache if it's less than an hour old. Otherwise, download
     *        the new trip data.
     *     2. If the trip was not actively tracking at lest check, use
     *        the cache if it's less than three hours old. Otherwise,
     *        download the new trip data.
     * 3. If new trip data is downloaded, overwrite any cached data.
     *
     * @param string $tripId
     * @param bool $forceDownload
     * @return string
     */
    public function get_trip_data(string $tripId, bool $forceDownload = false)
    {
        $tripFileName = ( $tripId ?? config('app.current_trip_id') ) . '.json';

        // Returns the cached trip data if the trip is inactive (i.e., finished)
        // or less than 1–3 hours old, depending on whether it was tracking
        // at last check or not.
        if (!$forceDownload && Storage::disk('local')->exists($tripFileName)) {
            Log::debug("Cached trip file '{$tripFileName}' found...");
            $cachedData = json_decode(Storage::disk('local')->get($tripFileName))->trip;

            if ($cachedData->is_active) {
                Log::debug("Cached trip file '{$tripFileName}' is for an active trip.");

                $cachedDataUpdatedAt = new DateTime($cachedData->updated_at);
                $now = new DateTime();
                $cachingTimeout = ($cachedData->is_tracking) ? 1 : 3;
                $cachedDataAge = intval(($now->getTimestamp() - $cachedDataUpdatedAt->getTimestamp()) / 3600);

                if ($cachedDataAge <= $cachingTimeout) {
                    Log::debug(
                        "Cached trip file '" .
                        $tripFileName .
                        "' is younger than " .
                        $cachingTimeout .
                        " hours, showing from cache..."
                    );
                    return $cachedData;
                }
            } else {
                Log::debug(
                    "Cached trip file '" .
                    $tripFileName .
                    "' is for an old trip, showing from cache..."
                );
                return $cachedData;
            }
        }

        // Otherwise, download the trip data from the Wayward API.
        if ($forceDownload) {
            Log::debug("Forcing download for '{$tripFileName}'.");
            Storage::disk('local')->delete($tripFileName);
        } else {
            Log::debug("No cached trip file found for '{$tripFileName}'.");
        }

        $client = new Client([
            'base_uri' => 'https://app.wayward.travel/',
            'timeout' => 20.0
        ]);

        $response = $client->get('trip/'.($tripId ?? config('app.current_trip_id')).'/user/zmld8ko6qy7d9j3xvq10/json');

        switch ($response->getStatusCode()) {
            case 200:
                $data = json_decode($response->getBody());

                $data->trip->locations = $this->decode_polyline($data->trip->route);

                // Cache the downloaded file if it does not exist locally.
                if (Storage::disk('local')->missing($tripFileName)) {
                    Log::debug("Caching new trip file '{$tripFileName}'.");
                    Storage::disk('local')->put($tripFileName, json_encode($data));
                } else {
                    $cachedData = json_decode(Storage::disk('local')->get($tripFileName));
                    if (end($data->trip->checkins)->date !== end($cachedData->trip->checkins)->date) {
                        Log::debug(
                            "Cached trip file '" .
                            $tripFileName .
                            "' has different 'updated_at' time, updating cache..."
                        );
                        Storage::disk('local')->put($tripFileName, json_encode($data));
                        // TODO: Cache photos locally
                    } else {
                        Log::debug(
                            "Cached trip file '" .
                            $tripFileName .
                            "' has same 'updated_at' time, showing from cache..."
                        );
                        $data = $cachedData;
                    }
                }

                return $data->trip;
            default:
        }
    }

    /*
     * Decode route polyline.
     *
     * Source: https://github.com/dyaaj/polyline-encoder
     *
     * @param object $value
     * @return object
     */
    public function decode_polyline($value)
    {
        $index = 0;
        $points = array();
        $lat = 0;
        $lng = 0;
        $id = 0;

        while ($index < strlen($value)) {
            $b;
            $shift = 0;
            $result = 0;
            do {
                $b = ord(substr($value, $index++, 1)) - 63;
                $result |= ($b & 0x1f) << $shift;
                $shift += 5;
            } while ($b > 31);
            $dlat = (($result & 1) ? ~($result >> 1) : ($result >> 1));
            $lat += $dlat;

            $shift = 0;
            $result = 0;
            do {
                $b = ord(substr($value, $index++, 1)) - 63;
                $result |= ($b & 0x1f) << $shift;
                $shift += 5;
            } while ($b > 31);
            $dlng = (($result & 1) ? ~($result >> 1) : ($result >> 1));
            $lng += $dlng;

            array_push($points, (object)[
                'id' => $id++,
                'latitude' => $lat/100000,
                'longitude' => $lng/100000
            ]);
        }

        return $points;
    }
}