<?php

/**
 * @file classes/author/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
 *
 * @ingroup author
 *
 * @see \PKP\author\Author
 *
 * @brief Operations for retrieving and modifying Author objects.
 */

namespace PKP\author;

use APP\author\Author;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\LazyCollection;
use PKP\core\EntityDAO;
use PKP\facades\Repo;
use PKP\services\PKPSchemaService;

/**
 * @template T of Author
 * @extends EntityDAO<T>
 */
class DAO extends EntityDAO
{
    /** @copydoc EntityDAO::$schema */
    public $schema = PKPSchemaService::SCHEMA_AUTHOR;

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

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

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

    /** @copydoc EntityDAO::$primaryTableColumns */
    public $primaryTableColumns = [
        'id' => 'author_id',
        'email' => 'email',
        'includeInBrowse' => 'include_in_browse',
        'publicationId' => 'publication_id',
        'seq' => 'seq',
        'userGroupId' => 'user_group_id',
    ];

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

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

    /**
     * Get an author.
     *
     * Optionally, pass the publication ID to only get an author
     * if it exists and is assigned to that publication.
     */
    public function get(int $id, ?int $publicationId = null): ?Author
    {
        // This is overridden due to the need to include submission_locale
        // to the fromRow function
        $row = DB::table('authors as a')
            ->join('publications as p', 'a.publication_id', '=', 'p.publication_id')
            ->join('submissions as s', 'p.submission_id', '=', 's.submission_id')
            ->where('a.author_id', '=', $id)
            ->when($publicationId !== null, fn (Builder $query) => $query->where('a.publication_id', '=', $publicationId))
            ->select(['a.*', 's.locale AS submission_locale'])
            ->first();
        return $row ? $this->fromRow($row) : null;
    }

    /**
     * Check if an author exists.
     *
     * Optionally, pass the publication ID to check if the author
     * exists and is assigned to that publication.
     */
    public function exists(int $id, ?int $publicationId = null): bool
    {
        return DB::table($this->table)
            ->where($this->primaryKeyColumn, '=', $id)
            ->when($publicationId !== null, fn (Builder $query) => $query->where($this->getParentColumn(), $publicationId))
            ->exists();
    }

    /**
     * Get the total count of rows 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('a.' . $this->primaryKeyColumn)
            ->pluck('a.' . $this->primaryKeyColumn);
    }

    /**
     * Get a collection of publications 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->author_id => $this->fromRow($row);
            }
        });
    }

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

        // Set the primary locale from the submission
        $author->setData('locale', $row->submission_locale);

        return $author;
    }

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

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

    /**
     * @copydoc EntityDAO::delete()
     */
    public function delete(Author $author)
    {
        DB::table('publications')
            ->where('primary_contact_id', $author->getId())
            ->update(['primary_contact_id' => null]);

        parent::_delete($author);
    }

    /**
     * Get the next sequence that should be used when adding a contributor to a publication
     */
    public function getNextSeq(int $publicationId): int
    {
        $nextSeq = 0;
        $seq = DB::table('authors as a')
            ->join('publications as p', 'a.publication_id', '=', 'p.publication_id')
            ->where('p.publication_id', '=', $publicationId)
            ->max('a.seq');

        if ($seq) {
            $nextSeq = $seq + 1;
        }

        return $nextSeq;
    }

    /**
     * Reset the order of contributors in a publication
     *
     * This method resets the seq property for each contributor in a publication
     * so that they are numbered sequentially without any gaps.
     *
     * eg - 1, 3, 4, 6 will become 1, 2, 3, 4
     */
    public function resetContributorsOrder(int $publicationId)
    {
        $authorIds = Repo::author()
            ->getCollector()
            ->filterByPublicationIds([$publicationId])
            ->orderBy(Repo::author()->getCollector()::ORDERBY_SEQUENCE)
            ->getIds();

        foreach ($authorIds as $seq => $authorId) {
            DB::table('authors')->where('author_id', '=', $authorId)->update(['seq' => $seq]);
        }
    }
}
