<?php

/**
 * @file classes/tasks/Depositor.inc.php
 *
 * Copyright (c) 2013-2020 Simon Fraser University
 * Copyright (c) 2003-2020 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file LICENSE.
 *
 * @class PLNPluginDepositor
 * @brief Class to perform automated deposits of PLN object.
 */

import('lib.pkp.classes.file.ContextFileManager');
import('lib.pkp.classes.scheduledTask.ScheduledTask');

class Depositor extends ScheduledTask {

	/** @var Plugin */
	var $_plugin;

	/**
	 * Constructor.
	 * @param $args array task arguments
	 */
	public function __construct($args) {
		PluginRegistry::loadCategory('generic');
		$this->_plugin = PluginRegistry::getPlugin('generic', PLN_PLUGIN_NAME);
		parent::__construct($args);
	}

	/**
	 * @copydoc ScheduledTask::getName()
	 */
	public function getName() {
		return __('plugins.generic.pln.depositorTask.name');
	}

	/**
	 * @copydoc ScheduledTask::executeActions()
	 */
	public function executeActions() {
		if (!$this->_plugin) return false;

		$this->addExecutionLogEntry('PLN Depositor executeActions started', SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);

		$journalDao = DAORegistry::getDAO('JournalDAO');

		// Get all journals
		$journals = $journalDao->getAll(true);

		// For all journals
		while ($journal = $journals->next()) {

			// if the plugin isn't enabled for this journal, skip it
			if (!$this->_plugin->getSetting($journal->getId(), 'enabled'))
				continue;

			$this->_plugin->registerDAOs();
			$this->_plugin->import('classes.Deposit');
			$this->_plugin->import('classes.DepositObject');
			$this->_plugin->import('classes.DepositPackage');

			$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.processing_for', array('title' => $journal->getLocalizedName())), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);

			// check to make sure curl is installed
			if (!$this->_plugin->curlInstalled()) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.curl_missing'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_CURL_MISSING);
				continue;
			}

			// check to make sure zip is installed
			if (!$this->_plugin->zipInstalled()) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.zip_missing'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_ZIP_MISSING);
				continue;
			}

			if (!$this->_plugin->tarInstalled()) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.tar_missing'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_TAR_MISSING);
				continue;
			}

			$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.getting_servicedocument'), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			// get the sword service document
			$sdResult = $this->_plugin->getServiceDocument($journal->getId());

			// if for some reason we didn't get a valid reponse, skip this journal
			if ($sdResult != PLN_PLUGIN_HTTP_STATUS_OK) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.http_error'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_HTTP_ERROR);
				continue;
			}

			// TODO: DEFSTAT - REMOVE COMMENTS HERE
			// if the pln isn't accepting deposits, skip this journal
			//if (!$this->_plugin->getSetting($journal->getId(), 'pln_accepting')) {
			//  $this->addExecutionLogEntry(__('plugins.generic.pln.notifications.pln_not_accepting'), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			//  continue;
			//}

			// if the terms haven't been agreed to, skip transfer
			if (!$this->_plugin->termsAgreed($journal->getId())) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.terms_updated'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_TERMS_UPDATED);
				continue;
			}

			// it's necessary that the journal have an issn set
			if (!$journal->getSetting('onlineIssn') &&
				!$journal->getSetting('printIssn') &&
				!$journal->getSetting('issn')) {
				$this->addExecutionLogEntry(__('plugins.generic.pln.notifications.issn_missing'), SCHEDULED_TASK_MESSAGE_TYPE_WARNING);
				$this->_plugin->createJournalManagerNotification($journal->getId(),PLN_PLUGIN_NOTIFICATION_TYPE_ISSN_MISSING);
				continue;
			}

			// update the statuses of existing deposits
			$this->addExecutionLogEntry(__("plugins.generic.pln.depositor.statusupdates"), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			$this->_processStatusUpdates($journal);

			// flag any deposits that have been updated and need to be rebuilt
			$this->addExecutionLogEntry(__("plugins.generic.pln.depositor.updatedcontent"), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			$this->_processHavingUpdatedContent($journal);

			// create new deposits for new deposit objects
			$this->addExecutionLogEntry(__("plugins.generic.pln.depositor.newcontent"), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			$this->_processNewDepositObjects($journal);

			// package any deposits that need packaging
			$this->addExecutionLogEntry(__("plugins.generic.pln.depositor.packagingdeposits"), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			$this->_processNeedPackaging($journal);

			// transfer the deposit atom documents
			$this->addExecutionLogEntry(__("plugins.generic.pln.depositor.transferringdeposits"), SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);
			$this->_processNeedTransferring($journal);
		}

		return true;
	}

	/**
	 * Go through existing deposits and fetch their status from the PLN
	 * @param $journal Journal
	 */
	protected function _processStatusUpdates($journal) {
		// get deposits that need status updates
		$depositDao = DAORegistry::getDAO('DepositDAO'); /** @var $depositDao DepositDAO */
		$depositQueue = $depositDao->getNeedStagingStatusUpdate($journal->getId());

		while ($deposit = $depositQueue->next()) {
			$this->addExecutionLogEntry(__('plugins.generic.pln.depositor.statusupdates.processing', 
				array('depositId' => $deposit->getId(), 
					'statusLocal' => $deposit->getLocalStatus(), 
					'statusProcessing' => $deposit->getProcessingStatus(), 
					'statusLockss' => $deposit->getLockssStatus(),
					'objectId' => $deposit->getObjectId(),
					'objectType' => $deposit->getObjectType())), 
				SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);

			$depositPackage = new DepositPackage($deposit, $this);
			$depositPackage->updateDepositStatus();
		}
	}

	/**
	 * Go thourgh the deposits and mark them as updated if they have been
	 * @param $journal Journal
	 */
	protected function _processHavingUpdatedContent($journal) {
		// get deposits that have updated content
		$depositObjectDao = DAORegistry::getDAO('DepositObjectDAO'); /** @var $depositObjectDao DepositObjectDAO */
		$depositObjectDao->markHavingUpdatedContent($journal->getId(), $this->_plugin->getSetting($journal->getId(), 'object_type'));
	}

	/**
	 * If a deposit hasn't been transferred, transfer it
	 * @param $journal Journal
	 */
	protected function _processNeedTransferring($journal) {
		// fetch the deposits we need to send to the pln
		$depositDao = DAORegistry::getDAO('DepositDAO'); /** @var $depositDao DepositDAO */
		$depositQueue = $depositDao->getNeedTransferring($journal->getId());

		while ($deposit = $depositQueue->next()) {
			$this->addExecutionLogEntry(__('plugins.generic.pln.depositor.transferringdeposits.processing', 
				array('depositId' => $deposit->getId(), 
					'statusLocal' => $deposit->getLocalStatus(), 
					'statusProcessing' => $deposit->getProcessingStatus(), 
					'statusLockss' => $deposit->getLockssStatus(),
					'objectId' => $deposit->getObjectId(),
					'objectType' => $deposit->getObjectType())), 
				SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);

			$depositPackage = new DepositPackage($deposit, $this);
			$depositPackage->transferDeposit();
			unset($deposit);
		}
	}

	/**
	 * Create packages for any deposits that don't have any or have been updated
	 * @param $journal Journal
	 */
	protected function _processNeedPackaging($journal) {
		$depositDao = DAORegistry::getDAO('DepositDAO'); /** @var $depositDao DepositDAO */
		$depositQueue = $depositDao->getNeedPackaging($journal->getId());
		$fileManager = new ContextFileManager($journal->getId());
		$plnDir = $fileManager->getBasePath() . PLN_PLUGIN_ARCHIVE_FOLDER;

		// make sure the pln work directory exists
		// TOOD: use FileManager calls instead of PHP ones where possible
		if ($fileManager->fileExists($plnDir, 'dir') !== true) {
			$fileManager->mkdirtree($plnDir);
		}

		// loop though all of the deposits that need packaging
		while ($deposit = $depositQueue->next()) {
			$this->addExecutionLogEntry(__('plugins.generic.pln.depositor.packagingdeposits.processing', 
				array('depositId' => $deposit->getId(), 
					'statusLocal' => $deposit->getLocalStatus(), 
					'statusProcessing' => $deposit->getProcessingStatus(), 
					'statusLockss' => $deposit->getLockssStatus(),
					'objectId' => $deposit->getObjectId(),
					'objectType' => $deposit->getObjectType())), 
				SCHEDULED_TASK_MESSAGE_TYPE_NOTICE);

			$depositPackage = new DepositPackage($deposit, $this);
			$depositPackage->packageDeposit();
		}
	}

	/**
	 * Create new deposits for deposit objects
	 * @param $journal Journal
	 */
	protected function _processNewDepositObjects($journal) {
		// get the object type we'll be dealing with
		$objectType = $this->_plugin->getSetting($journal->getId(), 'object_type');

		// create new deposit objects for any new OJS content
		$depositDao = DAORegistry::getDAO('DepositDAO'); /** @var $depositDao DepositDAO */
		$depositObjectDao = DAORegistry::getDAO('DepositObjectDAO'); /** @var $depositObjectDao DepositObjectDAO */
		$depositObjectDao->createNew($journal->getId(), $objectType);

		// retrieve all deposit objects that don't belong to a deposit
		$newObjects = $depositObjectDao->getNew($journal->getId(), $objectType);

		switch ($objectType) {
			case PLN_PLUGIN_DEPOSIT_OBJECT_ARTICLE:

				// get the new object threshold per deposit and split the objects into arrays of that size
				$objectThreshold = $this->_plugin->getSetting($journal->getId(), 'object_threshold');
				foreach (array_chunk($newObjects->toArray(),$objectThreshold) as $newObject_array) {

					// only create a deposit for the complete threshold, we'll worry about the remainder another day
					if (count($newObject_array) == $objectThreshold) {

						//create a new deposit
						$newDeposit = new Deposit($this->_plugin->newUUID());
						$newDeposit->setJournalId($journal->getId());
						$depositDao->insertObject($newDeposit);

						// add each object to the deposit
						foreach ($newObject_array as $newObject) {
							$newObject->setDepositId($newDeposit->getId());
							$depositObjectDao->updateObject($newObject);
						}
					}
				}
				break;
			case PLN_PLUGIN_DEPOSIT_OBJECT_ISSUE:
				// create a new deposit for reach deposit object
				while ($newObject = $newObjects->next()) {
					$newDeposit = new Deposit($this->_plugin->newUUID());
					$newDeposit->setJournalId($journal->getId());
					$depositDao->insertObject($newDeposit);
					$newObject->setDepositId($newDeposit->getId());
					$depositObjectDao->updateObject($newObject);
					unset($newObject);
				}
				break;
			default: assert(false);
		}
	}
}
