<?php namespace MapGuesser\Controller;

use DateTime;
use MapGuesser\Interfaces\Authentication\IUser;
use MapGuesser\Interfaces\Authorization\ISecured;
use MapGuesser\Interfaces\Request\IRequest;
use MapGuesser\Interfaces\Response\IContent;
use MapGuesser\PersistentData\Model\Map;
use MapGuesser\PersistentData\Model\Place;
use MapGuesser\PersistentData\PersistentDataManager;
use MapGuesser\Repository\MapRepository;
use MapGuesser\Repository\PlaceRepository;
use MapGuesser\Response\HtmlContent;
use MapGuesser\Response\JsonContent;
use MapGuesser\Util\Geo\Bounds;
use MapGuesser\Util\Panorama\Pov;

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

    private IRequest $request;

    private PersistentDataManager $pdm;

    private MapRepository $mapRepository;

    private PlaceRepository $placeRepository;

    public function __construct(IRequest $request)
    {
        $this->request = $request;
        $this->pdm = new PersistentDataManager();
        $this->mapRepository = new MapRepository();
        $this->placeRepository = new PlaceRepository();
    }

    public function authorize(): bool
    {
        $user = $this->request->user();

        return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
    }

    public function getMapEditor(): IContent
    {
        $mapId = (int) $this->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()),
            'bounds' => $map->getBounds()->toArray(),
            'places' => &$places
        ]);
    }

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

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

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

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

        \Container::$dbConnection->startTransaction();

        if ($mapId) {
            $map = $this->mapRepository->getById($mapId);
        } else {
            $map = new Map();
            $map->setName(self::$unnamedMapName);
            $this->pdm->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'));
                }

                $this->pdm->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'));

                $this->pdm->saveToDb($place);
            }
        }

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

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

                $this->pdm->deleteFromDb($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']));
        }

        $this->pdm->saveToDb($map);

        \Container::$dbConnection->commit();

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

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

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

        \Container::$dbConnection->startTransaction();

        $this->deletePlaces($map);

        $this->pdm->deleteFromDb($map);

        \Container::$dbConnection->commit();

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

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

    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;
    }
}