<?php
/**
 * @author     Mike Cochrane <mikec@mikenz.geek.nz>
 * @author     Nick Pope <nick@nickpope.me.uk>
 * @copyright  Copyright © 2010, Mike Cochrane, Nick Pope
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License v2.0
 * @package    Twitter
 */

/**
 * Twitter Regex Abstract Class
 *
 * Used by subclasses that need to parse tweets.
 *
 * Originally written by {@link http://github.com/mikenz Mike Cochrane}, this
 * is based on code by {@link http://github.com/mzsanford Matt Sanford} and
 * heavily modified by {@link http://github.com/ngnpope Nick Pope}.
 *
 * @author     Mike Cochrane <mikec@mikenz.geek.nz>
 * @author     Nick Pope <nick@nickpope.me.uk>
 * @copyright  Copyright © 2010, Mike Cochrane, Nick Pope
 * @license    http://www.apache.org/licenses/LICENSE-2.0  Apache License v2.0
 * @package    Twitter
 */
abstract class Twitter_Regex {

  /**
   * Expression to at sign characters
   *
   * @var  string
   */
  const REGEX_AT_SIGNS = '[@＠]';

  /**
   * Expression to match characters that may come before a URL.
   *
   * @var  string
   */
  const REGEX_URL_CHARS_BEFORE = '(?:[^-\\/"\':!=a-z0-9_@＠]|^|\\:)';

  /**
   * Expression to match the domain portion of a URL.
   *
   * @var  string
   */
  const REGEX_URL_DOMAIN = '(?:[^\\p{P}\\p{Lo}\\s][\\.-](?=[^\\p{P}\\p{Lo}\\s])|[^\\p{P}\\p{Lo}\\s])+\\.[a-z]{2,}(?::[0-9]+)?';

  /**
   * Expression to match handful of probable TLDs for protocol-less URLS.
   *
   * @var  string
   */
  const REGEX_PROBABLE_TLD = '/\\.(?:com|net|org|gov|edu)$/iu';

  /**
   * Expression to match characters that may come in the URL path.
   *
   * @var  string
   */
  const REGEX_URL_CHARS_PATH = '(?:(?:\\([a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_,~]+\\))|@[a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_,~]+\\/|[\\.\\,]?(?:[a-z0-9!\\*\';:=\\+\\$\\/%#\\[\\]\\-_~]|,(?!\s)))';

  /**
   * Expression to match characters that may come at the end of the URL path.
   *
   * @var  string
   */
  const REGEX_URL_CHARS_PATH_END = '[a-z0-9=#\\/]';

  /**
   * Expression to match characters that may come in the URL query string.
   *
   * @var  string
   */
  const REGEX_URL_CHARS_QUERY = '[a-z0-9!\\*\'\\(\\);:&=\\+\\$\\/%#\\[\\]\\-_\\.,~]';

  /**
   * Expression to match characters that may come at the end of the URL query
   * string.
   *
   * @var  string
   */
  const REGEX_URL_CHARS_QUERY_END = '[a-z0-9_&=#\\/]';

  /**
   * Expression to match a username followed by a list.
   *
   * @var  string
   */
  const REGEX_USERNAME_LIST = '/([^a-z0-9_\/]|^|RT:?)([@＠]+)([a-z0-9_]{1,20})(\/[a-z][-_a-z0-9\x80-\xFF]{0,24})?([@＠\xC0-\xD6\xD8-\xF6\xF8-\xFF]?)/iu';

  /**
   * Expression to match a username mentioned anywhere in a tweet.
   *
   * @var  string
   */
  const REGEX_USERNAME_MENTION = '/(^|[^a-z0-9_])[@＠]([a-z0-9_]{1,20})([@＠\xC0-\xD6\xD8-\xF6\xF8-\xFF]?)/iu';

  /**
   * Expression to match a hashtag.
   *
   * @var  string
   */
  const REGEX_HASHTAG = '/(^|[^0-9A-Z&\/\?]+)([#＃]+)([0-9A-Z_]*[A-Z_]+[a-z0-9_üÀ-ÖØ-öø-ÿ]*)/iu';

  /**
   * Expression to match whitespace.
   *
   * Single byte whitespace characters
   *   0x0009-0x000D White_Space # Cc # <control-0009>..<control-000D>
   *   0x0020        White_Space # Zs # SPACE
   *   0x0085        White_Space # Cc # <control-0085>
   *   0x00A0        White_Space # Zs # NO-BREAK SPACE
   * Multi byte whitespace characters
   *   0x1680        White_Space # Zs # OGHAM SPACE MARK
   *   0x180E        White_Space # Zs # MONGOLIAN VOWEL SEPARATOR
   *   0x2000-0x200A White_Space # Zs # EN QUAD..HAIR SPACE
   *   0x2028        White_Space # Zl # LINE SEPARATOR
   *   0x2029        White_Space # Zp # PARAGRAPH SEPARATOR
   *   0x202F        White_Space # Zs # NARROW NO-BREAK SPACE
   *   0x205F        White_Space # Zs # MEDIUM MATHEMATICAL SPACE
   *   0x3000        White_Space # Zs # IDEOGRAPHIC SPACE
   *
   * @var  string
   */
  const REGEX_WHITESPACE = '[\x09-\x0D\x20\x85\xA0]|\xe1\x9a\x80|\xe1\xa0\x8e|\xe2\x80[\x80-\x8a,\xa8,\xa9,\xaf\xdf]|\xe3\x80\x80';

  /**
   * Contains the complete valid URL pattern string.
   *
   * This should be generated the first time the constructor is called.
   *
   * @var  string  The regex pattern for a valid URL.
   */
  protected static $REGEX_VALID_URL = null;

  /**
   * Contains the reply username pattern string.
   *
   * This should be generated the first time the constructor is called.
   *
   * @var  string  The regex pattern for a reply username.
   */
  protected static $REGEX_REPLY_USERNAME = null;

  /**
   * The tweet to be used in parsing.  This should be populated by the
   * constructor of all subclasses.
   *
   * @var  string
   */
  protected $tweet = '';

  /**
   * This constructor is used to populate some variables.
   *
   * @param  string  $tweet  The tweet to parse.
   */
  protected function __construct($tweet) {
    if (is_null(self::$REGEX_VALID_URL)) {
      self::$REGEX_VALID_URL = '/(?:'             # $1 Complete match (preg_match already matches everything.)
        . '('.self::REGEX_URL_CHARS_BEFORE.')'    # $2 Preceding character
        . '('                                     # $3 Complete URL
        . '((?:https?:\\/\\/|www\\.)?)'           # $4 Protocol (or www)
        . '('.self::REGEX_URL_DOMAIN.')'          # $5 Domain(s) (and port)
        . '(\\/'.self::REGEX_URL_CHARS_PATH.'*'   # $6 URL Path
        . self::REGEX_URL_CHARS_PATH_END.'?)?'
        . '(\\?'.self::REGEX_URL_CHARS_QUERY.'*'  # $7 Query String
        . self::REGEX_URL_CHARS_QUERY_END.')?'
        . ')'
        . ')/iux';
    }
    if (is_null(self::$REGEX_REPLY_USERNAME)) {
      self::$REGEX_REPLY_USERNAME = '/^('.self::REGEX_WHITESPACE.')*[@＠]([a-zA-Z0-9_]{1,20})/';
    }
    $this->tweet = $tweet;
  }

}
