<?php namespace MapGuesser\Controller;

use DateTime;
use SokoWeb\Interfaces\Authentication\IUser;
use SokoWeb\Interfaces\Authentication\IAuthenticationRequired;
use SokoWeb\Interfaces\Authorization\ISecured;
use SokoWeb\Interfaces\Response\IContent;
use MapGuesser\PersistentData\Model\Challenge;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\Model\PlaceInChallenge;
use MapGuesser\Repository\ChallengeRepository;
use MapGuesser\Repository\GuessRepository;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\PlaceInChallengeRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Repository\UserInChallengeRepository;
use MapGuesser\Repository\UserPlayedPlaceRepository;
use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds;
use MapGuesser\Util\Panorama\Pov;

class MapAdminController implements IAuthenticationRequired, ISecured
{
    private static string $unnamedMapName = '[unnamed map]';

    private MapRepository $mapRepository;

    private PlaceRepository $placeRepository;

    private UserPlayedPlaceRepository $userPlayedPlaceRepository;

    private ChallengeRepository $challengeRepository;

    private GuessRepository $guessRepository;

    private PlaceInChallengeRepository $placeInChallengeRepository;

    private UserInChallengeRepository $userInChallengeRepository;

    public function __construct()
    {
        $this->mapRepository = new MapRepository();
        $this->placeRepository = new PlaceRepository();
        $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
        $this->challengeRepository = new ChallengeRepository();
        $this->guessRepository = new GuessRepository();
        $this->placeInChallengeRepository = new PlaceInChallengeRepository();
        $this->userInChallengeRepository = new UserInChallengeRepository();
    }

    public function isAuthenticationRequired(): bool
    {
        return true;
    }

    public function authorize(): bool
    {
        return \Container::$request->user()->hasPermission(IUser::PERMISSION_ADMIN);
    }

    public function getMapEditor(): IContent
    {
        $mapId = (int) \Container::$request->query('mapId');

        if ($mapId) {
            $map = $this->mapRepository->getById($mapId);
            $places = $this->getPlaces($map);
        } else {
            $map = new Map();
            $map->setName(self::$unnamedMapName);
            $places = [];
        }

        return new HtmlContent('admin/map_editor', [
            'mapId' => $mapId,
            'mapName' => $map->getName(),
            'mapDescription' => str_replace('<br>', "\n", $map->getDescription()),
            'mapUnlisted' => $map->getUnlisted(),
            'bounds' => $map->getBounds()->toArray(),
            'places' => &$places
        ]);
    }

    public function getPlace(): IContent
    {
        $placeId = (int) \Container::$request->query('placeId');

        $place = $this->placeRepository->getById($placeId);

        return new JsonContent(['panoId' => $place->getFreshPanoId()]);
    }

    public function saveMap(): IContent
    {
        $mapId = (int) \Container::$request->query('mapId');

        if ($mapId) {
            $map = $this->mapRepository->getById($mapId);
        } else {
            $map = new Map();
            $map->setName(self::$unnamedMapName);
            \Container::$persistentDataManager->saveToDb($map);
        }

        if (isset($_POST['added'])) {
            $addedIds = [];
            foreach ($_POST['added'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);

                $place = new Place();
                $place->setMap($map);
                $place->setLat((float) $placeRaw['lat']);
                $place->setLng((float) $placeRaw['lng']);
                $place->setPov(new Pov(
                    (float) $placeRaw['pov']['heading'],
                    (float) $placeRaw['pov']['pitch'],
                    (float) $placeRaw['pov']['zoom']
                ));

                if ($placeRaw['panoId'] === -1) {
                    $place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
                }

                \Container::$persistentDataManager->saveToDb($place);

                $addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()];
            }
        } else {
            $addedIds = [];
        }

        if (isset($_POST['edited'])) {
            foreach ($_POST['edited'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);

                $place = $this->placeRepository->getById((int) $placeRaw['id']);
                $place->setLat((float) $placeRaw['lat']);
                $place->setLng((float) $placeRaw['lng']);
                $place->setPov(new Pov(
                    (float) $placeRaw['pov']['heading'],
                    (float) $placeRaw['pov']['pitch'],
                    (float) $placeRaw['pov']['zoom']
                ));
                $place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));

                \Container::$persistentDataManager->saveToDb($place);
            }
        }

        if (isset($_POST['deleted'])) {
            foreach ($_POST['deleted'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);

                $place = $this->placeRepository->getById((int) $placeRaw['id']);

                $this->deletePlace($place);
            }
        }

        $mapBounds = $this->calculateMapBounds($map);

        $map->setBounds($mapBounds);
        $map->setArea($mapBounds->calculateApproximateArea());

        if (isset($_POST['name'])) {
            $map->setName($_POST['name'] ? $_POST['name'] : self::$unnamedMapName);
        }
        if (isset($_POST['description'])) {
            $map->setDescription(str_replace(["\n", "\r\n"], '<br>', $_POST['description']));
        }
        if (isset($_POST['unlisted'])) {
            $map->setUnlisted((bool)$_POST['unlisted']);
        }

        \Container::$persistentDataManager->saveToDb($map);

        return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
    }

    public function deleteMap(): IContent
    {
        $mapId = (int) \Container::$request->query('mapId');

        $map = $this->mapRepository->getById($mapId);

        $this->deletePlaces($map);

        \Container::$persistentDataManager->deleteFromDb($map);

        return new JsonContent(['success' => true]);
    }

    private function deletePlace(Place $place): void
    {
        foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) {
            \Container::$persistentDataManager->deleteFromDb($userPlayedPlace);
        }

        foreach ($this->challengeRepository->getAllByPlace($place) as $challenge) {
            $this->deleteChallenge($challenge);
        }

        \Container::$persistentDataManager->deleteFromDb($place);
    }

    private function deletePlaces(Map $map): void
    {
        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $this->deletePlace($place);
        }
    }

    private function deleteChallenge(Challenge $challenge): void
    {
        foreach ($this->userInChallengeRepository->getAllByChallenge($challenge) as $userInChallenge) {
            \Container::$persistentDataManager->deleteFromDb($userInChallenge);
        }

        foreach ($this->guessRepository->getAllInChallenge($challenge, ['place_in_challange']) as $guess) {
            \Container::$persistentDataManager->deleteFromDb($guess);
        }

        foreach ($this->placeInChallengeRepository->getAllByChallenge($challenge) as $placeInChallenge) {
            \Container::$persistentDataManager->deleteFromDb($placeInChallenge);
        }

        \Container::$persistentDataManager->deleteFromDb($challenge);
    }

    private function calculateMapBounds(Map $map): Bounds
    {
        $bounds = new Bounds();

        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $bounds->extend($place->getPosition());
        }

        return $bounds;
    }

    private function &getPlaces(Map $map): array
    {
        $places = [];

        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $noPano = $place->getPanoIdCachedTimestampDate() !== null && $place->getPanoIdCached() === null;

            $placeId = $place->getId();

            $places[$placeId] = [
                'id' => $placeId,
                'lat' => $place->getLat(),
                'lng' => $place->getLng(),
                'panoId' => null,
                'pov' => $place->getPov()->toArray(),
                'noPano' => $noPano
            ];
        }

        return $places;
    }
}