<?php

/**
 * Admin View Base
 * Core functionality Admin UI components
 * @package Simple Lightbox
 * @subpackage Admin
 * @author Archetyped
 */
class SLB_Admin_View extends SLB_Base_Object {
	/* Properties */

	/**
	 * Labels
	 * @var array (Associative)
	 */
	protected $labels = array();

	/**
	 * Function to handle building UI
	 * @var callback
	 */
	protected $callback = null;

	/**
	 * Capability for access control
	 * @var string
	 */
	protected $capability = 'manage_options';

	/**
	 * Icon to use
	 * @var string
	 */
	protected $icon = null;

	/**
	 * View parent ID/Slug
	 * @var string
	 */
	protected $parent = null;

	/**
	 * Whether parent is a custom view or a default WP one
	 * @var bool
	 */
	protected $parent_custom = true;

	/**
	 * If view requires a parent
	 * @var bool
	 */
	protected $parent_required = false;

	/**
	 * WP-Generated hook name for view
	 * @var string
	 */
	protected $hookname = null;

	/**
	 * Raw content parameters
	 * Stores pre-rendered content parameters
	 * Items stored by ID (key)
	 * @var array
	 */
	protected $content_raw = array();

	/**
	 * Parsed content parameters
	 * @var array
	 */
	protected $content = array();

	/**
	 * Messages to be displayed
	 * Indexed Array
	 * @var array
	 */
	protected $messages = array();

	/**
	 * Required properties
	 * Associative array
	 * > Key: Property name
	 * > Value: Required data type
	 * @var array
	 */
	private $required = array();

	/**
	 * Default required properties
	 * Merged into $required array with this->init_required()
	 * @see this->required for more information
	 * @var array
	 */
	private $_required = array(
		'id'     => 'string',
		'labels' => 'array',
	);

	/* Init */

	/**
	 * Constructor
	 * @return obj Current instance
	 */
	public function __construct( $id, $labels, $callback = null, $capability = null, $icon = null ) {
		$props = array(
			'labels'     => $labels,
			'callback'   => $callback,
			'capability' => $capability,
			'icon'       => $icon,
		);
		parent::__construct( $id, $props );
		$this->init_required();
		return $this;
	}

	protected function init_required() {
		$this->required = array_merge( $this->_required, $this->required );
		// Check for parent requirement
		if ( $this->parent_required ) {
			$this->required['parent'] = 'string';
		}
	}

	/**
	 * Set required feature
	 * @param string $feature Required feature
	 */
	protected function _require( $feature ) {
		if ( ! isset( $this->_required[ $feature ] ) ) {
			$this->_required[ $feature ] = true;
		}
		return $this;
	}

	/**
	 * Check if feature is required
	 * @param string $feature Feature to check for
	 * @return bool TRUE if feature required
	 */
	protected function _is_required( $feature ) {
		return ( isset( $this->_required[ $feature ] ) ) ? true : false;
	}

	/* Property Methods */

	/**
	 * Retrieve ID (Formatted by default)
	 * @param bool $formatted (optional) Whether ID should be formatted for external use or not
	 * @return string ID
	 */
	public function get_id( $formatted = true ) {
		$id = parent::get_id();
		if ( $formatted ) {
			$this->add_prefix_ref( $id );
		}
		return $id;
	}

	/**
	 * Retrieve raw ID
	 * @return string Raw ID
	 */
	public function get_id_raw() {
		return $this->get_id( false );
	}

	/**
	 * Retrieve label
	 * Uses first label (or default if defined) if specified type does not exist
	 * @param string $type Label type to retrieve
	 * @param string $default (optional) Default value if label type does not exist
	 * @return string Label text
	 */
	public function get_label( $type, $default = null ) {
		// Retrieve existing label type
		if ( $this->has_label( $type ) ) {
			return $this->labels[ $type ];
		}
		// Use default label if type is not set
		if ( empty( $default ) && ! empty( $this->labels ) ) {
			reset( $this->labels );
			$default = current( $this->labels );
		}

		return ( empty( $default ) ) ? '' : $default;
	}

	/**
	 * Set text labels
	 * @param array|string $labels
	 * @return obj Current instance
	 */
	public function set_labels( $labels ) {
		if ( empty( $labels ) ) {
			return this;
		}
		// Single string
		if ( is_string( $labels ) ) {
			$labels = array( $labels );
		}

		// Array
		if ( is_array( $labels ) ) {
			// Merge with existing labels
			if ( empty( $this->labels ) || ! is_array( $this->labels ) ) {
				$this->labels = array();
			}
			$this->labels = array_merge( $this->labels, $labels );
		}
		return $this;
	}

	/**
	 * Set single text label
	 * @uses this->set_labels()
	 * @param string $type Label type to set
	 * @param string $value Label value
	 * @return obj Current instance
	 */
	public function set_label( $type, $value ) {
		if ( is_string( $type ) && is_string( $value ) ) {
			$label = array( $type => $value );
			$this->set_labels( $label );
		}
		return $this;
	}

	/**
	 * Checks if specified label is set on view
	 * @param string $type Label type
	 * @return bool TRUE if label exists, FALSE otherwise
	 */
	public function has_label( $type ) {
		return ( isset( $this->labels[ $type ] ) );
	}

	/* Content */

	/**
	 * Add content block to view
	 * Child classes define method functionality
	 * @param string $id Content block ID
	 * @param array $args Content arguments (Defined by child class), converted to an object
	 * @return obj Current View instance
	 */
	public function add_content( $id, $args ) {
		// Save parameters
		$this->content_raw[ $id ] = (object) $args;
		// Clear parsed content
		$this->content = array();
		// Return instance reference
		return $this;
	}

	/**
	 * Retrieve content
	 */
	protected function get_content( $parsed = true ) {
		// Return raw content.
		if ( ! $parsed ) {
			return $this->content_raw;
		}

		// Return parsed content.
		if ( empty( $this->content ) && ! empty( $this->content_raw ) ) {
			$this->content = $this->parse_content();
		}

		return $this->content;
	}

	/**
	 * Parse content
	 * Child classes define functionality
	 * @return array Parsed content
	 */
	protected function parse_content() {
		return $this->get_content( false );
	}

	/**
	 * Check if content has been added to view
	 * @return bool TRUE if content added
	 */
	protected function has_content() {
		$raw = $this->get_content( false );
		return ! empty( $raw );
	}

	/**
	 * Render content
	 */
	protected function render_content( $context = 'default' ) {}

	/**
	 * Retrieve view messages
	 * @return array Messages
	 */
	protected function &get_messages() {
		if ( ! is_array( $this->messages ) ) {
			$this->messages = array();
		}
		return $this->messages;
	}

	/**
	 * Save message
	 * @param string $text Message text
	 * @return obj Current instance
	 */
	public function set_message( $text ) {
		$msgs =& $this->get_messages();
		$text = trim( $text );
		if ( empty( $msgs ) && ! empty( $text ) ) {
			$this->util->add_filter( 'admin_messages', $this->m( 'do_messages' ), 10, 1, false );
		}
		$msgs[] = $text;
		return $this;
	}

	/**
	 * Add messages to array
	 * Called by internal `admin_messages` filter hook
	 * @param array $msgs Aggregated messages
	 * @return array Merged messages array
	 */
	public function do_messages( $msgs = array() ) {
		$m =& $this->get_messages();
		if ( ! empty( $m ) ) {
			$msgs = array_merge( $msgs, $m );
		}
		return $msgs;
	}

	/**
	 * Retrieve view callback
	 * @return callback Callback (Default: standard handler method)
	 */
	public function get_callback() {
		return ( $this->has_callback() ) ? $this->callback : $this->m( 'handle' );
	}

	/**
	 * Set callback function for building item
	 * @param callback $callback Callback function to use
	 * @return obj Current instance
	 */
	public function set_callback( $callback ) {
		$this->callback = ( is_callable( $callback ) ) ? $callback : null;
		return $this;
	}

	/**
	 * Check if callback set
	 * @return bool TRUE if callback is set
	 */
	protected function has_callback() {
		return ( ! empty( $this->callback ) ) ? true : false;
	}

	/**
	 * Run callback
	 */
	public function do_callback() {
		call_user_func( $this->get_callback() );
	}

	/**
	 * Retrieve capability
	 * @return string Capability
	 */
	public function get_capability() {
		return $this->capability;
	}

	/**
	 * Set capability for access control
	 * @param string $capability Capability
	 * @return obj Current instance
	 */
	public function set_capability( $capability ) {
		if ( is_string( $capability ) && ! empty( $capability ) ) {
			$this->capability = $capability;
		}
		return $this;
	}

	/**
	 * Set icon
	 * @param string $icon Icon URI
	 * @return obj Current instance
	 */
	public function set_icon( $icon ) {
		if ( ! empty( $icon ) && is_string( $icon ) ) {
			$this->icon = $icon;
		}
		return $this;
	}

	protected function get_hookname() {
		return ( empty( $this->hookname ) ) ? '' : $this->hookname;
	}

	/**
	 * Set hookname
	 * @param string $hookname Hookname value
	 * @return obj Current instance
	 */
	public function set_hookname( $hookname ) {
		if ( ! empty( $hookname ) && is_string( $hookname ) ) {
			$this->hookname = $hookname;
		}
		return $this;
	}

	/**
	 * Retrieve parent
	 * Formats parent ID for custom parents
	 * @uses parent::get_parent()
	 * @return string Parent ID
	 */
	public function get_parent() {
		$parent = parent::get_parent();
		return ( $this->is_parent_custom() ) ? $this->add_prefix( $parent ) : $parent;
	}

	/**
	 * Set parent for view
	 * @param string $parent Parent ID
	 * @return obj Current instance
	 */
	public function set_parent( $parent ) {
		if ( $this->parent_required ) {
			if ( ! empty( $parent ) && is_string( $parent ) ) {
				$this->parent = $parent;
			}
		} else {
			$this->parent = null;
		}
		return $this;
	}

	/**
	 * Specify whether parent is a custom view or a WP view
	 * @param bool $custom (optional) TRUE if custom, FALSE if WP
	 * @return obj Current instance
	 */
	protected function set_parent_custom( $custom = true ) {
		if ( $this->parent_required ) {
			$this->parent_custom = ! ! $custom;
		}
		return $this;
	}

	/**
	 * Set parent as WP view
	 * @uses this->set_parent_custom()
	 * @return obj Current instance
	 */
	public function set_parent_wp() {
		$this->set_parent_custom( false );
		return $this;
	}

	/**
	 * Get view URI
	 * URI Structures:
	 *  > Top Level Menus: admin.php?page={menu_id}
	 *  > Pages: [parent_page_file.php|admin.php]?page={page_id}
	 *  > Section: [parent_menu_uri]#{section_id}
	 *
	 * @uses $admin_page_hooks to determine if page is child of default WP page
	 * @param string $file (optional) Base file name
	 * @param string $format (optional) Format string for URI
	 * @return string Object URI
	 */
	public function get_uri( $file = null, $format = null ) {
		static $page_hooks = null;
		$uri               = '';
		if ( empty( $file ) ) {
			$file = 'admin.php';
		}
		if ( $this->is_child() ) {
			$parent = str_replace( '_page_' . $this->get_id(), '', $this->get_hookname() );
			if ( is_null( $page_hooks ) ) {
				$page_hooks = array_flip( $GLOBALS['admin_page_hooks'] );
			}
			if ( isset( $page_hooks[ $parent ] ) ) {
				$file = $page_hooks[ $parent ];
			}
		}

		if ( empty( $format ) ) {
			$delim  = ( strpos( $file, '?' ) === false ) ? '?' : '&amp;';
			$format = '%1$s' . $delim . 'page=%2$s';
		}
		$uri = sprintf( $format, $file, $this->get_id() );

		return $uri;
	}

	/* Handlers */

	/**
	 * Default View handler
	 * Used as callback when none set
	 */
	public function handle() {}

	/* Validation */

	/**
	 * Check if instance is valid based on required properties/data types
	 * @return bool TRUE if valid, FALSE if not valid
	 */
	public function is_valid() {
		$invalid = false;
		foreach ( $this->required as $prop => $type ) {
			// Baseline validation.
			if (
				empty( $this->{$prop} )
				|| empty( $type )
				|| ! is_string( $type )
			) {
				return $invalid;
			}

			// Validate data type.
			$f = 'is_' . $type;
			if ( ! function_exists( $f ) || ! $f( $this->{$prop} ) ) {
				return $invalid;
			}
		}
		return true;
	}

	protected function is_child() {
		return $this->parent_required;
	}

	protected function is_parent_custom() {
		return ( $this->is_child() && $this->parent_custom ) ? true : false;
	}

	public function is_parent_wp() {
		return ( $this->is_child() && ! $this->parent_custom ) ? true : false;
	}

}
