<?php
/**
 * webtrees: online genealogy
 * Copyright (C) 2017 webtrees development team
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
namespace Fisharebest\Webtrees\Module;

use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Bootstrap4;
use Fisharebest\Webtrees\Controller\PageController;
use Fisharebest\Webtrees\Database;
use Fisharebest\Webtrees\Family;
use Fisharebest\Webtrees\Filter;
use Fisharebest\Webtrees\GedcomRecord;
use Fisharebest\Webtrees\Html;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Individual;
use Fisharebest\Webtrees\Media;
use Fisharebest\Webtrees\Module\BatchUpdate\BatchUpdateBasePlugin;
use Fisharebest\Webtrees\Note;
use Fisharebest\Webtrees\Repository;
use Fisharebest\Webtrees\Source;
use Fisharebest\Webtrees\Tree;

/**
 * Class BatchUpdateModule
 */
class BatchUpdateModule extends AbstractModule implements ModuleConfigInterface {
	/** @var string  Form parameter: chosen plugin*/
	private $plugin;

	/** @var string Form parameter: record to update */
	private $xref;

	/** @var string Form parameter: how to update record */
	private $action;

	/** @var string Form parameter: additional details for $action */
	private $data;

	/** @var BatchUpdateBasePlugin[] All available plugins */
	private $plugins;

	/** @var BatchUpdateBasePlugin  The current plugin */
	private $PLUGIN;

	/** @var string[] n array of all xrefs that might need to be updated */
	private $all_xrefs;

	/** @var string The previous xref to process */
	private $prev_xref;

	/** @var String The current xref being process */
	private $curr_xref;

	/** @var string The next xref to process */
	private $next_xref;

	/** @var GedcomRecord The record corresponding to $curr_xref */
	private $record;

	/**
	 * How should this module be labelled on tabs, menus, etc.?
	 *
	 * @return string
	 */
	public function getTitle() {
		return /* I18N: Name of a module */ I18N::translate('Batch update');
	}

	/**
	 * A sentence describing what this module does.
	 *
	 * @return string
	 */
	public function getDescription() {
		return /* I18N: Description of the “Batch update” module */ I18N::translate('Apply automatic corrections to your genealogy data.');
	}

	/**
	 * This is a general purpose hook, allowing modules to respond to routes
	 * of the form module.php?mod=FOO&mod_action=BAR
	 *
	 * @param string $mod_action
	 */
	public function modAction($mod_action) {
		switch ($mod_action) {
			case 'admin_batch_update':
				echo $this->main();
				break;

			default:
				http_response_code(404);
				break;
		}
	}

	/**
	 * Main entry point
	 *
	 * @return string
	 */
	private function main() {
		global $WT_TREE;

		$this->plugins = $this->getPluginList(); // List of available plugins
		$this->plugin  = Filter::get('plugin'); // User parameters
		$this->xref    = Filter::get('xref', WT_REGEX_XREF);
		$this->action  = Filter::get('action');
		$this->data    = Filter::get('data');

		// Don't do any processing until a plugin is chosen.
		if ($this->plugin && array_key_exists($this->plugin, $this->plugins)) {
			$this->PLUGIN = new $this->plugin;
			$this->PLUGIN->getOptions();
			$this->getAllXrefs();

			switch ($this->action) {
				case 'update':
					$record = self::getLatestRecord($this->xref, $this->all_xrefs[$this->xref]);
					if ($this->PLUGIN->doesRecordNeedUpdate($this->xref, $record)) {
						$newrecord = $this->PLUGIN->updateRecord($this->xref, $record);
						if ($newrecord != $record) {
							if ($newrecord) {
								GedcomRecord::getInstance($this->xref, $WT_TREE)->updateRecord($newrecord, !$this->PLUGIN->chan);
							} else {
								GedcomRecord::getInstance($this->xref, $WT_TREE)->deleteRecord();
							}
						}
					}
					$this->xref = $this->findNextXref($this->xref);
					break;
				case 'update_all':
					foreach ($this->all_xrefs as $xref => $type) {
						$record = self::getLatestRecord($xref, $type);
						if ($this->PLUGIN->doesRecordNeedUpdate($xref, $record)) {
							$newrecord = $this->PLUGIN->updateRecord($xref, $record);
							if ($newrecord != $record) {
								if ($newrecord) {
									GedcomRecord::getInstance($xref, $WT_TREE)->updateRecord($newrecord, !$this->PLUGIN->chan);
								} else {
									GedcomRecord::getInstance($xref, $WT_TREE)->deleteRecord();
								}
							}
						}
					}
					$this->xref = '';
					break;
			}

			// Make sure that our requested record really does need updating.
			// It may have been updated in another session, or may not have
			// been specified at all.
			if (array_key_exists($this->xref, $this->all_xrefs) &&
				$this->PLUGIN->doesRecordNeedUpdate($this->xref, self::getLatestRecord($this->xref, $this->all_xrefs[$this->xref]))) {
				$this->curr_xref = $this->xref;
			}
			// The requested record doesn't need updating - find one that does
			if (!$this->curr_xref) {
				$this->curr_xref = $this->findNextXref($this->xref);
			}
			if (!$this->curr_xref) {
				$this->curr_xref = $this->findPrevXref($this->xref);
			}
			// If we've found a record to update, get details and look for the next/prev
			if ($this->curr_xref) {
				$this->prev_xref = $this->findPrevXref($this->curr_xref);
				$this->next_xref = $this->findNextXref($this->curr_xref);
			}
		}

		// HTML common to all pages
		$controller = new PageController;
		$controller
			->setPageTitle(I18N::translate('Batch update'))
			->restrictAccess(Auth::isAdmin())
			->pageHeader();

		echo $this->getJavascript();
		echo Bootstrap4::breadcrumbs([
			route('admin-control-panel') => I18N::translate('Control panel'),
			route('admin-modules')       => I18N::translate('Module administration'),
		], $controller->getPageTitle());
		?>
		<h1><?= $controller->getPageTitle() ?></h1>

		<form id="batch_update_form" class="form-horizontal" action="module.php">
			<input type="hidden" name="mod" value="batch_update">
			<input type="hidden" name="mod_action" value="admin_batch_update">
			<input type="hidden" name="xref"   value="' . $this->xref . '">
			<input type="hidden" name="action" value=""><?php // will be set by javascript for next update  ?>
			<input type="hidden" name="data"   value=""><?php // will be set by javascript for next update  ?>
			<div class="row form-group">
				<label class="col-sm-3 col-form-label"><?= I18N::translate('Family tree') ?></label>
				<div class="col-sm-9">
		<?= Bootstrap4::select(Tree::getNameList(), $WT_TREE->getName(), ['id' => 'ged', 'name' => 'ged', 'onchange' => 'reset_reload();']) ?>
				</div>
			</div>
			<div class="row form-group">
				<label class="col-sm-3 col-form-label"><?= I18N::translate('Batch update') ?></label>
				<div class="col-sm-9">
					<select class="form-control" name="plugin" onchange="reset_reload();">
						<?php if (!$this->plugin): ?>
							<option value="" selected></option>
						<?php endif ?>
						<?php foreach ($this->plugins as $class => $plugin): ?>
							<option value="<?= $class ?>" <?= $this->plugin == $class ? 'selected' : '' ?>><?= $plugin->getName() ?></option>
					<?php endforeach ?>
					</select>
					<?php if ($this->PLUGIN): ?>
						<p class="small text-muted"><?= $this->PLUGIN->getDescription() ?></p>
		<?php endif ?>
				</div>
			</div>

				<?php if (!Auth::user()->getPreference('auto_accept')): ?>
				<div class="alert alert-danger">
				<?= I18N::translate('Your user account does not have “automatically accept changes” enabled. You will only be able to change one record at a time.') ?>
				</div>
			<?php endif ?>

			<?php // If a plugin is selected, display the details ?>
			<?php if ($this->PLUGIN): ?>
				<?= $this->PLUGIN->getOptionsForm() ?>
				<?php if (substr($this->action, -4) == '_all'): ?>
					<?php // Reset - otherwise we might "undo all changes", which refreshes the ?>
					<?php // page, which makes them all again!  ?>
					<script>reset_reload();</script>
			<?php else: ?>
					<hr>
					<div id="batch_update2" class="col-sm-12">
						<?php if ($this->curr_xref): ?>
							<?php // Create an object, so we can get the latest version of the name. ?>
								<?php $this->record = GedcomRecord::getInstance($this->curr_xref, $WT_TREE) ?>
							<div class="row form-group">
								<?= self::createSubmitButton(I18N::translate('previous'), $this->prev_xref) ?>
								<?= self::createSubmitButton(I18N::translate('next'), $this->next_xref) ?>
							</div>
							<div class="row form-group">
								<a class="lead" href="<?= e($this->record->url()) ?>"><?= $this->record->getFullName() ?></a>
								<?= $this->PLUGIN->getActionPreview($this->record) ?>
							</div>
							<div class="row form-group">
							<?= implode(' ', $this->PLUGIN->getActionButtons($this->curr_xref, $this->record)) ?>
							</div>
						<?php else: ?>
							<div class="alert alert-info"><?= I18N::translate('Nothing found.') ?></div>
					<?php endif ?>
					</div>
				<?php endif ?>
		<?php endif ?>
		</form>
		<?php
	}

	/**
	 * Find the next record that needs to be updated.
	 *
	 * @param string $xref
	 *
	 * @return string|null
	 */
	private function findNextXref($xref) {
		foreach (array_keys($this->all_xrefs) as $key) {
			if ($key > $xref) {
				$record = self::getLatestRecord($key, $this->all_xrefs[$key]);
				if ($this->PLUGIN->doesRecordNeedUpdate($key, $record)) {
					return $key;
				}
			}
		}

		return null;
	}

	/**
	 * Find the previous record that needs to be updated.
	 *
	 * @param string $xref
	 *
	 * @return string|null
	 */
	private function findPrevXref($xref) {
		foreach (array_reverse(array_keys($this->all_xrefs)) as $key) {
			if ($key < $xref) {
				$record = self::getLatestRecord($key, $this->all_xrefs[$key]);
				if ($this->PLUGIN->doesRecordNeedUpdate($key, $record)) {
					return $key;
				}
			}
		}

		return null;
	}

	/**
	 * Generate a list of all XREFs.
	 */
	private function getAllXrefs() {
		global $WT_TREE;

		$sql  = [];
		$vars = [];
		foreach ($this->PLUGIN->getRecordTypesToUpdate() as $type) {
			switch ($type) {
				case 'INDI':
					$sql[]  = "SELECT i_id, 'INDI' FROM `##individuals` WHERE i_file=?";
					$vars[] = $WT_TREE->getTreeId();
					break;
				case 'FAM':
					$sql[]  = "SELECT f_id, 'FAM' FROM `##families` WHERE f_file=?";
					$vars[] = $WT_TREE->getTreeId();
					break;
				case 'SOUR':
					$sql[]  = "SELECT s_id, 'SOUR' FROM `##sources` WHERE s_file=?";
					$vars[] = $WT_TREE->getTreeId();
					break;
				case 'OBJE':
					$sql[]  = "SELECT m_id, 'OBJE' FROM `##media` WHERE m_file=?";
					$vars[] = $WT_TREE->getTreeId();
					break;
				default:
					$sql[]  = "SELECT o_id, ? FROM `##other` WHERE o_type=? AND o_file=?";
					$vars[] = $type;
					$vars[] = $type;
					$vars[] = $WT_TREE->getTreeId();
					break;
			}
		}
		$this->all_xrefs = Database::prepare(implode(' UNION ', $sql) . ' ORDER BY 1 ASC')
				->execute($vars)
				->fetchAssoc();
	}

	/**
	 * Scan the plugin folder for a list of plugins
	 *
	 * @return BatchUpdateBasePlugin[]
	 */
	private function getPluginList() {
		$plugins    = [];
		$dir_handle = opendir(__DIR__ . '/BatchUpdate');
		while (($file = readdir($dir_handle)) !== false) {
			if (substr($file, -10) == 'Plugin.php' && $file !== 'BatchUpdateBasePlugin.php') {
				$class           = '\Fisharebest\Webtrees\Module\BatchUpdate\\' . basename($file, '.php');
				$plugins[$class] = new $class;
			}
		}
		closedir($dir_handle);

		return $plugins;
	}

	/**
	 * Javascript that gets included on every page
	 *
	 * @return string
	 */
	private function getJavascript() {
		return
			'<script>' .
			'function reset_reload() {' .
			' var bu_form=document.getElementById("batch_update_form");' .
			' bu_form.xref.value="";' .
			' bu_form.action.value="";' .
			' bu_form.data.value="";' .
			' bu_form.submit();' .
			'}</script>';
	}

	/**
	 * Create a submit button for our form
	 *
	 * @param string $text
	 * @param string $xref
	 * @param string $action
	 * @param string $data
	 *
	 * @return string
	 */
	public static function createSubmitButton($text, $xref, $action = '', $data = '') {
		return
			'<input class="btn btn-primary" type="submit" value="' . $text . '" onclick="' .
			'this.form.xref.value=\'' . e($xref) . '\';' .
			'this.form.action.value=\'' . e($action) . '\';' .
			'this.form.data.value=\'' . e($data) . '\';' .
			'return true;"' .
			($xref ? '' : ' disabled') . '>';
	}

	/**
	 * Get the current view of a record, allowing for pending changes
	 *
	 * @param string $xref
	 * @param string $type
	 *
	 * @return string
	 */
	public static function getLatestRecord($xref, $type) {
		global $WT_TREE;

		switch ($type) {
			case 'INDI':
				return Individual::getInstance($xref, $WT_TREE)->getGedcom();
			case 'FAM':
				return Family::getInstance($xref, $WT_TREE)->getGedcom();
			case 'SOUR':
				return Source::getInstance($xref, $WT_TREE)->getGedcom();
			case 'REPO':
				return Repository::getInstance($xref, $WT_TREE)->getGedcom();
			case 'OBJE':
				return Media::getInstance($xref, $WT_TREE)->getGedcom();
			case 'NOTE':
				return Note::getInstance($xref, $WT_TREE)->getGedcom();
			default:
				return GedcomRecord::getInstance($xref, $WT_TREE)->getGedcom();
		}
	}

	/**
	 * The URL to a page where the user can modify the configuration of this module.
	 * These links are displayed in the admin page menu.
	 *
	 * @return string
	 */
	public function getConfigLink() {
		return Html::url('module.php', [
			'mod'        => $this->getName(),
			'mod_action' => 'admin_batch_update',
		]);
	}
}
