<?php
class BotMsgException extends Exception {}
interface BotMsgInterface {
	function __construct(BotMsg $msg);
	function __toString();
	function sendPullResponse();
}

class BotMsg {
	private $beautiful = TRUE;
	private $parser = NULL;
	private $html = NULL;
	private $text = NULL;
	private $raw = '';
	
	/**
	 * Włącza lub wyłącza "upiększanie" konwertowanej do czystego tekstu wiadomości, np.:
	 * <b>abc</b> zamieniane jest na *abc*
	 * <h1>efg</h1> przechodzi w = efg =
	 * Domyślnie włączone
	 * @param bool $set Ustawienie "upiększania"
	 */
	function beautifulText($set = FALSE) {
		$this->beautiful = (bool)$set;
	}
	
	/**
	 * Konstruktor. De facto alias dla {@link BotMsg::append()}
	 */
	function __construct($str = NULL) {
		if($str !== NULL) {
			$this->append($str);
		}
	}
	
	/**
	 * Serializacja klasy wymaga zapisania tylko niektórych elementów
	 */
	function __sleep() {
		return array('beautiful', 'raw');
	}
	
	/**
	 * Alias dla {@link BotMsg::append()}
	 */
	function a($str) {
		$this->append($str);
	}
	
	/**
	 * Dodaje kod HTML na koniec wiadomości
	 * @param string $str Treść do dodania
	 */
	function append($str) {
		$this->text = $this->html = $this->parser = NULL;
		$this->raw .= (string)$str;
	}
	
	/**
	 * Zwraca wiadomość jako czysty tekst
	 * @return string Wiadomość
	 */
	function getText() {
		if($this->text === NULL) {
			$this->text = trim($this->parseTextDOM($this->getParser()->getElementsByTagName('body')->item(0)));
		}
		
		return $this->text;
	}
	
	/**
	 * Zwraca wiadomość jako kod HTML
	 * @return string Wiadomość
	 */
	function getHTML() {
		if($this->html === NULL) {
			$doc = $this->getParser();
			$this->parseHTMLDOM( $doc->getElementsByTagName('body')->item(0) );
			$this->html = $doc->saveXML( $doc->getElementsByTagName('body')->item(0) );
		}
		
		return (string)substr($this->html, 6, -7);
	}
	
	/**
	 * Zwraca treść wiadomości zapisaną przy użyciu {@link BotMsg::append()} bez żadnych modyfikacji
	 * @return string Oryginalna wiadomość
	 */
	function getRaw() {
		return $this->raw;
	}
	
	/**
	 * Zwraca wiadomość jako kod HTML
	 * @return string Wiadomość
	 */
	function __toString() {
		return $this->getHTML();
	}
	
	/**
	 * Zwraca kopię drzewa DOM wiadomości
	 * @return DOMDocument Wiadomość
	 */
	function getParser() {
		if($this->parser === NULL) {
			$this->parser = new DOMDocument('1.0', 'utf-8');
			try {
				$this->parser->loadHTML('<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body>'.$this->raw.'</body></html>');
			}
			catch(ErrorException $e) {
				if($e->getSeverity() != E_WARNING) {
					throw $e;
				}
			}
			
			foreach($this->parser->getElementsByTagName('a') as $node) {
				if(!$node->hasAttribute('href')) {
					$node->setAttribute('href', $node->textContent);
				}
			}
		}
		
		return $this->parser->cloneNode(TRUE);
	}
	
	private function parseTextDOM($dom) {
		if(!($dom instanceof DOMElement)) {
			throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
		}
		
		$return = '';
		foreach($dom->childNodes as $node) {
			if($node instanceof DOMText || $node instanceof DOMEntity) {
				$return .= strtr($node->nodeValue, array("\n" => '', "\r" => ''));
			}
			elseif($node instanceof DOMElement) {
				switch(strtolower($node->tagName)) {
					case 'b':
					case 'strong':
						$return .= ($this->beautiful ? '*' : '').$this->parseTextDOM($node).($this->beautiful ? '*' : '');
					break;
					case 'u':
						$return .= ($this->beautiful ? '_' : '').$this->parseTextDOM($node).($this->beautiful ? '_' : '');
					break;
					case 'i':
						$return .= ($this->beautiful ? '/' : '').$this->parseTextDOM($node).($this->beautiful ? '/' : '');
					break;
					case 'br':
						$return .= "\n";
					break;
					case 'p':
						if(substr($return, -1) != "\n") {
							$return .= "\n\n";
						}
						$return .= $this->parseTextDOM($node)."\n\n";
					break;
					case 'h1':
						if(substr($return, -1) != "\n") {
							$return .= "\n\n";
						}
						$return .= ($this->beautiful ? '= ' : '').$this->parseTextDOM($node).($this->beautiful ? ' =' : '')."\n";
					break;
					case 'h2':
						if(substr($return, -1) != "\n") {
							$return .= "\n\n";
						}
						$return .= ($this->beautiful ? '== ' : '').$this->parseTextDOM($node).($this->beautiful ? ' ==' : '')."\n";
					break;
					case 'h3':
						if(substr($return, -1) != "\n") {
							$return .= "\n\n";
						}
						$return .= ($this->beautiful ? '=== ' : '').$this->parseTextDOM($node).($this->beautiful ? ' ===' : '')."\n";
					break;
					case 'td':
						$return .= $this->parseTextDOM($node)."\t";
					break;
					case 'th':
						$return .= ($this->beautiful ? '*' : '').$this->parseTextDOM($node).($this->beautiful ? '*' : '')."\t";
					break;
					case 'tr':
						$return .= $this->parseTextDOM($node)."\n";
					break;
					case 'a':
						$return .= $this->parseTextDOM($node);
						
						if($node->getAttribute('href') != $node->textContent) {
							$return .= ' ('.$node->getAttribute('href').')';
						}
					break;
					case 'script':
					case 'style':
					case 'img':
					break;
					default:
						$return .= $this->parseTextDOM($node);
					break;
				}
			}
			else
			{
				throw new BotMsgException('Nieznany element DOM: '.get_class($node));
			}
		}
		
		return $return;
	}
	
	private function parseHTMLDOM($dom) {
		if(!($dom instanceof DOMNode)) {
			throw new BotMsgException('Nieznany element DOM: '.get_class($dom));
		}
		
		foreach($dom->childNodes as $node) {
			if($node instanceof DOMElement) {
				if($node->hasAttribute('color')) {
					$color = trim($node->getAttribute('color'));
					$node->removeAttribute('color');
					if(substr($color, 0, 1)=='#' AND (strlen($color)==4 OR strlen($color)==7) AND ctype_xdigit(substr($color, 1))) {
						$node->setAttribute('style', 'color:'.$color.';'.$node->getAttribute('style'));
					}
				}
				
				$this->parseHTMLDOM($node);
			}
		}
	}
}
?>