<?php
/**
 * @package   DPCalendar
 * @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
 */

namespace DigitalPeak\Component\DPCalendar\Administrator\Model;

\defined('_JEXEC') or die();

use DigitalPeak\Component\DPCalendar\Administrator\Helper\DPCalendarHelper;
use DigitalPeak\Component\DPCalendar\Administrator\Mail\NotificationMailTrait;
use DigitalPeak\Component\DPCalendar\Administrator\Table\BasicTable;
use DigitalPeak\Component\DPCalendar\Administrator\Table\LocationTable;
use DigitalPeak\Component\DPCalendar\Site\Helper\RouteHelper;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Application\CMSWebApplicationInterface;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\UserFactoryAwareInterface;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

class LocationModel extends AdminModel implements UserFactoryAwareInterface
{
	use NotificationMailTrait;

	protected $text_prefix = 'COM_DPCALENDAR_LOCATION';

	protected $batch_commands = [
		'language_id' => 'batchLanguage',
		'country_id'  => 'batchCountry',
	];

	protected function canDelete($record)
	{
		if (!empty($record->state) && $record->state != -2 && !Factory::getApplication()->isClient('api')) {
			return false;
		}

		return parent::canDelete($record);
	}

	public function getItem($pk = null)
	{
		$item = parent::getItem($pk);

		if (!$item) {
			return $item;
		}

		$item->rooms = $item->rooms && $item->rooms != '{}' ? json_decode((string)$item->rooms) : [];

		if (empty($item->color)) {
			$item->color = $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->getColor($item);
		}

		$item->tags = new TagsHelper();
		$item->tags->getTagIds($item->id, 'com_dpcalendar.location');

		// Convert the params field to an array.
		$registry = new Registry();
		if (!empty($item->metadata)) {
			$registry->loadString($item->metadata);
		}
		$item->metadata = $registry;

		$user         = $this->getCurrentUser();
		$item->params = new Registry($item->params);
		$item->params->set(
			'access-edit',
			$user->authorise('core.edit', 'com_dpcalendar')
			|| ($user->authorise('core.edit.own', 'com_dpcalendar') && $item->created_by == $user->id)
		);
		$item->params->set(
			'access-delete',
			$user->authorise('core.delete', 'com_dpcalendar')
			|| ($user->authorise('core.edit.own', 'com_dpcalendar') && $item->created_by == $user->id)
		);

		if ($item->country) {
			$country = $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Country', 'Administrator')->getItem($item->country);
			if ($country) {
				Factory::getApplication()->getLanguage()->load('com_dpcalendar.countries', JPATH_ADMINISTRATOR . '/components/com_dpcalendar');
				$item->country_code       = $country->short_code;
				$item->country_code_value = Text::_('COM_DPCALENDAR_COUNTRY_' . $country->short_code);
			}
		}

		return $item;
	}

	public function getTable($type = 'Location', $prefix = 'Administrator', $config = [])
	{
		return parent::getTable($type, $prefix, $config);
	}

	public function getForm($data = [], $loadData = true, string  $controlName = 'jform')
	{
		// Get the form.
		$form = $this->loadForm('com_dpcalendar.location', 'location', ['control' => $controlName, 'load_data' => $loadData]);

		// Modify the form based on access controls.
		if (!$this->canEditState((object)$data)) {
			// Disable fields for display.
			$form->setFieldAttribute('ordering', 'disabled', 'true');
			$form->setFieldAttribute('state', 'disabled', 'true');
			$form->setFieldAttribute('publish_up', 'disabled', 'true');
			$form->setFieldAttribute('publish_down', 'disabled', 'true');

			// Disable fields while saving.
			$form->setFieldAttribute('ordering', 'filter', 'unset');
			$form->setFieldAttribute('state', 'filter', 'unset');
			$form->setFieldAttribute('publish_up', 'filter', 'unset');
			$form->setFieldAttribute('publish_down', 'filter', 'unset');
		}

		if (!DPCalendarHelper::isCaptchaNeeded()) {
			$form->removeField('captcha');
		}

		if (Factory::getApplication() instanceof SiteApplication) {
			$form->setFieldAttribute('id', 'type', 'hidden');
		}

		$this->modifyField($form, 'country');
		$this->modifyField($form, 'province');
		$this->modifyField($form, 'city');
		$this->modifyField($form, 'zip');
		$this->modifyField($form, 'street');
		$this->modifyField($form, 'number');
		$this->modifyField($form, 'url');

		return $form;
	}

	public function save($data)
	{
		$app   = Factory::getApplication();
		$input = $app->getInput();

		// Alter the title for save as copy
		if ($input->get('task') == 'save2copy') {
			$origTable = $this->getTable();

			$origTable->load($input->getInt('l_id', 0));
			$data['alias'] = $origTable->title === $data['title'] ? $origTable->alias : '';

			if ($data['title'] == $origTable->title) {
				[$title, $alias] = $this->findNewTitle($data['alias'], $data['title']);
				$data['title']   = $title;
				$data['alias']   = $alias;
			} elseif ($data['alias'] == $origTable->alias) {
				$data['alias'] = '';
			}
		}

		if (!empty($data['country']) && !is_numeric($data['country'])) {
			$country = $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Country', 'Administrator', ['ignore_request' => true])->getItem(['short_code' => $data['country']]);
			if ($country && $country->id) {
				$data['country'] = $country->id;
			}
		}

		$success = parent::save($data);
		if (!$success) {
			return $success;
		}

		if ($this->getState('location.new') === true) {
			$data['id'] = $this->getState('location.id');
			if ($app instanceof CMSWebApplicationInterface) {
				$app->setUserState('dpcalendar.location.id', $data['id']);
			}
		}

		$this->sendNotificationMail(
			'location.' . ($this->getState('location.new') ? 'create' : 'edit'),
			[(object)$data],
			[
				'location'         => $data,
				'backLinkFull'     => RouteHelper::getLocationRoute((object)$data, true),
				'formattedAddress' => $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->format([(object)$data]),
			]
		);

		return $success;
	}

	private function modifyField(Form $form, string $name): void
	{
		$params = $this->getState('params');
		if (!$params) {
			$params = ComponentHelper::getParams('com_dpcalendar');

			$app = Factory::getApplication();
			if ($app instanceof SiteApplication) {
				$params = $app->getParams();
			}
		}

		$state = $params->get('location_form_' . $name, 1);
		switch ($state) {
			case 0:
				$form->removeField($name);
				break;
			case 2:
				$form->setFieldAttribute($name, 'required', 'true');
				break;
		}
	}

	protected function loadFormData()
	{
		$app  = Factory::getApplication();
		$data = $app instanceof CMSWebApplicationInterface ? $app->getUserState(
			'com_dpcalendar.edit.location.data',
			$app->getUserState('com_dpcalendar.edit.locationform.data', [])
		) : [];

		if (empty($data)) {
			$data = $this->getItem();
		}

		if (\is_array($data)) {
			$data = (object)$data;
		}

		// Forms can't handle registry objects on load
		if (isset($data->metadata) && $data->metadata instanceof Registry) {
			$data->metadata = $data->metadata->toArray();
		}

		foreach ($this->getDefaultValues($data) as $key => $value) {
			$data->{$key} = $value;
		}

		$this->preprocessData('com_dpcalendar.location', $data);

		return $data instanceof BasicTable ? $data->getData() : $data;
	}

	private function getDefaultValues(\stdClass $item): array
	{
		$params = $this->getParams();
		$data   = [];

		// Set the default values from the params
		if (!$item->country) {
			$data['country'] = $params->get('location_form_default_country');
		}

		if (!$item->latitude) {
			$data['latitude'] = $params->get('location_form_map_latitude', 47);
		}

		if (!$item->longitude) {
			$data['longitude'] = $params->get('location_form_map_longitude', 4);
		}

		return $data;
	}

	/**
	 * @param LocationTable $table
	 */
	protected function prepareTable($table)
	{
		$date = Factory::getDate();
		$user = $this->getCurrentUser();

		$table->title = htmlspecialchars_decode($table->title, ENT_QUOTES);
		$table->alias = $table->alias ? ApplicationHelper::stringURLSafe($table->alias) : '';

		if (empty($table->alias)) {
			$table->alias = ApplicationHelper::stringURLSafe($table->title);
		}

		if (empty($table->latitude) && empty($table->longitude)) {
			$latLong          = $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->getLocation($this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->format($table), false);
			$table->latitude  = $latLong->latitude;
			$table->longitude = $latLong->longitude;
		}

		if (empty($table->id)) {
			// Set ordering to the last item if not set
			if (empty($table->ordering)) {
				$this->getDatabase()->setQuery('SELECT MAX(ordering) FROM #__dpcalendar_locations');
				$max = $this->getDatabase()->loadResult();

				$table->ordering = $max + 1;
			} else {
				// Set the values
				$table->modified    = $date->toSql();
				$table->modified_by = $user->id;
			}

			if (empty($table->version)) {
				$table->version = 0;
			}

			// Increment the content version number.
			$table->version++;
		}

		if (!empty($table->state)) {
			return;
		}

		if (!$this->canEditState($table)) {
			return;
		}

		$table->state = 1;
	}

	protected function populateState()
	{
		$app = Factory::getApplication();

		$pk = $app->getInput()->getInt('l_id', 0);
		$this->setState('location.id', $pk);
		$this->setState('form.id', $pk);

		$return = $app->getInput()->get('return', '', 'base64');
		if ($return && !Uri::isInternal(base64_decode((string)$return))) {
			$return = null;
		}

		$params = $app instanceof SiteApplication ? $app->getParams() : ComponentHelper::getParams('com_dpcalendar');
		if (!$params->get('location_form_fields_order_')) {
			$params->set(
				'location_form_fields_order_',
				ComponentHelper::getParams('com_dpcalendar')->get('location_form_fields_order_', new \stdClass())
			);
		}
		$this->setState('params', $params);

		$this->setState('return_page', base64_decode((string)($return ?: '')));
	}

	public function delete(&$pks)
	{
		$success = parent::delete($pks);
		if (!$success) {
			return $success;
		}

		// Delete associations
		$pks = ArrayHelper::toInteger($pks);
		$this->getDatabase()->setQuery('delete from #__dpcalendar_events_location where location_id in (' . implode(',', ArrayHelper::toInteger($pks)) . ')');
		$this->getDatabase()->execute();

		$locations = [];
		foreach ($pks as $pk) {
			$locations[] = $this->getItem($pk);
		}

		$this->sendNotificationMail('location.delete', $locations, [
			'location'         => $locations[0],
			'formattedAddress' => $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->format($locations),
		]);

		return $success;
	}

	public function publish(&$pks, $value = 1)
	{
		$success = parent::publish($pks, $value);
		if (!$success) {
			return $success;
		}

		$locations = [];
		foreach ($pks as $pk) {
			$locations[] = $this->getItem($pk);
		}
		$this->sendNotificationMail('location.update', $locations, [
			'formattedAddress' => $this->bootComponent('dpcalendar')->getMVCFactory()->createModel('Geo', 'Administrator')->format($locations),
		]);

		return $success;
	}

	public function getReturnPage(): string
	{
		return base64_encode((string)($this->getState('return_page', '') ?: Uri::base(true)));
	}

	protected function batchCountry(string $value, array $pks): bool
	{
		if (!$this->getCurrentUser()->authorise('core.edit', 'com_dpcalendar')) {
			throw new \Exception(Text::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));
		}

		$pks = ArrayHelper::toInteger($pks);
		$this->getDatabase()->setQuery('update #__dpcalendar_locations set country = ' . (int)$value . ' where id in (' . implode(',', $pks) . ')');
		$this->getDatabase()->execute();

		// Clean the cache
		$this->cleanCache();

		return true;
	}

	private function getParams(): Registry
	{
		if ($params = $this->getState('params')) {
			return $params;
		}

		$app = Factory::getApplication();
		if ($app instanceof SiteApplication) {
			return $app->getParams();
		}

		return ComponentHelper::getParams('com_dpcalendar');
	}

	private function findNewTitle(string $alias, string $title): array
	{
		// Alter the title & alias
		$table      = $this->getTable();
		$aliasField = $table->getColumnAlias('alias');
		$titleField = $table->getColumnAlias('title');

		while ($table->load([$aliasField => $alias])) {
			if ($title === $table->$titleField) {
				$title = StringHelper::increment($title);
			}

			$alias = StringHelper::increment($alias, 'dash');
		}

		return [$title, $alias];
	}
}
