var React = require('react');
var ReactDOM = require('react-dom');
var ReactDOMServer = require('react-dom/server');

var normalizeProps = require('./normalizeProps');
var config = require('./config');

var UNWRAP_HTML_REGEXP = new RegExp('^<(\\S+)\\s+.*?(?:class="(.+?)"\\s*)?.*?(?:' + config.jsAttribute + '=\'(.+?)\'\\s*)?.*?(?:class="(.+?)"\\s*)?.*?>(.*?)</\\1>$');

var TAGS_WITHOUT_CHILDREN_REGEXP = /^(input|img|br|link|meta)$/;
var CUSTOM_MOD_NAME = 'сustom';

var BemMixin = {

    /**
     * Возвращает название BEM блока
     * @method getBlockName
     * @return {String}
     */
    getBlockName: function() {
        return String.prototype.toLowerCase.call(this.blockName || this.constructor.displayName);
    },

    /**
     * Возвращает карту свойств для формирования BEM JSON
     * @method getPropsMap
     * @return {Object}
     */
    getPropsMap: function() {
        return this.constructor.propsMap || {};
    },

    /**
     * Получает DOM элемент в который будут помещены дочерние компоненты
     * @method getChildrenContainer
     * @return {HTMLElement}
     */
    getChildrenContainer: function() {
        var value = this.childrenContainer;
        var block = this.block;

        return typeof value === 'function' ? value(block) : value && block.elem(value)[0];
    },

    /**
     * Получает BEM JSON соответствующий компоненту
     * @method getBemJson
     * @return {Object}
     */
    getBemJson: function() {
        var bemJson = {
            block: this.getBlockName(),
            mods: {}
        };

        var props = this.props;
        var propsMap = this.getPropsMap();

        for (var key in props) {
            if (!props.hasOwnProperty(key)) {
                continue;
            }

            var value = props[key];
            var scope = bemJson;
            var map = propsMap[key];

            if (key === 'mod') {
                scope.mods[CUSTOM_MOD_NAME] = value;

                continue;
            }

            if (key === 'children' && this.nested !== false) {
                value = this.renderChildren(value);
            }

            if (map === undefined) {
                map = (propsMap.mods || {})[key];

                if (map === undefined) {
                    continue;
                }

                if (map && value === true) {
                    value = map[1] || value;
                }

                scope = bemJson.mods;
            } else if (map) {
                var transform = map[2];

                if (typeof transform === 'function') {
                    value = transform(value);
                }
            }

            key = map && map[0] || key;

            scope[key] = value;
        }

        return bemJson;
    },

    /**
     * Вызывает обработчик из `props` для указанного события
     * @example
     *     this.callHandler('click', payload) // this.props.onClick(payload)
     * @method callHandler
     * @param  {String}    name  название события для обработчика
     */
    callHandler: function(name, payload) {
        name = 'on' + name[0].toUpperCase() + name.substr(1);

        var handler = this.props[name];

        if (typeof handler === 'function') {
            handler(payload);
        }
    },

    /**
     * Запрещает повторный рендеринг компонента
     * @see    https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
     * @method shouldComponentUpdate
     * @return {Boolean}
     */
    shouldComponentUpdate: function() {
        return false;
    },

    /**
     * Обновляет компонент на клиенте используя BEM API
     * @method componentWillReceiveProps
     * @param  {Object}  nextProps
     */
    componentWillReceiveProps: function(nextProps) {
        var block = this.block;
        var propsMap = this.getPropsMap();

        if (!block) {
            return;
        }

        for (var key in nextProps) {
            var value = nextProps[key];

            if (!nextProps.hasOwnProperty(key) || value === this.props[key]) {
                continue;
            }

            if (key === 'children') {
                this.renderChildren(value);

                continue;
            }

            if (key === 'mod') {
                block.setMod(CUSTOM_MOD_NAME, value);

                continue;
            }

            var map = propsMap[key];

            if (map === undefined) {
                map = propsMap.mods && propsMap.mods[key];

                if (map === undefined) {
                    continue;
                }

                if (map && value === true) {
                    value = map[1] || value;
                }

                block.setMod((map && map[0] || key), value);
            } else if (map && map[1]) {
                var transform = map[2];

                if (typeof transform === 'function') {
                    value = transform(value);
                }

                block[map[1]](value);
            }
        }
    },

    ensureElement: function() {
        var bemJson = config.bh.processBemJson(this.getBemJson());

        var html = config.bh.toHtml(bemJson);

        var el = document.createElement('div');

        el.innerHTML = html;
        el = el.firstChild;

        var className = this.props.className;

        if (className) {
            el.className += className;
        }

        document.body.appendChild(el);

        this._blockEl = el;
    },

    /**
     * Инициализирует BEM блок после вставки компонента в DOM
     * @method componentDidMount
     */
    componentDidMount: function() {
        if (this.nested === false) {
            this.ensureElement();
        }

        var block = config.ibem
            .init(config.jQuery(this._blockEl))
            .bem(this.getBlockName());

        this.block = block;

        var blockDidMount = this.blockDidMount;

        if (typeof blockDidMount === 'function') {
            blockDidMount(block);
        }

        this.renderChildren(this.props.children);
    },

    /**
     * Уничтожает BEM блок перед удаление компонента
     * @method componentWillUnmount
     */
    componentWillUnmount: function() {
        var container = this.getChildrenContainer();

        if (container) {
            ReactDOM.unmountComponentAtNode(container);
        }

        var block = this.block;

        var blockWillUnmount = this.blockWillUnmount;

        if (typeof blockWillUnmount === 'function') {
            blockWillUnmount(block);
        }

        config.ibem.detach(block.domElem, true);

        this.block = null;
        this._blockEl = null;
    },

    /**
     * Рендерит компонент, используя шаблоны из BEM
     * @method render
     * @return {ReactElement}
     */
    render: function() {
        if (this.nested === false) {
            return null;
        }

        var bemJson = this.getBemJson();

        bemJson = config.bh.processBemJson(bemJson);

        var matches = config.bh
            .toHtml(bemJson)
            .match(UNWRAP_HTML_REGEXP);

        var props = normalizeProps(bemJson.attrs);

        props.className = matches[2] || matches[4];
        props[config.jsAttribute] = matches[3];

        props.ref = function(element) {
            this._blockEl = element;
        }.bind(this);

        var tag = matches[1];
        var content = matches[5];

        if (tag === 'textarea') {
            props.defaultValue = content;
        } else if (!TAGS_WITHOUT_CHILDREN_REGEXP.test(tag)) {
            props.dangerouslySetInnerHTML = {
                __html: content
            };
        }

        return React.createElement(tag, props);
    },

    /**
     * Рендерит дочерние компоненты, оборачивая их в дополнительный элемент
     * @method renderChildren
     * @param  {HTMLElement}       [container]
     * @return {String|ReactElement}  если контейнер не указан, возвращается строка
     */
    renderChildren: function(children) {
        var wrapper = React.createElement('div', null, children);

        if (this.block) {
            var container = this.getChildrenContainer();

            if (container) {
                ReactDOM.render(wrapper, container);
            }

            return wrapper;
        }

        return ReactDOMServer.renderToString(wrapper);
    }
};

module.exports = BemMixin;
