<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\Local\CinemaAreaCategories;
use App\Entity\Local\CinemaAreaCategory;
use App\Entity\Vista\Area;
use App\Entity\Vista\GetSessionSeatPlanResponse;
use App\Entity\Vista\Row;
use App\Entity\Vista\Theatre;
use App\Exceptions\SeatPlanException;
use App\Utils\SeatPlanIconsMatcher;
use GuzzleHttp\Client;
use Symfony\Component\Serializer\SerializerInterface;
use App\Repository\Vista\SessionVistaRepository as SessionRepository;
use App\Repository\SeatPlanLocalRepository;
use Psr\Log\LoggerInterface;
class SeatPlanRepository
{
/**
* @var Client $vistaClient
*/
protected $vistaClient;
/**
* @var SerializerInterface $serializer
*/
protected $serializer;
/**
* @var SeatPlanIconsMatcher
*/
public $matcher;
/** @var SessionRepository */
private $sessionRepository;
/** @var SeatPlanLocalRepository */
protected $seatPlanLocalRepository;
/** @var LoggerInterface */
private $logger;
public function __construct(Client $vistaClient, SerializerInterface $serializer,
SeatPlanIconsMatcher $matcher, SessionRepository $sessionRepository,
SeatPlanLocalRepository $seatPlanLocalRepository, LoggerInterface $logger)
{
$this->vistaClient = $vistaClient;
$this->serializer = $serializer;
$this->matcher = $matcher;
$this->sessionRepository = $sessionRepository;
$this->seatPlanLocalRepository = $seatPlanLocalRepository;
$this->logger = $logger;
}
/**
* @param string $cinemaId
* @param string $sessionId
*
* @return string
*/
public function getSeatPlanRaw(string $cinemaId, string $sessionId)
{
$start = microtime(true);
$response = $this->vistaClient->get(sprintf('/WSVistaWebClient/RESTData.svc/cinemas/%s/sessions/%s/seat-plan', $cinemaId, $sessionId));
$this->logger->info(sprintf("profiling vista call :: /WSVistaWebClient/RESTData.svc/cinemas/%s/sessions/%s/seat-plan execution time: %f",
$cinemaId, $sessionId, microtime(true) - $start));
return $response->getBody()->getContents();
}
/**
* @param string $cinemaId
* @param string $sessionId
* @param CinemaAreaCategories[] $categories
*
* @return array
*/
public function getSeatPlan(string $cinemaId, string $sessionId, array $categories)
{
$seatPlanVista = $this->getSeatPlanRaw($cinemaId, $sessionId);
if ($session = $this->sessionRepository->findOneBy(['sessionId' => $sessionId, 'cinemaId' => $cinemaId])) {
$screenNumber = $session->getScreenNumber();
if ($seatPlanJson = $this->seatPlanLocalRepository->findOneBy(['cinemaId' => $cinemaId, 'screenNumber' => $screenNumber])) {
$seatPlanInput = $seatPlanJson->getSeatPlanJson();
}
}
return $this->getSeatPlanResponse($seatPlanVista, $categories);
}
public function getSeatPlanResponse(string $responseJson, array $categories)
{
/** @var GetSessionSeatPlanResponse $seatPlanResponse */
$seatPlanResponse = $this->serializer->deserialize($responseJson, GetSessionSeatPlanResponse::class, 'json');
if ($seatPlanResponse->getErrorDescription() || !$seatPlanResponse->getValue() instanceof Theatre) {
throw new SeatPlanException($seatPlanResponse->getErrorDescription() ?? 'An unexpected error has occured. Please try again later');
}
return array_merge([$seatPlanResponse->getErrorDescription()], $this->convertSeatPlan($seatPlanResponse->getValue(), $categories));
}
/**
* Copy similar fields from area to row
*
* @param Row $to
* @param Area $from
*/
private function copyFieldsFromArea(Row $to, Area $from)
{
$to->setAreaCategoryCode($from->getAreaCategoryCode())
->setDescription($from->getDescription())
->setRight($from->getRight())
->setColumnCount($from->getColumnCount())
->setNumber($from->getNumber());
}
/**
* Sort rows by bottom value (usort)
*
* @param Row $a
* @param Row $b
*
* @return int
*/
public function sortByBottom(Row $a, Row $b)
{
if ($a->getBottom() > $b->getBottom()) {
return -1;
} elseif ($a->getBottom() < $b->getBottom()) {
return 1;
}
return 0;
}
/**
* @param Theatre $seatPlan
* @param CinemaAreaCategories[]|null $categories
*
* @return array<int, array<Row>|int>
*/
protected function convertSeatPlan(Theatre $seatPlan, array $categories)
{
/** @var Row[] $seatPlanRows */
$seatPlanRows = [];
$totalSeatsCount = 0;
foreach ($seatPlan->getAreas() as $area) {
$totalSeatsCount += $area->getNumberOfSeats();
$areaRows = [];
$rowsList = $area->getRows();
$rowsCount = count($rowsList);
if (!($lastRow = $rowsList ? array_values(array_reverse($rowsList))[0] : null)) {
continue;
}
foreach ($rowsList as $rowIndex => $row) {
$this->copyFieldsFromArea($row, $area);
$row->setHeight(round(floatval($area->getHeight()) / $area->getRowCount(), 3));
$k = $rowsCount - $rowIndex - 1;
$row->setBottom(round(floatval($area->getBottom()) + $row->getHeight() * $k, 3));
foreach ($row->getSeats() as $seat) {
$seat->setAreaCategoryCode($row->getAreaCategoryCode());
$seat->setRowRight($row->getRight());
/** @var CinemaAreaCategory $category */
if ($category = $categories[intval($row->getAreaCategoryCode())] ?? []) {
$categoryId = $category ? $category->getAreaCategoryId() : -1;
$seat->setSeatIconId($this->matcher->getIconId($seat, $categoryId));
$seat->setSeatImprovedIconId($this->matcher->getImprovedIconId($seat, $categoryId));
}
}
$areaRows[] = $row;
}
$areaRows = $this->trimFirstEmptyRows($areaRows);
$areaRows = array_reverse($areaRows);
$areaRows = $this->trimFirstEmptyRows($areaRows);
$areaRows = array_reverse($areaRows);
$seatPlanRows = array_merge($seatPlanRows, $areaRows);
}
usort($seatPlanRows, [$this, 'sortByBottom']);
$seatPlanRows = $this->adjustNulls($seatPlanRows);
return [
$seatPlanRows,
$totalSeatsCount,
];
}
/**
* @param Row[] $list
*
* @return Row[]
*/
protected function trimFirstEmptyRows(array $list): array
{
$result = [];
$skip = true;
foreach ($list as $item) {
if (false === $skip || !is_null($item->getPhysicalName())) {
$skip = true;
$result[] = $item;
}
}
return $result;
}
/**
* @param Row[] $list
*
* @return Row[]
*/
protected function adjustNulls(array $list): array
{
$previous = null;
$result = [];
foreach ($list as $item) {
$current = $item->getPhysicalName();
if (null !== $previous || null !== $current) {
$result[] = $item;
}
$previous = $item->getPhysicalName();
}
return $result;
}
/**
* @param Row[] $seatPlanRows
* @param CinemaAreaCategory[] $categories
*/
public function setIconIds(array $seatPlanRows, array $categories)
{
foreach ($seatPlanRows as $row) {
$seats = $row->getSeats();
$seats or $seats = [];
$categoryId = -1;
$messages = '';
if ($categories) {
$categoryCode = intval($row->getAreaCategoryCode());
if (array_key_exists($categoryCode, $categories)) {
$category = $categories[intval($row->getAreaCategoryCode())];
$categoryId = $category->getAreaCategoryId();
} else {
$messages .= ' catecories does not consists category with code $categoryCode';
}
} else {
$messages .= ' catecories is empty';
}
if ($messages) {
error_log(sprintf("%s %s(%s) $messages", date('Y-m-d H:i:s'), __METHOD__, __LINE__));
}
foreach ($seats as $seat) {
$seat->setSeatIconId($this->matcher->getIconId($seat, $categoryId));
$seat->setSeatImprovedIconId($this->matcher->getImprovedIconId($seat, $categoryId));
}
}
}
}