/*==================================================================================================
= 名前: JSRendering
= 概要: JSによるレンダリングを行うライブラリ。
==================================================================================================*/

/*----- JSRendering初期定義 ----------------------------------------------------------------------*/

if (!$defined(mugendai.jsrendering)) {

    /*----- パッケージ管理 -----------------------------------------------------------------------*/

    /*
     * パッケージを生成する。
     */
    mugendai.jsrendering = { };

    /*----- レンダリングコントローラ定義 ---------------------------------------------------------*/

    /**
     * レンダリングコントローラ。
     * レンダリングをコントロールする。
     */
    mugendai.jsrendering.JSRendering = new Class( {

        /**
         * レンダラクラスセット。
         * KEY: レンダラ名, VALUE: レンダラクラス。
         */
        rendererClasses: { },

        /**
         * コンストラクタ。
         */
        initialize: function() {
        },

        /**
         * 指定要素に対してレンダリングを適用する。
         * @param elem 適用対象のscript要素
         */
        apply: function(elem) {

            //適用対象要素が妥当であるか確認する
            if (!$chk(elem) || elem.get("tag") != "script") {
                throw new Error("適用対象の要素が不正です。");
            }

            //適用対象要素を解析し、レンダリングパラメータを生成する
            var params = this.parseTargetElem(elem);

            //レンダラ名を取得する
            var rendererName = params.renderer;
            if (!$chk(rendererName)) {
                //レンダラ名が指定されていない場合は定義のみで終了する
                return;
            }

            //レンダラクラスを決定する
            var rendererClass = this.rendererClasses[rendererName];
            if (!$chk(rendererClass)) {
                throw new Error("レンダラクラス(" + rendererName + ")が見つかりません。");
            }

            //レンダラを生成し、レンダリングを行う
            var renderer = new rendererClass(params);
            renderer.render();

            //適用対象のscript要素を破棄する
            elem.destroy();

        },

        /**
         * 適用対象要素を解析し、レンダリングパラメータを生成する。
         * @param elem 適用対象のscript要素
         * @return レンダリングパラメータ
         *      KEY: パラメータ名, VALUE: パラメータ値
         */
        parseTargetElem: function(elem) {

            //src属性のパラメータ部を抜き出す
            if (!$pick(elem.get("src"), "").test(/\?(.*)/)) {
                return { };
            }
            var paramsAsString = RegExp.$1;

            //パラメータ部を連想配列に展開し返却する
            return paramsAsString.parseQueryString();

        }

    } );

    /*----- レンダリングパラメータ定義 -----------------------------------------------------------*/

    /**
     * コンテンツレンダリングパラメータ。
     * レンダリング内容の本体部を扱う為のレンダリングパラメータクラス。
     */
    mugendai.jsrendering.ContentsRenderingParams = new Class( {

        /**
         * レンダリング内容の本体部を取得する。
         * @return レンダリング内容の本体部
         */
        getContents: function() {
            return $splat(this.params.contents).join("");
        }

    } );

    /**
     * タグレンダリングパラメータ。
     * タグを扱う為のレンダリングパラメータクラス。
     */
    mugendai.jsrendering.TagRenderingParams = new Class( {

        /**
         * タグ名を取得する。
         * @return タグ名
         */
        getTag: function() {
            return $splat(this.params.tag).join("");
        }

    } );

    /**
     * 属性レンダリングパラメータ。
     * 属性を扱う為のレンダリングパラメータクラス。
     */
    mugendai.jsrendering.AttrsRenderingParams = new Class( {

        /**
         * レンダリングパラメータの属性値を示す部分の正規表現。
         */
        attrRegExp: /^attr:/,

        /**
         * 属性セットを取得する。
         * @return 属性セット
         *      KEY: 属性名, VALUE: 属性値
         */
        getAttrs: function() {

            //属性パラメータを抽出する
            var attrParams = $H(this.params).filter(function(value, key, params) {
                return key.test(this.attrRegExp);
            }, this);

            //属性パラメータのキーから、属性値を示す部分を取り除く
            var attrs = { };
            attrParams.each(function(value, key, attrParams) {
                this.result[key.replace(this.object.attrRegExp, "")] = $splat(value).join("");
            }, {
                object: this,
                result: attrs
            } );

            //属性セットを返却する
            return attrs;

        },

        /**
         * 属性を取得する。
         * @param name 属性名
         * @return 属性値
         */
        getAttr: function(name) {
            return this.getAttrs[name];
        },

        /**
         * 属性セットをHTMLで取得する。
         * @return 属性セット
         */
        getAttrsAsHTML: function() {
            var attrAsHTMLs = [ ];
            $H(this.getAttrs()).each(function(value, name, attrs) {
                this.result.push(this.object._makeAttrAsHTML(name, value));
            }, {
                object: this,
                result: attrAsHTMLs
            } );
            return attrAsHTMLs.join(" ");
        },

        /**
         * 属性をHTMLで取得する。
         * @param name 属性名
         * @return 属性値
         */
        getAttrAsHTML: function(name) {
            return this._makeAttrAsHTML(name, this.getAttrs[name]);
        },

        /**
         * 属性のHTMLを生成する。
         * @param name 属性名
         * @param value 属性値
         * @param quot 属性引用符
         */
        _makeAttrAsHTML: function(name, value, quot /* = "\"" */) {

            //パラメータのデフォルト値をセットする
            //  属性引用符
            if (!$defined(quot)) {
                quot = "\"";
            }

            //属性のHTMLを生成し返却する
            return name + "=" + quot + value + quot;

        }

    } );

    /*----- レンダラ定義 -------------------------------------------------------------------------*/

    /**
     * レンダラ。
     * レンダリングの適用処理を行う為の基底クラス。
     */
    mugendai.jsrendering.JSRenderer = new Class( {

        /**
         * レンダリングパラメータ。
         * KEY: パラメータ名, VALUE: パラメータ値。
         */
        params: { },

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  レンダリングパラメータ
            $extend(this.params, params);

            //レンダリングパラメータを調整する
            this.adjustParams();

        },

        /**
         * レンダリングパラメータを調整する。
         * 本メソッドでデフォルト値のセットや型変換を適宜行う。
         */
        adjustParams: function() {
            //デフォルトではNOP
        },

        /**
         * レンダリングを行う。
         * @return レンダリング結果
         */
        render: function() {
            //デフォルトではNOP
        }

    } );

    /**
     * HTMLレンダラ。
     * HTMLをレンダリングするレンダラクラス。
     */
    mugendai.jsrendering.HTMLJSRenderer = new Class( {

        Extends: mugendai.jsrendering.JSRenderer,

        Implements: mugendai.jsrendering.ContentsRenderingParams,

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         *      contents: HTMLコンテンツ (デフォルトは空)
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  基底クラス
            this.parent(params);

        },

        /**
         * レンダリングパラメータを調整する。
         */
        adjustParams: function() {

            //基底の処理を行う
            this.parent();

            //デフォルト値をセットする
            //  HTMLコンテンツ
            if (!$defined(this.params.contents)) {
                this.params.contents = "";
            }

        },

        /**
         * レンダリングを行う。
         */
        render: function() {

            //コンテンツをレンダリングする
            var contents = this.getContents();
            if ($chk(contents)) {
                document.write(contents);
            }

        }

    } );

    /**
     * テキストレンダラ。
     * テキストをHTMLエスケープしてレンダリングするレンダラクラス。
     */
    mugendai.jsrendering.TextJSRenderer = new Class( {

        Extends: mugendai.jsrendering.HTMLJSRenderer,

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         *      contents: テキストコンテンツ (デフォルトは空)
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  基底クラス
            this.parent(params);

        },

        /**
         * レンダリング内容の本体部を取得する。
         * @return レンダリング内容の本体部
         */
        getContents: function() {
            //HTMLエスケープした値をコンテンツとして返却する
            return this.parent().escapeHTML();
        }

    } );

    /**
     * 開始タグレンダラ。
     * 開始タグをレンダリングするレンダラクラス。
     */
    mugendai.jsrendering.STagJSRenderer = new Class( {

        Extends: mugendai.jsrendering.JSRenderer,

        Implements: [
            mugendai.jsrendering.TagRenderingParams,
            mugendai.jsrendering.AttrsRenderingParams
        ],

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         *      tag: タグ名
         *      attr:<属性名>: 属性値
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  基底クラス
            this.parent(params);

        },

        /**
         * レンダリングパラメータを調整する。
         */
        adjustParams: function() {

            //基底の処理を行う
            this.parent();

            //レンダリングパラメータをチェックする
            //  タグ名
            if (!$chk(this.params.tag)) {
                throw new Error("タグ名が指定されていません。");
            }

        },

        /**
         * レンダリングを行う。
         */
        render: function() {

            //開始タグをレンダリングする
            var tag = this.getTag();
            if ($chk(tag)) {

                //開始部をレンダリングする
                document.write("<");
                document.write(tag);

                //属性部をレンダリングする
                var attrs = this.getAttrsAsHTML();
                if ($chk(attrs)) {
                    document.write(" ");
                    document.write(attrs);
                }

                //終了部をレンダリングする
                document.write(">");

            }

        }

    } );

    /**
     * 終了タグレンダラ。
     * 終了タグをレンダリングするレンダラクラス。
     */
    mugendai.jsrendering.ETagJSRenderer = new Class( {

        Extends: mugendai.jsrendering.JSRenderer,

        Implements: mugendai.jsrendering.TagRenderingParams,

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         *      tag: タグ名
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  基底クラス
            this.parent(params);

        },

        /**
         * レンダリングパラメータを調整する。
         */
        adjustParams: function() {

            //基底の処理を行う
            this.parent();

            //レンダリングパラメータをチェックする
            //  タグ名
            if (!$chk(this.params.tag)) {
                throw new Error("タグ名が指定されていません。");
            }

        },

        /**
         * レンダリングを行う。
         */
        render: function() {

            //終了タグをレンダリングする
            var tag = this.getTag();
            if ($chk(tag)) {

                //開始部をレンダリングする
                document.write("</");
                document.write(tag);

                //終了部をレンダリングする
                document.write(">");

            }

        }

    } );

    /**
     * 空要素タグレンダラ。
     * 空要素タグをレンダリングするレンダラクラス。
     */
    mugendai.jsrendering.EETagJSRenderer = new Class( {

        Extends: mugendai.jsrendering.JSRenderer,

        Implements: [
            mugendai.jsrendering.TagRenderingParams,
            mugendai.jsrendering.AttrsRenderingParams
        ],

        /**
         * コンストラクタ。
         * @param params レンダリングパラメータ
         *      tag: タグ名
         *      attr:<属性名>: 属性値
         */
        initialize: function(params) {

            //インスタンスを初期化する
            //  基底クラス
            this.parent(params);

        },

        /**
         * レンダリングパラメータを調整する。
         */
        adjustParams: function() {

            //基底の処理を行う
            this.parent();

            //レンダリングパラメータをチェックする
            //  タグ名
            if (!$chk(this.params.tag)) {
                throw new Error("タグ名が指定されていません。");
            }

        },

        /**
         * レンダリングを行う。
         */
        render: function() {

            //空要素タグをレンダリングする
            var tag = this.getTag();
            if ($chk(tag)) {

                //開始部をレンダリングする
                document.write("<");
                document.write(tag);

                //属性部をレンダリングする
                var attrs = this.getAttrsAsHTML();
                if ($chk(attrs)) {
                    document.write(" ");
                    document.write(attrs);
                }

                //終了部をレンダリングする
                document.write(" />");

            }

        }

    } );

    /*----- 初期処理 -----------------------------------------------------------------------------*/

    /*
     * レンダリングコントローラをSingletonとして生成する。
     */
    mugendai.jsrendering.JSRendering = new mugendai.jsrendering.JSRendering();

    /*
     * レンダラクラスを登録する。
     */
    mugendai.jsrendering.JSRendering.rendererClasses["html"] = mugendai.jsrendering.HTMLJSRenderer;
    mugendai.jsrendering.JSRendering.rendererClasses["text"] = mugendai.jsrendering.TextJSRenderer;
    mugendai.jsrendering.JSRendering.rendererClasses["stag"] = mugendai.jsrendering.STagJSRenderer;
    mugendai.jsrendering.JSRendering.rendererClasses["etag"] = mugendai.jsrendering.ETagJSRenderer;
    mugendai.jsrendering.JSRendering.rendererClasses["eetag"] = mugendai.jsrendering.EETagJSRenderer;

}

/*----- レンダリングの実行 -----------------------------------------------------------------------*/

( function() {
    var elem = mugendai.JSLoader.getDeepestScriptElem();
    mugendai.jsrendering.JSRendering.apply(elem);
} )();
