import React from 'react';
import classnames from 'classnames';
import map from 'lodash/map';
import findIndex from 'lodash/findIndex';
import pullAt from 'lodash/pullAt';
import remove from 'lodash/remove';
import reactMixin from 'react-mixin';
import OnClickOutside from 'react-onclickoutside';
import {calculateTextWidth} from 'utils/html';
import {applyFiltersToTags} from 'utils/feeds';
import {fetchJsonp} from '../../../services/fetch';

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

    this.state = {
      tags: applyFiltersToTags(props.filters),
      autocomplete: [],   // autocomplete stores the list of suggestions and is set/changed by calculateAutocomplete()
      inputIndex: 9999,   // location of the search input box relative to the list of tags
      selected: 0,        // autocomplete suggestion that is selected
      search: '',         // sanitized value of the current search term (quotes removed and trimmed)
      tagSelected: null   // index of a tag that is currently selected,
    };

    this.handleClickOutside = this.handleClickOutside.bind(this);
    this._handleKeyDownOutside = this._handleKeyDownOutside.bind(this);
  }

  componentDidMount() {
    window.addEventListener('keydown', this._handleKeyDownOutside, false);
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      tags: applyFiltersToTags(nextProps.filters, this.state.tags)
    });
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this._handleKeyDownOutside);
  }

  handleClickOutside(e) {
    let target = e.target;
    while (target.parentNode) {
      if (target === this.refs.box) {
        return;
      }
      target = target.parentNode;
    }
    this.setState({tagSelected: null, inputIndex: 9999, showFilters: false});
  }

  _handleKeyDownOutside(e) {
    if (this.state.tagSelected === null) {
      return;
    }
    if (e.keyCode === 8) {
      this.removeTag(this.state.tagSelected);
      e.stopPropagation();
      e.preventDefault();
    } else {
      this.setState({tagSelected: null});
    }
  }

  containerClick(e) {
    if (e.target === this.refs.box) {
      this.setState({inputIndex: 9999, showFilters: false});
      this.refs.input.focus();
    }
  }

  /*
    Takes the current search term and a list of symbols returned from server autocomplete
    and creates list for dropdown.
  */
  calculateAutocomplete(search = '', serverSymbols = []) {
    this.setState({selected: 0});
    this.refs.dropdown.scrollTop = 0;
    if (search.trim().length < 1) {
      this.setState({autocomplete: []});
      return;
    }
    let list = [];
    // Get server symbols in proper order
    const symbols = map(serverSymbols, (c) =>
      ({
        label: c.symbol + ' | ' + c.name,
        value: c.symbol,
        heading: 'Symbol (' + c.exchange + ')',
        type: 'symbol'
      })
    );
    if (symbols.length) {
      const matchSymbol = findIndex(symbols, {value: search.toUpperCase()});
      if (matchSymbol > -1) {
        list = pullAt(symbols, matchSymbol);
      }
    }
    list.push({label: search, value: search, heading: 'Keyword', type: 'keyword'});
    this.setState({autocomplete: list.concat(symbols)});
  }

  adjustSearchInputWidth(DOMElement) {
    const boxWidth = this.refs.box.offsetWidth;
    const textWidth = Math.max(10, calculateTextWidth(DOMElement.value, DOMElement));
    DOMElement.style.width = (textWidth < boxWidth ? textWidth : boxWidth) + 'px';
    // Also adjust dropdown position
    // const dropdown = this.refs.dropdown;
    // if (DOMElement.offsetLeft + dropdown.offsetWidth > boxWidth) {
    //   dropdown.style.left = (boxWidth - dropdown.offsetWidth) + 'px';
    // } else {
    //   dropdown.style.left = DOMElement.offsetLeft + 'px';
    // }
    // dropdown.style.top = this.refs.box.offsetHeight + 'px';
  }

  clearSearch(focus = true) {
    const input = this.refs.input;
    input.value = '';
    this.setState({search: ''});
    this.calculateAutocomplete();
    this.adjustSearchInputWidth(input);
    if (focus) {
      input.focus();
    }
  }

  searchKeyDown(e) {
    const value = e.target.value;
    // Check for enter, space, double quote, or comma characters
    if (value.trim().length && (e.keyCode === 13 || e.keyCode === 188 || (e.keyCode === 32 && value.trim()[0] !== '"')
          || (e.keyCode === 222 && value.trim()[0] === '"')) ) {
      if (this.state.autocomplete.length) {
        this.addTag(this.state.autocomplete[this.state.selected]);
      } else {
        this.addTag({'type': 'keyword', 'value': value});
      }
      e.preventDefault();
      return;
    }
    // Check for backspace
    if (!value.length && e.keyCode === 8 && this.state.tags.length) {
      if (this.state.inputIndex === 0) {
        return;
      } else if (this.state.tags.length < this.state.inputIndex) {
        this.removeTag(this.state.tags.length - 1);
      } else {
        this.removeTag(this.state.inputIndex - 1);
      }
    }
    // Check for left
    if (e.keyCode === 37 && !value.length) {
      if (this.state.inputIndex > 0) {
        if (this.state.inputIndex > this.state.tags.length) {
          this.setState({inputIndex: this.state.tags.length - 1});
        } else {
          this.setState({inputIndex: this.state.inputIndex - 1});
        }
      }
    }
    // Check for up
    if (e.keyCode === 38) {
      e.preventDefault();
      if (this.state.selected > 0) {
        // 41 is height of li element...
        this.refs.dropdown.scrollTop -= 41;
        this.setState({selected: this.state.selected - 1});
      }
    }
    // Check for right
    if (e.keyCode === 39 && !value.length) {
      if (this.state.inputIndex < this.state.tags.length) {
        this.setState({inputIndex: this.state.inputIndex + 1});
      }
    }
    // Check for down
    if (e.keyCode === 40) {
      e.preventDefault();
      if (this.state.selected < (this.state.autocomplete.length - 1)) {
        this.refs.dropdown.scrollTop += 41;
        this.setState({selected: this.state.selected + 1});
      }
    }
  }

  searchChange(e) {
    const value = e.target.value.replace('"', '').trim();
    this.setState({search: value});
    // Adjust Size to match text length
    this.adjustSearchInputWidth(e.target);
    // Handle autocomplete
    if (!value.length) {
      this.calculateAutocomplete();
    } else {
      fetchJsonp('https://data.benzinga.com/rest/v2/autocomplete?query=' + value, {timeout: 1000})
        .then(req => req.json())
        .then(json => this.calculateAutocomplete(this.state.search, json.result))
        .catch(err => this.calculateAutocomplete(this.state.search));
    }
  }

  searchBlur() {
    this.clearSearch(false);
  }

  dropdownHover(i) {
    this.setState({selected: i});
  }

  tagClick(e, index) {
    e.stopPropagation();
    this.setState({inputIndex: index + 1, tagSelected: index});
    this.refs.input.focus();
  }

  removeTag(index) {
    const tagList = this.state.tags.slice();
    tagList.splice(index, 1);
    // Trigger change for tags
    this.props.onChange(tagList);
    this.setState({inputIndex: index, tagSelected: null});
    this.refs.input.focus();
  }

  addTag(dropdownItem) {
    const newTag = {value: dropdownItem.value, type: dropdownItem.type};
    // Remove tag if it already exists and move to end
    const tagList = this.state.tags.slice();
    remove(tagList, newTag);
    if (this.state.inputIndex > tagList.length + 1) {
      tagList.push(newTag);
    } else {
      tagList.splice(this.state.inputIndex, 0, newTag);
    }
    // Update states. After the queued state change is completed call onChange so that the feed's
    // filters are changed.
    this.setState({
      tags: tagList,
      inputIndex: this.state.inputIndex + 1
    }, () => this.props.onChange(this.state.tags));

    this.clearSearch();
    bzTrack.track('Feed: Searched', {
      params_added: newTag,
      total_params: tagList.length,
    });
  }

  renderTags(start, end) {
    return map(this.state.tags.slice(start, end), (v, i) =>
        (
          <span key={i} className={classnames('feed-search__tagbox')} onClick={(e) => this.tagClick(e, i + start)}>
            <span key={i} className={classnames('feed-search__tag', 'feed-search__tag--' + v.type, {selected: i + start === this.state.tagSelected})}>
              {v.value}
            </span>
          </span>
        )
    );
  }

  render() {
    const dropdownClasses = classnames('feed-search__drop',  {'feed-search__drop--expanded': this.state.autocomplete.length});
    return (
      <div className="feed-search" ref="box" onClick={(e) => this.containerClick(e)}>

        {this.renderTags(0, this.state.inputIndex)}
        <input type="text" className="feed-search__input" ref="input" onKeyDown={(e) => this.searchKeyDown(e)} onChange={(e) => this.searchChange(e)}
          onBlur={(e) => this.searchBlur()} placeholder="Search..."/>
        {this.renderTags(this.state.inputIndex)}

        <div className={dropdownClasses} ref="dropdown">
          <ul className="feed-search__drop-list" ref="dropdown">
            {map(this.state.autocomplete, (c, i) =>
              (
                <li className={classnames('feed-search__drop-item', {'feed-search__drop-item--selected': i === this.state.selected})} key={i} onMouseOver={(e) => this.dropdownHover(i)} onMouseDown={(e) => this.addTag(c)}>
                  <div className="feed-search__drop-item-head">{c.heading}</div>
                  <div className="feed-search__drop-item-desc">{c.label}</div>
                </li>
              )
            )}
          </ul>
        </div>

      </div>
    );
  }
}

FeedSearch.propTypes = {
  onChange: React.PropTypes.func,
  filters: React.PropTypes.object
};

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