<?php

/**
 * set ATOM_DIR to absolute path to atom library files.
 */
if (!defined('ATOM_DIR')) {
    define('ATOM_DIR', dirname(__FILE__) . DIRECTORY_SEPARATOR);
}

require_once(ATOM_DIR . 'Atom_Component_Creator.php');
require_once(ATOM_DIR . 'Atom_Global_Config.php');

/**
 * Class Atom
 * main class of Atom
 */
class Atom {

    private $componentCache = array();
    private $css = array();

    /**
     * 是否索引数组
     * @param $arr mixed
     * @return bool
     */
    private static function isPlainArray(&$arr) {
        if (is_array($arr)) {
            $i = 0;
            foreach ($arr as $k => $v) {
                if ($k !== $i++) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    /**
     * 增加组件目录
     * @param $dirs string[]
     */
    public function addComponentDir($dirs) {
        if (isset($dirs)) {
            if (is_string($dirs)) {
                array_unshift(Atom_Global_Config::$componentsDir, $dirs);
            }
            elseif (is_array($dirs)) {
                // 如果是索引数组
                if (self::isPlainArray($dirs)) {

                    foreach($dirs as $dir) {
                        if (is_string($dir)) {
                            array_unshift(Atom_Global_Config::$componentsDir, $dir);
                        }
                    }
                }
            }
        }
    }

    /**
     * @param $id string
     * @param $definition callable
     */
    public static function filter($id, $definition) {
        if (!is_callable($definition)) {
            trigger_error('[Atom Runtime] filter must be callable: ' . $id, E_USER_WARNING);
            return;
        }
        if (isset(Atom_Global_Config::$globalFilterMap[$id])) {
            trigger_error('[Atom Runtime] filter has been registered: ' . $id, E_USER_NOTICE);
        }
        Atom_Global_Config::$globalFilterMap[$id] = $definition;
    }

    /**
     * @param $id string
     * @param $phpFilePath callable
     */
    public function registerComponent($id, $phpFilePath) {
        if (isset(Atom_Global_Config::$globalAsset['components'][$id])) {
            trigger_error('[Atom Runtime] global component has been registered: ' . $id . '. [component]:' . $phpFilePath, E_USER_NOTICE);
        }
        Atom_Global_Config::$globalAsset['components'][$id] = $phpFilePath;
    }

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

        // 缓存，已经收集过的就不再收集了
        static $cache = array();

        if (is_array($components)) {
            // 收集子组件的配置
            foreach ($components as $subComponentPath) {

                if (isset($cache[$subComponentPath])) {
                    continue;
                }

                $creator = Atom_Component_Creator::getConfig($subComponentPath);

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

                $cache[$subComponentPath] = 1;

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

                $this->gatherComponentInfo($creator->config['components'], $css);
            }
        }

        return $css;
    }


    /**
     * render component and return vnode root
     * @param $componentPath string eg. 'album.php', 'album/index.php'
     * @param $data array
     * @return Atom_Vdom_VNode
     * @throws Exception
     */
    public function renderVNode ($componentPath, $data = null) {
        $componentInstance = Atom_Component_Creator::createInstance($componentPath);
        $this->componentCache[$componentPath] = $componentInstance;
        return $componentInstance->renderToVNode($data, array(), '', true);
    }

    /**
     * @param $componentPath
     * @param null $data
     * @return array
     */
    public function renderHtml ($componentPath, $data = null) {
        return $this->_renderHtml($componentPath, false, $data);
    }

    /**
     * 渲染为xml，一些语法上符合xml而非html格式
     * @param $componentPath
     * @param null $data
     * @return array
     */
    public function renderXml ($componentPath, $data = null) {
        return $this->_renderHtml($componentPath, true, $data);
    }

    /**
     * 输出组件的属性、css 等信息，不输出 html，为异步渲染提供接口
     * @param $componentPath
     * @return array
     */
    public function getComponentInfo($componentPath) {

        $css = array();

        $creator = Atom_Component_Creator::getConfig($componentPath);
        $config = &$creator->config;
        $props = isset($config['propKeys']) ? $config['propKeys'] : (
            self::isPlainArray($config['props']) ? $config['props'] : array_keys($config['props'])
        );

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

        $this->gatherComponentInfo($config['components'], $css);

        return array(
            'css' => $css,
            'js' => array(),
            'props' => $props,
        );
    }

    /**
     * todo: 是不是可以在VNode上支持toHtml (需考虑组件、子组件静态资源等信息。是否允许在非组件vnode上调用)
     * @param $componentPath
     * @param $isXml boolean
     * @param $data array 若之前已经调用过renderVNode，则会被忽略，无需再传入
     * @return array
     * @throws Exception
     */
    private function _renderHtml ($componentPath, $isXml, $data = null) {

        if (!isset($this->componentCache[$componentPath])) {
            // 若之前未调用过renderVNode，此处兼容调用
            $this->renderVNode($componentPath, $data);
        }

        $componentInstance = $this->componentCache[$componentPath];
        $res = $componentInstance->renderToHtml($isXml, true);

        if (!isset($res['css'])) {

            $css = array();
            $css[$componentInstance->componentPath] = $componentInstance->style;

            $this->gatherComponentInfo($componentInstance->options['components'], $css);

            $props = isset($componentInstance->propKeys) ? $componentInstance->propKeys : (
                self::isPlainArray($componentInstance->props) ? $componentInstance->props : array_keys($componentInstance->props)
            );

            return array(
                'html' => $res['html'],
                'css' => $css,
                'js' => array(),
                'props' => $props,
            );
        }

        return $res;
    }
}
