<?php

namespace WpieFw\Settings;

use WpieFw\Wpie\WpieGlobals;
use WpieFw\Notices\WpieNotices;
use WpieFw\Files\WpieFileFinder;
use WpieFw\Helpers\WpieSettingsHelper;
use WpieFw\Helpers\WpieMultisiteHelper;
use WpieFw\Settings\WpieSettingsPage;
use WpieFw\Settings\WpieSettingsValidator;
use WpieFw\Settings\Iterator\WpieAbstractSetting;
use WpieFw\Settings\Iterator\WpieSettingsCollection;
use WpieFw\Settings\Iterator\WpieSetting;
use WpieFw\Settings\I18n\WpieSettingsI18n;
use WpieFw\Exceptions\WpieInvalidArgumentException;
use WpieFw\Exceptions\WpieExceptionInterface;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * @author WeePie Plugins
 *
 */
final class WpieSettingsProcessor
{
	/**
	 * @var WpieSettingsCollection
	 */
	public $settings;

	/**
	 * The current WpieSetting instance
	 *
	 * If on a settings page, this holds the current WpieSetting instance
	 *
	 * @var WpieAbstractSetting
	 */
	public $setting = null;

	/**
	 * WpieSettingsPage Instance
	 *
	 * @var WpieSettingsPage
	 */
	public $settingsPage = null;

	/**
	 * Flag if current namespace has a settings page
	 *
	 * @var bool
	 */
	public $hasSettingsPage = false;

	/**
	 * WpieSettingsValidator instance
	 *
	 * @var WpieSettingsValidator
	 */
	private $validator;

	/**
	 * The active locale
	 *
	 * @var string
	 */
	private $locale;

	/**
	 * WpieSettingsI18n instance
	 *
	 * @var WpieSettingsI18n
	 */
	private $i18n;

	/**
	 * WpieGlobals instance
	 *
	 * @var WpieGlobals
	 */
	private $globals;

	/**
	 * Constructor
	 *
	 * @param WpieSettingsCollection $collection
	 * @param string $locale
	 * @param WpieGlobals $globals
	 */
	public function __construct( WpieSettingsCollection $collection, string $locale, WpieGlobals $globals )
	{
		// Store the settigs collection
		$this->settings = $collection;

		if( !$this->settings->hasCollection() ) {
			return;
		}

		// set the active locale
		$this->locale = $locale;
		// Store Plugin globals
		$this->globals = $globals;
		// Create WpieSettingsValidator instance
		$this->validator = new WpieSettingsValidator();

		// handle i18n strings
		// Create WpieFileFinder instance and set $recursive to FALSE
		$I18nFinder = new WpieFileFinder( $this->globals->get( 'settingsPath' ), 'php', '', 'i18n.php', false );
		$this->i18n = new WpieSettingsI18n( $this->settings, $I18nFinder, $this->globals->nameSpace );
		// Process  the file and include
		$this->i18n
			->process()
			->includeFile();

		foreach ( $this->settings as $setting ) {
			if( isset( $_REQUEST['option_page'] ) && $_REQUEST['option_page'] === $setting->getName() ) {
				// Set the current setting
				$this->setting = $setting;

				// Register the settings during the admin_init hook
				add_action( 'admin_init' , [ $this, 'register' ] );
				//
				add_filter( 'pre_update_option_'.$this->setting->getName(), [ $this, 'beforeUpdate' ], 10, 2 );
			}
		}
	}

	/**
	 * Get a setting by offset
	 *
	 * @param string $offset
	 *
	 * @uses WpieSettingsCollection::get()
	 *
	 * @since 2.0
	 *
	 * @return WpieAbstractSetting|boolean
	 */
	public function get( $offset = '' )
	{
		if( $this->settings->offsetExists( $offset ) ) {
			return $this->settings->get( $offset );
		} else {
			return false;
		}
	}

	/**
	 * Get a setting name
	 *
	 * @param string $offset
	 *
	 * @uses WpieSetting::getName()
	 *
	 * @return string
	 */
	public function getName( $offset = '' )
	{
		if( $setting = $this->get( $offset ) ) {
			return $setting->getName();
		} else {
			return '';
		}
	}

	/**
	 * Init the settings page
	 *
	 * @uses WpieSettingsPage::init()
	 */
	public function initSettingsPage()
	{
		if( null !== $this->settingsPage ) {
			$this->settingsPage->init( $this->settings );
		}
	}

	public function register()
	{
		if( function_exists( 'register_setting' ) ) {
			register_setting( $this->setting->getName(), $this->setting->getName(),  [ 'sanitize_callback' => [ $this, 'validate' ] ] );
		}
	}

	/**
	 * Callback for sanitize_option_{$option_name} hook
	 *
	 * The sanitize_option_{$option_name} hook is called threw 'register_setting()'
	 *
	 * @access public
	 *
	 * @param array $data
	 *
	 * @todo: implent hook params: $value, $option, $original_value?
	 *
	 * @since 1.2
	 */
	public function validate( $data )
	{
		static $counter = 0;
		$counter++;

		if( 2 === $counter ) {
			return $data;
		}

		try {
			$page = $_REQUEST['option_page'];

			if( !isset( $data[$this->globals->nameSpace.'_tab'] ) ) {
				return $data;
			}

			if( $this->globals->get( 'isResetting' ) )  {
				WpieNotices::add( $this->globals->nameSpace,  __( sprintf( '%s settings resetted', $this->globals->get( 'pluginName' ) ) , 'weepie' ), 'success', $page );
				return $data;
			}

			$warnings = $this->validator->validate( $data, $this->setting->getFieldsToValidate() );

			do_action( $this->globals->nameSpace . '_validate_settings_'.$page, $data, $page );

			// let modules hook into the validate process
			$warnings = apply_filters( $this->globals->nameSpace . '_settings_warnings_'.$page, $warnings, $data );
			$warnings = apply_filters( $this->globals->nameSpace . '_settings_warnings', $warnings, $page, $data );

			$this->validator->hasWarnings = ( empty( $warnings ) ) ? false : true;

			// add WP setting errors if errors occured
			if( $this->validator->hasWarnings ) {
				foreach( $warnings as $name => $warning ) {
					if( '' !== $name && $this->setting->has( $name ) ) {
						$data[$name] = $this->setting->get( $name );
					}

					WpieNotices::add( $this->globals->nameSpace, $warning, 'warning', $page );
				}
			}

			// return form data
			$data = apply_filters( $this->globals->nameSpace . '_validated_data', $data, $page, $this->validator->hasWarnings );
			$data = apply_filters( $this->globals->nameSpace . '_validated_data_'.$page, $data, $this->validator->hasWarnings );

		} catch ( WpieExceptionInterface $e ) {
			WpieNotices::add( $this->globals->nameSpace, $e->getMessage(), 'error', $page );
		}

		unset( $warnings );

		return $data;
	}

	/**
	 * Do some setting related actions before updating the settings option
	 *
	 * This is a callback hooked to filter 'pre_update_option_{$option_name}'
	 *
	 * @access public
	 *
	 * @param array $data, the new values
	 * @param array $option the old values
	 *
	 * @since 1.2
	 *
	 * @return array
	 */
	public function beforeUpdate( $data, $option )
	{
		try {
			// return if the submitted setting is not the current setting
			$page = $_POST['option_page'];
			if( $page !== $this->setting->getName() ) {
				return $option;
			}

			if( $this->globals->get( 'isResetting' ) )  {
				do_action( $this->globals->nameSpace . '_do_reset' );
				return $option;
			}

			if( is_array( $data ) ) {
				// if no warnings occured, show update notice
				if( false === $this->validator->hasWarnings ) {
					WpieNotices::add( $this->globals->nameSpace,  __( sprintf( '%s settings updated', $this->globals->get( 'pluginName' ) ) , 'weepie' ), 'success', $page );
				}

				if( !$this->setting->isLanguaged() && $data === $option ) {
					return $option;
				} elseif( $this->setting->isLanguaged() && $data === $option[$this->locale] ) {
					return $option;
				} elseif( $data === $option ) {
					return $option;
				}

				// update the settings stack with the new values
				$this->_update( $data, $page );

				if( !$this->setting->isLanguaged() ) {
					return $this->setting->getstack();
				} else {
					$option[$this->locale] = $this->setting->getstack();
					return $option;
				}
			} else {
				return $option;
			}
		} catch ( WpieExceptionInterface $e ) {
			WpieNotices::add( $this->globals->nameSpace, $e->getMessage(), 'error', $page );
		}
	}

	/**
	 * Satinize fields in given array
	 *
	 * @access private
	 *
	 * @param array $data
	 * @param array $skip Skip fieds to skip
	 *
	 * @since 1.2
	 *
	 * @return $data
	 */
	private function satinize( array $data = [], array $skip = [] ) {
		foreach ( $data as $field => $value ) {
			if( !is_string( $value ) ) {
				continue;
			}
			if( empty( $skip ) ) {
				$data[$field] = sanitize_text_field( $value );
			} elseif( !empty( $skip ) && !in_array( $field, $skip ) ) {
				$data[$field] = sanitize_text_field( $value );
			}
		}

		return $data;
	}

	/**
	 * Update the stack
	 *
	 * NOTE:checkboxes cant use the isset check!
	 *
	 * @access private
	 *
	 * @uses self::satinize()
	 * @uses apply_filters() to let modules hook into this process
	 *
	 * @param array $data
	 * @param string 	$setting
	 *
	 * @since 	1.2
	 */
	private function _update( $data, $page = '' )
	{
		if( '' === $page ) {
			throw new WpieInvalidArgumentException( 'Cannot persist settings data. Parameter $page is empty.' );
		}

		// extra check, only update current setting
		if( $page !== $this->setting->getName() ) {
			return;
		}

		// get all fields
		$fields = $this->setting->getFields();
		// get the fields to be validated
		$fieldsToValidate = $this->setting->getFieldsToValidate();

		// check for fields that can skip satinizing (checkboxex, selectboxes etc.)
		$skip = [];
		foreach ( $fieldsToValidate as $name => $field ) {
			if( !WpieSettingsHelper::isFieldToSanitize( $field ) ) {
				$skip[] = $name;
			}
		}

		/**
		 * Filter the $skip array
		 *
		 * @param array 	$skip
		 * @param string $setting
		 *
		 * @since 1.x
		 */
		$skip = apply_filters( $this->globals->nameSpace . '_settings_skip_fields', $skip, $page );

		// do the satinizing
		$data = $this->satinize( $data, $skip );

		foreach ( $fields as $name => $field ) {
			if( WpieSettingsHelper::isCheckbox( $field ) ) {
				$this->setting->set( $name, ( isset( $data[$name] ) ? true : false ) );
			} elseif( WpieSettingsHelper::isWpPostTypeList( $field ) ) {
				$isClickSelect = WpieSettingsHelper::isClickSelect( $field );
				if( $isClickSelect ) {
					$attributes = WpieSettingsHelper::getAttr( $field );
					$context = ( isset( $attributes['_context'] ) && '' != $attributes['_context'] ) ? $attributes['_context'] : false;
					if( false !== $context ) {
						$context = str_replace( '-', '_', $context );
						$name = $context . '_selected';
						if( isset( $data[$name] ) ) {
							$this->setting->set( $name,	(int)$data[$name] );
						}
					}
				}
			} else {
				if( isset( $data[$name] ) )	{
					$this->setting->set( $name,	$data[$name] );
				}
			}
		}

		/**
		 * Let modules alter the stack
		 *
		 * @param array $moduleUpdateSettings
		 * @param array $data
		 * @param array $page
		 */
		$moduleUpdateSettings = apply_filters( $this->globals->nameSpace . '_update_settings', [], $data, $page );

		if( !empty( $moduleUpdateSettings ) ) {
			foreach ( $moduleUpdateSettings as $k => $v ) {
				$this->setting->set( $k, $v );
			}
		}
	}
}