<?php
/**
 * Defines list and formats of command line arguments
 *
 * @author Patrick Forget <patforg at webtrendi.com>
 */

namespace Clapp;

/**
 * Defines list and formats of command line arguments
 *
 * @author Patrick Forget <patforg at webtrendi.com>
 */
class CommandLineArgumentDefinition
{

    /**
     * @var array
     */
    private $definitions = array();

    /**
     * long names as keys and array of properties as values
     *
     * properties are as follows
     * * string "shortName" one letter char to the corresponding short name
     * * boolean "isMultipleAllowed" true if mutliple instances of the param are allowed
     * * mixed "parameterType" false if paramters are not alloweda value,
     *       otherwise a string with the value "integer" or "string"
     * * string "description" description of the parameter
     * @var array
     */
    private $longNames = array();
    
    /**
     * list of short names as keys and their long name equivalent as values
     * @var array
     */
    private $shortNames = array();

    /**
     * Flag if arguments have been parsed in to params
     * @var boolean
     */
    private $isParsed = false;

    /**
     * class constructor
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param array $definitions contains list of allowed parameters
     *     the key is the long name of the parameter followed by a pipe (|)
     *     then a single character specifying the short name.
     *
     *     If the parameter allows for arguments then an equal sign (=)
     *     follows and then the type of paramter.
     *
     *     Allowed types are either i, int or integer for integer  types
     *     and s, str or string for string types.
     *
     *     If a parameter can appear more than once the last character of
     *     the key should be a plus character (+).
     *
     *     The value of the entry is the definition of what the paramter
     *     does.
     */
    public function __construct($definitions)
    {
        if (is_array($definitions)) {
            $this->definitions = $definitions;
        } //if
    } // __construct()

    /**
     * checks if parameter is allowed
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name either short or long name of the parameter to check
     *
     * @return boolean true if definition exisits, false otherwise
     */
    public function paramExists($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        if (strlen($name) == 1) {
            return isset($this->shortNames[$name]);
        } else {
            return isset($this->longNames[$name]);
        } //if
    } // paramExists($name)
    

    /**
     * checks if parameter allows a value if so what type
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name either short or long name of the parameter to check
     *
     * @return boolean|string false doesn't allow value, The value "string" or "integer" depending which type it allows
     */
    public function allowsValue($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);

        if (isset($this->longNames[$longName])) {
            return $this->longNames[$longName]['parameterType'] !== false ? true : false;
        } else {
            return false;
        } //if
    } // allowsValue()
    
    /**
     * returns the type of value allowed
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     */
    public function getValueType($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);

        if (isset($this->longNames[$longName]['parameterType'])
            && $this->longNames[$longName]['parameterType'] !== false) {
            return $this->longNames[$longName]['parameterType'];
        } else {
            return '';
        } //if
    } // getValueType()
    

    /**
     * checks if pamultiple instance of parameter are allowed
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name either short or long name of the parameter to check
     *
     * @return boolean false if parameter doesn't allow multiple values, true if it does
     */
    public function allowsMultiple($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);

        if (isset($this->longNames[$longName])) {
            return $this->longNames[$longName]['isMultipleAllowed'];
        } else {
            return false;
        } //if
    } // allowsMultiple()

    /**
     * retreive short name of a parameter using its long name
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name long name of the parameter to check
     *
     * @return string character of the short name or null if it doesn't exist
     */
    public function getShortName($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        if (isset($this->longNames[$name])) {
            return $this->longNames[$name]['shortName'];
        } else {
            return null;
        } //if
    } // getShortName($name)
    
    /**
     * retreive long name of a parameter using its short name
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name short name of the parameter to check
     *
     * @return string long name or null if it doesn't exist
     */
    public function getLongName($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        if (isset($this->shortNames[$name])) {
            return $this->shortNames[$name];
        } else {
            return null;
        } //if
    } // getLongName($name)

    /**
     * retreive description of a paramter
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     *
     * @param string $name either short or long name of the parameter to check
     *
     * @return string description or null if it doesn't exist
     */
    public function getDescription($name)
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        $longName = (strlen($name) == 1 ? ( isset($this->shortNames[$name]) ? $this->shortNames[$name] : '') : $name);

        if (isset($this->longNames[$longName])) {
            return $this->longNames[$longName]['description'];
        } else {
            return null;
        } //if
    } // getDescription()
    
    /**
     * builds a usage definition based on definition of params
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     */
    public function getUsage()
    {
        if (!$this->isParsed) {
            $this->parseDefinitions();
        } //if

        /* build list of argument names and calculate
           the first column width so we can pad to 
           align definitions */
        $firstCol = array();
        $longestDef = 0;
        foreach (array_keys($this->longNames) as $longName) {
            ob_start();
            echo "--{$longName}|-{$this->getShortName($longName)}";

            if ($this->allowsValue($longName)) {
                echo "={$this->getValueType($longName)}";
            } //if

            if ($this->allowsMultiple($longName)) {
                echo "+";
            } //if

            $defLength = ob_get_length();

            $longestDef = max($longestDef, $defLength);

            $firstCol[$longName] = ob_get_contents();
            ob_end_clean();

        } //foreach

        $firstColMaxWidth = $longestDef + 4;

        ob_start();

        foreach ($firstCol as $longName => $def) {
            $currentDefLength = strlen($def);

            $padding = str_repeat(" ", $firstColMaxWidth - $currentDefLength);

            echo "{$def}{$padding}{$this->getDescription($longName)}", PHP_EOL;
        } //foreach

        echo PHP_EOL;

        $usage = ob_get_contents();
        ob_end_clean();
        
        return $usage;

    } // getUsage()
    

    /**
     * parses the definitions
     *
     * @author Patrick Forget <patforg at webtrendi.com>
     */
    protected function parseDefinitions()
    {
        foreach ($this->definitions as $nameDef => $description) {
            $nameParts = explode("|", $nameDef);

            if (sizeof($nameParts) !== 2) {
                throw new \UnexpectedValueException("Unexpected argument name definition expecting \"longName|char\"");
            } //if

            $longName = $nameParts[0];
            $isMulti = false;
            $parameterType = false;

            $shortNameLength = strlen($nameParts[1]);

            if ($shortNameLength == 1) {
                $shortName = $nameParts[1];
            } else {
                $secondChar = substr($nameParts[1], 1, 1);

                switch ($secondChar) {
                    case '=':
                        $shortNameParts = explode("=", $nameParts[1]);

                        $shortName = $shortNameParts[0];
                        $parameterTypeString = $shortNameParts[1];

                        if (substr($parameterTypeString, -1) === '+') {
                            $isMulti = true;
                            $parameterTypeString = substr($parameterTypeString, 0, -1); // remove trailing +
                        } //if

                        switch ($parameterTypeString) {
                            case 'i':
                            case 'int':
                            case 'integer':
                                $parameterType = 'integer';
                                break;
                            case 's':
                            case 'str':
                            case 'string':
                                $parameterType = 'string';
                                break;
                            default:
                                throw new \UnexpectedValueException("Expecting parameter type".
                                   " to be either integer or string");
                                break;
                        } //switch

                        break;
                    case '+':
                        if ($shortNameLength > 2) {
                            throw new \UnexpectedValueException("Multiple flag charachter (+)".
                               " should be last character in definition");
                        } //if

                        $shortName = substr($nameParts[1], 0, 1);
                        $isMulti = true;

                        break;
                    default:
                        throw new \UnexpectedValueException("Expecting short name definition to be a single char");
                        break;
                } // switch

            } //if

            if (isset($this->longNames[$longName])) {
                throw new \UnexpectedValueException("Cannot redefine long name {$longName}");
            } //if

            if (isset($this->shortNames[$shortName])) {
                throw new \UnexpectedValueException("Cannot redefine short name {$shortName}");
            } //if

            $this->longNames[$longName] = array(
                'shortName' => $shortName,
                'isMultipleAllowed' => $isMulti,
                'parameterType' => $parameterType,
                'description' => $description
            );

            $this->shortNames[$shortName] = $longName;

        } //foreach

        $this->isParsed = true;
    } // parseDefinitions()
}
