<?php
/**
 * This file contains the {@link KeyboardDecoder} class.
 * @copyright (c) 2015, Scriptel Corporation.
 */
namespace com\scriptel;

/**
 * A utility for quickly searching for keyboard values.
 */
class KeyboardDecoder {
    /**
     * Map of keyboard protocol values indexed on character values.
     * @var KeyboardValue
     */
    private $values = array();
    /**
     * Indicates this type is on the X-axis
     */
    const X = 1;
    /**
     * Indicates this type is on the Y-axis
     */
    const Y = 2;
    /**
     * Indicates this type is a most significant digit.
     */
    const MSD = 4;
    /**
     * Indicates this type is a least significant digit.
     */
    const LSD = 8;

    /**
     * Constructor which remembers the keyboard tables for later search.
     * For efficency, the values are stored statically.
     * @param integer $xValues A two dimensional table of keyboard values. [0] contains the MSDs, [1] the LSDs.
     * @param integer $yValues A two dimensional table of keyboard values. [0] contains the MSDs, [1] the LSDs.
     */
    public function __construct($xValues, $yValues) {
        for($i = 0; $i < count($xValues[0]); $i++) {
            $this->values[$xValues[0][$i]] = new KeyboardValue($i * 23, self::MSD | self::X);
            $this->values[$yValues[0][$i]] = new KeyboardValue($i * 23, self::MSD | self::Y);
        }

        for($i = 0; $i < count($xValues[1]); $i++) {
            $this->values[$xValues[1][$i]] = new KeyboardValue($i, self::LSD | self::X);
            $this->values[$yValues[1][$i]] = new KeyboardValue($i, self::LSD | self::Y);
        }
    }

    /**
     * Searches the tables for a value represented by the character. If it is an
     * MSD it is scaled appropriately.
     * @param string $ch
     * @return int The value of the character.
     * @throws SignatureInvalidException
     */
    public function getValue($ch) {
        $charToNum = ord($ch);
        /* @var KeyboardValue */
        $value = $this->values[$charToNum];
        if ($value === NULL) {
            throw new SignatureInvalidException("Unable to map character '" . $ch . "' to a value in the signature protocol table.");
        }
        return $value->getValue();
    }


    /**
     * This method takes four characters and attempts to decode them into
     * an x and y coordinate component used with the uncompressed EasyScript
     * protocol.
     * @param string[] $chrs Character array to decode.
     * @return integer[] An array with two elements, the first element being the x-axis component, the second being the y-axis component.
     * @throws IllegalArgumentException
     * @throws SignatureInvalidException
     */
    public function getValues($chrs) {
        if(count($chrs) !== 4) {
            throw new IllegalArgumentException("Character array length was not what was expected (4): " . count($chrs));
        }

        $retr = array(0, 0);
        $hasXMSD = FALSE;
        $hasXLSD = FALSE;
        $hasYMSD = FALSE;
        $hasYLSD = FALSE;

        //Ensure all characters map to the signature protocol table.
        for($i = 0, $charNum = count($chrs); $i < $charNum; $i++) {
            $val = $this->values[ord($chrs[$i])];
            if($val === NULL) {
                throw new SignatureInvalidException("Unable to map character '" . $chrs[$i] . "' to a value in the signature protocol table.");
            }

            $type = $val->getType();
            switch($type) {
                case self::X | self::MSD:
                    $retr[0] += $val->getValue();
                    $hasXMSD = TRUE;
                    break;
                case self::X | self::LSD:
                    $retr[0] += $val->getValue();
                    $hasXLSD = TRUE;
                    break;
                case self::Y | self::MSD:
                    $retr[1] += $val->getValue();
                    $hasYMSD = TRUE;
                    break;
                case self::Y | self::LSD:
                    $retr[1] += $val->getValue();
                    $hasYLSD = TRUE;
                    break;
                default:
                    throw new SignatureInvalidException("Detected strange type: " . $this->getTypeName($type));
            }
        }

        if(!($hasXMSD && $hasXLSD && $hasYMSD && $hasYLSD)) {
            throw new SignatureInvalidException("Missing required coordinate component.");
        }

        return $retr;
    }

    /**
     * This method turns a type into a human-readable string.
     * @param integer $type
     * @return integer[]
     */
    private function getTypeName($type) {
        $retr = (($type & self::MSD) > 0) ? "Most significant digit" : "Least significant digit";
        $retr += " of the coordinate on the ";
        $retr += (($type & self::X) > 0) ? "X-axis" : "Y-axis";
        return $retr;
    }
}


/**
 * Small helper class used to store both the value and type of a keyboard character
 * in the values map.
 */
 class KeyboardValue {
        /**
         * Value of the keyboard character.
         */
        private $value;
        /**
         * Type of the keyboard character, (X or Y) | (MSD or LSD).
         */
        private $type;

        /**
         * Constructor, creates a new instance of this class.
         * @param integer $value Value of the keyboard character.
         * @param integer $type Type of the keyboard character, (X or Y) | (MSD or LSD).
         */
        public function __construct($value, $type) {
            $this->value = $value;
            $this->type = $type;
        }
        /**
         * Gets the value of the keyboard character.
         * @return integer Value of the keyboard character.
         */
        function getValue() {
            return $this->value;
        }
        /**
         * Gets the type of the keyboard character, (X or Y) | (MSD or LSD).
         * @return integer Type of the keyboard character, (X or Y) | (MSD or LSD).
         */
        function getType() {
            return $this->type;
        }
}