<?php

/**
 * Represents an admin page. This class is not used directly. Instead, more specific child classes are used.
 *
 * @package Phosphor
 * @since 2.3.0
 **/

class Phosphor_Options_Framework {

    /**
     * The slug of this page.
     *
     * @access protected
     * @var string
     **/
	protected $page_slug;

	/**
	 * The title of this page.
	 *
     * @access protected
	 * @var string
	 **/
	protected $page_title;

	/**
	 * The id that will be used in the get_option() function.
	 *
     * @access protected
	 * @var string
	 **/
	protected $option_id;

	/**
	 * Class constructor. Initializes all the class variables.
	 *
	 * @var string $page_slug Page slug.
	 * @var string $page_title Page Title.
	 * @var string $option_id The option ID that will be used in the get_option() function.
	 **/
	public function __construct($page_slug, $page_title, $option_id)
	{
		$this->load_dependencies();

		$this->page_slug 	= $page_slug;
		$this->page_title 	= $page_title;
		$this->option_id 	= $option_id;
		$this->form 		= new Phosphor_Backend_Form();
		$this->sections 	= $this->process_sections($this->get_settings_sections());
		$this->fields 		= $this->process_fields($this->get_settings_fields());
		$this->options 		= get_option( $this->option_id );
		$this->hidden_fields = array();

		$this->register_hooks();
	}

	function load_dependencies() {
		require_once 'class.backend-form.php';
	}

	public function restore_defaults() {
		if( !count($_POST) )
			return;

		foreach ($this->sections as $section_id => $section) {
			if( isset( $_POST[ 'restore-' . $section_id . '-sec' ] ) ) {

				$_POST[ $this->option_id ][ $section_id ] = $this->init_partial(
					array(),
					$this->fields[ $section_id ],
					true
				);
			}
		}

		if( isset($_POST['restore-all']) ) {
			$_POST[ $this->option_id ] = $this->init_settings( array(), true );
		}
	}

	/**
	 * Registers hooks with WordPress using add_action() and add_filter() functions.
	 **/
	protected function register_hooks() {
		add_action( 'admin_menu', array($this, 'add_page') );
		add_action( 'admin_init', array($this, 'create_options') );
		add_action( 'admin_enqueue_scripts', array($this, 'enqueue') );
		add_action( 'after_switch_theme', array($this, 'save_default_settings') );

		add_action( 'admin_init', array($this, 'restore_defaults'), -999 );

		// add_action( 'admin_init', array($this, 'testing_defaults') );
		// add_action( 'admin_init', array($this, 'testing_sanitization') );
		// add_action( 'admin_init', array($this, 'testing_init') );
	}

	/**
	 * Function for returning an array of settings sections. This needs to be overriden by a child class in order for the options system to work.
	 *
	 * @return array Array of settings sections
	 **/
	protected function get_settings_sections(){
		die('get_settings_sections() is an abstract function and needs to be overridden by a child class');
	}

	/**
	 * Function for returning an array of settings fields. This needs to be overriden by a child class in order for the options system to work.
	 *
	 * @return array Array of settings sections
	 **/
	protected function get_settings_fields() {
		die('get_settings_fields() is an abstract function and needs to be overridden by a child class');
	}


	/**
	 * This function gets called whenever the theme is activated. It extracts the default values from the fields array and saves them in the options table.
	 **/
	public function save_default_settings() {
		$curr_options 	= get_option($this->option_id, array());
		$curr_options 	= $this->init_settings( $curr_options );
		update_option( $this->option_id, $curr_options );
	}

	/**
	 * Takes an array of options currently saved in the DB and fills in the blanks with default values. It is used by save_default_settings().
	 *
	 * @var array An array of options.
	 * @return array An array with all the missing items filled with defaults.
	 **/
	public function init_settings( $curr_options ){

		foreach ($this->fields as $section_key => $section_fields) {
			$curr_options[ $section_key ] = $this->init_partial(
				isset( $curr_options[ $section_key ] ) ? $curr_options[ $section_key ] : array(),
				$section_fields
			);
		}
		return $curr_options;
	}

	/**
	 * Responsible for filling in the blanks with default values. This function is called recursively to take care of nested fields.
	 *
	 * @var array A portion of the original options array.
	 * @var array An array of fields. This could consist of nested fields or the fields in a section.
	 * @return array The first argument with all the missing items filled with defaults.
	 **/
	public function init_partial( $current_options, $partial_fields ) {

		foreach ($partial_fields as $field_id => $field) {

			if( $this->form->is_nested($field) ){
				$sub_fields = isset( $field['items'] ) ? $field['items'] : array();
				$current_options[ $field_id ] = $this->init_partial(
					isset( $current_options[ $field_id ] ) ? $current_options[ $field_id ] : array(),
					$sub_fields
				);
			}
			else {

				// If there is nothing present in the DB against this field's ID then save the default value.
				// If the field is hidden then overwrite its value in the current options with the default value.

				// TODO: Empty values are valid so they should be handled properly.
				if( !isset($current_options[ $field_id ]) || $this->form->is_hidden($field) ){
					$current_options[ $field_id ] = $field['def'];
				}
			}
		}
		return $current_options;
	}

	/**
	 * Sanitizes the values input by the user. This function is the callback used in the register_setting() function provided by WordPress.
	 *
	 * @var array The array containing all the values entered by the user.
	 * @return array An array containing sanitized values.
	 **/
	public function sanitize_input( $input_items ){
		foreach ($this->fields as $section_key => $section_fields) {
			$input_items[ $section_key ] = $this->sanitize_partial( $input_items[ $section_key ], $section_fields );
		}

		return $input_items;
	}

	/**
	 * Sanitizes some user-entered values at a time. This function is called recursively to take care of nested fields.
	 *
	 * @var array An array containing some of the the values entered by the user.
	 * @var array An array of fields corresponding to the values entered by the user.
	 * @return array An array containing sanitized values.
	 **/
	public function sanitize_partial( $input_items, $section_fields ){
		foreach ($section_fields as $field_id => $field) {
			if( $this->form->is_nested($field) ){
				$sub_fields = isset( $field['items'] ) ? $field['items'] : array();
				$input_items[ $field_id ] = $this->sanitize_partial( $input_items[ $field_id ], $sub_fields );
			}
			else {
				$input_items[ $field_id ] = $this->form->sanitize( $input_items[ $field_id ], $field['type'] );
			}
		}

		return $input_items;
	}

	/**
	 * Adds missing arguments to each settings section in the passed array.
	 *
	 * @var array A 2D array containing the information for all the settings sections.
	 * @return array A modified array in which all the sections have the requred arguments.
	 **/
	private function process_sections($sections) {
		return array_map(array($this, 'set_section_defaults'), $sections);
	}

	/**
	 * Uses the wp_parse_args() function to add the missing arguments to a section array.
	 *
	 * @var array An array containing infomation about a single settings section.
	 * @return array The passed array with all the missing items filled in.
	 **/
	private function set_section_defaults( $section ){
		$defaults = array(
			'title' 				=> '',
			'callback' 				=> null,
			'customizer_section' 	=> false
		);

		return wp_parse_args($section, $defaults);
	}

	/**
	 * Adds missing arguments to each field in the passed array.
	 *
	 * @var array A 2D array containing the information for all the fields. In this array fields are grouped by sections.
	 * @return array A modified array in which all the fields have the requred arguments.
	 **/
	private function process_fields($fields){
		foreach ($fields as $section_key => $section_fields) {
			$fields[ $section_key ] = $this->process_fields_partial( $fields[ $section_key ] );
		}
		return $fields;
	}

	/**
	 * Adds missing arguments to each field in the passed array. This function is called recursively to cater nested fields.
	 *
	 * @var array A 2D array containing the information for all the fields. In this array the fields are not grouped.
	 * @return array A modified array in which all the fields have the requred arguments.
	 **/
	private function process_fields_partial( $partial_fields ) {
		foreach ($partial_fields as $field_key => $field) {
			$partial_fields[ $field_key ] = $this->form->parse_args($field);

			if( $this->form->is_nested($field) ){
				$sub_fields = isset( $field['items'] ) ? $field['items'] : array();
				$partial_fields[ $field_key ]['items'] = $this->process_fields_partial( $sub_fields );
			}
		}

		return $partial_fields;
	}

	public function testing_defaults() {
		echo "<pre>";
		var_dump( $this->process_sections( $this->get_settings_sections() ) );
		var_dump( $this->process_fields( $this->get_settings_fields() ) );
		echo "</pre>";
		die();
	}

	public function testing_sanitization() {
		if( !count($_POST) )
			return;

		echo "<pre>";
		var_dump( $this->sanitize_input( $_POST[ $this->option_id ] ) );
		echo "</pre>";
		die();
	}

	public function testing_init() {
		echo "<pre>";
		var_dump( $this->init_settings( array() ) );
		echo "</pre>";
		die();
	}

	/**
	 * Enqueues the CSS and JS files required for the proper functioning of the plugin.
	 *
	 * @var string $hook The hook of the page currently being viewed by the user.
	 **/
	public function enqueue($hook) {
		if( $this->page_hook != $hook )
			return;

		wp_enqueue_script( "phosphor-op-script" );

		wp_enqueue_style( "phosphor-op-styles" );
		wp_enqueue_style( "phosphor-fontawesome" );
		wp_enqueue_style( "phosphor-jquery-ui" );
	}

	/**
	 * Adds the options page to the admin menu using the add_submenu_page() function.
	 **/
	public function add_page() {
		$this->page_hook = add_theme_page(
			$this->page_title, 		// Page Title
			$this->page_title, 		// Menu title
			'manage_options', 		// Capability
			$this->page_slug, 		// Page slug
			array($this, 'display') // Callback for displaying the page
		);
	}

	public function section_callback($section){
		?>
			</div> <!-- end section head -->
			<div class="op-section-body">
				<?php submit_button( __('Restore Section Defaults', 'phosphor'), 'button op-restore op-section-restore', 'restore-' . $section['id'] . '-sec', false ); ?>
				<?php
					if( isset($this->hidden_fields[ $section['id'] ]) ){
						foreach ($this->hidden_fields[ $section['id'] ] as $field_args) {
							$this->form->render( $field_args );
						}					
					}
				?>
		<?php

		$user_callback = isset( $this->sections[ $section['id'] ]['callback'] ) && is_callable( $this->sections[ $section['id'] ]['callback'] ) ? $this->sections[ $section['id'] ]['callback'] : null;
		if( $user_callback ) {
			call_user_func( $user_callback, $section );
		}
	}

	/**
	 * Registers all the settings sections and fields. 
	 **/
	public function create_options() {
		$page 			= $this->option_id;
		$callback 		= array($this->form, 'render');
		$seperator_count = 0;
		$end_section 	= end($this->sections);
		$sec_callback 	= array($this, 'section_callback');
		reset($this->sections);

		foreach ($this->sections as $section_id => $section) {

			$title_prefix = isset( $section['icon'] ) ? sprintf('<i class="fa fa-%s"></i> ', $section['icon']) : '';

			add_settings_section(
				$section_id,
				$title_prefix . $section['title'],
				$sec_callback,
				$page
			);

			register_setting(
				$page,
				$this->option_id,
				array($this, 'sanitize_input')
			);

			if(
				is_array( $this->fields[ $section_id ] )
				&& count( $this->fields[ $section_id ] )
			){
				foreach ( $this->fields[ $section_id ] as $field_id => $field ) {
					$indices 	= array($section_id, $field_id);
					$args 		= array(
						'current' 		=> $this->form->get_current_val($this->options, $indices),
						'field_name' 	=> $this->form->make_field_name($this->option_id, $indices)
					);

					// If this is a hidden field then render it manually otherwise add a settings field.
					if( $this->form->is_hidden($field) ){
						$this->hidden_fields[ $section_id ][] = array_merge( $field, $args );
					}
					else {
						add_settings_field(
							$field_id,
							$field['title'],
							$callback,
							$page,
							$section_id,
							array_merge( $field, $args )
						);
					}
				}
			}

			if( $section != $end_section ){
				add_settings_section(
					'noid_section_' . $seperator_count,
					null,
					array($this, 'section_seperator'),
					$page
				);

				$seperator_count++;
			}
		}
	}

	/**
	 * Outputs the contents of the page with the help of WordPress' settings API.
	 **/
	public function display(){
	    ?>
	    <div class="wrap">
	    	<h2><i class="fa fa-gear"></i> <?php echo $this->page_title; ?></h2>
	    	<?php settings_errors(); ?>
	    	<div class="op-container">
		    	<div class="op-main">
				    <form method="post" action="options.php">
				    	<?php settings_fields( $this->option_id ); ?>
				    	<div class="op-settings-section">
				    		<div class="op-section-head">
							    <?php do_settings_sections( $this->option_id ); ?>
							</div>
						</div>
						<p>
							<?php
								submit_button( __('Save Changes', 'phosphor'), 'primary', 'save-changes', false);
								submit_button( __('Restore Default Settings', 'phosphor'), 'button op-restore op-full-restore', 'restore-all', false );
							?>
						</p>
					</form>
				</div>
				<div class="op-sidebar">
					<?php $this->display_sidebar(); ?>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Callback function for seperator sections. These sections are registered just so this function can output some HTML between the real sections. 
	 **/
	public function section_seperator() {
		?>
				</div> <!-- end section body -->
			</div> <!-- end settings section -->
			<div class="op-settings-section">
				<div class="op-section-head">
		<?php
	}

	/**
	 * If required, the class extending this class can override this function to display content as a sidebar. 
	 **/
	protected function display_sidebar() {}
}

?>