<?php
/**
 * @file classes/submission/DAO.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class DAO
 *
 * @brief Read and write submissions to the database.
 */

namespace PKP\submission;

use APP\core\Application;
use APP\facades\Repo;
use APP\submission\Collector;
use APP\submission\Submission;
use Illuminate\Support\Collection;
use Illuminate\Support\Enumerable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\EntityDAO;
use PKP\core\traits\EntityWithParent;
use PKP\db\DAORegistry;
use PKP\log\event\EventLogEntry;
use PKP\log\SubmissionEmailLogDAO;
use PKP\note\NoteDAO;
use PKP\notification\NotificationDAO;
use PKP\query\QueryDAO;
use PKP\services\PKPSchemaService;
use PKP\stageAssignment\StageAssignmentDAO;
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
use PKP\submission\reviewRound\ReviewRoundDAO;

/**
 * @template T of Submission
 * @extends EntityDAO<T>
 */
class DAO extends EntityDAO
{
    use EntityWithParent;

    /** @copydoc EntityDAO::$schema */
    public $schema = PKPSchemaService::SCHEMA_SUBMISSION;

    /** @copydoc EntityDAO::$table */
    public $table = 'submissions';

    /** @copydoc EntityDAO::$settingsTable */
    public $settingsTable = 'submission_settings';

    /** @copydoc EntityDAO::$primaryKeyColumn */
    public $primaryKeyColumn = 'submission_id';

    /** @copydoc SchemaDAO::$primaryTableColumns */
    public $primaryTableColumns = [
        'id' => 'submission_id',
        'contextId' => 'context_id',
        'currentPublicationId' => 'current_publication_id',
        'dateLastActivity' => 'date_last_activity',
        'dateSubmitted' => 'date_submitted',
        'lastModified' => 'last_modified',
        'locale' => 'locale',
        'stageId' => 'stage_id',
        'status' => 'status',
        'submissionProgress' => 'submission_progress',
    ];

    /**
     * Get the parent object ID column name
     */
    public function getParentColumn(): string
    {
        return 'context_id';
    }

    /**
     * Instantiate a new DataObject
     */
    public function newDataObject(): Submission
    {
        return app(Submission::class);
    }

    /**
     * Get the total count of submissions matching the configured query
     */
    public function getCount(Collector $query): int
    {
        return $query
            ->getQueryBuilder()
            ->getCountForPagination();
    }

    /**
     * Get a list of ids matching the configured query
     *
     * @return Collection<int,int>
     */
    public function getIds(Collector $query): Collection
    {
        return $query
            ->getQueryBuilder()
            ->select('s.' . $this->primaryKeyColumn)
            ->pluck('s.' . $this->primaryKeyColumn);
    }

    /**
     * Get a collection of announcements matching the configured query
     * @return LazyCollection<int,T>
     */
    public function getMany(Collector $query): LazyCollection
    {
        $rows = $query
            ->getQueryBuilder()
            ->get();

        return LazyCollection::make(function () use ($rows) {
            foreach ($rows as $row) {
                yield $row->submission_id => $this->fromRow($row);
            }
        });
    }

    /**
     * Get the submission id by its url path
     */
    public function getIdByUrlPath(string $urlPath, int $contextId): ?int
    {
        $publication = DB::table('publications as p')
            ->leftJoin('submissions as s', 's.submission_id', '=', 'p.submission_id')
            ->where('s.context_id', '=', $contextId)
            ->where('p.url_path', '=', $urlPath)
            ->first();

        return $publication
            ? $publication->submission_id
            : null;
    }

    /**
     * Get submission ids that have a matching setting
     */
    public function getIdsBySetting(string $settingName, $settingValue, int $contextId): Enumerable
    {
        return DB::table($this->table . ' as s')
            ->join($this->settingsTable . ' as ss', 's.submission_id', '=', 'ss.submission_id')
            ->where('ss.setting_name', '=', $settingName)
            ->where('ss.setting_value', '=', $settingValue)
            ->where('s.context_id', '=', (int) $contextId)
            ->select('s.submission_id')
            ->pluck('s.submission_id');
    }

    /**
     * Retrieve a submission by public id
     *
     * @param string $pubIdType One of the NLM pub-id-type values or
     * 'other::something' if not part of the official NLM list
     * (see <http://dtd.nlm.nih.gov/publishing/tag-library/n-4zh0.html>).
     * @param null|mixed $contextId
     */
    public function getByPubId(string $pubIdType, string $pubId, $contextId = null): ?Submission
    {
        // Add check for incoming DOI request for legacy calls that bypass the Submission Repository
        if ($pubIdType == 'doi') {
            return $this->getByDoi($pubId, $contextId);
        } else {
            $qb = DB::table('publication_settings ps')
                ->join('publications p', 'p.publication_id', '=', 'ps.publication_id')
                ->join('submissions s', 'p.publication_id', '=', 's.current_publication_id')
                ->where('ps.setting_name', '=', 'pub-id::' . $pubIdType)
                ->where('ps.setting_value', '=', $pubId);

            if ($contextId) {
                $qb->where('s.context_id', '=', (int) $contextId);
            }

            $row = $qb->get(['s.submission_id'])->first();

            return $row
                ? $this->get($row->submission_id)
                : null;
        }
    }

    /**
     * Retrieve a submission by its current publication's DOI
     */
    public function getByDoi(string $doi, int $contextId): ?Submission
    {
        $q = DB::table($this->table, 's')
            ->leftJoin('publications AS p', 'p.publication_id', '=', 's.current_publication_id')
            ->leftJoin('dois AS d', 'd.doi_id', '=', 'p.doi_id')
            ->where('d.doi', '=', $doi)
            ->where('s.context_id', '=', $contextId);
        $row = $q->select(['s.submission_id AS submission_id'])->get()->first();
        return $row ? $this->get($row->submission_id) : null;
    }

    /**
     * @copydoc EntityDAO::fromRow()
     */
    public function fromRow(object $row): Submission
    {
        $submission = parent::fromRow($row);

        $submission->setData(
            'publications',
            Repo::publication()->getCollector()
                ->filterBySubmissionIds([$submission->getId()])
                ->getMany()
                ->remember()
        );

        return $submission;
    }

    /**
     * @copydoc EntityDAO::_insert()
     */
    public function insert(Submission $submission): int
    {
        return parent::_insert($submission);
    }

    /**
     * @copydoc EntityDAO::_update()
     */
    public function update(Submission $submission)
    {
        parent::_update($submission);
    }

    /**
     * @copydoc EntityDAO::_delete()
     */
    public function delete(Submission $submission)
    {
        parent::_delete($submission);
    }

    /**
     * @copydoc \PKP\core\EntityDAO::deleteById()
     */
    public function deleteById(int $id)
    {
        $submission = Repo::submission()->get($id);

        // Delete publications
        $publications = Repo::publication()->getCollector()
            ->filterBySubmissionIds([$id])
            ->getMany();

        foreach ($publications as $publication) {
            Repo::publication()->delete($publication);
        }

        // Delete submission files.
        $submissionFiles = Repo::submissionFile()
            ->getCollector()
            ->filterBySubmissionIds([$submission->getId()])
            ->getMany();

        foreach ($submissionFiles as $submissionFile) {
            Repo::submissionFile()->delete($submissionFile);
        }

        Repo::decision()->deleteBySubmissionId($id);

        $reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
        $reviewAssignmentDao->deleteBySubmissionId($id);

        $reviewRoundDao = DAORegistry::getDAO('ReviewRoundDAO'); /** @var ReviewRoundDAO $reviewRoundDao */
        $reviewRoundDao->deleteBySubmissionId($id);

        // Delete the queries associated with a submission
        $queryDao = DAORegistry::getDAO('QueryDAO'); /** @var QueryDAO $queryDao */
        $queryDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);

        // Delete the stage assignments.
        $stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
        $stageAssignments = $stageAssignmentDao->getBySubmissionAndStageId($id);
        while ($stageAssignment = $stageAssignments->next()) {
            $stageAssignmentDao->deleteObject($stageAssignment);
        }

        $noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
        $noteDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);

        $submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
        $submissionCommentDao->deleteBySubmissionId($id);

        // Delete any outstanding notifications for this submission
        $notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
        $notificationDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);

        Repo::eventLog()->getCollector()
            ->filterByAssoc(Application::ASSOC_TYPE_SUBMISSION, [$id])
            ->getMany()
            ->each(function (EventLogEntry $logEntry) {
                Repo::eventLog()->delete($logEntry);
            });

        $submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
        $submissionEmailLogDao->deleteByAssoc(Application::ASSOC_TYPE_SUBMISSION, $id);

        parent::deleteById($id);
    }
}
