<?php
/**
 * An element (token) generated by the scan
 *
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
 * @copyright Copyright 2010-2014 PhpCss Team
 */

namespace PhpCss\Scanner {

  use BadMethodCallException;
  use InvalidArgumentException;

  /**
   * An element (token) generated by the scan
   *
   * @property-read integer $type
   * @property-read string $content
   * @property-read integer $length
   * @property-read integer $position
   */
  class Token {

    // special any token (a joker)
    public const ANY = 255;

    //whitespace
    public const WHITESPACE = 1;
    public const NUMBER = 2;
    public const IDENTIFIER = 3;

    //simple selectors
    public const CLASS_SELECTOR = 11;
    public const ID_SELECTOR = 12;
    public const PSEUDO_CLASS = 13;
    public const PSEUDO_CLASS_POSITION = 14;
    public const PSEUDO_ELEMENT = 15;

    // attribute selectors - [...]
    public const ATTRIBUTE_SELECTOR_START = 20;
    public const ATTRIBUTE_SELECTOR_END = 21;
    public const ATTRIBUTE_OPERATOR = 22;

    // parentheses - (...)
    public const PARENTHESES_START = 31;
    public const PARENTHESES_END = 32;

    //selector separator
    public const COMBINATOR = 41;
    public const SEPARATOR = 42;

    //single quoted strings
    public const SINGLEQUOTE_STRING_START = 100;
    public const SINGLEQUOTE_STRING_END = 101;
    // double quoted strings
    public const DOUBLEQUOTE_STRING_START = 102;
    public const DOUBLEQUOTE_STRING_END = 103;
    // string general
    public const STRING_CHARACTERS = 110;
    public const STRING_ESCAPED_CHARACTER = 111;

    private static $_names = [
      self::ANY => 'ANY',
      self::WHITESPACE => 'WHITESPACE',
      self::NUMBER => 'NUMBER',
      self::IDENTIFIER => 'IDENTIFIER',
      self::CLASS_SELECTOR => 'SIMPLESELECTOR_CLASS',
      self::ID_SELECTOR => 'SIMPLE_SELECTOR_ID',
      self::PSEUDO_CLASS => 'PSEUDOCLASS',
      self::PSEUDO_CLASS_POSITION => 'PSEUDOCLASS_POSITION',
      self::PSEUDO_ELEMENT => 'PSEUDOELEMENT',
      self::ATTRIBUTE_SELECTOR_START => 'SIMPLESELECTOR_ATTRIBUTE_START',
      self::ATTRIBUTE_SELECTOR_END => 'SIMPLESELECTOR_ATTRIBUTE_END',
      self::ATTRIBUTE_OPERATOR => 'SIMPLESELECTOR_ATTRIBUTE_OPERATOR',
      self::PARENTHESES_START => 'PARENTHESES_START',
      self::PARENTHESES_END => 'PARENTHESES_END',
      self::COMBINATOR => 'SELECTOR_COMBINATOR',
      self::SEPARATOR => 'SELECTOR_SEPARATOR',
      self::SINGLEQUOTE_STRING_START => 'STRING_SINGLE_QUOTE_START',
      self::SINGLEQUOTE_STRING_END => 'STRING_SINGLE_QUOTE_END',
      self::DOUBLEQUOTE_STRING_START => 'STRING_DOUBLE_QUOTE_START',
      self::DOUBLEQUOTE_STRING_END => 'STRING_DOUBLE_QUOTE_END',
      self::STRING_CHARACTERS => 'STRING_CHARACTERS',
      self::STRING_ESCAPED_CHARACTER => 'STRING_ESCAPED_CHARACTER',
    ];

    /**
     * Token type
     * @var integer
     */
    private $_type;
    /**
     * Token string content
     * @var string
     */
    private $_content;
    /**
     * Token string content length
     * @var integer
     */
    private $_length;
    /**
     * Byte position the token was found at
     * @var integer
     */
    private $_position;

    /**
     * Construct and initialize token
     *
     * @param integer $type
     * @param string $content
     * @param integer $position
     */
    public function __construct(int $type = 0, string $content = '', int $position = -1) {
      $this->_type = $type;
      $this->_content = $content;
      $this->_length = strlen($content);
      $this->_position = $position;
    }

    /**
     * Get token attribute
     *
     * @param string $name
     * @return int|string
     * @throws InvalidArgumentException
     */
    public function __get(string $name) {
      switch ($name) {
      case 'type' :
        return $this->_type;
      case 'content' :
        return $this->_content;
      case 'length' :
        return $this->_length;
      case 'position' :
        return $this->_position;
      }
      throw new InvalidArgumentException();
    }

    /**
     * Has token property
     *
     * @param string $name
     * @return bool
     */
    public function __isset(string $name): bool {
      switch ($name) {
      case 'type' :
      case 'content' :
      case 'length' :
      case 'position' :
        return TRUE;
      }
      return FALSE;
    }

    /**
     * Do not allow to set attributes
     *
     * @param string $name
     * @param mixed $value
     * @return void
     * @throws BadMethodCallException
     */
    public function __set(string $name, $value): void {
      throw new BadMethodCallException();
    }

    /**
     * Convert token object to string
     * @return string
     */
    public function __toString() {
      return 'TOKEN::'.self::typeToString($this->type).
        ' @'.$this->position.' '.$this->quoteContent($this->content);
    }

    /**
     * Return string representation of token type
     *
     * @param integer $type
     * @return string
     */
    public static function typeToString(int $type): string {
      return self::$_names[$type];
    }

    /**
     * Escape content for double quoted, single line string representation
     *
     * @param string $content
     * @return string
     */
    protected function quoteContent(string $content): string {
      return "'".str_replace(
          ['\\', "\r", "\n", "'"],
          ['\\\\', '\\r', '\\n', "\\'"],
          $content
        )."'";
    }
  }
}