<?php
/**
 * Code Manager tools
 *
 * @package .
 */

/**
 * Class CM_Tools
 *
 * This class is available from the Code Manager editor. The purpose is to help developers to easily access objects
 * from an action hook or filter.
 *
 * Usage:
 *      $cm = new CM_Tools( $self );
 *
 *      $constants       = $cm->get_constants();
 *      $constant_exists = $cm->has_constant('CONSTANT_NAME');
 *      $constant_value  = $cm->get_constant_value('CONSTANT_NAME');
 *
 *      $properties      = $cm->get_properties();
 *      $property_exists = $cm->has_property('property_name');
 *      $property_value  = $cm->get_property_value('property_name');
 *      $cm->set_property_value('property_name', 'new_property_value');
 *
 *      $methods       = $cm->get_methods();
 *      $method_exists = $cm->has_method('method_name');
 *      $cm->invoke_method('method_name', 'argument_value'));
 *
 *      $menu_slug      = CM_Tools::get_menu_slug();
 *      $argument_value = CM_Tools::get_argument('table_name');
 *      $full_path      = CM_Tools::get_full_path();
 *      $admin_page     = CM_Tools::get_page();
 *
 *      CM_Tools::log($obj);
 *
 * @author  Peter Schulz
 * @since   1.0.0
 */
class CM_Tools {

	/**
	 * CM tool object
	 *
	 * @var null
	 */
	private $obj = null;

	/**
	 * Reflection object
	 *
	 * @var ReflectionClass|null
	 */
	private $reflection = null;

	/**
	 * Constructor
	 *
	 * @param array $obj Arguments.
	 */
	public function __construct( $obj ) {
		try {
			$this->obj        = $obj;
			$this->reflection = new ReflectionClass( $obj );
		} catch ( ReflectionException $exception ) {
			var_dump( 'CODE MANAGER ERROR: Class not found' );
		} catch ( Exception $exception ) {
			var_dump( 'CODE MANAGER ERROR: Could not instantiate CM_Tools' );
		}
	}

	/**
	 * Returns string representation of reflection object
	 *
	 * @return string
	 */
	public function toString() {
		return $this->reflection->__toString();
	}

	/**
	 * Get constants of reflection object
	 *
	 * @return array|null
	 */
	public function get_constants() {
		return null !== $this->reflection ? $this->reflection->getConstants() : null;
	}

	/**
	 * Does reflection object have constants?
	 *
	 * @param string $constant_name Constant name.
	 * @return bool|null
	 */
	public function has_constant( $constant_name ) {
		return null !== $this->reflection ? $this->reflection->hasConstant( $constant_name ) : null;
	}

	/**
	 * Get constant value of reflection object
	 *
	 * @param string $constant_name Constant name.
	 * @return false|mixed|void|null
	 */
	public function get_constant_value( $constant_name ) {
		if ( null === $this->reflection ) {
			return null;
		}

		if ( null !== $this->reflection ) {
			if ( $this->has_constant( $constant_name ) ) {
				return $this->reflection->getConstant( $constant_name );
			} else {
				var_dump( "CODE MANAGER ERROR: Constant '{$constant_name}' not found" );
				return null;
			}
		}
	}

	/**
	 * Get reflection object properties
	 *
	 * @return array|null
	 */
	public function get_properties() {
		if ( null === $this->reflection ) {
			return null;
		}

		$properties =
			$this->reflection->getProperties(
				ReflectionProperty::IS_PUBLIC |
				ReflectionProperty::IS_PROTECTED |
				ReflectionProperty::IS_PRIVATE
			);

		$all_properties = array();
		foreach ( $properties as $property ) {
			if ( $property->isPrivate() ) {
				$access = 'private';
			} elseif ( $property->isProtected() ) {
				$access = 'protected';
			} elseif ( $property->isPublic() ) {
				$access = 'public';
			}

			$all_properties[ $property->getName() ] =
				array(
					'static' => $property->isStatic(),
					'access' => $access,
					'value'  => $this->get_property_value( $property->getName() ),
				);
		}

		return $all_properties;
	}

	/**
	 * Check if reflection object has property
	 *
	 * @param string $property_name Property name.
	 * @return bool|null
	 */
	public function has_property( $property_name ) {
		return null !== $this->reflection ? $this->reflection->hasProperty( $property_name ) : null;
	}

	/**
	 * Get property value of reflection object
	 *
	 * @param string $property_name Property name.
	 * @return mixed|void|null
	 */
	public function get_property_value( $property_name ) {
		if ( null === $this->reflection ) {
			return null;
		}

		if ( $this->has_property( $property_name ) ) {
			try {
				$property_name = $this->reflection->getProperty( $property_name );
				$property_name->setAccessible( true );

				return $property_name->getValue( $this->obj );
			} catch ( Throwable $exception ) {
				var_dump( 'CODE MANAGER ERROR: ' . $exception->getMessage() );
			}
		} else {
			var_dump( "CODE MANAGER ERROR: Property '{$property_name}' not found" );
			return null;
		}
	}

	/**
	 * Set value of reflection property
	 *
	 * @param string $property_name Property name.
	 * @param string $value Property value.
	 * @return void|null
	 */
	public function set_property_value( $property_name, $value ) {
		if ( null === $this->reflection ) {
			return null;
		}

		if ( $this->has_property( $property_name ) ) {
			try {
				$property = $this->reflection->getProperty( $property_name );
				$property->setAccessible( true );

				return $property->setValue( $this->obj, $value );
			} catch ( Throwable $exception ) {
				var_dump( 'CODE MANAGER ERROR: ' . $exception->getMessage() );
			}
		} else {
			var_dump( "CODE MANAGER ERROR: Property '{$property_name}' not found" );
			return null;
		}
	}

	/**
	 * Get reflection object methods
	 *
	 * @return array|null
	 */
	public function get_methods() {
		if ( null === $this->reflection ) {
			return null;
		}

		$methods =
			$this->reflection->getMethods(
				ReflectionProperty::IS_PUBLIC |
				ReflectionProperty::IS_PROTECTED |
				ReflectionProperty::IS_PRIVATE
			);

		$all_methods = array();
		foreach ( $methods as $method ) {
			if ( $method->isPrivate() ) {
				$access = 'private';
			} elseif ( $method->isProtected() ) {
				$access = 'protected';
			} elseif ( $method->isPublic() ) {
				$access = 'public';
			}

			$all_methods[ $method->getName() ] =
				array(
					'static' => $method->isStatic(),
					'access' => $access,
				);
		}

		return $all_methods;
	}

	/**
	 * Does reflection object have methods?
	 *
	 * @param string $method_name Method name.
	 * @return bool|null
	 */
	public function has_method( $method_name ) {
		return null !== $this->reflection ? $this->reflection->hasMethod( $method_name ) : null;
	}

	/**
	 * Execute method of reflection object
	 *
	 * @param string $method_name Method name.
	 * @param string $args Method arguments.
	 * @return mixed|void
	 */
	public function invoke_method( $method_name, $args = array() ) {
		if ( null !== $this->reflection && $this->has_method( $method_name ) ) {
			try {
				$method = new ReflectionMethod( $this->obj, $method_name );
				$method->setAccessible( true );

				return $method->invoke( $this->obj, $args );
			} catch ( Throwable $exception ) {
				var_dump( 'CODE MANAGER ERROR: ' . $exception->getMessage() );
			}
		} else {
			var_dump( "CODE MANAGER ERROR: Method '{$method_name}' not found" );
		}
	}

	/**
	 * Get menu slug
	 *
	 * @return mixed|null
	 */
	public static function get_menu_slug() {
		if ( is_admin() ) {
			return isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : null;
		} else {
			return null;
		}
	}

	/**
	 * Get URL argument
	 *
	 * @param string $argument_name Argument name.
	 * @return null
	 */
	public static function get_argument( $argument_name ) {
		return isset( $_REQUEST[ $argument_name ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ $argument_name ] ) ) : null;
	}

	/**
	 * Get full path
	 *
	 * @return mixed
	 */
	public static function get_full_path() {
		return isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : null;
	}

	/**
	 * Get current page
	 *
	 * @return null
	 */
	public static function get_page() {
		if ( is_admin() ) {
			global $pagenow;
			return $pagenow;
		} else {
			return null;
		}
	}

	/**
	 * Write message to temporary file (saved in uploads)
	 *
	 * @param object $obj Object to be written to log file.
	 * @return void
	 */
	public static function log( $obj ) {
		try {
			$upload_dir = self::get_plugin_upload_dir();
			$file_name  = 'cm_tools.log';
			if ( ! is_dir( $upload_dir ) ) {
				mkdir( $upload_dir, 0755, true );
			}
			$file = fopen( "{$upload_dir}{$file_name}", 'a' );
			fwrite( $file, date( 'd-m-Y h:i' ) . ' ' );
			switch ( gettype( $obj ) ) {
				case 'array':
				case 'object':
					fwrite( $file, json_encode( $obj ) );
					break;
				default:
					fwrite( $file, $obj );
			}
			fwrite( $file, "\n" );
			fclose( $file );
		} catch ( \Exception $exception ) {
			echo 'ERROR: ' . $exception->getMessage();
		}
	}

	/**
	 * Get plugin upload folder
	 *
	 * @return string
	 */
	public static function get_plugin_upload_dir() {
		$uploads = wp_upload_dir();
		return $uploads['basedir'] . DIRECTORY_SEPARATOR . 'code-manager' . DIRECTORY_SEPARATOR;
	}

	/**
	 * Get user login
	 *
	 * @return string
	 */
	public static function get_current_user_login() {
		global $current_user;
		if ( isset( $current_user->user_login ) ) {
			return $current_user->user_login;
		} else {
			$wp_user = wp_get_current_user();
			if ( isset( $wp_user->data->user_login ) ) {
				return $wp_user->data->user_login;
			} else {
				return 'anonymous';
			}
		}
	}

	/**
	 * Get user id
	 *
	 * @return int
	 */
	public static function get_current_user_id() {
		global $current_user;
		if ( isset( $current_user->ID ) ) {
			return $current_user->ID;
		} else {
			$wp_user = wp_get_current_user();
			if ( isset( $wp_user->ID ) ) {
				return $wp_user->ID;
			} else {
				return - 1;
			}
		}
	}

	/**
	 * Run shortcode from anywhere with code id
	 *
	 * @param integer $code_id Code ID.
	 * @return void
	 */
	public static function run_shortcode_id_from_anywhere( $code_id ) {
		$cm = new Code_Manager\Code_Manager();

		if ( is_array( $code_id ) ) {
			foreach ( $code_id as $id ) {
				echo $cm->run_shortcode_id_from_anywhere( $id );
			}
		} else {
			$cm->run_shortcode_id_from_anywhere( $code_id );
		}
	}

	/**
	 * Run shortcode from anywhere width code name
	 *
	 * @param string $code_name Code name.
	 * @return void
	 */
	public static function run_shortcode_name_from_anywhere( $code_name ) {
		$cm = new Code_Manager\Code_Manager();

		if ( is_array( $code_name ) ) {
			foreach ( $code_name as $name ) {
				$cm->run_shortcode_name_from_anywhere( $name );
			}
		} else {
			$cm->run_shortcode_name_from_anywhere( $code_name );
		}
	}

}
