<?php
    //========================================================================================================================
    /**
     * MVCビュー基本クラス
     *
     * ページ内の部分の描画にテンプレートを適用する
     *
     * LICENSE: This source file is subject to GNU GENERAL PUBLIC LICENSE Version 3
     * http://www.gnu.org/licenses/gpl-3.0.txt.
     *
     * @package    LabbitBox
     * @subpackage core
     * @copyright  2010 LabbitBox Development Team.
     * @license    GPL v3 http://www.gnu.org/licenses/gpl-3.0.txt
     * @author     neobaba <neobaba@labbitbox.org>
     * @version    v0.1
     * @since      2010/2/26
     * @link       http://labbitbox.org/
     * @see        http://labbitbox.org/
     */
    //========================================================================================================================
    class Region {
        public $name;
        protected $_doc;
        protected $_parent          = NULL;
        protected $_className       = '';
        protected $_childRegionList = array();
        protected $_elementList     = array();
        protected $_modifyList      = array();
        protected $_keywordList     = array();
        protected $_clientProcList  = array();
        private $_childKeywords     = array();  //トップリージョン専用：子リージョンのキーワードリストを保持する

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * コンストラクタ/デストラクタ
         *
         * @param string    $templateName   テンプレート名
         * @param string    $templateDir    テンプレート格納ディレクトリ
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function __construct($templateName = '', $templateDir = ''){
            $this->name     = get_class($this);

            //基本DOMオブジェクト生成
            $this->_doc     = new DOMDocument();
        
            //テンプレートDOMオブジェクト生成
            if ($templateName != ''){
                $this->setTemplate($templateName, $templateDir);
           }
        }

        /**
         * クラス識別名取得
         * 各サブクラスでセットされたclassNameを返す
         * @return string クラス識別名
         */
        public function className(){
            return $this->_className;
        }
        
        //-------------------------------------------------------------------------------------------------------------------
        /**
         * テンプレートセット
         * @param string    $templateName   テンプレートパス
         *                                  id付divタグ：$templateName が存在しないパスの場合はの$templateNameをIDとしてdivタグを生成する
         *                                  id無divタグ：テンプレート名を指定しない場合はIDなしのdivタグを生成する
         * @param string    $templatePath テンプレート格納パス
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function setTemplate($templateName = '', $templateDir = ''){
            //DOMオブジェクトテンプレートロード
            $this->_doc->preserveWhiteSpace = false;
            $this->_doc->formatOutput       = true;
            if ($templateName != ''){
                $wTemplatePath  = ($templateDir == '' ? $templateName : $templateDir.'/'.$templateName);
                if (file_exists($wTemplatePath)){
                    $this->_doc->loadHTMLFile($wTemplatePath);
                }
                else{
                    $this->_doc->loadHTML('<div id="'.$wTemplatePath.'"></div>');
                }
            }
            else{
                $this->textTemplate();
            }
        }
        
        //-------------------------------------------------------------------------------------------------------------------
        /**
         * テキストテンプレートセット
         * @param string    $templateText   テンプレートHTMLテキスト
         *                                  テキストを指定しない場合は規定のHTML文字列をロードする
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function textTemplate($templateText = ''){
            if ($templateText == ''){
            $html = <<<__TEXT
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="head">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title id="title">LabbitBox</title>
</head>
<body id="body">
</body>
</html>
__TEXT;
            }
            else{
                $meta = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">';
                $html = $meta.$templateText;
            }
            
            //テンプレートロード
            $this->_doc->loadHTML($html);
        }
        
        //-------------------------------------------------------------------------------------------------------------------
        /**
         * レスポンス文字列取得
         * 自DOMオブジェクトからHTML文字列に取得する
         * @param boolean   $output     出力要否：trueを指定した場合はレスポンスを出力する
         * @return string   レスポンス文字列(引数$outputにTRUEを指定した場合は戻り値なしで直接出力する)
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function response($output = TRUE){
            $wDoc       = $this->build();

            if (count($this->_childKeywords) == 0){
                if ($output === TRUE){
                    print $wDoc->saveHTML();
                }
                else{
                    return $wDoc->saveHTML();
                }
            }
            else{
                $htmlText   = $wDoc->saveHTML();
                foreach ($this->_childKeywords as $_elementID => $_replaceInfo){
                    $htmlText   = str_replace('(#$'.$_elementID.'#)', $_replaceInfo['element']->draw(), $htmlText);
                    if (method_exists($_replaceInfo['element'], 'clientProc')){
                        $this->_clientProcList[$_elementID]    = $_replaceInfo['element']->clientProc();
                    }
                } 
                if ($output === TRUE){
                    print $htmlText;
                }
                else{
                    return $htmlText;
                }
            }
        }

        /**
         * エレメント個別特殊処理
         */
        public function clientProc(){
            return $this->_clientProcList;
        }

        //----------------------------------------------------------------------
        /**
         * 親ビューセット
         * @param object    $parent     親ビューオブジェクト
         */
        //----------------------------------------------------------------------
        public function setParent($parent){
            $this->_parent  = $parent;
        }

        //----------------------------------------------------------------------
        /**
         * 親ビューゲット
         * @return object   親ビューオブジェクト
         */
        //----------------------------------------------------------------------
        public function getParent(){
            return $this->_parent;
        }

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * トップビュー取得
         * @return object   トップビューオブジェクト
         */ 
        //-------------------------------------------------------------------------------------------------------------------
        public function getTopRegion(){
            $region     = $this;
            $parent     = $this->_parent;
            while ($parent !== NULL){
                $region = $parent;
                $parent = $parent->getParent();
            }
            return $region;
        }

        public function setChildKeyword($aElementID, $aReplaceInfo){
           $this->_childKeywords[$aElementID]  = $aReplaceInfo;
        }

        //----------------------------------------------------------------------
        /**
         * 従属(子)ビュー追加
         * @param object    $childRegion    従属ビューオブジェクト
         * @param string    $containerID    コンテナーエレメントID：HTML展開時に従属ビューと差し替えるエレメントのID
         */
        //----------------------------------------------------------------------
        public function appendRegion($childRegion, $containerID){
            $this->addChildRegion($childRegion, $containerID, 'APPEND');
        }

        public function displaceRegion($childRegion, $containerID){
            $this->addChildRegion($childRegion, $containerID, 'DISPLACE');
        }

        public function replaceRegion($childRegion, $containerID){
            $this->addChildRegion($childRegion, $containerID, 'REPLACE');
        }

        private function addChildRegion($childRegion, $containerID, $mode){
            // $this->_childRegionList[$containerID]   = array('mode' => $mode, 'region' => $childRegion);
            $this->_childRegionList[]   = array('container'=>$containerID, 'mode' => $mode, 'region' => $childRegion);
            $childRegion->setParent($this);
        }

        //----------------------------------------------------------------------
        /**
         * エレメント追加
         * @param object    $element        追加エレメント
         * @param string    $containerID    追加先エレメントID：NULLの場合はBodyタグ
         */
        //----------------------------------------------------------------------
        public function appendElement($element, $containerID = NULL){
            $this->addElement($element, $containerID, 'APPEND');
        }

        public function displaceElement($element, $containerID = NULL){
            $this->addElement($element, $containerID, 'DISPLACE');
        }

        public function replaceElement($element, $containerID = NULL){
            $this->addElement($element, $containerID, 'REPLACE');
        }

        private function addElement($element, $containerID, $mode){
            $this->_elementList[$element->id] = array('element' => $element, 'containerID' => $containerID, 'mode' => $mode);
        }

        //----------------------------------------------------------------------
        /**
         * 置換キーワード追加(パフォーマンス考慮でDOMを使用しないコンポーネントで使用する)
         * @param string    $Keyword        追加置換キーワード
         * @param string    $containerID    追加先置換キーワードID：NULLの場合はBodyタグ
         */
        //----------------------------------------------------------------------
        public function appendKeyword($Keyword, $containerID = NULL, $element){
            $this->addKeyword($Keyword, $containerID, 'APPEND', $element);
        }

        public function displaceKeyword($Keyword, $containerID = NULL, $element){
            $this->addKeyword($Keyword, $containerID, 'DISPLACE', $element);
        }

        public function replaceKeyword($Keyword, $containerID = NULL, $element){
            $this->addKeyword($Keyword, $containerID, 'REPLACE', $element);
        }

        private function addKeyword($Keyword, $containerID, $mode, $element){
            $this->_keywordList[$Keyword] = array('containerID' => $containerID, 'mode' => $mode, 'element' => $element);
        }
        
        //----------------------------------------------------------------------
        /**
         * エレメント更新用オブジェクト追加
         * @param object    $element        追加エレメント
         * @param string    $containerID    追加先エレメントID：NULLの場合はBodyタグの従属エレメントとなる
         */
        //----------------------------------------------------------------------
        public function addModifyElement($modifyElement){
            $this->_modifyList[$modifyElement->id] = $modifyElement;
        }



        /**
         * ModifyElementの取得
         *
         * ModifyElementが生成されていない場合にのみ生成
         *
         * @param string $elementId 設定先エレメントのID
         *
         * @return ModifyElement
         */
        public function getModifyElementById($elementId){
            if($this->_modifyElements[$elementId]===NULL){
                // ModifyElementが生成済みではない場合にのみ生成
                $this->_modifyElements[$elementId] = new ModifyElement($this, $elementId);
            }
            return $this->_modifyElements[$elementId];
        }



        /**
         * スタイル値の設定
         *
         * @param string $elementId 設定先エレメントのID
         * @param string $styleName スタイル名
         * @param string $value     スタイル設定値
         */
        public function style($elementId, $styleName, $value){
            $mod = $this->getModifyElementById($elementId);
            $mod->style($styleName, $value);
        }



        /**
         * 属性値の設定
         *
         * @param string $elementId     設定先エレメントのID
         * @param string $attributeName attribute名
         * @param string $value         attribute設定値
         */
        public function attribute($elementId, $attributeName, $value){
            $mod = $this->getModifyElementById($elementId);
            $mod->attribute($attributeName, $value);
        }



        /**
         * value値の設定
         *
         * @param string $elementId 設定先エレメントのID
         * @param string $value     value属性設定値
         */
        public function value($elementId, $value){
            $this->attribute($elementId, 'value', $value);
        }



        /**
         * テキストの設定
         *
         * @param string $elementId 設定先エレメントのID
         * @param string $value     innerText設定値
         */
        public function text($elementId, $value){
            $mod = $this->getModifyElementById($elementId);
            $mod->text($value);
        }



        /**
         * innerHTMLの設定
         *
         * text()とは異なり生のHTMLを挿入出来ます
         *
         * @param string $elementId 設定先エレメントのID
         * @param string $value     innerHtml設定値
         */
        public function innerHtml($elementId, $value){
            require_once(BASE_LIB.'/ModifyHtmlText.php');
            new ModifyHtmlText($this, $elementId, $value);
        }






        //----------------------------------------------------------------------
        /**
         * DOM組み立て
         * 指定コンテナに従属ビューを展開し自DOMオブジェクトを返す
         * (アプリケーション全体としては最下層の従属ビューからルートビューまで繰り返すことによりHTMLページ全体のDOMオブジェクトを組み立てる)
         */
        //----------------------------------------------------------------------
        public function build(){
           //エレメント更新
            foreach($this->_modifyList as $_modifyElement){
                $_modifyElement->modify();
            }
            
            //追加エレメント挿入
            foreach($this->_elementList as $_element){
                //イベント登録
                $_element['element']->entryEvent($this);

                //DOM不使用コンポーネントは対象外
                if (!array_key_exists($_element['element']->id, $this->_keywordList)){
                    $this->mountElement($_element['element'], $_element['containerID'], $_element['mode']);
                }
            }
            
            //置換キーワード挿入
            $wTopRegion     = $this->getTopRegion();
            foreach($this->_keywordList as $_elementID => $_keyword){
                $keyword    = '(#$'.$_elementID.'#)';
                $textNode   = new HtmlElement($_elementID, 'TEXT_NODE', $keyword);
                $this->mountElement($textNode, $_keyword['containerID'], $_keyword['mode']);

                $wTopRegion->setChildKeyword($_elementID, $_keyword);
            }
            
            //所属リージョン構築
            foreach($this->_childRegionList as $_child){
                $childRegion    = $_child['region'];
                $_containerID   = $_child['container'];
                if ($_containerID == 'body'){
                    $parentNode   = $this->_doc->getElementsByTagName('body')->item(0);
                }
                else{
                    $parentNode   = $this->getElementById($_containerID);
                }
                
                $childRoot    = $childRegion->build()->getElementsByTagName('body')->item(0)->firstChild;
                $childRoot    = $this->_doc->importNode($childRoot, true);
                
                switch ($_child['mode']){
                    case 'APPEND':
                        $parentNode->appendChild($childRoot);
                        break;
                        
                    case 'DISPLACE':
                        $parentNode->parentNode->replaceChild($childRoot, $parentNode);
                        break;
                        
                    case 'REPLACE':
                        for($i = $parentNode->childNodes->length; $i > 0; $i--){
                            $parentNode->removeChild($parentNode->childNodes->item($i - 1));
                        }
                        $parentNode->appendChild($childRoot);
                        break;
                }
            }
   
            return $this->_doc;
        }
        
        //-------------------------------------------------------------------------------------------------------------------
        /**
         * エレメントをビューへ追加
         */
        //-------------------------------------------------------------------------------------------------------------------
        private function mountElement($element, $container, $mode){
            if ($container != NULL){
                $wContainer     = $this->getElementById($container);
            }
            else{
                if ($this->_doc->getElementById('body') !== NULL){
                    $wContainer    = $this->_doc->getElementsByTagName('body')->item(0)->firstChild;
                    if ($wContainer->nodeName == '#text'){
                        $wContainer = $this->_doc->getElementsByTagName('body')->item(0);
                    }
                }
                else{
                    $wContainer = $this->_doc;
                }
            }
            
            switch ($mode){
                case 'APPEND':
                    $wContainer->appendChild($element->get($this));
                    break;
                    
                case 'DISPLACE':
                    $wContainer->parentNode->replaceChild($element->get($this), $wContainer);
                    break;
                    
                case 'REPLACE':
                    for($i = $wContainer->childNodes->length; $i > 0; $i--){
                        $wContainer->removeChild($wContainer->childNodes->item($i - 1));
                    }
                    $wContainer->appendChild($element->get($this));
                    break;
            }
        }

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * HTML文字列作成
         * response(FALSE) と同一処理
         *
         * @return string   DOMオブジェクトをHTML文字列で取得
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function toString(){
            return $this->response();
        }

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * DOMドキュメント取得
         *
         * @return object   DOMオブジェクト
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function doc(){
            return $this->_doc;
        }

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * DOMエレメントのID指定による取得
         * @param string    $agName エレメントID
         * @param object    $doc          解析対象設定オブジェクト（開発用）
         * @return object   エレメントオブジェクト
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function getElementById($id, $doc = NULL){
            $wkDoc  = ($doc === NULL ? $this->_doc : $doc);
            $xpath = new DOMXPath($wkDoc);
            return $xpath->query("//*[@id='$id']")->item(0);
        }

        //-------------------------------------------------------------------------------------------------------------------
        /**
         * エレメント連想配列の属性指定による取得
         * @param string    $attributeName 属性名
         * @param string    $valute        属性値
         * @param object    $doc           解析対象設定オブジェクト（開発用）
         * @return array                   エレメントID配列
         */
        //-------------------------------------------------------------------------------------------------------------------
        public function getElementsArrayByAttribute($attributeName, $value, $doc = NULL){
            $wkDoc = ($doc === NULL ? $this->_doc : $doc);
            $xpath = new DOMXPath($wkDoc);
            $nodes = $xpath->query("//*[@$attributeName='$value']");
            $ids   = array();
            for($i=0; $i<$nodes->length; $i++){
                foreach($nodes->item($i)->attributes as $n=>$v){
                    $ids[$i][$n] = $v->value;
                }
                $ids[$i]['innerHTML'] = $nodes->item($i)->nodeValue;
            }
            return $ids;
        }
    }

?>
