<?php

/* For licensing terms, see /license.txt */

declare(strict_types=1);

namespace Chamilo\CoreBundle\Entity;

use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use Chamilo\CoreBundle\Traits\UserExtraFieldFilterTrait;
use Chamilo\CourseBundle\Entity\CQuiz;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Quiz user attempts.
 */
#[ApiResource(
    operations: [
        new Get(security: 'is_granted("VIEW", object)'),
        new GetCollection(security: 'is_granted("ROLE_USER")'),
    ],
    normalizationContext: [
        'groups' => ['track_e_exercise:read'],
    ],
    denormalizationContext: [
        'groups' => ['track_e_exercise:write'],
    ],
    security: 'is_granted("ROLE_USER")'
)]
#[ORM\Table(name: 'track_e_exercises')]
#[ORM\Index(columns: ['exe_user_id'], name: 'idx_tee_user_id')]
#[ORM\Index(columns: ['c_id'], name: 'idx_tee_c_id')]
#[ORM\Index(columns: ['session_id'], name: 'session_id')]
#[ORM\Entity]
#[ApiFilter(
    filterClass: SearchFilter::class,
    properties: [
        'user' => 'exact',
        'quiz' => 'exact',
        'course' => 'exact',
        'session' => 'exact',
    ]
)]
#[ApiFilter(
    filterClass: OrderFilter::class,
    properties: [
        'user.username',
        'user.fisrname',
        'user.lastname',
        'startDate',
        'exeDate',
        'exeDuration',
        'score',
        'status',
        'userIp',
    ]
)]
#[ApiFilter(
    filterClass: DateFilter::class,
    properties: ['startDate']
)]
class TrackEExercise
{
    use UserExtraFieldFilterTrait;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'exe_id', type: 'integer')]
    #[ORM\Id]
    #[ORM\GeneratedValue(strategy: 'IDENTITY')]
    protected int $exeId;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'])]
    #[ORM\JoinColumn(name: 'exe_user_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    protected User $user;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\ManyToOne(targetEntity: Course::class)]
    #[ORM\JoinColumn(name: 'c_id', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
    protected Course $course;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\ManyToOne(targetEntity: Session::class)]
    #[ORM\JoinColumn(name: 'session_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
    protected ?Session $session = null;

    #[Assert\NotBlank]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'exe_date', type: 'datetime', nullable: false)]
    protected DateTime $exeDate;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\ManyToOne(targetEntity: CQuiz::class, inversedBy: 'attempts')]
    #[ORM\JoinColumn(name: 'exe_exo_id', referencedColumnName: 'iid', nullable: true, onDelete: 'SET NULL')]
    protected ?CQuiz $quiz = null;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'score', type: 'float', precision: 6, scale: 2, nullable: false)]
    protected float $score;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'max_score', type: 'float', precision: 6, scale: 2, nullable: false)]
    protected float $maxScore;

    #[Assert\NotBlank]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'user_ip', type: 'string', length: 45, nullable: false)]
    protected string $userIp;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'status', type: 'string', length: 20, nullable: false)]
    protected string $status;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'data_tracking', type: 'text', nullable: false)]
    protected string $dataTracking;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'start_date', type: 'datetime', nullable: false)]
    protected DateTime $startDate;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'steps_counter', type: 'smallint', nullable: false)]
    protected int $stepsCounter;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'orig_lp_id', type: 'integer', nullable: false)]
    protected int $origLpId;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'orig_lp_item_id', type: 'integer', nullable: false)]
    protected int $origLpItemId;

    #[Assert\NotNull]
    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'exe_duration', type: 'integer', nullable: false)]
    protected int $exeDuration;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'expired_time_control', type: 'datetime', nullable: true)]
    protected ?DateTime $expiredTimeControl = null;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'orig_lp_item_view_id', type: 'integer', nullable: false)]
    protected int $origLpItemViewId;

    #[Groups(['track_e_exercise:read'])]
    #[Assert\NotNull]
    #[ORM\Column(name: 'questions_to_check', type: 'text', nullable: false)]
    protected string $questionsToCheck;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\Column(name: 'blocked_categories', type: 'text', nullable: true)]
    protected ?string $blockedCategories;

    /**
     * @var Collection<int, TrackEAttempt>
     */
    #[Groups(['track_e_exercise:read'])]
    #[ORM\OneToMany(mappedBy: 'trackExercise', targetEntity: TrackEAttempt::class, cascade: ['persist'])]
    protected Collection $attempts;

    #[Groups(['track_e_exercise:read'])]
    #[ORM\OneToMany(mappedBy: 'trackExercise', targetEntity: TrackEAttemptQualify::class, orphanRemoval: true)]
    private Collection $revisedAttempts;

    public function __construct()
    {
        $this->attempts = new ArrayCollection();
        $this->revisedAttempts = new ArrayCollection();
        $this->questionsToCheck = '';
        $this->blockedCategories = '';
        $this->dataTracking = '';
        $this->exeDuration = 0;
        $this->score = 0;
        $this->status = 'incomplete';
        $this->stepsCounter = 0;
        $this->exeDate = new DateTime();
        $this->startDate = new DateTime();
    }

    public function getExeDate(): DateTime
    {
        return $this->exeDate;
    }

    public function setExeDate(DateTime $exeDate): self
    {
        $this->exeDate = $exeDate;

        return $this;
    }

    public function getQuiz(): ?CQuiz
    {
        return $this->quiz;
    }

    public function setQuiz(CQuiz $cQuiz): self
    {
        $this->quiz = $cQuiz;

        return $this;
    }

    public function getUserIp(): string
    {
        return $this->userIp;
    }

    public function setUserIp(string $userIp): self
    {
        $this->userIp = $userIp;

        return $this;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    public function setStatus(string $status): self
    {
        $this->status = $status;

        return $this;
    }

    public function getDataTracking(): string
    {
        return $this->dataTracking;
    }

    public function setDataTracking(string $dataTracking): self
    {
        $this->dataTracking = $dataTracking;

        return $this;
    }

    public function getStartDate(): DateTime
    {
        return $this->startDate;
    }

    public function setStartDate(DateTime $startDate): self
    {
        $this->startDate = $startDate;

        return $this;
    }

    public function getStepsCounter(): int
    {
        return $this->stepsCounter;
    }

    public function setStepsCounter(int $stepsCounter): self
    {
        $this->stepsCounter = $stepsCounter;

        return $this;
    }

    public function getOrigLpId(): int
    {
        return $this->origLpId;
    }

    public function setOrigLpId(int $origLpId): self
    {
        $this->origLpId = $origLpId;

        return $this;
    }

    public function getOrigLpItemId(): int
    {
        return $this->origLpItemId;
    }

    public function setOrigLpItemId(int $origLpItemId): self
    {
        $this->origLpItemId = $origLpItemId;

        return $this;
    }

    public function getExeDuration(): int
    {
        return $this->exeDuration;
    }

    public function setExeDuration(int $exeDuration): self
    {
        $this->exeDuration = $exeDuration;

        return $this;
    }

    public function getExpiredTimeControl(): ?DateTime
    {
        return $this->expiredTimeControl;
    }

    public function setExpiredTimeControl(?DateTime $expiredTimeControl): self
    {
        $this->expiredTimeControl = $expiredTimeControl;

        return $this;
    }

    public function getOrigLpItemViewId(): int
    {
        return $this->origLpItemViewId;
    }

    public function setOrigLpItemViewId(int $origLpItemViewId): self
    {
        $this->origLpItemViewId = $origLpItemViewId;

        return $this;
    }

    public function getQuestionsToCheck(): string
    {
        return $this->questionsToCheck;
    }

    public function setQuestionsToCheck(string $questionsToCheck): self
    {
        $this->questionsToCheck = $questionsToCheck;

        return $this;
    }

    public function getExeId(): int
    {
        return $this->exeId;
    }

    public function getScore(): float
    {
        return $this->score;
    }

    public function setScore(float $score): self
    {
        $this->score = $score;

        return $this;
    }

    public function getMaxScore(): float
    {
        return $this->maxScore;
    }

    public function setMaxScore(float $maxScore): self
    {
        $this->maxScore = $maxScore;

        return $this;
    }

    public function getUser(): User
    {
        return $this->user;
    }

    public function setUser(User $user): self
    {
        $this->user = $user;

        return $this;
    }

    public function getCourse(): Course
    {
        return $this->course;
    }

    public function setCourse(Course $course): self
    {
        $this->course = $course;

        return $this;
    }

    /**
     * @return Collection<int, TrackEAttempt>
     */
    public function getAttempts(): Collection
    {
        return $this->attempts;
    }

    /**
     * @param Collection<int, TrackEAttempt> $attempts
     */
    public function setAttempts(Collection $attempts): self
    {
        $this->attempts = $attempts;

        return $this;
    }

    public function addAttempt(TrackEAttempt $attempt): self
    {
        if (!$this->attempts->contains($attempt)) {
            $this->attempts[] = $attempt;
            $attempt->setTrackEExercise($this);
        }

        return $this;
    }

    public function getSession(): ?Session
    {
        return $this->session;
    }

    public function setSession(?Session $session): self
    {
        $this->session = $session;

        return $this;
    }

    public function getAttemptByQuestionId(int $questionId): ?TrackEAttempt
    {
        $criteria = Criteria::create();
        $criteria->where(Criteria::expr()->eq('questionId', $questionId))->setMaxResults(1);
        $attempt = $this->attempts->matching($criteria)->first();

        if (!empty($attempt)) {
            return $attempt;
        }

        return null;
    }

    /**
     * @return Collection<int, TrackEAttemptQualify>
     */
    public function getRevisedAttempts(): Collection
    {
        return $this->revisedAttempts;
    }

    public function addRevisedAttempt(TrackEAttemptQualify $revisedAttempt): static
    {
        if (!$this->revisedAttempts->contains($revisedAttempt)) {
            $this->revisedAttempts->add($revisedAttempt);
            $revisedAttempt->setTrackExercise($this);
        }

        return $this;
    }

    public function removeRevisedAttempt(TrackEAttemptQualify $revisedAttempt): static
    {
        if ($this->revisedAttempts->removeElement($revisedAttempt)) {
            // set the owning side to null (unless already changed)
            if ($revisedAttempt->getTrackExercise() === $this) {
                $revisedAttempt->setTrackExercise(null);
            }
        }

        return $this;
    }

    #[Groups(['track_e_exercise:read'])]
    public function isRevised(): bool
    {
        return $this->revisedAttempts->count() > 0;
    }
}
