import React from 'react';
import size from 'lodash/size';
import difference from 'lodash/difference';
import indexOf from 'lodash/indexOf';
import isEqual from 'lodash/isEqual';
import filter from 'lodash/filter';
import classnames from 'classnames';
import reactMixin from 'react-mixin';
import OnClickOutside from 'react-onclickoutside';
import map from 'lodash/map';
import * as stylus from '../../../assets/styles/app/ui/dropdown.styl';


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

    this.state = {
      maxHeight: false,
      showDropdown: false,
      showAllChecked: false,
      managingChecks: false,
    };

    this.handleClickOutside = this.handleClickOutside.bind(this);
    this.isSelected = this.isSelected.bind(this);
    this.getSourceId = this.getSourceId.bind(this);
    this.getSourceValue = this.getSourceValue.bind(this);
    this.getAllSelection = this.getAllSelection.bind(this);
    this.setMaxHeight = this.setMaxHeight.bind(this);
  }

  componentDidMount() {
    this.setMaxHeight();
    // Recalculate the max height whenever the window is resized
    window.addEventListener('resize', this.setMaxHeight);
  }

  componentWillReceiveProps(nextProps) {
    const nextState = {};
    const numSelected = this.getNumSelected(nextProps.selectedList, nextProps.sources);
    if (numSelected === size(this.props.sources)) {
      if (! this.state.managingChecks && nextProps.noSelectedIsAll) {
        nextState.managingChecks = true;
        nextState.showAllChecked = true;
      }
    } else if (numSelected === 0) {
      if (! this.state.managingChecks && nextProps.noSelectedIsAll) {
        nextState.managingChecks = true;
        nextState.showAllChecked = false;
      } else {
        if (! nextProps.noSelectedIsAll) {
          nextState.managingChecks = false;
        }
      }
    } else {
      if (this.state.managingChecks) {
        nextState.managingChecks = false;
      }
    }

    if (Object.keys(nextState).length !== 0) {
      this.setState(nextState);
    }
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.setMaxHeight);
  }

  setMaxHeight() {
    let maxHeight = Math.floor(window.innerHeight / 2);
    if (this.refs.dropdown) {
      const dropdownOffset = this.refs.filter.getBoundingClientRect().top + 10;
      const windHeight = window.innerHeight * 0.8;
      maxHeight = Math.floor(windHeight - dropdownOffset);
    }
    this.setState({maxHeight: maxHeight});
  }

  getSourceValue(source) {
    if (this.props.getValue) {
      return this.props.getValue(source);
    }
    return source;
  }

  getSourceId(source) {
    if (this.props.getId) {
      return this.props.getId(source);
    }
    return source;
  }

  getAllSelection() {
    const list = [];
    for (const sIndex in this.props.sources) {
      const sourceId = this.getSourceId(this.props.sources[sIndex]);
      this.selectItem(list, sourceId);
    }
    return list;
  }

  // Figures out what to say in the label before the collection name
  getLabelSpecifier(numSelected, numSources) {
    if (numSelected < numSources) {
      if (numSelected === 0) {
        if (this.props.noSelectedIsAll) {
          return 'All';
        }
        return this.props.noneSelectedText;
      }
      return numSelected;
    }

    if (this.props.displayAllWhenAll) {
      return 'All';
    }

    return numSelected;
  }

  getNumSelected(list = this.props.selectedList, sources = this.props.sources) {
    if (this.props.flattenIdList) {
      // Go through each source's ids and check if all of them are in selectedList.
      // If so, that source is selected.
      const idMap = map(sources, this.getSourceId);
      const completeSelections = filter(idMap, (ids) => this.isSelected(ids, list));
      return completeSelections.length;
    }
    return list.length;
  }

  // Doesn't fire any callbacks, just adds an item to a list.
  // In its own function so the behavior is standardized
  selectItem(list, sourceId) {
    if (this.props.flattenIdList) {
      for (const id of sourceId) {
        list.push(id);
      }
    } else {
      list.push(sourceId);
    }
  }

  // Doesn't fire any callbacks, just removes an item from a list.
  // In its own function so the behavior is standardized
  unselectItem(list, sourceId) {
    if (this.props.flattenIdList) {
      for (const id of sourceId) {
        const index = indexOf(list, id);
        if (index !== -1) {
          list.splice(index, 1);
        }
      }
    } else {
      const index = indexOf(list, sourceId);
      if (indexOf(list, sourceId) !== -1) {
        list.splice(index, 1);
      }
    }
  }

  // Says whether the thing designated by an id is selected.
  isSelected(sourceId, list = this.props.selectedList) {
    if (this.props.flattenIdList) {
      // Strict membership - We need to have all the ids for a source for it to
      // be considered selected.
      for (const id of sourceId) {
        if (indexOf(list, id) === -1) {
          return false;
        }
      }
      return true;
    }

    return indexOf(list, sourceId) !== -1;
  }

  handleClickOutside(e) {
    if (!this.state.showDropdown) {
      return;
    }
    let target = e.target;
    while (target.parentNode) {
      if (target === this.refs.filter) {
        return;
      }
      target = target.parentNode;
    }
    this.setState({showDropdown: false});
  }

  handleSetAllSelection(doSelectAll) {
    let ch = function() {};
    if (! this.state.managingChecks || ! this.props.noSelectedIsAll) {
      if (doSelectAll && ! this.props.noSelectedIsAll) {
        ch = () => this.props.onChange(this.getAllSelection());
      } else {
        // Change so that nothing is selected.
        ch = () => this.props.onChange([]);
      }
    }

    if (this.props.noSelectedIsAll) {
      const nextState = {
        showAllChecked: doSelectAll,
        managingChecks: true
      };
      // We need to set state before the callback so that when we recieve new props
      // the state is already modified.
      this.setState(nextState, ch);
    } else {
      ch();
    }
  }

  handleClick(source, selectItem) {
    const sourceId = this.getSourceId(source);
    let list = this.props.selectedList.slice();
    if (! this.state.managingChecks || ! this.state.showAllChecked) {
      if (selectItem) {
        this.selectItem(list, sourceId);
      } else {
        this.unselectItem(list, sourceId);
      }
    } else {
      list = this.getAllSelection();
      this.unselectItem(list, sourceId);
    }
    this.props.onChange(list);
  }

  toggleMenu() {
    this.setState({showDropdown: !this.state.showDropdown});
  }

  renderModalPrompt() {
    return (
      <div
        className="checkboxDropdown-advancedLink"
        onClick={this.props.openAdvancedModal}
      >
        Advanced Options
      </div>
    );
  }

  renderSetAllButton(doSelectAll) {
    let selectionText = 'Clear Selected';
    if (doSelectAll) {
      selectionText = 'Select All';
    }
    return (
      <div className="dropdown-checkbox__selectall" onClick={() => this.handleSetAllSelection(doSelectAll)}>
        {selectionText}
      </div>
    );
  }

  render() {
    const numSources = size(this.props.sources);
    const numSelected = this.getNumSelected();
    let doSelectAll = (numSources / 2) - numSelected > 0;
    doSelectAll = numSelected === 0;

    if (this.state.managingChecks) {
      doSelectAll = !this.state.showAllChecked;
    }

    let dropdownStyle = {};
    if (this.state.maxHeight && this.state.maxHeight > 0) {
      dropdownStyle = {
        maxHeight: this.state.maxHeight + 'px'
      };
    }
    const classN = classnames('dropdown-checkbox',
                              {[this.props.className]: this.props.className},
                              {'dropdown-checkbox--open': this.state.showDropdown},
                              {'dropdown-checkbox--double': this.props.wide});
    let toggleButtonLabel = this.getLabelSpecifier(numSelected, numSources);
    toggleButtonLabel = toggleButtonLabel + ' ' + this.props.collectionName;
    if (numSelected !== 1 && !this.props.noPluralForm) {
      toggleButtonLabel = toggleButtonLabel + 's';
    }
    return (
      <div className={classN} ref="filter">
        <button className="dropdown-checkbox__toggler" onClick={() => this.toggleMenu()}>
          {toggleButtonLabel}
        </button>
        <ul className="dropdown-checkbox__menu" style={dropdownStyle}>
          {this.renderSetAllButton(doSelectAll)}
          {!this.props.loading && map(this.props.sources, (c, i) => {
            let isSel = this.isSelected(this.getSourceId(c));
            if (this.state.managingChecks) {
              isSel = this.state.showAllChecked;
            }
            return (<li key={i}
                        className={classnames({'active': isSel})}
                        onClick={() => this.handleClick(c, !isSel)}>
                      <label>{this.getSourceValue(c)}</label>
                    </li>);
          }
          )}
          {this.props.loading && <li key="loading">Loading {this.props.collectionName}...</li>}
          {this.props.openAdvancedModal && this.renderModalPrompt()}
        </ul>
      </div>
    );
  }
}

CheckboxSelectorDropdown.propTypes = {
  // Things that we need checkboxes for
  sources: React.PropTypes.any.isRequired,

  // The name of the collection to show in the label
  collectionName: React.PropTypes.string.isRequired,

  // Return somthing to identify the item in selectedList
  getId: React.PropTypes.func,

  // If getId returns an array, flatten the list and add each element to selectedList
  // for the onChange callback
  flattenIdList: React.PropTypes.bool,

  // What to display when nothing is selected
  noneSelectedText: React.PropTypes.string,

  // Returns a string to display to represent the item
  getValue: React.PropTypes.func,

  // A list of identifiers for sources that are selected
  selectedList: React.PropTypes.array.isRequired,

  // A callback that is passed a modified selectedList
  onChange: React.PropTypes.func.isRequired,

  // A class to add to the root element of this
  className: React.PropTypes.string,

  // Consider everything selected when nothing is
  noSelectedIsAll: React.PropTypes.bool,

  // Show 'All {collectionName}' when everything is selected
  displayAllWhenAll: React.PropTypes.bool,

  // Don't show an 's' at the end of the collection name if more than 1 is selected
  noPluralForm: React.PropTypes.bool,

  // Whether or not we are loading stuff...
  loading: React.PropTypes.bool,

  // Sets the dropdown to be much wider than normal.
  wide: React.PropTypes.bool,

  // A callback that should tigger a modal with advanced options
  openAdvancedModal: React.PropTypes.func
};
CheckboxSelectorDropdown.defaultProps = {
  noneSelectedText: 'No',
};

reactMixin.onClass(CheckboxSelectorDropdown, OnClickOutside);
export default CheckboxSelectorDropdown;
