<?php

class Alarm
{
    const MONDAY = 1;
    const TUESDAY = 2;
    const WEDNESDAY = 3;
    const THURSDAY = 4;
    const FRIDAY = 5;
    const SATURDAY = 6;
    const SUNDAY = 7;

    private const ALL_DAYS = [
        self::MONDAY,
        self::TUESDAY,
        self::WEDNESDAY,
        self::THURSDAY,
        self::FRIDAY,
        self::SATURDAY,
        self::SUNDAY
    ];

    const TYPE_REPEATING = true;
    const TYPE_NON_REPEATING = false;
    const STATUS_ACTIVE = true;
    const STATUS_INACTIVE = false;

    private $time;
    private $daysOfWeek;
    private $type;
    private $status;

    public function __construct(int $hour, int $minute)
    {
        $this->setTime($hour, $minute);
        $this->setDaysOfWeek(self::ALL_DAYS);
        $this->setType(self::TYPE_REPEATING);
        $this->setStatus(self::STATUS_ACTIVE);
    }

    public function setTime(int $hour, int $minute): void
    {
        if ($hour > 23 || $hour < 0 || $minute > 60 || $minute < 0) {
            throw new InvalidArgumentException('Неправильное время');
        }

        if ($hour < 10) {
            $hour = 0..$hour;
        }

        if ($minute < 10) {
            $minute = 0..$minute;
        }

        $this->time = $hour.':'.$minute;
    }

    public function setDaysOfWeek(array $daysOfWeek): void
    {
        foreach ($daysOfWeek as $dayOfWeek) {
            if ($dayOfWeek > 7 || $dayOfWeek < 1) {
                throw new InvalidArgumentException('Неправильное значение в массиве с днями недели');
            }
        }

        $this->daysOfWeek = $daysOfWeek;
    }

    public function setType(bool $type): void
    {
        if ($this->status == Alarm::STATUS_INACTIVE && $type == Alarm::TYPE_NON_REPEATING) {
            throw new InvalidArgumentException('Нельзя делать одноразовой неактивную тревогу');
        }

        $this->type = $type;
    }

    public function setStatus(bool $status): void
    {
        if ($status == self::STATUS_INACTIVE && $this->type == self::TYPE_NON_REPEATING) {
            throw new InvalidArgumentException('Нельзя изменять статус одноразовой тревоги');
        }

        $this->status = $status;
    }

    public function getTime(): string
    {
        return $this->time;
    }

    public function getDaysOfWeek(): array
    {
        return $this->daysOfWeek;
    }

    public function isRepeating(): bool
    {
        return $this->type;
    }

    public function isActive(): bool
    {
        return $this->status;
    }

    public function findNearestAlarmTime(DateTimeImmutable $currentTime): DateTimeImmutable
    {
        $spelling = [
            '', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'
        ];

        foreach ($this->daysOfWeek as $dayOfWeek) {

            if ($dayOfWeek > 7 || $dayOfWeek < 1) {
                throw new InvalidArgumentException('Неправильное значение в массиве с днями недели');
            }

            $ordinal = ($currentTime->format('H:i') > $this->time) ? 'next' : '';
            $datesAndTimes[] = $currentTime->modify($ordinal.' '.$spelling[$dayOfWeek].' '.$this->time);

        }

        sort($datesAndTimes);

        return $datesAndTimes[0];
    }
}

class AlarmClock
{
    private $alarms;

    public function addAlarm(Alarm $alarm): void
    {
        $this->alarms[] = $alarm;
    }

    public function setAlarm(int $id, Alarm $alarm): void
    {
        if (!$this->alarms) {
            throw new Exception('Нет тревог');
        }
        if (!$this->alarms[$id]) {
            throw new InvalidArgumentException('Несуществующая тревога');
        }

        $this->alarms[$id] = $alarm;
    }

    public function deleteAlarm(Alarm $alarm): void
    {
        if (!$this->alarms) {
            throw new Exception('Нет тревог');
        }

        foreach ($this->alarms as $key => $object) {
            if ($object == $alarm) {
                unset($this->alarms[$key]);
            }
        }
    }

    public function findNearestAlarm(DateTimeImmutable $currentTime): Alarm
    {
        if (!$this->alarms) {
            throw new Exception('Нет тревог');
        }

        $alarms = [];

        foreach ($this->alarms as $alarm) {

            $alarmTime = $alarm->findNearestAlarmTime($currentTime);
            $alarms[] = [$alarmTime, $alarm];

        }

        sort($alarms);

        return $alarms[0][1];
    }

    public function findTriggeredAlarm(DateTimeImmutable $currentTime): ?Alarm
    {
        if (!$this->alarms) {
            throw new Exception('Нет тревог');
        }

        foreach ($this->alarms as $alarm) {
            if ($this->isTriggeredAlarm($alarm, $currentTime)
                && $alarm->isActive() == Alarm::STATUS_ACTIVE) {
                return $alarm;
            }
        }

        return null;
    }

    public function deleteProcessedNonRepeatingAlarms(DateTimeImmutable $currentTime): void
    {
        if (!$this->alarms) {
            throw new Exception('Нет тревог');
        }

        foreach ($this->alarms as $alarm) {
            if ($this->isTriggeredAlarm($alarm, $currentTime)
                && $alarm->isRepeating() == Alarm::TYPE_NON_REPEATING) {
                $this->deleteAlarm($alarm);
            }
        }
    }

    private function isTriggeredAlarm(Alarm $alarm, DateTimeImmutable $currentTime): bool
    {
        $alarmTime = $alarm->findNearestAlarmTime($currentTime);
        return ($alarmTime->format('Y-m-d H:i') == $currentTime->format('Y-m-d H:i'));
    }
}