<?php

namespace Atom\runtime_7456b800;

require_once(ATOM_DIR . "lib/runtime_7456b800/util/Element.php");
require_once(ATOM_DIR . "lib/runtime_7456b800/util/Attrs.php");
require_once(ATOM_DIR . "lib/runtime_7456b800/util/Shared.php");
require_once(ATOM_DIR . "lib/runtime_7456b800/util/Options.php");

require_once(ATOM_DIR . "lib/runtime_7456b800/instance/Atom_Component.php");
require_once(ATOM_DIR . 'lib/runtime_7456b800/instance/Atom_Config_Base.php');
require_once(ATOM_DIR . 'lib/runtime_7456b800/instance/helpers/Children.php');

\Atom_Component_Creator::$cache["\\Atom\\runtime_7456b800\\Atom_Component"] = 1;

/**
 * 组件实例，负责组件的渲染
 *
 * @package Atom\runtime_7456b800
 * @auther lvsheng / chenxiao07
 */
class Atom_Component {

    /**
     * 暂存子组件传递给父组件的数据
     *
     * @var array
     */
    private static $messages = array();

    public $_dataCallable;
    public $_d = array();
    public $options = array();
    public $scopedSlots = array();
    public $slots = array();

    public $style;
    public $script;
    public $props;
    public $propKeys;

    /**
     * 根节点需要的数据，从父组件传入
     * @var array
     */
    public $_rootData;

    /**
     * 组件实例的php文件路径
     * @var string
     */
    public $componentPath = "";

    /**
     * 父组件Component
     * @var Atom_Component
     */
    public $context = null;

    /**
     * @var string
     */
    public $_renderCallable;
    /**
     * @var string
     */
    public $_configClassName;
    /**
     * @var Atom_Config_Base
     */
    public $_configInstance;

    /**
     * @var string
     */
    public $_scopeId;

    /**
     * @var Closure[]
     */
    public $_staticRenders = array();

    /**
     * @var 当前组件拥有的指令
     */
    private $directives = null;

    /**
     * @var string[]
     */
    private $_computedFunctions;

    /**
     * 父组件的 scopeId
     * @var string
     */
    public $_psid = '';

    /**
     * 组件用到的生命周期钩子函数
     *
     * @var array
     */
    private $_hooks = null;

    // html
    private $html = '';

    // css
    private $css = array();

    // js
    private $js = array();

    private $_cid = null;

    private $_parentCid = null;

    private $_cssModules = null;

    /**
     * This function is executed automatically when a compiled .php file is included
     * - Accept the config from compiled atom component file
     * @param $config array
     * @param $config['data'] array
     * @param $config['components'] array
     * @param $config['style'] string
     * @param $config['script'] string
     * @param $config['props'] string[]
     * @param $config['_scopeId'] string
     * @param $config['staticRender'] Closure[]
     * @param $config['render'] callable
     * @param $config['configClass'] (optional) string
     * @return bool flag if should define render function
     */
    private function configure ($config) {

        $this->_dataCallable = $config['data'];
        $this->options['components'] = $config['components'];

        if (isset($config['style'])) {
            $this->style = $config['style'];
        }

        // @TODO 此功能暂未实现
        // if (isset($config['script'])) {
        //     $this->script = $config['script'];
        // }

        if (isset($config['props'])) {
            // todo: 在编译时禁止掉中划线命名?
            $this->props = $this->normalizeProps($config['props']);
        }

        if (isset($config['propKeys'])) {
            $this->propKeys = $config['propKeys'];
        }

        if (isset($config['_scopeId'])) {
            $this->_scopeId = $config['_scopeId'];
        }

        if (isset($config['staticRender'])) {
            $this->_staticRenders = $config['staticRender'];
        }

        if (isset($config['computedFunctions'])) {
            $this->_computedFunctions = $config['computedFunctions'];
        }

        if (isset($config['hooks'])) {
            $this->_hooks = $config['hooks'];
        }

        $this->_renderCallable = $config['render'];

        if (isset($config['configClass'])) {
            $this->_configClassName = $config['configClass'];
        }
        else {
            $this->_configClassName = __NAMESPACE__ . '\\Atom_Config_Base';
        }

        if (isset($config['cssModules'])){
            $this->_cssModules = $config['cssModules'];
        }

    }

    /**
     * default filter
     * @param $args array
     * @return string
     */
    private function defaultFilter($args) {
        $val = $args[0];
        return Atom_Util_Shared::isPrimitive($val) ? $val : '';
    }


    /**
     * normalizeProps
     * @param $props
     * @return array
     */
    private function normalizeProps ($props) {
        $res = array();
        foreach ($props as $key => $prop) {
            if (is_numeric($key)) {
                $res[$key] = Atom_Util_Shared::camelize($prop);
            }
            else {
                $res[Atom_Util_Shared::camelize($key)] = $props[$key];
            }
        }
        return $res;
    }

    /**
     * Atom_Component constructor.
     * @param $componentPath string - the path of component instance
     * @param $context Atom_Component
     * @param $config array
     * @param $config['data'] array
     * @param $config['components'] array
     * @param $config['style'] string
     * @param $config['script'] string
     * @param $config['props'] string[]
     * @param $config['_scopeId'] string
     * @param $config['staticRender'] Closure[]
     * @param $config['render'] callable
     * @param $config['configClass'] (optional) string
     */
    public function __construct($componentPath, $context = null, $config = array()) {
        $this->configure($config);
        $this->componentPath = $componentPath;
        $this->context = $context;
    }

    /**
     * call static render function
     * @param $index
     * @return null
     * @throws Exception
     */
    public function _m($index) {
        $staticRenderCallable = $this->_staticRenders[$index];
        if (!isset($staticRenderCallable)) {
            throw new \Exception('[Atom Runtime] no static render function found of component: ' . $this->componentPath . ' at index: ' . $index);
        }
        if (!is_callable($staticRenderCallable)) {
            throw new \Exception('[Atom Runtime] static render of component: ' . $this->componentPath . ' at index ' . $index . ' is not callable!');
        }
        return call_user_func($staticRenderCallable, $this);
    }

    /**
     * create string component
     * @param $tag string 元素标签名
     * @param $data array 元素上添加的属性
     * @param $children mixed 子元素数组
     * @param $normalizationType int 子元素数组进行归一化处理的级别
     * @return string
     */
    public function _sc($tag = null, $data = null, $children = null, $normalizationType = 1) {

        // tagName 都没有，直接返回空
        if (!$tag || !is_string($tag)) {
            return "<!---->";
        }

        // transition-group 的特殊处理
        if ($tag === 'transition-group') {
            $tag = 'span';
            if (is_array($data) && is_array($data['attrs'])) {
                if (is_string($data['attrs']['tag'])) {
                    $tag = $data['attrs']['tag'];
                    unset($data['attrs']['tag']);
                }
                foreach ($data['attrs'] as $k => $v) {
                    if (Atom_Util_Attrs::isTransitionProps($k)) {
                        unset($data['attrs'][$k]);
                    }
                }
            }
        }

        // 普通 dom
        if (Atom_Util_Element::isReservedTag($tag)) {
            $sChildren = Atom_Children::stringify($children, $normalizationType);
            $tag = Atom_Util_Element::parsePlatformTagName($tag);
            return $this->renderElement($tag, $data, $sChildren, $isXml);
        }

        // 直接返回
        if ($tag === 'keep-alive' || $tag === 'transition') {
            $sChildren = Atom_Children::stringify($children, $normalizationType);
            return $sChildren;
        }

        // 自定义组件
        $componentName = Atom_Util_Options::resolveAsset($this->options, 'components', $tag);
        if ($componentName) {
            $component = \Atom_Component_Creator::createInstance($componentName, $this);
            $children = Atom_Children::normalize($children);
            $component->init($data, $children, false);
            return $component->getOuterHtml($isXml);
        }

        // fallback
        $sChildren = Atom_Children::stringify($children, $normalizationType);
        return $this->renderElement($tag, $data, $sChildren, $isXml);

    }

    /**
     * root element data merge
     * @param $data array 属性
     * @param $children mixed 子元素数组
     * @param $normalizationType int 子元素数组进行归一化处理的级别
     * @return string
     */
    public function _mp($data) {

        if (empty($this->_rootData)) {
            return $data;
        }

        $use = $data;

        $use['attrs'] = isset($use['attrs']) ? $use['attrs'] : array();
        if (isset($this->_rootData['attrs'])) {
            $use['attrs'] = array_merge($use['attrs'], $this->_rootData['attrs']);
        }

        // 合并 staticClass
        $staticClass = '';
        if (!empty($use['staticClass'])) {
            $staticClass = $use['staticClass'];
        }
        if (!empty($this->_rootData['staticClass'])){
            $staticClass .= ($staticClass ? ' ' : '') . $this->_rootData['staticClass'];
        }

        // 合并 class, 全转成字符串，避免后面再执行一遍 handleClass
        $klass = array();
        if (isset($use['class'])) {
            $klass = Atom_Util_Element::handleClass($use['class']);
            unset($use['class']);
        }
        if (isset($this->_rootData['class'])) {
            $klass = array_merge($klass, Atom_Util_Element::handleClass($this->_rootData['class']));
        }
        if (count($klass) > 0) {
            $staticClass .= ($staticClass ? ' ' : '') . join(' ', $klass);
        }
        $use['staticClass'] = $staticClass === '' ? null : $staticClass;

        // 合并staticStyle
        $staticStyle = array();
        if (isset($use['staticStyle'])) {
            $staticStyle = $use['staticStyle'];
        }
        // 合并 style, 也合并到 staticStyle 上，避免后面再执行一遍 handleStyle
        if (isset($use['style'])) {
            $placeHolderStyle = Atom_Util_Element::handleStyle($use['style']);
            foreach ($placeHolderStyle as $key => $val) {
                $staticStyle[$key] = $val;
            }
            unset($use['style']);
        }

        // 传递 directives a-show
        $directives = array();
        if (isset($use['directives']) && isset($use['directives']['show'])) {
            $directives['show'] = $use['directives']['show'];
            unset($use['directives']);
        }
        if (isset($this->directives) && isset($this->directives['show'])) {
            $directives['show'] = $this->directives['show'];
        }

        if (isset($directives['show']) && !$directives['show']['value']) {
            $staticStyle['display'] = 'none';
        }

        if (isset($this->_rootData['staticStyle'])){
            foreach ($this->_rootData['staticStyle'] as $key => $val) {
                $staticStyle[$key] = $val;
            }
        }
        if (isset($this->_rootData['style'])){
            $componentStyle = Atom_Util_Element::handleStyle($this->_rootData['style']);
            foreach ($componentStyle as $key => $val) {
                $staticStyle[$key] = $val;
            }
        }

        $use['staticStyle'] = count($staticStyle) > 0 ? $staticStyle : null;

        // domProps 传递
        if (isset($data['domProps'])) {
            if (isset($this->_rootData['domProps'])) {
                if (isset($this->_rootData['domProps']['textContent']) || isset($this->_rootData['domProps']['innerHTML'])) {
                    unset($data['domProps']['textContent']);
                    unset($data['domProps']['innerHTML']);
                }
                $this->_rootData['domProps'] = array_merge($data['domProps'], $this->_rootData['domProps']);
            }
            else {
                $this->_rootData['domProps'] = $data['domProps'];
            }
        }
        $use['domProps'] = $this->_rootData['domProps'];

        $use['rawAttrsMap'] = isset($use['rawAttrsMap']) ? $use['rawAttrsMap'] : array();
        if (isset($this->_rootData['rawAttrsMap'])) {
            $use['rawAttrsMap'] = array_merge($use['rawAttrsMap'], $this->_rootData['rawAttrsMap']);
        }

        // scopeId
        // if (isset($this->_scopeId)) {
        //     $psid = $use['scopeId'] ? (' ' . $use['scopeId']) : '';
        //     $use['scopeId'] = $this->_scopeId . $psid;
        // }

        return $use;
    }

    /**
     * root element data
     * @param $data array 属性
     * @param $children mixed 子元素数组
     * @param $normalizationType int 子元素数组进行归一化处理的级别
     * @return string
     */
    public function _p($data = null, $isXml = false) {
        $use = $this->_mp($data);
        $markup = $this->renderStartingTagContent($use, $isXml);
        return $markup ? $markup : "";
    }

    /**
     * create text node
     * @param $str
     * @return string
     */
    public function _v($str) {
        return htmlspecialchars($str, ENT_QUOTES);
    }

    /**
     * to string
     * @param $val
     * @return string
     */
    public function _s($val) {
        if (Atom_Util_Shared::isPrimitive($val)) {
            return (string)$val;
        }
        elseif ($val === true) {
            return 'true';
        }
        elseif ($val === false) {
            return 'false';
        }
        elseif (is_array($val)) {
            return json_encode($val);
        }
        return '';
    }

    /**
     * call filter function
     * @param $id
     * @param $args
     * @return string
     */
    public function _f($id, $args) {

        $filterId = 'filter_' . $id;

        if (method_exists($this->_configInstance, $filterId)) {
            // 尝试调用自定义filter
            return call_user_func_array(array($this->_configInstance, $filterId), $args);
        }

        // 尝试调用全局filter
        $filterCallable = Atom_Util_Options::getFilter($id);

        if (!isset($filterCallable) || !is_callable($filterCallable)) {
            trigger_error('[Atom Runtime] unregistered filter called!' . $id, E_USER_WARNING);
            return $this->defaultFilter($args);
        }

        return call_user_func_array($filterCallable, $args);
    }

    /**
     * call function
     * @param $id
     * @param $args
     * @return mixed
     * @throws Exception
     */
    public function _call($id, $args) {
        $methodName = $id;
        if (!isset($this->_configInstance)) {
            throw new \Exception('[Atom Runtime] _configInstance of component ' . $this->componentPath . ' is not defined when _call '. $id . '!');
        }
        if (!method_exists($this->_configInstance, $methodName)) {
            throw new \Exception('[Atom Runtime] function ' . $id . ' of component ' . $this->componentPath . ' is not exist when _call!');
        }

        return call_user_func_array(array($this->_configInstance, $methodName), $args);
    }

    /**
     * loop the array and call the function
     * @param $arr
     * @param $alias
     * @param $iterator1
     * @param $iterator2
     * @param $fn
     * @return array|void
     */
    public function _l($arr, $alias, $iterator1, $iterator2, $fn) {
        if (!$arr) {
            return;
        }
        $ret = array();
        if (isset($this->_d[$alias])) {
            $originAlias = $this->_d[$alias];
        }
        if (isset($this->_d[$iterator1])) {
            $originIterator1 = $this->_d[$iterator1];
        }
        if (isset($this->_d[$iterator2])) {
            $originIterator2 = $this->_d[$iterator2];
        }
        if (is_string($arr)) {
            $arr = str_split($arr);
        }
        elseif (is_numeric($arr)) {
            $tmp = array();
            for ($i = 0; $i < $arr; ++$i) {
                $tmp[$i] = $i + 1;
            }
            $arr = $tmp;
        }
        if (is_array($arr)) {
            foreach ($arr as $k => $v) {
                $this->_d[$iterator1] = $k;
                $this->_d[$alias] = $v;
                array_push($ret, $fn($this));
            }
        }
        if (isset($originAlias)) {
            $this->_d[$alias] = $originAlias;
        }
        if (isset($originIterator1)) {
            $this->_d[$iterator1] = $originIterator1;
        }
        if (isset($originIterator2)) {
            $this->_d[$iterator2] = $originIterator2;
        }
        return $ret;
    }

    /**
     * render slot
     * @param $name
     * @param $fallback
     * @param null $props
     * @param null $bindObject
     * @return mixed
     */
    public function _t($name, $fallback = '', $props = null, $bindObject = null) {

        $scopedSlotsFn = isset($this->scopedSlots[$name]) ? $this->scopedSlots[$name] : null;

        if (isset($scopedSlotsFn) && is_array($scopedSlotsFn) && count($scopedSlotsFn) > 1) {

            if (!is_array($props)) {
                $props = array();
            }

            if (is_array($bindObject)) {
                $props = Atom_Util_Shared::extendData($props, $bindObject);
            }

            $scopeName = $scopedSlotsFn[0];

            // 缓存原数据
            $originData = isset($this->context->_d[$scopeName]) ? $this->context->_d[$scopeName] : null;

            // 为了在scopedSlot在父元素的context下渲染的同时，又能使用子组件的数据，临时修改
            $this->context->_d[$scopeName] = $props;

            // 在父元素的context下渲染scopedSlot
            $res = $scopedSlotsFn[1]($this->context);

            // 恢复原数据
            if (isset($originData)) {
                $this->context->_d[$scopeName] = $originData;
            }

        }
        else if (isset($this->slots[$name])) {
            $res = $this->slots[$name];
        }

        $res = isset($res) ? $res : $fallback;
        return Atom_Children::stringify($res, 2);
    }

    /**
     * 是否需要替换 children 内容
     * 用于完成 v-text / v-html 指令
     * @return bool
     */
    public function _nrc() {
        $domProps = $this->_rootData['domProps'];
        return isset($domProps['innerHTML']) || isset($domProps['textContent']);
    }

    /**
     * 获取 v-text / v-html 指定的 children 内容
     *
     * @return string
     */
    public function _rc() {

        $domProps = $this->_rootData['domProps'];

        if (isset($domProps['innerHTML'])) {
            return $domProps['innerHTML'];
        }

        if (isset($domProps['textContent'])) {
            return $this->_ssrEscape($domProps['textContent']);
        }
    }

    /**
     * create empty node
     * @return string
     */
    public function _e() {
        return "<!---->";
    }

    /**
     * number add or string concat
     * @param $a
     * @param $b
     * @return int|string
     */
    public function _a($a, $b) {
        if (!is_string($a) && is_numeric($a) && !is_string($b) && is_numeric($b)) {
            return $a + $b;
        }
        return $a . $b;
    }

    // _and / _or are not used for now
    // /**
    //  * "&&" in js
    //  * @param $a
    //  * @param $b
    //  * @return any
    //  */
    // public function _and($a, $b) {
    //     return $a ? $b : $a;
    // }

    // /**
    //  * "||" in js
    //  * @param $a
    //  * @param $b
    //  * @return any
    //  */
    // public function _or($a, $b) {
    //     return $a ? $a : $b;
    // }

    /**
     * loose equal
     * @param $a
     * @param $b
     * @return bool
     */
    public function _q($a, $b) {
        $aArray = is_array($a);
        $bArray = is_array($b);
        if ($aArray && $bArray) {
            if (json_encode($a) === json_encode($b)) {
                return true;
            }
        }
        elseif (!$aArray && !$bArray) {
            if ((string)$a === (string)$b) {
                return true;
            }
        }
        return false;
    }

    /**
     * loose indexOf
     * @param $arr
     * @param $val
     * @return int|string
     */
    public function _i($arr, $val) {
        if (!is_array($arr)) {
            throw new \Exception("cannot run `indexOf` on a no-array object");
        }
        foreach($arr as $i => $v) {
            if ($this->_q($v, $val)) {
                return $i;
            }
        }
        return -1;
    }

    /**
     * resolveScopedSlots 将scopedSlots数组形式转为map形式(为了使key即slotName能够由表达式计算，即支持slot属性通过v-bind使用)
     * @param $fns [string, Function][]
     * @param $ret array
     * @return array
     */
    public function _u($fns, $ret = array()) {
        foreach ($fns as $fn) {
            if (Atom_Util_Shared::isPlainArray($fn)) {
                $this->_u($fn, $ret);
            } else {
                $ret[$fn['key']] = array($fn['scope'], $fn['fn']);
            }
        }
        return $ret;
    }

    /**
     * resolveNamedSlots
     * @param $fns [string, Function][]
     * @param $ret array
     * @return array
     */
    public function _un($fns, $ret = array()) {
        foreach ($fns as $fn) {
            if (Atom_Util_Shared::isPlainArray($fn)) {
                $ret = $this->_un($fn, $ret);
            }
            elseif (isset($ret[$fn['key']])) {
                $ret[$fn['key']] = array_merge($ret[$fn['key']], Atom_Children::normalize($fn['value'], 2));
            }
            else {
                $ret[$fn['key']] = Atom_Children::normalize($fn['value'], 2);
            }
        }
        return $ret;
    }

    /**
     * get length
     * @param $val
     * @return int
     */
    public function _len($val) {
        if (is_string($val)) {
            return mb_strlen($val, 'utf8');
        }
        elseif (Atom_Util_Shared::isPlainArray($val)) {
            return count($val);
        }
        else {
            return $val['length'];
        }
    }

    /**
     * 支持 a-bind 绑定一个有属性的对象 或 对象数组
     * @param $data   array   当前元素的 data 数组
     * @param $tag    string  当前元素的 tag
     * @param $value  array   a-bind 绑定的对象或数组
     * @param $asProp boolean 当为 true 时，表示是 a-bind.prop ，当前不支持改写法
     * @return array
     */
    public function _b($data, $tag, $value, $asProp = false) {
        if ($value == null) {
            return $data;
        }
        if (!is_array($value)) {
            trigger_error('[Atom Runtime] a-bind without argument expects an Object or Array value! [components]:' . $this->componentPath, E_USER_WARNING);
            return $data;
        }
        if (Atom_Util_Shared::isPlainArray($value)) {
            // 把数组转成对象
            $ret = array();
            foreach ($value as $k => $v) {
                if (!is_array($v)) {
                    throw new \Exception('[Atom Runtime] a-bind without argument expects an Object or Array of objects value! [components]:' . $this->componentPath);
                }
                $ret = array_merge($ret, $v);
            }
            $value = $ret;
        }
        foreach($value as $key => $val) {
            $hash = array();
            if ($key === 'class' || $key === 'style') {
                $hash = &$data;
            }
            else {
                if ($asProp) {
                    throw new \Exception('[Atom Runtime] a-bind.prop is not supported yet! [components]:' . $this->componentPath);
                }
                else {
                    if (!isset($data['attrs'])){
                        $data['attrs'] = array();
                    }
                    $hash = &$data['attrs'];
                }
            }

            if (!array_key_exists($key, $hash)){
                $hash[$key] = $val;
            }
            unset($hash);
        }
        return $data;
    }

    /**
     * escape html special chars
     *
     * @param string $str input string
     * @return string
     */
    public function _ssrEscape($str) {
        return htmlspecialchars($str, ENT_QUOTES);
    }

    /**
     * render string-template into string in ssr-compile result
     *
     * @param string $open tag open string
     * @param string $close tag close string
     * @param array<mixed> $children children nodes
     * @param number $normalizationType children normalization type
     * @return string
     */
    public function _ssrNode($open, $close = null, $children = null, $normalizationType = null) {

        if (!empty($children)) {
            switch ($normalizationType) {
                case 1:
                    $children = Atom_Children::normalize($children, 1);
                    break;
                case 2:
                    $children = Atom_Children::normalize($children, 2);
                    break;
                default:
                    break;
            }
        }

        $html = $open;

        if (!empty($children)) {
            foreach ($children as $child) {
                $html .= $child;
            }
        }

        if (!empty($close)) {
            $html .= $close;
        }

        return $html;

    }


    /**
     * render ssr list item
     *
     * First, this will temporary modify the context for loop render;
     * while the render function finished, this will recover the context;
     *
     * @param mixed    $value
     * @param string   $alias
     * @param string   $iterator1
     * @param string   $iterator2
     * @param Function $render
     * @return string
     */
    public function _ssrList($value, $alias, $iterator1, $iterator2, $render) {
        $html = '';

        if (empty($value)) {
            return $html;
        }

        if (isset($this->_d[$alias])) {
            $originAlias = $this->_d[$alias];
        }
        if (isset($this->_d[$iterator1])) {
            $originIterator1 = $this->_d[$iterator1];
        }
        if (isset($this->_d[$iterator2])) {
            $originIterator2 = $this->_d[$iterator2];
        }

        if (is_array($value)) {
            $i = 0;
            foreach ($value as $k => $v) {
                $this->_d[$alias] = $v;
                $this->_d[$iterator1] = $k;
                $this->_d[$iterator2] = $i;
                $html .= call_user_func($render, $this);
                $i++;
            }
        }
        else if (is_integer($value)) {
            for ($i = 0; $i < $value; $i++) {
                $this->_d[$alias] = $i + 1;
                $this->_d[$iterator1] = $i;
                $html .= call_user_func($render, $this);
            }
        }
        else if (is_string($value)) {
            $count = strlen($value);
            for ($i = 0; $i < $count; $i++) {
                $this->_d[$alias] = $value[$i];
                $this->_d[$iterator1] = $i;
                $html .= call_user_func($render, $this);
            }
        }

        if (isset($originAlias)) {
            $this->_d[$alias] = $originAlias;
        }
        if (isset($originIterator1)) {
            $this->_d[$iterator1] = $originIterator1;
        }
        if (isset($originIterator2)) {
            $this->_d[$iterator2] = $originIterator2;
        }

        return $html;
    }

    /**
     * render an attribute into string using in ssrCompiled component
     *
     * @param string $key attribute name
     * @param string $value attribute value
     * @return string
     */
    public function _ssrAttr($key, $value) {
        return $this->renderAttr($key, $value);
    }

    /**
     * render attributes into string using in ssrCompiled component
     *
     * @param mixed $attrs array of attributes
     * @return string
     */
    public function _ssrAttrs($attrs) {
        // handle a-bind with [{}, {}, ...]
        if (Atom_Util_Shared::isPlainArray($attrs)) {
            $mergedAttrs = array();
            foreach ($attrs as $attr) {
                if (is_array($attr)) {
                    $mergedAttrs = array_merge($mergedAttrs, $attr);
                }
            }
            $attrs = $mergedAttrs;
        }
        return $this->renderAttrs($attrs);
    }

    /**
     * render dom props into string using in ssrCompiled component
     *
     * @param mixed $domProps array of dom props
     * @return string
     */
    public function _ssrDOMProps($domProps) {
        $res = '';
        foreach ($domProps as $key => $value) {
            $attr = isset(Atom_Util_Element::$propsToAttrMap[$key]) ? Atom_Util_Element::$propsToAttrMap[$key] : strtolower($key);
            if (Atom_Util_Attrs::isValidAttr($attr)) {
                $res .= $this->renderAttr($attr, $value);
            }
        }
        return $res;
    }


    /**
     * 组件的初始化
     * @param  array   $data      数据
     * @param  array   $children  子节点
     * @param  array   $context   该组件所在的组件
     * @param  boolean $isRoot    是否是根节点
     */
    public function init($data, $children, $isRoot = false) {

        // 处理组件属性
        $props = array();

        // 构建 props 数据
        if ($isRoot) {
            // 根组件(入口组件) props 传递
            if (isset($data) && is_array($data)) {
                $props = $data;
            }

            // 如果不是索引数组，处理一下默认值
            if (!empty($this->props)) {
                foreach ($this->props as $key => $prop) {
                    if (is_array($prop) && !isset($data[$key]) && isset($prop['default'])) {
                        $props[$key] = $prop['default'];
                    }
                }
            }
        }
        else if (!empty($this->props)) {

            foreach ($this->props as $key => $prop) {

                $useBoolean = false;
                $propTypes = array();

                if (is_numeric($key)) {
                    $key = $prop;
                }
                // 如果是一个 Boolean 类型，attr 为空字符串表示值为 true
                else {
                    $useBoolean = is_array($prop) && $prop["_useBoolean"];
                }

                if (isset($data['attrs'][$key])) {
                    $props[$key] = $data['attrs'][$key];
                    if ($useBoolean && $props[$key] === "") {
                        $props[$key] = true;
                    }
                    unset($data['attrs'][$key]);
                } else {
                    $altKey = Atom_Util_Shared::inCamelize($key);
                    // 没传的时候使用默认值
                    if (!isset($data['attrs'][$altKey]) && isset($prop['default'])) {
                        $props[$key] = $prop['default'];
                    } else if (isset($data['attrs'][$altKey])) {
                        $props[$key] = $data['attrs'][$altKey];
                        if ($useBoolean && $props[$key] === "") {
                            $props[$key] = true;
                        }
                        unset($data['attrs'][$altKey]);
                    }
                }
            }
        }

        if (!$isRoot) {
            // 初始化组件 cid
            $this->_cid = $data['cid'];
            $this->_parentCid = $data['parentCid'];
        }

        // 初始化 parent scope id
        if (isset($data['scopeId'])) {
            $this->_psid = $data['scopeId'];
            // $this->_psidattr = ($this->_psid ? ' ' : '') . $data['scopeId'];
        }

        // 用 props 作为初始数据
        $this->_d = $props;

        // 处理 children
        $this->slots = array();

        // 将 children 放到 slots.default 中
        if ($children && count($children) > 0) {
            $this->slots['default'] = Atom_Children::skipEmptyChidlren($children);
        }

        // 将 named slots 合并到 slots 上
        if (isset($data['namedSlots'])) {
            foreach ($data['namedSlots'] as $name => $slot) {
                $this->slots[$name] = Atom_Children::skipEmptyChidlren($slot);
            }
        }

        // 处理 scopedSlots
        if (isset($data['scopedSlots']) && is_array($data['scopedSlots'])) {
            $this->scopedSlots = $data['scopedSlots'];
        }

        // 处理 directives
        if (isset($data['directives'])) {
            $this->directives = $data['directives'];
        }

        // 生成根节点需要的数据
        $this->_rootData = $data;

        // 实例化 Atom_Config_Base */
        $className = $this->_configClassName;
        if (!isset($className)) {
            throw new \Exception('[Atom Runtime] configClassName of component ' . $this->componentPath . ' is not defined!');
        }

        $instance = new $className($this->_renderCallable, $this->componentPath);
        $this->_configInstance = $instance;

        // props 传递完毕后，执行 data (保证此时已可使用 props 中数据)
        if (!isset($this->_dataCallable)) {
            throw new \Exception('[Atom Runtime] data function not defined in component ' . $this->componentPath . ' !');
        }
        if (!is_callable($this->_dataCallable)) {
            throw new \Exception('[Atom Runtime] data function of component ' . $this->componentPath . ' is not callable!');
        }

        $dataCallableResult = call_user_func($this->_dataCallable, $this);
        foreach ($dataCallableResult as $k => $v) {
            $this->_d[$k] = $v;
        }

        // 最后处理 computed 属性
        if (!empty($this->_computedFunctions)) {
            foreach ($this->_computedFunctions as $k) {
                $method = 'computed_' . $k;
                if (method_exists($instance, $method)) {
                    try {
                        $this->_d[$k] = $instance->{$method}($this);
                    }
                    catch (\Exception $e) {
                        throw new \Exception('[Atom Runtime] computed function of component ' . $this->componentPath . ' throws error: ' . $e->getMessage());
                    }
                }
            }
        }

        // 添加 css-modules
        if (isset($this->_cssModules)) {
            foreach ($this->_cssModules as $k => $v) {
                $this->_d[$k] = $v;
            }
        }

        // 触发 hooks
        $this->callHook('created');
    }

    /**
     * 触发生命周期函数
     * @param $name string 名称
     */
    private function callHook($name) {
        if ($this->_hooks[$name]) {
            try {
                $this->_configInstance->{$name}($this);
            }
            catch (\Exception $e) {
                throw new \Exception('[Atom Runtime] hook function "' . $name . '" of component ' . $this->componentPath . ' throws error: ' . $e->getMessage());
            }
        }
    }

    /**
     * 暂存子组件需要传递给父组件的数据
     * @param $name string 名称
     */
    public function dispatch($name, $data) {
        // 没有父亲，就啥也不干了
        if (!isset($this->_parentCid)) {
            return;
        }
        if (!isset(self::$messages[$this->_parentCid])) {
            self::$messages[$this->_parentCid] = array();
        }
        if (!isset(self::$messages[$this->_parentCid][$name])) {
            self::$messages[$this->_parentCid][$name] = array();
        }
        self::$messages[$this->_parentCid][$name][] = $data;
    }

    /**
     * 父组件获取子组件的数据
     * @param $name string 名称
     * @return any
     */
    public function getMessage($name) {
        if (!isset(self::$messages[$this->_cid]) || !isset(self::$messages[$this->_cid][$name])) {
            return array();
        }
        $data = self::$messages[$this->_cid][$name];
        self::$messages[$this->_cid][$name] = array();
        return $data;
    }

    /**
     * 渲染html结果。只允许在根组件上调用
     * todo: 是否应再加一个单独取 css, js 等信息的接口
     * @param $isXml boolean
     * @return array
     * @throws Exception
     */
    public function renderToHtml($isXml) {

        $this->html = $this->getOuterHtml($isXml);

        // 清空暂存的数据
        self::$messages = array();

        $this->gatherComponentInfo();

        return array(
            /** @var string */
            'html' => $this->html,
            /** @var array 组件自身及其子组件的css列表 path=>styleString */
            'css' => $this->css,
            /** @var array 组件自身及其子组件的js列表 path=>scriptString*/
            'js' => $this->js,
            /** @var string[] 最外层组件的props */
            'props' => $this->propKeys
        );
    }

    /**
     * 将当前组件树渲染至vnode
     * 注意：若多次调用，只有第一次会真正渲染（外界需避免多次$data不同）
     * @param $data array|null
     * @param $children array
     * @param $tag string
     * @param $isRoot boolean 是否是根节点
     * @return Atom_Component
     * @throws Exception
     *
     */
    public function renderToVNode ($data = null, $children = array(), $tag = '', $isRoot = false) {
        $this->init($data, $children, $isRoot);
        return $this;
    }

    /**
     * 用于收集本组件及后代组件的静态资源等信息，以备输出
     * @param $config array
     */
    private function gatherComponentInfo ()  {

        if (isset($this->style)) {
            $this->css[$this->componentPath] = $this->style;
        }
        if (isset($this->script)) {
            $this->js[$this->componentPath] = $this->script;
        }

        // 收集子组件的配置
        $this->gatherSubComponentInfo($this->options['components']);
    }

    /**
     * 用于收集本组件及后代组件的静态资源等信息，以备输出
     * @param $config array
     */
    private function gatherSubComponentInfo ($components)  {

        if (is_array($components)) {
            // 收集子组件的配置
            foreach ($components as $subComponentName => $subComponentPath) {
                $creator = \Atom_Component_Creator::getConfig($subComponentPath);
                $config = $creator->config;

                if (isset($config['style'])) {
                    $this->css[$creator->path] = $config['style'];
                }

                // @TODO 此功能暂未实现
                // if (isset($config['script'])) {
                //     $this->js[$creator->path] = $config['script'];
                // }

                $this->gatherSubComponentInfo($config['components']);
            }
        }
    }

    /**
     * 生成 html 字符串
     *
     * @param $isXml bool
     * @return string
     */
    public function getOuterHtml($isXml) {
        return $this->_configInstance->render($this, $isXml);
    }

    /**
     * render class into string using in ssrCompile result
     *
     * @param string $static static class names
     * @param mixed $binding binding class names, can an `array` or a `object`
     * @return string
     */
    public function _ssrClass($static, $binding) {
        $classes = array();

        // static class: <div class="xxx xxx" />
        // just put it in
        if (!empty($static)) {
            array_push($classes, $static);
        }

        // binding classes: <div :class="[a, {}]" /> or <div :class="{a: true, b: false}" />
        if (!empty($binding)) {
            $classes = array_merge($classes, Atom_Util_Element::handleClass($binding));
        }

        return empty($classes) ? '' : ' class="' . implode(' ', $classes) . '"';
    }

    /**
     * render style into string using in ssrCompiled component
     *
     * @param array $static static styles
     * @param mixed $binding binding styles, such as an `array` or an `object`
     * @param array $aShow `a-show` directive style, only can be either `array()` or `array('display' => 'none')`
     * @return string
     */
    public function _ssrStyle($static, $binding, $aShow) {
        $styles = array();
        if (!empty($static)) {
            $styles = array_merge($styles, $static);
        }

        if (!empty($binding)) {
            $styles = array_merge($styles, Atom_Util_Element::handleStyle($binding));
        }

        if (!empty($aShow)) {
            $styles = array_merge($styles, $aShow);
        }

        if (empty($styles)) {
            return  '';
        }

        $markup = '';
        foreach ($styles as $key => $value) {
            if ($value === null) {
                $value = 'null';
            }
            $markup .= $key . ':' . (string)$value . ';';
        }

        return ' style="' . $markup . '"';
    }

    /**
     * @param $tag string
     * @param $data array
     * @param $isXml bool
     * @return string
     */
    private function renderStartingTag($tag, $data, $isXml) {

        $markup = '<' . $tag;
        $markup .= $this->renderStartingTagContent($data, $isXml);
        $markup .= '>';

        return $markup;
    }

    /**
     * @param $data array
     * @param $isXml bool
     * @return string
     */
    private function renderStartingTagContent($data, $isXml) {

        $markup = '';

        // 如果整体是空的，直接返回
        if (empty($data)) {
            return $markup;
        }

        // 处理 attrs
        if (!empty($data['attrs']) > 0) {
            $markup .= $this->renderAttrs($data['attrs'], $data['rawAttrsMap']);
        }

        // 处理 class
        if (isset($data['staticClass']) || isset($data['class'])) {
            $markup .= $this->_ssrClass($data['staticClass'], $data['class']);
        }

        // 处理 style、a-show
        $aShow = (isset($data['directives']['show']) && !$data['directives']['show']['value']) ? array('display' => 'none') : array();
        $markup .= $this->_ssrStyle($data['staticStyle'], $data['style'], $aShow);

        // 处理scopeId
        if (!empty($data['scopeId'])) {
            $markup .= ' ' . $data['scopeId'];
        }

        return $markup;
    }

    /**
     * render array of attributes into string
     *
     * @param mixed $attrs array of attributes
     * @return string
     */
    private function renderAttrs($attrs, &$rawAttrMaps = null) {
        $markup = '';
        foreach ($attrs as $k => $v) {
            $markup .= $this->renderAttr($k, $v, $rawAttrMaps != null && $rawAttrMaps[$k]);
        }
        return $markup;
    }

    /**
     * render attribute into string
     *
     * @param string $key attribute name
     * @param string $key attribute value
     * @param boolean $raw whether attibute is raw
     * @return string
     */
    private function renderAttr($key, $value, $raw = false) {

        $falsy = Atom_Util_Attrs::isFalsyAttrValue($value);

        if (Atom_Util_Attrs::isBooleanAttr($key) && !$falsy) {
            return ' ' . $key . '="' . $key . '"';
        }

        if (Atom_Util_Attrs::isEnumeratedAttr($key)) {
            return ' ' . $key . '="' . ((Atom_Util_Attrs::isFalsyAttrValue($value) || $value === 'false') ? 'false' : 'true') . '"';
        }

        // 如果 value 是 null / undefined / false，那么不输出属性名
        if ($falsy) {
            return '';
        }

        /**
         * php中字符串连接跟js的处理不一样
         * 针对不同类型的数据，我们在php中模拟js数据类型的字符串连接
         */
        if (is_array($value)) {
            $value = Atom_Util_Shared::isPlainArray($value)
                // 模拟js中的数组
                ? join(',', $value)
                // 模拟js中的对象
                : '[object Object]';
            return ' ' . $key . '="' . htmlspecialchars($value) . '"';
        }

        // 模拟js中的true
        if ($value === true) {
            $value = 'true';
        }

        return ' ' . $key . '="' . ($raw ? $value : htmlspecialchars($value)) . '"';
    }

    /**
     * render attribute into raw string
     *
     * @param string $key attribute name
     * @param string $key attribute value
     * @return string
     */
    public function _ssrRawAttr($name, &$value) {
        return $this->renderAttr($name, $value, true);
    }

    /**
     * 兼容老的 vnode 接口
     * @param $name string
     * @param $value mixed
     */
    public function setAttribute($name, $value = "") {

        if (!isset($this->_rootData['attrs']) || !is_array($this->_rootData['attrs'])) {
            $this->_rootData['attrs'] = array();
        }

        $this->_rootData['attrs'][$name] = $value;
    }

    /**
     * @param string $tag 标签
     * @param array $data 数据
     * @param array $children 子节点
     * @param bool $isXml 是否是 xml
     * @return string
     */
    private function renderElement($tag, &$data, &$children, $isXml) {
        $this->renderDomProps($data, $children);
        $start = $this->renderStartingTag($tag, $data, $isXml);
        $children = isset($children) ? $children : '';
        return $start . $children . "</" . $tag . ">";
    }

    /**
     * 处理 domProps 属性
     * @param $data
     * @param $children
     */
    public function renderDomProps(&$data, &$children) {

        $props = isset($data['domProps']) ? $data['domProps'] : null;

        if (is_array($props) && count($props) > 0) {

            $attrs = isset($data['attrs']) ? $data['attrs'] : array();

            foreach ($props as $k => $v) {

                if ($k === 'innerHTML') {
                    $children = $this->_s($v);
                    continue;
                }

                if ($k === 'textContent') {
                    $children = $this->_ssrEscape($v);
                    continue;
                }

                $attr = isset(Atom_Util_Element::$propsToAttrMap[$k]) ? Atom_Util_Element::$propsToAttrMap[$k] : strtolower($k);

                if (Atom_Util_Attrs::isValidAttr($attr) && !isset($attrs[$attr])) {
                    $attrs[$attr] = $v;
                    $data['attrs'] = $attrs;
                }
            }
        }
    }

}
