<?php

namespace WpieFw\Settings\Iterator;

use WpieFw\Files\WpieFile;
use WpieFw\Helpers\WpieSettingsHelper;
use WpieFw\Helpers\WpieMultilangHelper;
use WpieFw\Settings\WpieSettingsValues;
use WpieFw\Settings\Parser\WpieAbstractSettingsParser;
use WpieFw\Exceptions\WpieInvalidArgumentException;
use WpieFw\Exceptions\WpieUnexpectedValueException;

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

abstract class WpieAbstractSetting implements \IteratorAggregate {
	/**
	 * @var string
	 */
	const PRIO = '_wpie_prio';

	/**
	 * @var string
	 */
	const IDX = '_wpie_name';

	/**
	 * @var string
	 */
	const IS_DEFAULT = '_wpie_is_default';

	/**
	 * @var WpieAbstractSettingsParser
	 */
	private $parser;

	/**
	 * @var WpieSettingsValues
	 */
	private $values;

	/**
	 * Data saved in the wp_options table
	 *
	 * @var array
	 */
	protected $stored = [];

	/**
	 * The keys of the stored data
	 *
	 * @var array
	 */
	protected $storedKeys = [];

	/**
	 * The first key (locale) of the stored data
	 *
	 * @var string
	 */
	protected $firstLocale = '';

	/**
	 * Data wich needs to saved to the wp_options table
	 *
	 * @var array
	 */
	protected $dataToPersist = [];

	/**
	 * @var array
	 */
	protected $stack = [];

	/**
	 * The settings name
	 *
	 * @var string
	 */
	protected $name = null;

	/**
	 * The setting fields as an array
	 *
	 * @var array
	 */
	protected $array = [];

	/**
	 * The fields from the settings array
	 *
	 * @var array
	 */
	protected $fields = [];

	/**
	 * The fields to be validated
	 *
	 * The fields from the settings array that are indicated to be validated
	 *
	 * @var array
	 */
	protected $fieldsToValidate = [];

	/**
	 * @var array
	 */
	protected $defaults = [];

	/**
	 * @var array
	 */
	protected $I18n = [];

	/**
	 * @var string
	 */
	protected $idx = '';

	/**
	 * @var integer
	 */
	protected $priority = 1;

	/**
	 * @var boolean
	 */
	protected $isDefault = false;

	/**
	 * Flag if setting is new (not persisted yet)
	 *
	 * @var boolean
	 */
	protected $isNew = false;

	/**
	 * Flag if setting is languaged
	 *
	 * @var boolean
	 */
	protected $isLanguaged = false;

	/**
	 * Constructor
	 *
	 * @param string $idx
	 * @param WpieAbstractSettingsParser $parser
	 * @param WpieSettingsValues $values
	 *
	 * @throws WpieInvalidArgumentException if $idx param is not given
	 */
	public function __construct( string $idx, WpieAbstractSettingsParser $parser, WpieSettingsValues $values )
	{
		// Throw an Exception if an idx is not given
		if( !$idx ) {
			throw new WpieInvalidArgumentException( 'Could not construct WpieSetting instance. No valid idx.' );
		}

		// Store constructor params
		$this->idx = strtolower( str_replace( '-', '_', $idx ) );
		$this->parser = $parser;
		$this->values = $values;

		// set the settings name
		$this->name = $this->values->getDefinitionValue( 'setting', [ 'IDX' => $this->idx ] );

		$this->init();
		$this->initStack();

		// persist if the setting is new
		if( $this->isNew ) {
			$this->mergeSettingsTmp();

			// data to persist from merging settings tmp is handled
			// at the end of the constructor
			if( !$this->hasDataToPersist() ) {
				$this->persist();
			}

			$this->values->persist( 'array', $this->array, $this->idx );

			if( $this->isDefault ) {
				$this->values->persistDefault( $this->name );
			}
		}

		// in case there is data to persist, save the data and clear dataToPersist
		if( $this->hasDataToPersist() ) {
			$this->persistWith( $this->dataToPersist );
		}
	}

	/**
	 * Declare most params for the setting
	 */
	public function init()
	{
		// Get stored stack for the setting
		$this->stored = $this->values->getSetting( $this->idx );
		$this->isNew = ( empty( $this->stored ) );

		if( $this->isNew ) {
			// Parse all
			$this->parser->parse();
			$this->array = $this->parser->getParsedArray();
			$this->defaults = $this->parser->getParsedDefaults();
			$this->I18n = $this->parser->getParsedI18n();
			// Meta data
			$this->priority = $this->parser->getParsedPriority();
			$this->isDefault = $this->parser->getParsedIsDefault();
		} else {
			$this->I18n = [];
			$this->array = $this->values->getArray( $this->idx );

			// Expecting an array here. In case an old weepie framework settings field entry is still active, a string is returned
			// There no other way then throwing an Exception then here
			if( !is_array( $this->array ) ) {
				throw new WpieUnexpectedValueException( sprintf( __( 'Could not init setting for index %s. No array retured by WpieSettingsValues::getArray().', 'weepie' ), $this->idx ) );
			}

			// check for stored option that still has language indexes
			$this->isLanguaged = WpieMultilangHelper::isLanguagedOption( $this->stored );
			if( $this->isLanguaged ) {
				$this->storedKeys = array_keys( $this->stored );
				$this->firstLocale = $this->storedKeys[0];
			}
		}

		$this->fields = WpieSettingsHelper::getFields( $this->array );
		$this->fieldsToValidate = WpieSettingsHelper::getFieldsToValidate( $this->fields );
	}

	/**
	 * Declare the stack
	 */
	abstract protected function initStack();

	/**
	 * Add an entry to the stack
	 *
	 * @param string $key
	 * @param mixed $value
	 * @param bool $persist
	 *
	 * @return \WpieFw\Settings\Iterator\WpieSetting
	 */
	abstract public function set( string $key, $value, bool $persist = false );

	/**
	 * Set the settings file WpieFile
	 *
	 * @param WpieFile $file
	 */
	public function setFile( WpieFile $file )
	{
		$this->file = $file;
	}

	/**
	 * Determine if stack has entry
	 *
	 * @param string $key
	 *
	 * @return boolean
	 */
	public function has( $key = '' )
	{
		return ( isset( $this->stack[$key] ) );
	}

	/**
	 * Persist the stack
	 *
	 * @uses WpieSettingsValues::persist()
	 *
	 * @return \WpieFw\Settings\Iterator\WpieSetting
	 */
	public function persist()
	{
		$this->values->persist( 'setting', $this->stack, $this->idx );

		$this->stored = $this->stack;

		return $this;
	}

	/**
	 * Persist given data
	 *
	 * @param array $data
	 *
	 * @uses WpieSettingsValues::persist()
	 *
	 * @return \WpieFw\Settings\Iterator\WpieSetting
	 */
	public function persistWith( array $data = [] )
	{
		$this->values->persist( 'setting', $data, $this->idx );

		$this->stored = $data;
		$this->dataToPersist = [];

		return $this;
	}

	/**
	 * Delete the persisted stack
	 *
	 * @uses WpieSettingsValues::delete()
	 *
	 * @return \WpieFw\Settings\Iterator\WpieSetting
	 */
	public function delete()
	{
		$this->values->delete( 'setting', $this->idx );

		return $this;
	}

	/**
	 * Get an entry from the stack
	 *
	 * @param string $key
	 * @param mixed $default
	 *
	 * @return mixed|null
	 */
	public function get( $key = '', $default = null )
	{
		if( $this->has( $key ) ) {
			return $this->stack[$key];
		} else {
			return $default;
		}
	}

	/**
	 * @return \Traversable
	 */
	public function getIterator(): \Traversable
	{
		return new \ArrayIterator( $this->stack );
	}

	/**
	 * Get the settings data
	 *
	 *  @return array
	 */
	public function getStack()
	{
		return $this->stack;
	}

	/**
	 * Get the stored settings data
	 *
	 *  @return array
	 */
	public function getStored()
	{
		return $this->stored;
	}

	/**
	 * Get the settings isNew flag
	 *
	 *  @return bool
	 */
	public function getIsNew()
	{
		return $this->isNew;
	}

	/**
	 * Get the settings defaults
	 *
	 * @return array
	 */
	public function getDefaults()
	{
		return $this->defaults;
	}

	/**
	 * Get the settings I18n values
	 *
	 * @return array
	 */
	public function getI18nValues()
	{
		return $this->I18n;
	}

	/**
	 * Get the setting priority
	 *
	 * @return array
	 */
	public function getPriority()
	{
		return $this->priority;
	}

	/**
	 * Get the setting idx
	 *
	 * @return string
	 */
	public function getIdx()
	{
		return $this->idx;
	}

	/**
	 * Get the setting content
	 *
	 * @uses WpieAbstractSettingsParser::getContents()
	 *
	 * @return string
	 */
	public function getContents()
	{
		return $this->parser->getContents();
	}

	/**
	 * Get the setting fields array
	 *
	 * @return array
	 */
	public function getArray()
	{
		return $this->array;
	}

	/**
	 * Get the fields from the setting fields array
	 *
	 * @return array
	 */
	public function getFields()
	{
		return $this->fields;
	}

	/**
	 * Get fields to validate
	 *
	 * Get all fields from the array that needs validation.
	 * These fields have an attribute validate="y"
	 *
	 * @return array
	 */
	public function getFieldsToValidate()
	{
		return $this->fieldsToValidate;
	}

	/**
	 * Get the setting isDefault flag
	 *
	 * @return bool
	 */
	public function getIsDefault()
	{
		return $this->isDefault;
	}

	/**
	 * Get the setting name
	 *
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Get the setting isDefault flag
	 *
	 * @return bool
	 */
	public function isLanguaged()
	{
		return $this->isLanguaged;
	}

	/**
	 * Determine if data needs to be saved to the wp_options table
	 *
	 * @return boolean
	 */
	protected function hasDataToPersist()
	{
		return ( !empty( $this->dataToPersist ) );
	}

	/**
	 * Merge the current stack with a temporary backup stack
	 *
	 * @param array $backup
	 *
	 * @uses array_merge
	 */
	abstract protected function doMergeSettingsTmp( array $backup = [] );

	/**
	 * @return \WpieFw\Settings\Iterator\WpieSetting
	 */
	private function mergeSettingsTmp()
	{
		// merge temp settings
		$backup = $this->values->getSettingTmp( $this->idx );

		if( !empty( $backup ) ) {
			$this->doMergeSettingsTmp( $backup );
		}

		// Delete the temp option always
		$this->values->deleteSettingTmp( $this->idx );

		return $this;
	}
}