import React from 'react';
import classnames from 'classnames';
import map from 'lodash/map';
import SearchSingle from './SearchSingle';
import reactMixin from 'react-mixin';
import OnClickOutside from 'react-onclickoutside';
import findIndex from 'lodash/findIndex';
import Papa from 'papaparse';

/**
 * An interface for autocompletes where you are building a list out of the
 *   completions the user selects.
 * Can handle pasting lists of tags and drag-and-dropping csv files.
 */
class SearchMulti extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputEmpty: true,
      inputIndex: 9999,
      tagSelected: null,
      isSelected: false
    };

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

  /**
   * Handles discrepancy between Immutable.List.size and Array.length.
   */
  getTagsLength() {
    if (Array.isArray(this.props.tags)) {
      return this.props.tags.length;
    }
    return this.props.tags.size();
  }

  /**
   * Handles discrepancy between Immutable.List.size and Array.length.
   */
  getTag(index) {
    if (Array.isArray(this.props.tags)) {
      return this.props.tags[index];
    }
    return this.props.tags.get(index);
  }

  handleClickOutside() {
    if (this.state.isSelected) {
      this.setState({
        isSelected: false,
        inputIndex: 9999
      });
    }
  }

  focusInput() {
    this.setState({
      isSelected: true,
      inputIndex: 9999
    });
  }

  parseCSV(parsedData) {
    let tags = [];
    let tagColumn = -1;
    for (let i = 0; i < parsedData.data.length; i++) {
      const tArr = parsedData.data[i];
      if (i === 0) {
        for (let o = 0; o < tArr.length; o++) {
          if (tArr[o].match(this.props.csvColumnHeader) !== null) {
            tagColumn = o;
            break;
          }
        }
        if (tagColumn !== -1) {
          continue;
        }
      }

      if (tagColumn !== -1) {
        tags.push(tArr[tagColumn]);
      } else {
        tags = tags.concat(tArr);
      }
    }
    tags = map(tags, (tag) => tag.trim());
    this.props.onAddMultiTag(tags, this.state.inputIndex);
  }

  handlePaste(e) {
    if (this.props.pasteAsCSV) {
      e.preventDefault();
      const clipData = e.clipboardData.getData('text');
      // Delimiters: spaces, commas, surrounded by single or double quotes.
      // const clipTags = clipData.split(/(?:\s|,|;)+|(?:'(.*?)')|(?:"(.*?)")/).filter((str) => str);
      const dat = Papa.parse(clipData).data;
      let data = [];
      for (const d of dat) {
        if (Array.isArray(d)) {
          data = data.concat(map(d, (dat) => dat.trim()));
        } else {
          data.push((d.trim()));
        }
      }
      // We need to call props.onAddMultiTag since 'd' was not taken directly
      // from props.tags, and may need to be validated.
      this.props.onAddMultiTag(data, this.state.inputIndex);
    } else if (this.props.onPaste) {
      this.props.onPaste(e);
    }
  }

  handleDrop(e) {
    e.preventDefault();
    if ('files' in e.dataTransfer) {
      for (let i = 0; i < e.dataTransfer.files.length; i++) {
        const file = e.dataTransfer.files[i];
        Papa.parse(file, {skipEmptyLines: true, complete: this.parseCSV.bind(this)});
      }
    }
  }

  handleChange(e, value) {
    if ((value.length === 0) !== this.state.inputEmpty) {
      this.setState({inputEmpty: ! this.state.inputEmpty});
    }

    if (this.props.onChange) {
      this.props.onChange(e, value);
    }
  }


  addTag(value, item) {
    const newTag = {value: item.value, type: item.type};
    const tagList = this.props.tags.slice();
    // Remove tag if it already exists and move to end
    const ind = findIndex(tagList, newTag);
    if (ind === -1) {
      this.props.onRemoveTag(newTag, ind);
    }

    if (this.state.inputIndex > tagList.length + 1) {
      this.props.onAddTag(newTag, tagList.length);
    } else {
      this.props.onAddTag(newTag, this.state.inputIndex);
    }

    this.setState({inputIndex: this.state.inputIndex + 1});
  }

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

  handleKeyDown(e) {
    const doTransparentKeyDown = function(event) {
      if (this.props.onKeyDown) {
        this.props.onKeyDown(event);
      }
    }.bind(this, e);
    const text = e.target.value;
    switch (e.key) {
    case 'ArrowLeft':
      if (!text.length && this.state.inputIndex > 0) {
        if (this.state.inputIndex > this.getTagsLength()) {
          this.setState({inputIndex: this.getTagsLength() - 1}, doTransparentKeyDown);
        } else {
          this.setState({inputIndex: this.state.inputIndex - 1}, doTransparentKeyDown);
        }
      }
      break;

    case 'ArrowRight':
      if (!text.length && this.state.inputIndex < this.getTagsLength()) {
        this.setState({inputIndex: this.state.inputIndex + 1}, doTransparentKeyDown);
      } else {
        doTransparentKeyDown();
      }
      break;

    case 'Backspace':
      const tagLoc = this.getTagsLength() - 1;
      const inpLoc = this.state.inputIndex - 1;
      if (this.state.inputIndex === 0 || text.length !== 0) {
        doTransparentKeyDown();
        return;
      } else if (tagLoc < inpLoc) {
        this.props.onRemoveTag(this.getTag(tagLoc), tagLoc);
      } else {
        this.props.onRemoveTag(this.getTag(inpLoc), inpLoc);
      }
      break;

    default:
      doTransparentKeyDown();
      break;
    }
  }

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

  renderConsolidation() {
    return (
      <span className="Search-tag">
        {this.props.tags.length} hidden
      </span>
    );
  }

  renderInput() {
    const {searchClass, csvColumnHeader, pasteAsCSV, onAddTag, onAddMultiTag, onRemoveTag, onChange, tags, multiPlaceholder, ...childProps} = this.props;
    return React.createElement(
      this.props.searchClass,
      Object.assign({className: 'Search-input'},
        childProps, {
          isSelected: this.state.isSelected,
          onSelect: (item, value) => this.addTag(value, item),
          onKeyDown: (e) => this.handleKeyDown(e),
          onDrop: (e) => this.handleDrop(e),
          onPaste: (e) => this.handlePaste(e),
          onChange: (e, value) => this.handleChange(e, value),
          placeholder: multiPlaceholder,
          dynamicSizing: true
        }
      )
    );
  }

  render() {
    const searchClasses = classnames('Search Search-multi', {'Search--hasresults': this.props.tags[0]});

    return (
      <div className={searchClasses} onClick={() => this.focusInput()}>
        {
          (this.props.consolidationLimit < 0 || this.props.tags.length < this.props.consolidationLimit) &&
            this.renderTags(0, this.state.inputIndex) ||
            this.renderConsolidation()
        }
        {this.renderInput()}
        {
          (this.props.consolidationLimit < 0 || this.props.tags.length < this.props.consolidationLimit) &&
          this.renderTags(this.state.inputIndex)
        }
      </div>
    );
  }
}
/*
 * Check Autocomplete for additional props
 */
SearchMulti.propTypes = {
  /**
   * function(object tag, int index)
   *   Fired when the user adds a tag from the completion list.
   *   tag is the object in completions the user selected.
   *   index is the index in completions.
  */
  onAddTag: React.PropTypes.func,
  /**
   * function(Array tags, int index)
   *   Fired when the user adds a list of tags (via pasting or dropping a csv file).
   *   tags is an array of strings parsef from whatever the user gave us.
   *   index is the index in completions.
  */
  onAddMultiTag: React.PropTypes.func,
  /**
   * function(object tag, int index)
   *   Fired when the user want to remove a tag from the list.
   *   tag is the object to be removed from props.tags.
   *   index is the index in completions.
  */
  onRemoveTag: React.PropTypes.func,
  /**
   * function(event event)
   *   Fired when the user pressed a key down.
   *   Added for transparency.
   *   Not fired when:
   *     Backspace is used to signify removing a tag.
   *     Enter is used to select a completion (behavior defined in Autocomplete).
   *   event is the unpooled event passed to this via React.
  */
  onKeyDown: React.PropTypes.func,
  /**
   * Any object that can be enumerated. Most likely an array or an Immutable.List.
   *   The list of tags this helps to build.
  */
  tags: React.PropTypes.any.isRequired,
  /**
   * Any object that can be enumerated. Most likely an array or an Immutable.List.
   *   The list of completions to used for autocomlete.
  */
  completions: React.PropTypes.any.isRequired,
  /**
   * function(object tag, boolean isHighlighted)
   *   Function used to render the tags in the autocomplete list.
   *   Returns a JSX component.
  */
  renderItem: React.PropTypes.func,
  /**
   * boolean, default: true
   *   Whether or not to capture things that are pasted and parse them as a CSV.
   *   If true parses whatever was pasted into a list and fires onAddMultiTag.
   *   If false it just does default behavior (allow the paste to add stuff to the input).
  */
  pasteAsCSV: React.PropTypes.bool,
  /**
   * function(event event)
   *   Fired when the user pastes something and props.pasteAsCSV is false.
   *   Added for transparency.
  */
  onPaste: React.PropTypes.func,
  /**
   * function(event event, string value)
   *   Fired when the user makes a change to the input.
   */
  onChange: React.PropTypes.func,
  /**
   * RegExp, default: match 'symbol','tag', or 'instrument' ignoring case
   *   Regex to use when looking for headers in a CSV file that signify the column
   *     containing the values we want.
  */
  csvColumnHeader: React.PropTypes.instanceOf(RegExp),
  /**
   * React Class, default: SearchSingle
   *   A class to be used as an input to this.
   *   Should have the same props/interface as SearchSingle.
   */
  searchClass: React.PropTypes.any,
  /**
   * String
   *   A string to display as placeholder text if no tags were added and the input
   *     is empty.
   */
  multiPlaceholder: React.PropTypes.string,
  consolidationLimit: React.PropTypes.number
};
SearchMulti.defaultProps = {
  multiPlaceholder: '',
  consolidationLimit: 100,
  searchClass: SearchSingle,
  csvColumnHeader: /symbol|tag|instrument/i,
  pasteAsCSV: true,
  onAddTag(data, index) {},
  onAddMultiTag(data, index) {},
  onRemoveTag(data, index) {},
  renderItem(item, isHighlighted) {
    return (
    <li className={classnames('SearchResult', {'is-selected': isHighlighted})}>
      <div className="SearchResult-head">{item.heading || item.type}</div>
      <div className="SearchResult-desc">{item.label}</div>
    </li>
    );
  }
};

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