<?php
/**
 * Get Options Class
 * Pure PHP OOP implementation of getopt
 * @author chris@fuelforthefire.ca
 * @copyright Fuel for the Fire
 * @version 0.2
 * @date 2013-06-17
 */

/**
 * Get Options
 * Recieves array of arguments and evaluates them
 * @name GetOpts
 * @package cli
 */
class GetOpts
{
	/**
	 * Arguments
	 * internal storage of the arguments to parse
	 * @var array
	 * @access private
	 */
	private $aArgs;

	/**
	* Return Unknown Flag
	* If set unknown options will be returned as '?'
	* @var bool
	* @access private
	*/
	private $bShowUnknown;

	/**
	 * Argument Count
	 * The number of arguments passed to the class
	 * @var integer
	 * @access private
	 */
	private $iArgCnt;

	/**
	 * Argument Index
	 * last index in the argument array we looked at
	 * @var int
	 * @access private
	 */
	private $iArgIndex;

	/**
	 * Options
	 * an array of acceptable short options (-o)
	 * @var array
	 * @access private
	 */
	private $aOptions;

	/**
	 * Long Options
	 * an array of acceptable long options (--option)
	 * @var mixed
	 */
	private $aLongOpts;

	/**
	 * Constructor
	 * Sets up the arguments to be evaluated
	 * @name GetOpts
	 * @param array $in_arguments		Arguments to evaluate
	 * @param string $in_options		Options to look for
	 * @param string $in_longopts		Long options to look for
	 * @param bool $in_return_unknown	Should we return unknown arguments?
	 * @return GetOpts
	 */
	public function __construct(array $in_arguments, /*string*/ $in_options, array $in_longopts = array(), /*bool*/ $in_return_unknown = false)
	{
		// Store and count arguments
		$this->aArgs		= $in_arguments;
		$this->iArgCnt		= count($this->aArgs);

		// Init last index
		$this->iArgIndex	= null;

		// Store short and long options
		$this->aOptions		= array();
		$this->aLongOpts	= array();
		$this->parseOptions($in_options, $in_longopts);

		// Check for unknown arguments?
		$this->bShowUnknown	= $in_return_unknown;
	}

	/**
	 * Next Option
	 * returns the next option in the list of arguments
	 * @name next
	 * @access public
	 * @return array|false				0 => option, 1 => value
	 */
	public function next()
	{
		// Check if we've run before
		if(is_null($this->iArgIndex))
		{
			// Skip the first argument if it's the name of the current script
			$this->iArgIndex = (isset($_SERVER['argv'][0])	&&
								$_SERVER['argv'][0] == $_SERVER['SCRIPT_NAME']) ?
								0 :
								-1;
		}

		// Check if we're done
		if(++$this->iArgIndex >= $this->iArgCnt)
		{
			return null;
		}

		// Check if it's a long option
		if($this->aArgs[$this->iArgIndex]{0} == '-' && $this->aArgs[$this->iArgIndex]{1} == '-')
		{
			// If there's an equal sign in the argument
			if($iPos = strpos($this->aArgs[$this->iArgIndex], '='))
			{
				$sArg	= substr($this->aArgs[$this->iArgIndex], 2, ($iPos-2));
				$sVal	= substr($this->aArgs[$this->iArgIndex], $iPos+1);
			}
			else
			{
				$sArg	= substr($this->aArgs[$this->iArgIndex], 2);
				$sVal	= null;
			}

			// Is it in the approved list
			if(isset($this->aLongOpts[$sArg]))
			{
				// Does the option have an optional parameter?
				if($this->aLongOpts[$sArg])
				{
					// If we already have it
					if(!is_null($sVal))
					{
						return array($sArg, $sVal);
					}
					else
					{
						return array($sArg, $this->aArgs[++$this->iArgIndex]);
					}
				}
				else
				{
					return array($sArg, false);
				}
			}
			else
			{
				// If we're allowed to show unknowns
				if($this->bShowUnknown)
				{
					return array('?', $this->aArgs[$this->iArgIndex]);
				}
				// Else try to get the next option
				else
				{
					return $this->next();
				}
			}
		}
		// Check that the first character of the argument is '-' or '/'
		else if(in_array($this->aArgs[$this->iArgIndex]{0}, array('-', '/')))
		{
			// Store the argument
			$sArg	= $this->aArgs[$this->iArgIndex]{1};

			// We have an option, is it in the approved list?
			if(isset($this->aOptions[$sArg]))
			{
				// Does the option have an optional parameter?
				if($this->aOptions[$sArg])
				{
					// The argument does not contain the value, so get it from the next argument
					if(strlen($this->aArgs[$this->iArgIndex]) == 2)
					{
						return array($sArg, $this->aArgs[++$this->iArgIndex]);
					}
					// Else, pull out the value from the argument
					else
					{
						return array($sArg, substr($this->aArgs[$this->iArgIndex], 2));
					}
				}
				// Else it's set or not set
				else
				{
					return array($sArg, false);
				}
			}
			else
			{
				// If we're allowed to show unknowns
				if($this->bShowUnknown)
				{
					return array('?', $this->aArgs[$this->iArgIndex]);
				}
				// Else try to get the next option
				else
				{
					return $this->next();
				}
			}
		}
		else
		{
			// If we're allowed to show unknowns
			if($this->bShowUnknown)
			{
				return array('?', $this->aArgs[$this->iArgIndex]);
			}
			// Else try to get the next option
			else
			{
				return $this->next();
			}
		}
	}

	/**
	 * Parse Options
	 * parses the options passed to the class into an array and stores them locally
	 * @name parseOptions
	 * @access private
	 * @param string $in_options		The options passed to the class
	 * @param array $in_longopts		The long options passed to the class
	 * @return void
	 */
	private function parseOptions(/*string*/ $in_options, array $in_longopts)
	{
		// Parse the short options
		$aOpts	= str_split($in_options);
		$iCnt	= count($aOpts);
		for($i = 0; $i < $iCnt; ++$i)
		{
			// If the option needs an argument
			if($aOpts[$i+1] == ':') {
				$this->aOptions[$aOpts[$i]]	=  true;
				++$i;
			} else {
				$this->aOptions[$aOpts[$i]]	= false;
			}
		}

		// Parse the long options
		foreach($in_longopts as $sOpt)
		{
			// Get the length of the string
			$iLen	= strlen($sOpt);

			// If the option needs an argument
			if(':' == $sOpt[$iLen-1]) {
				$this->aLongOpts[substr($sOpt, 0, -1)]	= true;
			} else{
				$this->aLongOpts[$sOpt]	= false;
			}
		}
	}
}