// uuid() {{{
// copy from http://www.broofa.com/blog/2008/09/javascript-uuid-function/
var uuid = (function () {
    // Private array of chars to use
    var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');

    return function (len, radix) {
        var chars = CHARS, uuid = [], rnd = Math.random;
        radix = radix || chars.length;

        if (len) {
            // Compact form
            for (var i = 0; i < len; i++) uuid[i] = chars[0 | rnd()*radix];
        } else {
            // rfc4122, version 4 form
            var r;

            // rfc4122 requires these characters
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';

            // Fill in random data.  At i==19 set the high bits of clock sequence as
            // per rfc4122, sec. 4.1.5
            for (var i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | rnd()*16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
                }
            }
        }

        return uuid.join('').toLowerCase();
    };
})();
// }}}

// sprintf() {{{
/*
**  sprintf.js -- POSIX sprintf(3) style formatting function for JavaScript
**  Copyright (c) 2006-2007 Ralf S. Engelschall <rse@engelschall.com>
**  Partly based on Public Domain code by Jan Moesen <http://jan.moesen.nu/>
**  Licensed under GPL <http://www.gnu.org/licenses/gpl.txt>
**
**  $LastChangedDate$
**  $LastChangedRevision$
*/

/*  make sure the ECMAScript 3.0 Number.toFixed() method is available  */
if (typeof Number.prototype.toFixed != "undefined") {
    (function(){
        /*  see http://www.jibbering.com/faq/#FAQ4_6 for details  */
        function Stretch(Q, L, c) {
            var S = Q;
            if (c.length > 0) {
                while (S.length < L) {
                    S = c+S;
                }
            }
            return S;
        }
        function StrU(X, M, N) { /* X >= 0.0 */
            var T, S;
            S = new String(Math.round(X * Number("1e"+N)));
            if (S.search && S.search(/\D/) != -1) {
                return ''+X;
            }
            with (new String(Stretch(S, M+N, '0'))) {
                return substring(0, T=(length-N)) + '.' + substring(T);
            }
        }
        function Sign(X) {
            return X < 0 ? '-' : '';
        }
        function StrS(X, M, N) {
            return Sign(X)+StrU(Math.abs(X), M, N);
        }
        Number.prototype.toFixed = function(n) { return StrS(this, 1, n); };
    })();
}

/*  the sprintf() function  */
var sprintf = function () {
    /*  argument sanity checking  */
    if (!arguments || arguments.length < 1)
        alert("sprintf:ERROR: not enough arguments");

    /*  initialize processing queue  */
    var argumentnum = 0;
    var done = "", todo = arguments[argumentnum++];

    /*  parse still to be done format string  */
    var m;
    while (m = /^([^%]*)%(\d+$)?([#0 +'-]+)?(\*|\d+)?(\.\*|\.\d+)?([%diouxXfFcs])(.*)$/.exec(todo)) {
        var pProlog    = m[1],
        pAccess    = m[2],
        pFlags     = m[3],
        pMinLength = m[4],
        pPrecision = m[5],
        pType      = m[6],
        pEpilog    = m[7];

        /*  determine substitution  */
        var subst;
        if (pType == '%')
            /*  special case: escaped percent character  */
            subst = '%';
        else {
            /*  parse padding and justify aspects of flags  */
            var padWith = ' ';
            var justifyRight = true;
            if (pFlags) {
                if (pFlags.indexOf('0') >= 0)
                    padWith = '0';
                if (pFlags.indexOf('-') >= 0) {
                    padWith = ' ';
                    justifyRight = false;
                }
            }
            else
                pFlags = "";

            /*  determine minimum length  */
            var minLength = -1;
            if (pMinLength) {
                if (pMinLength == "*") {
                    var access = argumentnum++;
                    if (access >= arguments.length)
                        alert("sprintf:ERROR: not enough arguments");
                    minLength = arguments[access];
                }
                else
                    minLength = parseInt(pMinLength, 10);
            }

            /*  determine precision  */
            var precision = -1;
            if (pPrecision) {
                if (pPrecision == ".*") {
                    var access = argumentnum++;
                    if (access >= arguments.length)
                        alert("sprintf:ERROR: not enough arguments");
                    precision = arguments[access];
                }
                else
                    precision = parseInt(pPrecision.substring(1), 10);
            }

            /*  determine how to fetch argument  */
            var access = argumentnum++;
            if (pAccess)
                access = parseInt(pAccess.substring(0, pAccess.length - 1), 10);
            if (access >= arguments.length)
                alert("sprintf:ERROR: not enough arguments");

            /*  dispatch into expansions according to type  */
            var prefix = "";
            switch (pType) {
            case 'd':
            case 'i':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = subst.toString(10);
                if (pFlags.indexOf('#') >= 0 && subst >= 0)
                    subst = "+" + subst;
                if (pFlags.indexOf(' ') >= 0 && subst >= 0)
                    subst = " " + subst;
                break;
            case 'o':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = subst.toString(8);
                break;
            case 'u':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = Math.abs(subst);
                subst = subst.toString(10);
                break;
            case 'x':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = subst.toString(16).toLowerCase();
                if (pFlags.indexOf('#') >= 0)
                    prefix = "0x";
                break;
            case 'X':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = subst.toString(16).toUpperCase();
                if (pFlags.indexOf('#') >= 0)
                    prefix = "0X";
                break;
            case 'f':
            case 'F':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0.0;
                subst = 0.0 + subst;
                if (precision > -1) {
                    if (subst.toFixed)
                        subst = subst.toFixed(precision);
                    else {
                        subst = (Math.round(subst * Math.pow(10, precision)) / Math.pow(10, precision));
                        subst += "0000000000";
                        subst = subst.substr(0, subst.indexOf(".")+precision+1);
                    }
                }
                subst = '' + subst;
                if (pFlags.indexOf("'") >= 0) {
                    var k = 0;
                    for (var i = (subst.length - 1) - 3; i >= 0; i -= 3) {
                        subst = subst.substring(0, i) + (k === 0 ? "." : ",") + subst.substring(i);
                        k = (k + 1) % 2;
                    }
                }
                break;
            case 'c':
                subst = arguments[access];
                if (typeof subst != "number")
                    subst = 0;
                subst = String.fromCharCode(subst);
                break;
            case 's':
                subst = arguments[access];
                if (precision > -1)
                    subst = subst.substr(0, precision);
                if (typeof subst != "string")
                    subst = "";
                break;
            }

            /*  apply optional padding  */
            var padding = minLength - subst.toString().length - prefix.toString().length;
            if (padding > 0) {
                var arrTmp = new Array(padding + 1);
                if (justifyRight)
                    subst = arrTmp.join(padWith) + subst;
                else
                    subst = subst + arrTmp.join(padWith);
            }

            /*  add optional prefix  */
            subst = prefix + subst;
        }

        /*  update the processing queue  */
        done = done + pProlog + subst;
        todo = pEpilog;
    }
    return (done + todo);
};
// }}}

/* the getHttpURL() function {{{ */
// 从字符串 str 中解析出 http,https 地址，如果没有找到，则返回空字符串 ''
var getHttpURL = function(str) {
    if (!$chk(str)) return '';
    var matchs = str.match(/https?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/);
    return ($chk(matchs) && $chk(matchs[0])) ? matchs[0] : '';
};
// }}}

// Config {{{
var Config = new Class({
    _config: {},
    set: function() {
        var silent;     // 当声明了slient之后，不会触发set事件
        if ($type(arguments[0]) === 'object') {
            silent = arguments[1];
            var set = arguments[0], config = this._config;
            for (var key in set) {
                config[key] = set[key];
            }
        } else {
            var key = arguments[0], val = arguments[1], silent = arguments[2], set = {};
            set[key] = val;
            this._config[key] = val;
        }

        if (!silent && this.fireEvent) {
            this.fireEvent('set', [set, this]);
        }
        return this;
    },
    get: function(/* arg1, arg2[, arg3[, ...]] */) {
        if (!arguments.length) return this._config;

        var result = [], config = this._config;
        for (var i = 0, len = arguments.length; i < len; i++) {
            var key = arguments[i];
            if (!this.has(key)) { throw new Error('specified config key ['+ key +'] not exist'); }

            if (len == 1) { return config[key]; }
            result.push(config[key]);
        }

        return result.associate(arguments);
    },
    has: function(key) {
        return (key in this._config);
    }
});
// }}}

// SurveypieWidget {{{
var SurveypieWidget = new Class({
    _place: {},
    _place_els: {},
    getEl: function(/* string */place) {
        if (place) return this._getPlaceEl(place);
        return this.el;
    },
    _getPlaceEl: function(/* string */name) {
        var els = this._place_els;

        if (els[name]) return els[name];

        var selector = this._place[name];
        if (!selector) { throw new Error('SurveypieWidget._getPlaceEl(): invalid place ['+ name +']'); }

        var el = this.el.getElement(selector);
        els[name] = el;
        return el;
    }
});
// }}}

// xml utils {{{
// Based on <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247> (copy from ajaxslt)
var DOM_ELEMENT_NODE = 1;
var DOM_ATTRIBUTE_NODE = 2;
var DOM_TEXT_NODE = 3;
var DOM_CDATA_SECTION_NODE = 4;
var DOM_ENTITY_REFERENCE_NODE = 5;
var DOM_ENTITY_NODE = 6;
var DOM_PROCESSING_INSTRUCTION_NODE = 7;
var DOM_COMMENT_NODE = 8;
var DOM_DOCUMENT_NODE = 9;
var DOM_DOCUMENT_TYPE_NODE = 10;
var DOM_DOCUMENT_FRAGMENT_NODE = 11;
var DOM_NOTATION_NODE = 12;

/**
 * http://developer.mozilla.org/en/docs/DOMParser
 * 接受一段xml，解析成为xml document对象
 * 调用时可用try{}catch(e){}捕捉错误，错误对象具有parser xml message属性
 * @param   {String}    xml string
 * @return  {Object}    xml document object
 */
function xDocument(/* String */xml) {
    if ( typeof xml != 'string' ) return xml;

    var doc = null;
    if (window.ActiveXObject) {
        var ActiveIds = ['MSXML2.DOMDocument.6.0', 'MSXML2.DOMDocument.3.0', 'MSXML2.DOMDocument.5.0', 'MSXML2.DOMDocument.4.0', 'MSXML2.DOMDocument'];
        try {
            for (var i = 0, id; id = ActiveIds[i]; i++) {
                var doc = new ActiveXObject(id);
                doc.async = false;
                doc.setProperty('SelectionLanguage', 'XPath');
                doc.loadXML(xml);
                break;
            }
        } catch (ex) {
        } finally {
            if (doc && doc.parseError && doc.parseError.errorCode !== 0) {
                throw new Error(doc.parseError.reason);
            }
        }
    } else if (typeof DOMParser != 'undefined') {
        var parser = new DOMParser();
        var doc = parser.parseFromString(xml, 'text/xml');
        if (doc.documentElement.nodeName == 'parsererror') {
            throw new Error(doc.documentElement.firstChild.nodeValue);
        }
    } else { return false; }

    return doc;
}

/**
 * 获取指定节点或文档对象的xml内容
 * port from ajaxslt xText()
 * @param   {Object}    xml DOM Node or xml Document
 * @return  {String}    xml string
 */
function xText(/* Document|Element */node) {
    if (typeof node == 'string') { return node; }

    if (node.innerXML) {
        return node.innerXML;
    } else if (node.xml) {
        return node.xml;
    } else if (typeof XMLSerializer != 'undefined') {
        return (new XMLSerializer()).serializeToString(node);
    } else { return false; }
}

/**
 * 获取所有节点的内容
 * port from ajaxslt xValue()
 * @param   {Object}    xml DOM Node or xml Document
 * @return  {String}
 */
function xValue(/* Document|Element */node) {
    var val = '';
    if (node.nodeType == DOM_TEXT_NODE ||
        node.nodeType == DOM_CDATA_SECTION_NODE ||
        node.nodeType == DOM_ATTRIBUTE_NODE) {
        val += node.nodeValue;
    } else if (node.nodeType == DOM_ELEMENT_NODE ||
               node.nodeType == DOM_DOCUMENT_NODE ||
               node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) {
        for (var len = node.childNodes.length, i = 0; i < len; i++) {
            val += arguments.callee(node.childNodes[i]);
        }
    }
    return val;
}

/**
 * 获取节点属性，以对象形式返回
 * @param   {Object}
 * @return  {Object}
 */
function xGetAttr(/* Element */node, /* Boolean */typeFormat) {
    var result = {}, typeFormat = typeFormat ? true : false;
    var nodeMap = node.attributes;
    if (nodeMap) {
        for (var len = nodeMap.length, i = 0; i < len; i++) {
            var aNode = nodeMap.item(i);
            var value = aNode.nodeValue;
            if (typeFormat) { value = value.test(/^\d+$/) ? value.toInt() : value; }
            result[aNode.nodeName] = value;
        }
    }
    return result;
}

/**
 * 设置节点属性
 * @param   {Object}    xml DOM Node
 * @param   {Object}    attribute data as object type
 * @return  {Void}
 */
function xSetAttr(/* Element */node, /* Object */attributes) {
    var attributes = attributes || {};
    for (var key in attributes) {
        node.setAttribute(key, attributes[key]);
    }
}

/**
 * 使用xpath在xml树中查询
 * http://developer.mozilla.org/en/docs/Introduction_to_using_XPath_in_JavaScript
 * @param   {Object}    xml Document or xml DOM Node
 * @param   {String}    xpath query string
 * @param   {Boolean}   if set true, only return first result node
 * @return  {Mixed}     return array include all nodes or first node
 */
function xSelect(/* Document|Element */root, /* String */query, /* Boolean */returnFirst) {
    var nodes = [];
    if (window.ActiveXObject) {
        if (returnFirst) return root.selectSingleNode(query);
        var search = root.selectNodes(query);
        for (var i = 0, node; node = search[i]; i++) nodes.push(node);
    } else if (document.evaluate) {
        /**
         * DOMParser的return type有ordered和unordered两种
         * ordered会按照树中间的顺序排列结果，unordered不一定
         * 另外还有一种snapshots的，这种结果是树节点的一个clone
         * 也就是说，如果操作结果节点，原来的节点不会有改变
         * 这里使用非clone方式
         */
        var returnType = returnFirst ? XPathResult.FIRST_ORDERED_NODE_TYPE : XPathResult.ORDERED_NODE_ITERATOR_TYPE;
        var doc = (root.nodeType == DOM_DOCUMENT_NODE) ? root : root.ownerDocument;
        var root = (root.nodeType == DOM_DOCUMENT_NODE) ? root.documentElement : root;
        var result = doc.evaluate(query, root, null, returnType, null);

        if (returnFirst) return result.singleNodeValue;

        var nextNode = result.iterateNext();
        while (nextNode) {
            nodes.push(nextNode);
            nextNode = result.iterateNext();
        }
    }
    return nodes;
}

/**
 * http://developer.mozilla.org/en/docs/Transforming_XML_with_XSLT
 * http://developer.mozilla.org/en/docs/Using_the_Mozilla_JavaScript_interface_to_XSL_Transformations
 * @param   {Mixed}     source xml, xml string or xml Document or xml DOM Node
 * @param   {Mixed}     xslt style sheet, xml string or xml Document or xml DOM Node
 * @param   {Boolean}   if set true, return processed xml as Document
 * @return  {Mixed}     return string or Document
 */
function xProcess(/* String|Document|Element */srcDoc, /* String|Document|Element */stylesheet, /* Boolean */returnAsDoc) {
    var returnAsDoc = (typeof returnAsDoc == 'undefined') ? false : returnAsDoc;
    try {
        var srcDoc = (typeof srcDoc == 'string') ? xDocument(srcDoc) : srcDoc;
        var stylesheet = (typeof stylesheet == 'string') ? xDocument(stylesheet) : stylesheet;
    } catch(e) { throw e; }

    if (typeof XSLTProcessor != 'undefined') {
        var processor = new XSLTProcessor();
        try {
            processor.importStylesheet(stylesheet);
            var dest = processor.transformToDocument(srcDoc);
            return returnAsDoc ? dest : xText(dest);
        } catch(e) { throw new Error('xslt process failed'); }
    } else if (window.ActiveXObject) {
        try {
            var dest = srcDoc.transformNode(stylesheet);
            return returnAsDoc ? xDocument(dest) : dest;
        } catch(e) { throw new Error('xslt process failed'); }
    }

    return false;
}
// }}}

/* mootools implement {{{ */
String.implement({
    isHtml: function() {
        return /<\s?(\w+)[^<>]*>.*<\s?\/\s?\1\s?>|<\s?\w+[^<>]*\/?>/.test(this);
    },
    nl2br: function() {
        return this.replace(/(\r\n)|(\r)|(\n)/g, '<br/>');
    },
    htmlspecialchars: function() {
        if (!this.length || !this.trim().length) {
            return this;
        } else if (/[&<>"']/.test(this)) {
            var result;
            result = this.replace(/&/g, '&amp;');
            result = result.replace(/</g, '&lt;');
            result = result.replace(/>/g, '&gt;');
            result = result.replace(/"/g, '&quot;');
            result = result.replace(/'/g, '&#039;');
            return result;
        } else {
            return this;
        }
    }
});

Element.implement({
    addClassOnOver: function(/* string */class_name) {
        this.addEvents({
            'mouseover': (function(class_name) {
                this.addClass(class_name);
            }).bind(this, class_name),
            'mouseout': (function(class_name) {
                this.removeClass(class_name);
            }).bind(this, class_name)
        });
    },
    show: function(/* string */type) {
        var type = type || '';
        this.setStyle('display', type);
        return this;
    },
    hide: function() {
        this.setStyle('display', 'none');
        return this;
    },
    getLayout: function() {
        var styles = this.getStyles('padding', 'margin', 'border-width');
        var padding = styles['padding'].split(' ').map(function(item) { return item.toInt(); });
        var margin = styles['margin'].split(' ').map(function(item) { return item.toInt(); });
        var border = styles['border-width'].split(' ').map(function(item) { return item.toInt(); });

        var layout = {
            'padding': {
                'top': padding[0],
                'right': $defined(padding[1]) ? padding[1] : padding[0],
                'bottom': $defined(padding[2]) ? padding[2] : padding[0],
                'left': $defined(padding[3]) ? padding[3] : padding[0]
            },
            'margin': {
                'top': margin[0],
                'right': $defined(margin[1]) ? margin[1] : margin[0],
                'bottom': $defined(margin[2]) ? margin[2] : margin[0],
                'left': $defined(margin[3]) ? margin[3] : margin[0]
            },
            'border': {
                'top': border[0],
                'right': $defined(border[1]) ? border[1] : border[0],
                'bottom': $defined(border[2]) ? border[2] : border[0],
                'left': $defined(border[3]) ? border[3] : border[0]
            }
        };

        var size = this.getSize();
        layout['height'] = size.y
            - layout['border']['top'] - layout['border']['bottom']
            - layout['padding']['top'] - layout['padding']['bottom'];
        layout['width'] = size.x
            - layout['border']['left'] - layout['border']['right']
            - layout['padding']['left'] - layout['padding']['right'];

        layout['total'] = {};
        layout['total']['height'] = layout['height']
            + layout['margin']['top'] + layout['margin']['bottom']
            + layout['border']['top'] + layout['border']['bottom']
            + layout['padding']['top'] + layout['padding']['bottom'];

        layout['total']['width'] = layout['width']
            + layout['margin']['left'] + layout['margin']['right']
            + layout['border']['left'] + layout['border']['right']
            + layout['padding']['left'] + layout['padding']['right'];

        return layout;
    },
    fitParent: function(/* string */selector) {
        var offset_els = [], offset = 0;
        offset_els.extend(this.getAllPrevious()).extend(this.getAllNext());
        for (var i = 0, len = offset_els.length; i < len; i++) {
            var el = offset_els[i];
            if (el.get('tag') == 'script') { continue; }
            if (el.getStyle('display') == 'none' || el.getStyle('visibility') == 'hidden' || el.getStyle('position') == 'absolute') { continue; }
            offset += el.getLayout()['total']['height'];
        }

        var this_layout = this.getLayout();
        offset += this_layout['total']['height'] - this_layout['height'];

        var height = this.getParent(selector).getLayout()['height'] - offset;
        this.setStyle('height', height > 0 ? height : 0);
    },
    cover: function(/* string|HTMLElement */target, /* object */styles) {
        var target = $(target), target_layout = target.getLayout();
        var this_layout = this.getLayout();
        if (styles) this.setStyles(styles);

        this.setPosition(target.getPosition());

        this.setStyles({
            'height': target_layout['height'],
            'width': target_layout['width']
        });
    },	     
    nextFocus: function(next) {
        this.addEvent(
            'keydown', 
            function(event) {
                if (event.key == 'enter') 
                    next.focus();
            });
        return next;
    }
});
/* }}} */

function contact() {
    return $A(arguments).join('');
}

/* Dialog {{{ */
var Dialog = new Class({
    Implements: [Config, Events],
    _config: {
        'modal': true,
        'dragable': false,
        'title': '',
        'auto_scroll': false,
        'footer_align': 'center',
        'width': 'auto',
        'height': 'auto',
        'top': 'auto',
        'left': 'auto',
        'z_index': 2000
    },
    initialize: function(/* HTMLElement|string */el, /* object */config) {
        this.set(config);
        this.el = $(el) || new Element('div');
        this._fixElement()._fixLayout()._bindEvents();
    },
    _fixElement: function() {
        var box = new Element('div', {
            'class': 'windowbox',
            'styles': {
                'visibility': 'hidden',
                'position': 'absolute',
                'z-index': this.get('z_index')
            }
        });
        this.box = box;

        var root = this.el;
        box.appendChild(root);

        if (!root.hasClass('container')) { root.addClass('container'); }
        root.setStyle('position', 'relative');
        root.setStyles(this.get('height', 'width'));

        var header = root.getElement('.header');
        if (!header) {
            header = new Element('div', {'class': 'header'});
            header.inject(root, 'top');
        }

        var header_title = header.getElement('.title');
        if (!header_title) {
            header_title = new Element('span', {'class': 'title'});
            header_title.inject(header, 'top');
        }

        var title = this.get('title');
        this.header_title = header_title;
        this.setTitle(title);

        var header_tool = header.getElement('.tool');
        if (!header_tool) {
            header_tool = new Element('span', {'class': 'tool', 'text': 'x'});
            header_tool.inject(header, 'bottom');
        }

        var body = root.getElement('.body');
        if (!body) {
            body = new Element('div', {'class': 'body'});
            body.inject(header, 'after');
        }
        body.setStyle('overflow', this.get('auto_scroll') ? 'auto' : 'hidden');

        var footer = root.getElement('.footer');
        if (!footer) {
            footer = new Element('div', {'class': 'footer'});
            footer.inject(body, 'after');
        }
        footer.setStyle('text-align', this.get('footer_align'));

        if (this.get('modal')) { this._modal_layer = new Dialog.Modal(); }

        $(document.body).appendChild(box);

        return this;
    },
    setTitle: function (title) {
        if (title) { this.header_title.set('html', title); }
    },
    setWidth: function(value) {
        this.el.setStyles('width', value);
    },
    setHeight: function(value) {
        this.el.setStyles('height', value);
    },
    _bindEvents: function() {
        var header = this.getEl('header');
        if (this.get('dragable')) {
            try {
                header.setStyle('cursor', 'move');
                new Drag(this.box, {'handle': header});
            } catch(ex) {}
        }

        header.getElement('.tool').addEvent('click', this.hide.bind(this));

        if (this.get('top') == 'auto' || this.get('left') == 'auto') {
            window.addEvents({
                'resize': this._fixPosition.bind(this),
                'scroll': this._fixPosition.bind(this)
            });
        }

        return this;
    },
    _fixLayout: function() {
        var root = this.el;

        var root_layout = root.getLayout();
        var header_layout = this.getEl('header').getLayout();
        var body = this.getEl('body'), body_layout = body.getLayout();
        var footer_layout = this.getEl('footer').getLayout();

        var offset = [
            header_layout['total']['height'],
            footer_layout['total']['height'],
            body_layout['total']['height'] - body_layout['height']
        ];

        var sum = 0;
        for (var i = 0, len = offset.length; i < len; i++) {
            sum += offset[i];
        }

        body.setStyle('height', root_layout['height'] - sum);

        return this;
    },
    _fixPosition: function() {
        var box = this.box, box_size = box.getSize();
        var body_size = $(document.body).getSize();
        var styles = {}, config = this.get('left', 'top');

        styles['top'] = (config['top'] == 'auto') ? ((body_size['y'] - box_size['y']) / 2) : config['top'];
        styles['left'] = (config['left'] == 'auto') ? ((body_size['x'] - box_size['x']) / 2) : config['left'];

        styles['top'] += $(document).getScroll()['y'];

        box.setStyles(styles);
        return this;
    },
    setBody: function(/* HTMLElement|string */content, /* string */method) {
        var body = this.getEl('body');
        body.empty();

        if ($type(content) == 'string') {
            body.set('html', content);
        } else {
            body.appendChild(content);
        }
        this._fixLayout();

        return this;
    },
    addButton: function(/* string|object */properties, /* function */callback) {
        if ($type(properties) == 'string') { properties = {'text': properties}; }
        properties['type'] = 'button';

        var button = new Element('button', properties);

        if ($type(callback) == 'function') {
            button.addEvent('click', callback);
        }
        this.getEl('footer').appendChild(button);
        this._fixLayout();

        return button;
    },
    getEl: function(/* string */place) {
        switch (place) {
        case 'header': return this.el.getElement('.header'); break;
        case 'body': return this.el.getElement('.body'); break;
        case 'footer': return this.el.getElement('.footer'); break;
        default: return this.el;
        }
    },
    show: function() {
        this._fixPosition();

        if (this._modal_layer) { this._modal_layer.show(this.get('z_index') - 10); }
        this.box.setStyle('visibility', 'visible');

        this.fireEvent('show', this);
        this._visible = true;
    },
    hide: function(/* string|event */event) {
        this.box.setStyle('visibility', 'hidden');

        if (this._modal_layer) { this._modal_layer.hide(); }
        if ($type(event) == 'string') { this.fireEvent(event, this); }
        this.fireEvent('hide', this);
        this._visible = false;
    },
    isVisible: function() {
        return this._visible || false;
    }
});

Dialog.Modal = new Class({
    initialize: function() {
        var body = $(document.body);
        this.layer = new Element('div', {
            'styles': {
                'display': 'none',
                'position': 'absolute',
                'top': 0,
                'left': 0,
                'width': '100%',
                'background': '#000000',
                'opacity': 0,
                'filter': 'alpha(opacity=0)',
                '-moz-opacity': 0,
                'z-index': 1000
            }
        });
        this.layer.setStyle('opacity', 0.5);
        body.appendChild(this.layer);
        if (Browser.Engine.trident4) {
            this.iframe = new Element('iframe', {
                'src': 'javascript: void(0)',
                'scrolling': 'no',
                'marginWidth': 0,
                'marginHeight': 0,
                'styles': {
                    'display': 'none',
                    'position': 'absolute',
                    'top': 0,
                    'left': 0,
                    'width': '100%',
                    'opacity': 0,
                    'filter': 'alpha(opacity=0)',
                    'z-index': 9500
                }
            });
            body.appendChild(this.iframe);
        }

        this._fixSize();
        window.addEvent('resize', this._fixSize.bind(this));
    },
    _fixSize: function() {
        var body = $(document.body);
        var styles = {
            'height': Math.max(body.getStyle('height').toInt(), body.getSize().y)
        };

        this.layer.setStyles(styles);
        if (this.iframe) { this.iframe.setStyles(styles); }
    },
    show: function(/* integer */zIndex) {
        var zIndex = zIndex || 10000;
        this.layer.setStyles({'display': '', 'z-index': zIndex});
        if (this.iframe) { this.iframe.setStyles({'visibility': 'visible', 'display': '', 'z-index': zIndex - 10}); }
    },
    hide: function() {
        this.layer.setStyle('display', 'none');
        if (this.iframe) { this.iframe.setStyle('display', 'none'); }
    }
});
/* }}} */

/* StyleSheet {{{ */
if (!window.getComputedStyle) { // for ie6
    window.getComputedStyle = function(el, pseudo) {
        this.el = el;
        this.getPropertyValue = function(prop) {
            var re = /(\-([a-z]){1})/g;
            if (prop == 'float') prop = 'styleFloat';
            if (re.test(prop)) {
                prop = prop.replace(re, function () {
                    return arguments[2].toUpperCase();
                });
            }
            return el.currentStyle[prop] ? el.currentStyle[prop] : null;
        };
        return this;
    };
}

var StyleSheet = new Class({
    initialize: function(/* DOM StyleSheet Object */sheet) {
        this.sheet = sheet;
    },
    /**
     * sheet.set('.survey-title', 'text-align', 'center');
     * sheet.set('.survey-title', 'text-align', '');
     */
    set: function(/* string */selector, /* string */name, /* string */value) {
        var rule = this.getRule(selector);
        if ($chk(rule)) rule.style[name.camelCase()] = value;
        else this.append(selector, name, value);
    },
    getRule: function(/* string */selector) {
        var rules = this.rules();
        for (var len = rules.length, i = 0; i < len; i++) {
            var rule = rules[i];
            if (rule.selectorText && rule.selectorText.toLowerCase() == selector) return rule;
        }
        return null;
    },
    enable: function() { this.sheet.disabled = false; return this; },
    disable: function() { this.sheet.disabled = true; return this; },
    isDisabled: function() { return this.sheet.disabled; }
});
/* }}} */

if (Browser.Engine.trident) { // for ie
    StyleSheet.implement(
        {
            rules: function() { return this.sheet.rules; },
            get: function(selector, property) {
                var rule = this.getRule(selector);
                return ($chk(rule)) ? rule.style[property.camelCase()] : null;
            },
            append: function(selector, name, value) {
                this.sheet.addRule(selector, name.hyphenate()+':'+value);
            },
            getCssText: function() {
                return this.sheet.cssText;
            },
            setCssText: function(css) {
                this.sheet.cssText = css;
            }
        });
} else {
    StyleSheet.implement(
        {
            rules: function() { return this.sheet.cssRules; },
            get: function(selector, property) {
                var rule = this.getRule(selector);
                return ($chk(rule)) ? rule.style.getPropertyValue(property) : null;
            },
            append: function(selector, name, value) {
                this.sheet.insertRule(
                    selector + ' {' + name.hyphenate() + ':' + value + ';}',
                    this.rules().length);
            },
            getCssText: function() {
                var sheet = this.sheet, css = [];
                for (var len = sheet.cssRules.length, i = 0; i < len; i++) {
                    css.push(sheet.cssRules[i].cssText);
                }
                return css.join("\n");
            },
            setCssText: function(css) {
                var $el = $(this.sheet.ownerNode);
                $el.set('text', css || 'body {}');
                this.sheet = $el.sheet;
            }
        });
}



// 翻译指定文字
function T(key) {
    var dict = window.dict || {};
    return dict.hasOwnProperty(key) ? dict[key] : key;
}

// 替换指定字符串，用法： tprint('${a} is ${b}', {a:'A', b:'B'}) >> "A is B"
function tprint(tplString, obj) {
    return tplString.replace(
            /\$\{(\w+)\}/g, // 抽取 ${}
        function(match,key) {
            return ((key in obj) ? obj[key] : match);
        });
}

function getOrCreateElement(el, create_func) {
    return $chk(el) ? el: create_func();
}

var Widget = window.Widget || {};

/* Widget.Accordion {{{ */
Widget.Accordion = new Class({
    initialize: function(){
	var params = Array.link(arguments, {'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined});
	this.togglers = $$(params.togglers);
	this.elements = $$(params.elements);
	this.container = document.id(params.container);

        this.initWidget();
        commander.register(this, [
            ['skin manager layout setup complete', this.onResize, 0]
        ]);
    },
    initWidget: function() {
        if (!this.elements.length || !this.togglers.length) return;
        this.display(0);
	for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
    },
    addSection: function(toggler, element){
	toggler = document.id(toggler);
	element = document.id(element);
	this.togglers.include(toggler);
	this.elements.include(element);
	var idx = this.togglers.indexOf(toggler);
	toggler.addEvent('click', this.display.pass(idx, this));
    },
    display: function(index) {
        index = index || 0;
        if ($chk(this.last_toggler)) this.last_toggler.removeClass('selected');
        this.last_toggler = this.togglers[index].addClass('selected');
        this.elements.hide();
        this.elements[index].show();

    },
    onResize: function() {
        var $toggler, offset = 0;
        for (var i = 0, n = this.togglers.length; i < n; i++) {
            $toggler = this.togglers[i];
            offset += $toggler.getLayout()['total']['height'];
        }
        var height = this.container.getLayout()['total']['height'] - offset;
        if (height<0) return;
        this.elements.setStyle('height', height);
    }
});
/* }}} */

/* Widget.TwoStateButton {{{ */
/* html 结构要求 [off_value 可选，如不提供则等于 '']
 * <label title="/images/icon/icon_font_bold.gif"> // 图片地址
 *   <input type="checkbox|radio" name="font-weight" value="bolder" off_value="normal">
 *   粗体
 * </label>
 * */
Widget.TwoStateButton = new Class({
    Implements: Options,
    options: {
        on: $empty, // 按下时调用函数，有两个参数 on(button, value)
        off: $empty // 从按下状态弹回时的调用函数
    },
    initialize: function(){
	var params = Array.link(arguments, {'el': Element.type, 'options': Object.type});
	this.setOptions(params.options || {});
	this.el = document.id(params.el);

        this.initWidget();
    },
    initWidget: function() {
        if (this.inited) return;
        this.inited = true;

        var $label = this.el;
        $label.hide();

        var $input = this.el.getElement('input');
        this.value = $input.get('value');
        this.off_value = $input.getProperty('off_value') || '';

        this.state = $input.get('checked'); // 按下状态为 true
        this.$widget = new Element(
            "img", {
                'class': this.state
                    ? 'two_state_button button_state_on' 
                    : 'two_state_button',
                'src': $label.getProperty("title"),
                'title': $label.get('text')
            })
            .addEvent('click', this.toggle.bind(this))
            .inject($label, 'after');
    },
    toggle: function() {
        this.state ? this.off() : this.on();
    },
    on: function() {
        if (!this.$widget.hasClass('button_state_on')) {
            this.$widget.addClass('button_state_on');
            this.options.on.run([this, this.value], this);
        }
        this.state = true;
    },
    off: function() {
        if (this.$widget.hasClass('button_state_on')) {
            this.$widget.removeClass('button_state_on');
            this.options.off.run([this, this.off_value], this);
        }
        this.state = false;
    },
    getValue: function() {
        return this.value;
    }
});
/* }}} */

/* Widget.Confirm {{{ */
// <a href="/method=delete" id="trigger">删除</a>
// 用法： new Widget.Confirm($('trigger'), {
//             'yes':['确定', onYesBack],  // [按钮显示文字， 按钮点击调用函数]
//             'no':['取消', onNoCallback],
//             'help':['帮助', onHelpCallback] });
Widget.Confirm = new Class({
    initialize: function() {
        var params = Array.link(arguments, {'trigger': Element.type, 'confirms': Object.type});
        this.trigger = $(params.trigger);
        this.confirms = params.confirms;
        this.initWidget();
    },
    initWidget: function() {
        var $confirm = this.$confirm = new Element('div', {'class':'widget_confirm'});
	for (var key in this.confirms){
            this.addButton(key, this.confirms[key], $confirm);
        }
        $confirm.inject(this.trigger, 'after').hide();
        this.trigger.addEvent(
            'click', 
            function() {
                this.trigger.hide();
                $confirm.show();
                return false;
            }.bind(this));
    },
    addButton: function(label, opt, $container) {
        var caption = opt[0];
        var callback = opt[1];
        Elements.from(contact('<button type="button" class="confirm_btn confirm_',label,'">',caption,'</button>'))
            .addEvent('click', 
                      function(){
                          this.$confirm.hide();
                          callback();
                          this.trigger.show();
                      }.bind(this))
            .inject($container);
    }
});
/* }}} */


function copyToClipboard(txt){
    if (window.clipboardData) {
        window.clipboardData.clearData();
        window.clipboardData.setData("Text", txt);
    }else if (navigator.userAgent.indexOf("Opera") != -1) {
        window.location = txt;
    }else if (window.netscape) {
        try{
            netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
        }catch(e){
            window.alert("Your browser prevents you to use clipboard. Please open 'about:config' to activate 'signed.applets.codebase_pricipal_support' first and try again.");
            return false;
        }
        var clip = Components.classes["@mozilla.org/widget/clipboard;1"].createInstance(Components.interfaces.nsIClipboard);
        if (!clip){ return false; }
        var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
        if (!trans){ return false; }
        trans.addDataFlavor('text/unicode');
        var str = new Object();
        var len = new Object();
        var str = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
        var copytext = txt;
        str.data = copytext;
        trans.setTransferData("text/unicode",str,copytext.length*2);
        var clipid = Components.interfaces.nsIClipboard;
        if (!clip){ return false; }
        clip.setData(trans,null,clipid.kGlobalClipboard);
        window.alert('复制成功');
    }
    return true;
}

function part2el() {}

/* the array_walker() function {{{ */
// 从数组中循环依次取值
function array_walker(arra7) {
    var l = arra7.length;
    var i = -1;
    return function(idx) { // 如 idx 为正数，返回对应数组值。否则重置 idx。
        if ($chk(idx)) {
            if (idx < 0) i = -1;
            else return arra7[idx % l];
        } else {
            i++;
            return arra7[i % l];
        }
    };
};
/* }}} */

var DomUtils = {
    logLastValue: function($el) {
        $el.store('last_val', $el.get('value').trim());
    },
    isChange: function($el) {
        var last_val = $el.retrieve('last_val');
        var new_val = $el.get('value').trim();
        return (new_val != last_val);
    }
}

var EventPlus = {};
(function() {
    var Connects = {
        'changep' : function($input) {
            $input.addEvents({
                'focus': function(event) {
                    DomUtils.logLastValue($(event.target));
                },
                'blur': function(event) {
                    var $el = $(event.target);
                    if (DomUtils.isChange($el)) $el.fireEvent('changep');
                },
                'keydown': function(event) {
                    var $el = $(event.target);
                    if (event.key == 'enter' && DomUtils.isChange($el)) {
                        DomUtils.logLastValue($el);
                        $el.fireEvent('changep');
                    }
                }
            });
        }
    };

    // 提供额外的事件，暂时只实现了对 change 事件的扩展：
    // 1 输入框失去焦点时检查值是否被更改
    // 2 输入框内回车时检查值是否被更改
    // 当 1 || 2 时，触发 changep 事件
    // 例: EventPlus.connect(this.el, 'changep', this.onChange.bind(this));
    EventPlus.connect = function($el, evt, callback) {
        var conn = Connects[evt.toLowerCase()];
        if ($type(conn) != 'function') return;
        conn($el);
        if ($type(callback) == 'function') $el.addEvent('changep', callback);
    };
})();

var SWP = window.SWP || {}; // 名字空间：善为派

(function(win, ns) {
    var dlg, opt, activeTextEl;

    // private    
    var handleButton = function(button){
        if(dlg.isVisible()){
            dlg.hide();
            opt.fn.delay(1, opt.scope||window, 
                         [button, activeTextEl.get('value').trim(), opt]);
        }
    };
    
    ns.MessageBox = {
        getDialog: function(titleText) {
            if (!dlg) {
                dlg = new Dialog(null, {
                    'title': titleText, 'width': 320, 'height': 130
                });
                activeTextEl = new Element('input');
                activeTextEl.addEvent('keydown', function(event) {
                    if (event.key == 'enter') {
                        event.stopPropagation();
                        handleButton('yes');
                    } else if (event.key == 'esc') {
                        event.stopPropagation();
                        handleButton('cancel');
                    }
                });

                dlg.setBody(activeTextEl);
                dlg.addButton({'text':T('yes'), 'class':'normal simple ok'}, handleButton.pass('yes'));
                dlg.addButton({'text':T('cancel'), 'class':'normal simple cancel'}, handleButton.pass('cancel'));
                dlg.addEvent('show', function() { activeTextEl.focus(); activeTextEl.select(); });
            }
            return dlg;
        },
        isVisible: function(){
            return dlg && dlg.isVisible();
        },
        hide: function() {
            if(this.isVisible()) {
                dlg.hide();
            }
            return this;
        },
        prompt: function(options) {
            if(this.isVisible()){
                this.hide();
            }
            opt = options || {};
            var d = this.getDialog(opt.title || "&#160;");
            d.setTitle(opt.title || "&#160;");

            activeTextEl.set('value', opt['value']);
            if (opt.maxlength)
                activeTextEl.set('maxlength', opt.maxlength);
            if (opt.width) d.setWidth(opt.width);
            if (opt.height) d.setWidth(opt.height);

            opt.maxlength = opt.maxlength || 999;
            activeTextEl.set('maxlength', opt.maxlength);

            if(!d.isVisible()) {
                d.show();
            }
        }
    };
    ns.msg = ns.MessageBox;
})(window, SWP);

(function(win, ns) {
    ns.util = ns.util || {};

    // eval b/a
    ns.util.evalPercent = function(b, a, suffix) {
        var r, suffix = suffix || '%';
        if (a <= 0) return '';
        return ( Math.round(10000 * b / a) / 100 ) + suffix;
    };
})(window, SWP);

// 分享到指定的网站
function publish_to(site, url, title) {
    var url = encodeURIComponent(url), title = encodeURIComponent(title);
    switch (site) {
        case 'douban':
            var url = 'http://www.douban.com/recommend/?url='+ url +'&title='+ url +'&v=1';
            break;
        case 'sina':
            var url = 'http://v.t.sina.com.cn/share/share.php?url='+ url +'&title='+ title +'&source=bookmark';
            break;
        case 'sohu':
            var url = 'http://t.sohu.com/third/post.jsp?url='+ url +'&title='+ title +'&content=utf-8';
            break;
        case 'kaixin001':
            var url = 'http://www.kaixin001.com/repaste/share.php?rtitle='+ url +'&title='+ title;
            break;
        case 'qqzone':
            var url = 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url='+ url +'&title='+ title;
            break;
        case '163':
            var url = 'http://t.163.com/article/user/checkLogin.do?info='+ title +' '+ url +'&source='+ encodeURIComponent('网易新闻');
            break;
        default:
            return false;
    }
    window.open(url, '_blank');
    return true;
}

/* Survey_Manager.Tree {{{ */
var Droppables = new Class({

    Implements: [Events, Options],

    options: {/*
	onSort: $empty(element, clone),
	onStart: $empty(element, clone),
	onComplete: $empty(element),*/
	snap: 4,
	opacity: 1,
	clone: false,
	revert: false,
	handle: false,
	constrain: false
    },

    initialize: function(draggables, droppables, options){
	this.setOptions(options);
	this.elements = [];
        this.addItems(draggables);
        this.droppables = droppables;
        this.dropTarget = null;
	this.idle = true;

	if (!this.options.clone) this.options.revert = false;
	if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
    },

    addItems: function(){
	Array.flatten(arguments).each(function(element){
	    this.elements.push(element);
	    var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
	    var reset = element.retrieve('sortables:reset', this.reset.bindWithEvent(this, element));
	    (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
	}, this);
	return this;
    },

    removeItems: function(){
	return $$(Array.flatten(arguments).map(function(element){
	    this.elements.erase(element);
	    var start = element.retrieve('sortables:start');
	    (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
	    
	    return element;
	}, this));
    },

    getClone: function(event, element){
	if (!this.options.clone) return new Element('div').inject(document.body);
	if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
	return element.clone(true).setStyles({
	    margin: '0px',
	    position: 'absolute',
	    visibility: 'hidden',
            'z-index': 9999,
	    'width': element.getStyle('width')
	}).inject($(document.body)).setPosition(element.getPosition(element.getOffsetParent()));
    },

    getDroppables: function(){
        var droppables = $type(this.droppables) == 'function' ?
            this.droppables() : this.droppables;
        return droppables.erase(this.element).erase(this.clone);
    },

    insert: function(dragging, element){
	var droppables = this.getDroppables(),
            overed = droppables.filter(this.checkAgainst, this).getLast();
        droppables.each(function(el) {el.removeClass('drag-hover');});
        element.addClass('drag-hover');
        this.dropTarget = element;
	this.fireEvent('drop', [this.element, element, this.clone]);
    },

    start: function(event, element){
	if (!this.idle) return;
	this.idle = false;
	this.element = element;
	this.opacity = element.get('opacity');
	this.clone = this.getClone(event, element);

	this.drag = new Drag.Move(this.clone, {
	    snap: this.options.snap,
	    container: this.options.container,
	    droppables: this.getDroppables(),
	    onSnap: function(){
		event.stop();
		this.clone.setStyle('visibility', 'visible');
		this.element.set('opacity', this.options.opacity || 0);
		this.fireEvent('start', [this.element, this.clone]);
	    }.bind(this),
	    onEnter: this.insert.bind(this),
	    onCancel: this.reset.bind(this),
	    onComplete: this.end.bind(this)
	});

	this.clone.inject(this.element, 'before');
	this.drag.start(event);
    },

    end: function(){
	this.drag.detach();
	this.element.set('opacity', this.opacity);
	if (this.effect){
	    var dim = this.element.getStyles('width', 'height');
	    var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
	    this.effect.element = this.clone;
	    this.effect.start({
		top: pos.top,
		left: pos.left,
		width: dim.width,
		height: dim.height,
		opacity: 0.25
	    }).chain(this.reset.bind(this));
	} else {
	    this.reset();
	}
	this.fireEvent('complete', [this.element, this.dropTarget]);
    },

    reset: function(){
	this.idle = true;
        this.getDroppables().each(function(el) {el.removeClass('drag-hover');});
	this.clone.destroy();
    }
});
/* }}} */

// {{{ SurveyFuncSelector

// 调查表功能购买窗口
var SurveyFuncSelector = new Class({
    Extends: Dialog,
    initialize: function() {
        this.enabled_functions = [];
        this.current_coin = 0;

        this.parent(null, {
            'width': 560,
            'height': 500,
            'title': 'Pro Functions'
        });

        var form = $('survey_functions_selector');
        this.form = form;

        this.setBody(form);
        this.addButton({'text': 'Confirm', 'class': 'confirm'}, this.save.bind(this));
        this.addButton({'text': 'Add Coins'}, function() { window.open('/paypal/buy', '_top'); });
        this.addButton({'text': 'Cancel', 'class': 'cancel'}, this.hide.bind(this));

        this.$all_enable = form.getElement('input[name="functions[]"][value="all"]');
        this.$save_button = this.getEl().getElement('button.confirm');

        this.getEl('body').addEvent('click', (function(event) {
            var target = $(event.target);
            if (target.get('tag') != 'input') return;
            if (target.get('type') != 'checkbox') return;

            var form = this.form;
            if (target.get('checked')) {
                var val = target.get('value');

                if (val == 'all') {
                    form.getElements('input').erase(target).set('checked', false);
                } else {
                    this.$all_enable.set('checked', false);
                }
            }

            var choice = this.getChoice();
            form.getElement('.cost').set('text', choice['cost']);

            if (choice['cost'] > this.current_coin) {
                this.showOops();
            } else {
                this.hideOops();
            }
        }).bindWithEvent(this));
    },
    load: function(survey_sn) {
        // 载入环境数据
        var req = new Request({
            'url': '/payment/function',
            'method': 'get',
            'data': {'survey_sn': survey_sn},
            'onSuccess': (function(response) {
                var rep = JSON.decode(response, true);
                if (!rep) return false;

                this.survey_sn = survey_sn;
                this.enabled_functions = rep['enabled_functions'];
                this.current_coin = rep['current_coin'];
                this.all_enabled = rep['all_enabled'];

                if (!this.all_enabled)
                    this.show();
            }).bind(this)
        });
        req.send();
    },
    show: function() {
        var form = this.form;
        var search = form.getElements('input[name="functions[]"]');
        var enabled = this.enabled_functions;
        var functions = [];
        for (var i = 0, el; el = search[i]; i++) {
            var func = el.get('value');
            if (func == 'all') continue;

            if (enabled.contains(func)) {
                el.set('disabled', true);
            } else {
                el.set('disabled', false);
            }
            el.set('checked', false);
        }

        if (enabled.length) {
            this.$all_enable.set('disabled', true).set('checked', false);
        } else {
            this.$all_enable.set('disabled', false).set('checked', false);
        }

        this.hideOops();

        this.parent();
    },
    showOops: function() {
        var div = this.form.getElement('.message');
        div.getElement('.wallet').set('text', this.current_coin);
        div.setStyle('display', '');

        this.$save_button.set('disabled', true);
    },
    hideOops: function() {
        this.form.getElement('.message').setStyle('display', 'none');
        this.$save_button.set('disabled', false);
    },
    getChoice: function() {
        // 返回选择的所有功能和总金额
        var functions = [], cost = 0;

        var search = this.form.getElements('input[type=checkbox]:checked');
        for (var i = 0, el; el = search[i]; i++) {
            functions.push(el.get('value'));
            cost = cost + el.get('cost').toInt();
        }

        return {
            'functions': functions,
            'cost': cost
        };
    },
    save: function() {
        var choice = this.getChoice();
        var functions = choice['functions'], cost = choice['cost'];

        if (!functions.length) return this.hide();

        if (cost > this.current_coin)
            return this.showOops();

        var req = new Request({
            'url': '/payment/function',
            'data': {'survey_sn': this.survey_sn, 'functions': functions},
            'async': false,
            'onRequest': (function() {
                this.$save_button.set('disabled', true);
            }).bind(this),
            'onComplete': (function() {
                this.$save_button.set('disabled', false);
            }).bind(this),
            'onSuccess': (function(response) {
                var response = JSON.decode(response, true);

                var reload = function(delay) {
                    if (delay) {
                        (function() {
                            window.location.reload();
                        }).delay(delay);
                    } else {
                        window.location.reload();
                    }
                };

                var alert = new Dialog(null, {
                    'title': 'Pro Function',
                    'width': 200,
                    'height': 100
                });

                if (response) {
                    alert.setBody('<div style="text-align:center;">Function enabled.</div>');
                    alert.addButton('Ok', reload);
                    reload(3000);
                } else {
                    alert.setBody('<div style="text-align:center;">Function enable failed.</div>');
                    alert.addButton('Ok', alert.hide.bind(alert));
                }
                this.hide();
                alert.show();
            }).bind(this)
        });
        req.send();
    }
});

var showSurveyFuncSelector = (function() {
    var dialog;

    return function(event) {
        if (!dialog) dialog = new SurveyFuncSelector();

        var survey_sn = $(event.target).get('sn');
        dialog.load(survey_sn);
    };
})();
// }}}

// {{{ surveyFuncEnableDialog
var surveyFuncEnableDialog = new Class({
    Extends: Dialog,
    initialize: function() {
        this.parent(null, {
            'height': 250,
            'width': 460,
            'title': 'Pro Function'
        });

        var form = $('enable_survey_function_body');
        this.form = form;

        this.setBody(form);
        this.$save_button = this.addButton({'text':'Confirm', 'class':'confirm'}, this.submit.bind(this));
        this.addButton({'text': 'Add Coins'}, function() { window.open('/paypal/buy', '_top'); });
        this.addButton('Cancel', this.hide.bind(this));

        form.getElements('label').each(function(label) {
            var id = uuid();
            label.set('for', id);
            label.getPrevious('input').set('id', id);
        });

        form.getElements('input').addEvent('click', (function(event) {
            var target = $(event.target);
            if (!target.get('checked')) return;

            if (target.get('cost') > this.current_coin) {
                this.showOops();
            } else {
                this.hideOops();
            }
        }).bindWithEvent(this));
    },
    showOops: function() {
        var div = this.form.getElement('.message');
        div.getElement('.wallet').set('text', this.current_coin);
        div.setStyle('display', '');

        this.$save_button.set('disabled', true);
    },
    hideOops: function() {
        this.form.getElement('.message').setStyle('display', 'none');
        this.$save_button.set('disabled', false);
    },
    show: function(config, func) {
        var form = this.form;
        var func_config = config[func];

        form.getElement('input[name="function"]').set({'value': func, 'checked': false, 'cost': func_config['cost']});
        form.getElement('span[name="function_desc"]').set('text', func_config['desc']);
        form.getElement('span[name="function_cost"]').set('text', func_config['cost']);

//        var enable_all = true;
//        for (var i in config) {
//            if (config.hasOwnProperty(i) && config[i]['enable']) {
//                enable_all = false;
//                break;
//            }
//        }
//
//        form.getElement('input[name="function"][value="all"]').set({'checked': false, 'disabled': !enable_all});

        if (func_config['cost'] > this.current_coin) {
            this.showOops();
        } else {
            this.hideOops();
        }

        this.parent();
    },
    load: function(survey_sn, func) {
        // TODO: 需要提示保存，然后再继续
        var req = new Request({
            'url': '/survey/functions',
            'method': 'get',
            'data': {'sn': survey_sn},
            'noCache': true,
            'onSuccess': (function(response) {
                var response = JSON.decode(response, true);
                if (response) {
                    this.survey_sn = survey_sn;
                    this.current_coin = response['current_coin'];
                    this.show(response['functions'], func);
                }
            }).bind(this)
        });
        req.send();
    },
    submit: function() {
        var form = this.form;
        var input = form.getElement('input');
        if (!input) this.hide();

        var func = input.get('value');
        var request = new Request({
            'url': '/survey/functions',
            'method': 'post',
            'data': {'sn': this.survey_sn, 'function': func},
            'async': false,
            'noCache': true,
            'onRequest': (function() {
                this.$save_button.set('disabled', true);
            }).bind(this),
            'onComplete': (function() {
                this.$save_button.set('disabled', true);
            }).bind(this),
            'onSuccess': (function(response) {
                var response = JSON.decode(response, true);
                this.hide();

                var reload = function(delay) {
                    if (delay) {
                        (function() {
                            window.location.reload();
                        }).delay(delay);
                    } else {
                        window.location.reload();
                    }
                };

                var alert = new Dialog(null, {
                    'title': 'Pro Function',
                    'width': 200,
                    'height': 100
                });

                if (response) {
                    alert.setBody('<div style="text-align:center;">Function enabled.</div>');
                    alert.addButton('Ok', reload);
                    reload(3000);
                } else {
                    alert.setBody('<div style="text-align:center;">Function enable failed.</div>');
                    alert.addButton('Ok', alert.hide.bind(alert));
                }

                alert.show();
            }).bind(this)
        });
        request.send();
    }
});

var showFuncEnableDialog = (function() {
    var dialog;
    return function(event) {
        if (!dialog) dialog = new surveyFuncEnableDialog();

        var target = $(event.target);
        var survey_sn = target.get('sn'), func = target.get('function');
        dialog.load(survey_sn, func);
    };
}());
// }}}
/*
用例：clippy($('copy_link'), "<?php echo $invite_url; ?>");
参数：
placer: 放置 flash 的 container 元素，比如 $('copy_link')
copytext: 将放在剪贴板中的文字
copied: 复制成功后的提示文字
copyto: 复制提示文字
bgcolor: flash 背景色
*/
var clippy = function(placer, copytext, copied, copyto, bgcolor) {
    if (!copytext) alert('illegal copytext!');
    placer = $(placer);
    copytext = escape(copytext).replace(/=/ig, '%3D').replace(/&/ig, '%26');
    copied = copied || 'copied!';
    copyto = copyto || 'copy to clipboard';
    bgcolor = bgcolor || '#ffffff';

    var swf = '/swf/clippy.swf',
        html = [
        '<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"',
        'width="110"',
        'height="14"',
        'id="clippy" >',
        '<param name="movie" value="#{swf}"/>',
        '<param name="allowScriptAccess" value="always" />',
        '<param name="quality" value="high" />',
        '<param name="scale" value="noscale" />',
        '<param NAME="FlashVars" value="copied=#{copied}&copyto=#{copyto}&text=#{text}">',
        '<param name="bgcolor" value="#{bgcolor}">',
        '<embed src="#{swf}"',
        'width="110"',
        'height="14"',
        'name="clippy"',
        'quality="high"',
        'allowScriptAccess="always"',
        'type="application/x-shockwave-flash"',
        'pluginspage="http://www.macromedia.com/go/getflashplayer"',
        'FlashVars="copied=#{copied}&copyto=#{copyto}&text=#{text}"',
        'bgcolor="#{bgcolor}"',
        '/>',
        '</object>'
    ].join('\n');

    html = html.replace(/#{swf}/ig, swf)
        .replace(/#{copyid}/ig, copytext)
        .replace(/#{copied}/ig, copied)
        .replace(/#{copyto}/ig, copyto)
        .replace(/#{text}/ig, copytext)
        .replace(/#{bgcolor}/ig, bgcolor);

    placer.set('html', html);
    //(new Element('span', {html: html})).inject(placer);
};

