'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactAddonsPureRenderMixin = require('react-addons-pure-render-mixin');

var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

var _reactCodemirror = require('react-codemirror');

var _reactCodemirror2 = _interopRequireDefault(_reactCodemirror);

var _codemirror = require('codemirror');

var _codemirror2 = _interopRequireDefault(_codemirror);

var _Rx = require('rxjs/Rx');

var _Rx2 = _interopRequireDefault(_Rx);

require('codemirror/addon/hint/show-hint');

require('codemirror/addon/hint/anyword-hint');

var _actions = require('../../actions');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

// Hint picker
const pick = (cm, handle) => handle.pick();

function goLineUpOrEmit(editor) {
  const cursor = editor.getCursor();
  if (cursor.line === 0 && cursor.ch === 0 && !editor.somethingSelected()) {
    _codemirror2.default.signal(editor, 'topBoundary');
  } else {
    editor.execCommand('goLineUp');
  }
}

function goLineDownOrEmit(editor) {
  const cursor = editor.getCursor();
  const lastLineNumber = editor.lastLine();
  const lastLine = editor.getLine(lastLineNumber);
  if (cursor.line === lastLineNumber && cursor.ch === lastLine.length && !editor.somethingSelected()) {
    _codemirror2.default.signal(editor, 'bottomBoundary');
  } else {
    editor.execCommand('goLineDown');
  }
}

_codemirror2.default.keyMap.basic.Up = 'goLineUpOrEmit';
_codemirror2.default.keyMap.basic.Down = 'goLineDownOrEmit';
_codemirror2.default.commands.goLineUpOrEmit = goLineUpOrEmit;
_codemirror2.default.commands.goLineDownOrEmit = goLineDownOrEmit;

class Editor extends _react2.default.Component {

  constructor(props) {
    super(props);
    this.shouldComponentUpdate = _reactAddonsPureRenderMixin2.default.shouldComponentUpdate.bind(this);
    this.state = {
      source: this.props.input
    };
    this.onChange = this.onChange.bind(this);

    this.hint = this.completions.bind(this);
    this.hint.async = true;

    // Remember the name of the theme that's applied so that when it changes we
    // can force codemirror to remeasure.
    this.theme = null;
  }

  componentDidMount() {
    // On first load, if focused, set codemirror to focus
    if (this.props.focused) {
      this.refs.codemirror.focus();
    }

    function formChangeObject(cm, change) {
      return {
        cm,
        change
      };
    }

    const cm = this.refs.codemirror.getCodeMirror();
    cm.on('topBoundary', this.props.focusAbove);
    cm.on('bottomBoundary', this.props.focusBelow);

    const inputEvents = _Rx2.default.Observable.fromEvent(cm, 'change', formChangeObject).filter(x => x.change.origin === '+input');

    // TODO: The subscription created here needs to be cleaned up when the cell
    //       is deleted
    inputEvents.debounceTime(20)
    // Pass through changes that aren't newlines
    .filter(event => event.change.text.length === 1 || event.change.text.length === 2 && !(event.change.text[0] === '' && event.change.text[1] === ''))
    // Pass through only partial tokens that are composed of words
    .filter(event => {
      const editor = event.cm;
      const tokenRange = editor.findWordAt(editor.getCursor());
      const token = editor.getRange(tokenRange.anchor, tokenRange.head);
      return (/(\w|\.)/.test(token)
      );
    }).subscribe(event => {
      if (!event.cm.state.completionActive) {
        event.cm.execCommand('autocomplete');
      }
    }, error => {
      // TODO: This error needs to be propagated through our store, by refactoring
      //       this entire setup to an observable (no explicit subscribes)
      console.error(error);
    });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      source: nextProps.input
    });
  }

  componentDidUpdate(prevProps) {
    if (this.props.focused && prevProps.focused !== this.props.focused) {
      this.refs.codemirror.focus();
    } else if (!this.props.focused && prevProps.focused !== this.props.focused) {
      const cm = this.refs.codemirror.getCodeMirror();
      cm.getInputField().blur();
    }

    if (this.theme !== this.props.theme) {
      this.theme = this.props.theme;
      this.refs.codemirror.getCodeMirror().refresh();
    }
  }

  onChange(text) {
    if (this.props.onChange) {
      this.props.onChange(text);
    } else {
      this.setState({
        source: text
      });
      this.context.store.dispatch((0, _actions.updateCellSource)(this.props.id, text));
    }
  }

  completions(editor, callback) {
    const cursor = editor.getCursor();
    if (this.props.getCompletions) {
      this.props.getCompletions(editor.getValue(), cursor.ch).then(results => callback({
        list: results.matches,
        from: {
          line: cursor.line,
          ch: results.cursor_start
        },
        to: {
          line: cursor.line,
          ch: results.cursor_end
        }
      }));
    }
  }

  render() {
    const options = {
      mode: this.props.language,
      lineNumbers: this.props.lineNumbers,
      lineWrapping: this.props.lineWrapping,
      theme: this.props.cmtheme,
      autofocus: false,
      hintOptions: {
        hint: this.hint,
        completeSingle: false, // In automatic autocomplete mode we don't want override
        extraKeys: {
          Right: pick
        }
      },
      extraKeys: {
        'Ctrl-Space': 'autocomplete'
      }
    };
    return _react2.default.createElement(
      'div',
      { className: 'input' },
      _react2.default.createElement(_reactCodemirror2.default, {
        value: this.state.source,
        ref: 'codemirror',
        className: 'cell_cm',
        options: options,
        onChange: this.onChange
      })
    );
  }
}
exports.default = Editor;
Editor.propTypes = {
  id: _react2.default.PropTypes.string,
  input: _react2.default.PropTypes.any,
  getCompletions: _react2.default.PropTypes.any,
  language: _react2.default.PropTypes.string,
  lineNumbers: _react2.default.PropTypes.bool,
  lineWrapping: _react2.default.PropTypes.bool,
  onChange: _react2.default.PropTypes.func,
  theme: _react2.default.PropTypes.string,
  cmtheme: _react2.default.PropTypes.string,
  focused: _react2.default.PropTypes.bool,
  focusAbove: _react2.default.PropTypes.func,
  focusBelow: _react2.default.PropTypes.func
};
Editor.contextTypes = {
  store: _react2.default.PropTypes.object
};
Editor.defaultProps = {
  language: 'python',
  lineNumbers: false,
  cmtheme: 'composition',
  focused: false
};