import classNames from 'classnames';
import extend from 'extend';
import flatten from 'lodash.flatten';
import React, { PropTypes } from 'react';

import KdChickletsContainer from '../KdChickletsContainer';
import KdChickletsContainerDraggable from '../KdChickletsContainerDraggable';
import * as constants from '../../constants';

export default function wrap(WrappedComponent, draggable = false) {
  const defaultProps = {
    theme: {},
    selectBehavior: constants.ADD_CHICKLET,
    isDisabled: false,
    chicklets: {
      sortable: false,
      visible: true,
      fullwidth: false,
      insertAtBeginning: false,
      buttons: {
        ascDesc: { visible: false },
        info: { visible: false },
      },
      events: {},
      container: { visible: true },
      clearAll: { visible: true },
    },
  };

  const propTypes = {
    theme: PropTypes.oneOfType([
      PropTypes.object,
      PropTypes.func,
    ]),
    itemHandler: PropTypes.func,
    selectBehavior: PropTypes.string,
    kdFilterModel: PropTypes.object.isRequired,

    events: PropTypes.shape({
      onSelect: PropTypes.func,
      onChange: PropTypes.func,
    }),

    api: PropTypes.shape({
      url: PropTypes.string.isRequired,
      key: PropTypes.string.isRequired,
    }),

    mainSelector: PropTypes.shape({
      visible: PropTypes.bool,
      selected: PropTypes.string,

      optionValues: PropTypes.shape({
        selectOption: PropTypes.shape({
          visible: PropTypes.bool,
          label: PropTypes.string,
        }),
        anyOption: PropTypes.shape({
          visible: PropTypes.bool,
          label: PropTypes.string,
        }),
        customOptions: PropTypes.array,
        userSpecifiedOption: PropTypes.shape({
          visible: PropTypes.bool,
          label: PropTypes.string,
        }),
      }),
    }),

    chicklets: PropTypes.shape({
      sortable: PropTypes.bool,
      visible: PropTypes.bool,
      fullwidth: PropTypes.bool,
      insertAtBeginning: PropTypes.bool,
      prefix: PropTypes.string,

      buttons: PropTypes.shape({
        ascDesc: PropTypes.shape({ visible: PropTypes.bool }),
        info: PropTypes.shape({ visible: PropTypes.bool }),
      }),

      events: PropTypes.shape({
        onClick: PropTypes.func,
        onClose: PropTypes.func,
        onDrop: PropTypes.func,
      }),

      clearAll: PropTypes.shape({
        visible: PropTypes.bool,
        onClick: PropTypes.func,
      }),
    }),
  };

  class ItemContainerWrapper extends React.Component {
    constructor(props) {
      super(props);

      this.mainSelector = extend(true, {}, defaultProps.mainSelector, this.props.mainSelector);
      this.chicklets = extend(true, {}, defaultProps.chicklets, this.props.chicklets);
      this.errors = null;

      this.state = { items: [], option: null, item: null };
      this.addItem = this.addItem.bind(this);
      this.removeItem = this.removeItem.bind(this);
      this.changeOption = this.changeOption.bind(this);
      this.clearChicklets = this.clearChicklets.bind(this);
      this.setChicklets = this.setChicklets.bind(this);
      this.updateItemPosition = this.updateItemPosition.bind(this);
      this.toggleItemDirection = this.toggleItemDirection.bind(this);
      this.lastInitialValue = this.lastInitialValue.bind(this);
      this.hasInitialValues = this.hasInitialValues.bind(this);
      this.addError = this.addError.bind(this);
      this.clearErrors = this.clearErrors.bind(this);
      this.setState = this.setState.bind(this);
      this.containerVisible = this.containerVisible.bind(this);
    }


    hasErrors() {
      return !this.errors;
    }

    getErrors() {
      return this.errors;
    }

    addItem({
      value,
      chickletLabel,
      option,
      inputVisible,
      remove,
      category,
      direction,
      errors,
      kdFilterOptions = {
        model: this.props.kdFilterModel,
        operator: 'Eq',
        itemHandler,
      },
    }) {
      let { items } = this.state;

      if (!value || !chickletLabel || errors) {
        if (errors) {
          this.addError({ errors });
        }
        return this.removeItem(option);
      }

      const newItem = {
        value,
        option,
        direction,
        label: chickletLabel,
        key: option,
        category,
        kdFilterOptions,
        chicklets: this.chicklets,
        onRemove: () => {
          this.removeItem(option);
          this.props.events && this.props.events.onChange && this.props.events.onChange();
          remove && remove();
        },
      };

      if (this.isChickletNone()) {
        newItem.autocompleteVisible = inputVisible;
        newItem.isChickletNone = this.isChickletNone();
        newItem.hasMainSelector = this.mainSelector.visible;

        items = [newItem];
      } if (this.isAddChickletSingle()) {
        const itemsMap = items.reduce((map, item) => {
          map[item.kdFilterOptions.model.getEntityType()] = item;
          return map;
        }, {});

        itemsMap[newItem.kdFilterOptions.model.getEntityType()] = newItem;
        items = Object.keys(itemsMap).map(key => itemsMap[key]);
      } if (this.isAddChickletSingleRegardlessOfType()) {
        items = [newItem];
      } else {
        const index = items.findIndex(item => item.key === option);
        if (index > -1) {
          items.splice(index, 1, newItem);
        } else {
          items = this.chicklets.insertAtBeginning ? [newItem, ...items] : [...items, newItem];
        }
      }

      this.updateKdModel({ ...newItem, items });

      this.setState({ items, item: newItem, option });
      this.clearErrors();
    }

    setChicklets(chicklets) {
      let items = chicklets.map(opt => (
        Object.assign(
          {},
          opt,
          {
            label: opt.chickletLabel,
            key: opt.option,
            onRemove: () => {
              this.removeItem(opt.option);
              opt.remove && opt.remove();
              this.props.events && this.props.events.onChange && this.props.events.onChange();
            },
          },
        )
      ));

      if (items && items.length) {
        const item = [...items].pop();
        if (this.isChickletNone() || this.isAddChickletSingleRegardlessOfType()) {
            items = [item];
        } if (this.isAddChickletSingle()) {
          const itemsMap = items.reduce((map, item) => {
            map[item.kdFilterOptions.model.getEntityType()] = item;
            return map;
          }, {});

          itemsMap[newItem.kdFilterOptions.model.getEntityType()] = newItem;
          items = Object.keys(itemsMap).map(key => itemsMap[key]);
        }

        this.updateKdModel({ ...item, items });
        this.setState({ items, item, option: item.option });
        this.clearErrors();
      }
    }


    containerVisible() {
      const { chicklets: { container: { visible } } } = this;
      return visible;
    }

    setState(nextState, callback) {
      if (this.containerVisible()) {
        React.Component.prototype.setState.call(this, nextState, callback);
      } else {
        this.state = Object.assign({}, this.state, nextState);
      }
    }

    getItems() {
      return this.state.items;
    }

    removeItem(option) {
      const { items } = this.state;
      const index = items.findIndex(item => item.key === option);
      if (index > -1) {
        const item = items.splice(index, 1)[0];
        this.updateKdModel({ ...item, items });
      }
      this.setState({ items, item: null });
    }

    addError({ errors }) {
      this.errors = errors;
      if (this.isChickletNone()) {
        this.clearChicklets();
      }
    }

    clearErrors() {
      this.errors = null;
    }

    updateKdModel({ kdFilterOptions, items }) {
      const { model, itemHandler } = kdFilterOptions;

      if (items.length) {
        let selected = [];

        // check if its an ordered model
        if (model.addSortOrderItem) {
          selected = items.map(chicklet => ({
            direction: chicklet.direction,
            propertyName: chicklet.value,
          }));
        } else {
          const grouped = items.reduce((obj, item) => {
            const newObj = Object.assign({}, obj);
            const { operator } = item.kdFilterOptions;
            (newObj[operator] = newObj[operator] || []).push(item);
            return newObj;
          }, {});

          selected = Object.keys(grouped).map((operator) => {
            const values = flatten(grouped[operator].map(({ value, label }) => (
              itemHandler(
                {
                  value,
                  label,
                  operator,
                  entityType: model && model.getEntityType && model.getEntityType(),
                },
              )
            )));

            return {
              operator,
              values,
            };
          });
        }
        model.setSelected(selected);
      } else if (model.clearSortOrder) {
        model.clearSortOrder();
      } else {
        model.clearSelected();
      }
    }

    changeOption(option) {
      this.setState({ option });
    }

    clearChicklets() {
      const { kdFilterModel } = this.props;
      const { chicklets: { clearAll } } = this;

      this.setState(
        Object.assign({}, this.state, { items: [], item: null }),
      );

      if (clearAll && clearAll.onClick) {
        clearAll.onClick();
      }
      if (kdFilterModel && kdFilterModel.clearSortOrder) {
        kdFilterModel.clearSortOrder();
      } else if (kdFilterModel) {
        kdFilterModel.clearSelected();
      }
    }


    updateItemPosition(value, position) {
      const { items } = this.state;
      const { kdFilterModel } = this.props;
      const itemIndex = items.findIndex(elem => (elem.value === value));
      const item = items.splice(itemIndex, 1);

      items.splice(position, 0, item[0]);

      this.setState(Object.assign({}, this.state, items), () => {
        const selectedItems = kdFilterModel.getSortOrderModelValue();
        const valueItem = selectedItems.splice(itemIndex, 1);

        selectedItems.splice(position, 0, valueItem[0]);
        kdFilterModel.setSelected(
          selectedItems.map(selectedItem => (
            {
              propertyName: selectedItem.propertyName,
              direction: selectedItem.direction,
            }
          ))
        );
      });
    }

    toggleItemDirection(value) {
      const { kdFilterModel } = this.props;

      const items = this.state.items.map((item, index) => {
        if (item.value === value) {
          const direction = (item.direction === constants.ASC) ? constants.DESC : constants.ASC;

          const selectedItems = kdFilterModel.getSortOrderModelValue();
          selectedItems[index].direction = direction;
          kdFilterModel.setSelected(
            selectedItems.map(selectedItem => (
              {
                propertyName: selectedItem.propertyName,
                direction: selectedItem.direction,
              }
            ))
          );

          return Object.assign({}, item, { direction });
        }

        return item;
      });

      this.setState(Object.assign({}, this.state, { items }));
    }

    isChickletNone() {
      return (constants.CHICKLET_NONE === this.props.selectBehavior);
    }

    isAddChickletSingle() {
      return (constants.ADD_CHICKLET_SINGLE === this.props.selectBehavior);
    }

    isAddChickletSingleRegardlessOfType() {
      return (constants.ADD_CHICKLET_SINGLE_REGARDLESS_OF_TYPE == this.props.selectBehavior);
    }

    isAddChicklet() {
      return (constants.ADD_CHICKLET === this.props.selectBehavior);
    }

    lastInitialValue() {
      const { items } = this.state;
      return (items.length ? items : this.props.selectedValuesInitial || []).slice(-1)[0];
    }

    hasInitialValues() {
      return !!(this.props.selectedValuesInitial && this.props.selectedValuesInitial.length);
    }

    render() {
      const { sortable } = this.chicklets;
      const { theme } = this.props;
      return (
        <div className={classNames('c-kd', theme['c-kd'])}>
          <WrappedComponent
            {...this.props}
            onItemAdded={this.addItem}
            onItemRemoved={this.removeItem}
            onOptionSelected={this.changeOption}
            onError={this.addError}
            clearErrors={this.clearErrors}
            isChickletNone={this.isChickletNone()}
            onClearChicklets={this.clearChicklets}
            onSetChicklets={this.setChicklets}
            emptyItems={!this.state.items.length}
            lastInitialValue={this.lastInitialValue}
            hasInitialValues={this.hasInitialValues}
          />

          {
            !sortable &&
              this.containerVisible() &&
                <KdChickletsContainer
                  theme={theme}
                  items={this.state.items}
                  isChickletNone={this.isChickletNone()}
                />
          }

          {
            !this.isChickletNone() &&
                sortable &&
                  this.containerVisible() &&
                    <KdChickletsContainerDraggable
                      theme={theme}
                      updateItemPosition={this.updateItemPosition}
                      toggleItemDirection={this.toggleItemDirection}
                      chicklets={this.chicklets}
                      items={this.state.items}
                    />
          }
        </div>
      );
    }
  }

  ItemContainerWrapper.defaultProps = defaultProps;
  ItemContainerWrapper.propTypes = propTypes;

  class resetWrapper extends React.Component {
    constructor(props) {
      super(props);

      this.reset = this.reset.bind(this);
      this.hardReset = this.hardReset.bind(this);
      this.executeValidation = this.executeValidation.bind(this);
      this.getValidation = this.getValidation.bind(this);
      this.getChicklets = this.getChicklets.bind(this);

      this.state = {
        component: () => <ItemContainerWrapper ref={ ref => this.Component = ref } { ...this.props } />,
      };
    }

    reset() {
      this.setState({
        component: () => <ItemContainerWrapper ref={ ref => this.Component = ref } { ...this.props } />,
      });
    }

    hardReset() {
     const propsWithOutInitial = extend(true, {}, this.props);
     if (this.props.selectedValuesInitial && this.props.selectedValuesInitial.length) {
      delete propsWithOutInitial.selectedValuesInitial;
     }
     this.setState({
        component: () => <ItemContainerWrapper ref={ ref => this.Component = ref } { ...propsWithOutInitial } />,
      });
    }

    executeValidation() {
      return this.Component && this.Component.hasErrors();
    }


    getValidation() {
      return this.Component && this.Component.getErrors();
    }

    getChicklets() {
      return this.Component && this.Component.getItems();
    }

    clearAll() {
      this.Component && this.Component.clearChicklets();
      this.hardReset();
    }

    render() {
      const Component = this.state.component;
      return (
        <div>
          <Component />
        </div>
      );
    }
  }

  return resetWrapper;
}
