<?php

/**
 * THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * @package fusionLib
 * @copyright Copyright (c) 2006 - 2013 fusionLib. All rights reserved.
 * @link http://fusionlib.com
 */

/**
 * flTextStemmerEn Class
 *
 * Implementation of Porter Stemming Algorithm.
 *
 * @package fusionLib
 */
class flTextStemmerEn extends flTextStemmer {

	/**
	 * Regular expression for matching a consonant
	 *
	 * @var string
	 */
	private $consonant = '(?:[bcdfghjklmnpqrstvwxz]|(?<=[aeiou])y|^y)';

	/**
	 * Regular expression for matching a vowel
	 *
	 * @var string
	 */
	private $vowel = '(?:[aeiou]|(?<![aeiou])y)';

	/**
	 * Replaces the ending of the word with the replacement characters if the test matches.
	 *
	 * @param string $word The words to operate on
	 * @param string $test The character ending to check for
	 * @param string $replacement The replace ment characters
	 * @param int $m The optional value of m the string must exceed to perform the replacement
	 * @return bool true if the ending was replaced else false
	 */
	private function replace(& $word, $test, $replacement, $m = null) {
		$length = -strlen($test);
		if(substr($word, $length) == $test) {
			$str = substr($word, 0, $length);
			if(is_null($m) || $this->m($str) > $m)
				$word = $str . $replacement;
			return true;
		}
		return false;
	}

	/**
	 * Calculate the measure of a string.
	 *
	 * @param string $str The string to calculate the measure of
	 * @return int The measure of the string
	 */
	private function m($str) {
		// Calculate m
		$m = array();
		$t = preg_replace("/{$this->consonant}*(.*){$this->vowel}*/", '$1', $str); // Strip [C] and [V]
		preg_match_all("/({$this->vowel}+{$this->consonant}+)/", $t, $m);
		return count($m[1]);
	}

	/**
	 * Stem a word based on the Porter Stemming Algorithm.
	 *
	 * See http://www.tartarus.org/~martin/PorterStemmer/def.txt
	 *
	 * @param string $word The lowercase word to stem.
	 * @return string The stemmed word.
	 */
	function stem($word) {
		if(strlen($word) >= 3) {

			// Step 1a
			$word = preg_replace('/(?:(ss)es|(i)es|(ss)|(s))$/', '$1$2$3', $word);

			// Step 1b
			if(!$this->replace($word, 'eed', 'ee', 0)) {
				if(preg_match("/{$this->vowel}.*(ed|ing)$/", $word) && ($this->replace($word, 'ed', '') || $this->replace($word, 'ing', ''))) {
					if(preg_match('/(at|bl|iz)$/', $word))
						$word .= 'e';
					else if(substr($word, -2, 1) == substr($word, -1) && preg_match("/{$this->consonant}{2}$/", $word) && substr($word, -1) != 'l' && substr($word, -1) != 's' && substr($word, -1) != 'z')
						$word = substr($word, 0, -1);
					else if($this->m($word) == 1 && preg_match("/{$this->consonant}{$this->vowel}[^aeiouwxy]$/", $word))
						$word .= 'e';
				}
			}

			// Step 1c
			$word = preg_replace("/^(.*{$this->vowel}.*)y$/", '$1i', $word);

			// Step 2
			switch(substr($word, -2, 1)) {
				case 'a':
					$this->replace($word, 'ational', 'ate', 0) || $this->replace($word, 'tional', 'tion', 0);
					break;
				case 'c':
					$this->replace($word, 'enci', 'ence', 0) || $this->replace($word, 'anci', 'ance', 0);
					break;
				case 'e':
					$this->replace($word, 'izer', 'ize', 0);
					break;
				case 'g':
					$this->replace($word, 'logi', 'log', 0);
					break;
				case 'l':
					$this->replace($word, 'bli', 'ble', 0) || $this->replace($word, 'alli', 'al', 0) || $this->replace($word, 'entli', 'ent', 0) || $this->replace($word, 'eli', 'e', 0) || $this->replace($word, 'ousli', 'ous', 0);
					break;
				case 'o':
					$this->replace($word, 'ization', 'ize', 0) || $this->replace($word, 'ation', 'ate', 0) || $this->replace($word, 'ator', 'ate', 0);
					break;
				case 's':
					$this->replace($word, 'alism', 'al', 0) || $this->replace($word, 'iveness', 'ive', 0) || $this->replace($word, 'fulness', 'ful', 0) || $this->replace($word, 'ousness', 'ous', 0);
					break;
				case 't':
					$this->replace($word, 'aliti', 'al', 0) || $this->replace($word, 'iviti', 'ive', 0) || $this->replace($word, 'biliti', 'ble', 0);
					break;
			}

			// Step 3
			switch(substr($word, -1)) {
				case 'e':
					$this->replace($word, 'icate', 'ic', 0) || $this->replace($word, 'ative', '', 0) || $this->replace($word, 'alize', 'al', 0);
					break;
				case 'i':
					$this->replace($word, 'iciti', 'ic', 0);
					break;
				case 'l':
					$this->replace($word, 'ical', 'ic', 0) || $this->replace($word, 'ful', '', 0);
					break;
				case 's':
					$this->replace($word, 'ness', '', 0);
					break;
			}

			// Step 4
			switch(substr($word, -2, 1)) {
				case 'a':
					$this->replace($word, 'al', '', 1);
					break;
				case 'c':
					$this->replace($word, 'ance', '', 1) || $this->replace($word, 'ence', '', 1);
					break;
				case 'e':
					$this->replace($word, 'er', '', 1);
					break;
				case 'i':
					$this->replace($word, 'ic', '', 1);
					break;
				case 'l':
					$this->replace($word, 'able', '', 1) || $this->replace($word, 'ible', '', 1);
					break;
				case 'n':
					$this->replace($word, 'ant', '', 1) || $this->replace($word, 'ement', '', 1) || $this->replace($word, 'ment', '', 1) || $this->replace($word, 'ent', '', 1);
					break;
				case 'o':
					if(!preg_match('/[st]ion$/', $word) || !$this->replace($word, 'ion', '', 1))
						$this->replace($word, 'ou', '', 1);
					break;
				case 's':
					$this->replace($word, 'ism', '', 1);
					break;
				case 't':
					$this->replace($word, 'ate', '', 1) || $this->replace($word, 'iti', '', 1);
					break;
				case 'u':
					$this->replace($word, 'ous', '', 1);
					break;
				case 'v':
					$this->replace($word, 'ive', '', 1);
					break;
				case 'z':
					$this->replace($word, 'ize', '', 1);
					break;
			}

			// Step 5a
			if(substr($word, -1) == 'e') {
				$m = $this->m($w = substr($word, 0, -1));
				if($m > 1 || ($m == 1 && !preg_match("/{$this->consonant}{$this->vowel}[^aeiouwxy]$/", $w)))
					$word = $w;
			}

			// Step 5b
			if(substr($word, -2) == 'll' && $this->m($word) > 1)
				$word = substr($word, 0, -1);
		}

		return $word;
	}
}
