'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ConnectedNotebook = exports.Notebook = undefined;

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactDom = require('react-dom');

var _reactDom2 = _interopRequireDefault(_reactDom);

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

var _reactAddonsPureRenderMixin2 = _interopRequireDefault(_reactAddonsPureRenderMixin);

var _reactDnd = require('react-dnd');

var _reactDndHtml5Backend = require('react-dnd-html5-backend');

var _reactDndHtml5Backend2 = _interopRequireDefault(_reactDndHtml5Backend);

var _reactRedux = require('react-redux');

var _immutable = require('immutable');

var _immutable2 = _interopRequireDefault(_immutable);

var _transforms = require('./transforms');

var _cell = require('./cell/cell');

var _cell2 = _interopRequireDefault(_cell);

var _draggableCell = require('./cell/draggable-cell');

var _draggableCell2 = _interopRequireDefault(_draggableCell);

var _cellCreator = require('./cell/cell-creator');

var _cellCreator2 = _interopRequireDefault(_cellCreator);

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

var _execute = require('../epics/execute');

var _completion = require('../kernel/completion');

var _completion2 = _interopRequireDefault(_completion);

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

// Always set up the markdown mode
require('codemirror/mode/markdown/markdown');

const mapStateToProps = state => ({
  theme: state.app.theme,
  notebook: state.document.get('notebook'),
  channels: state.app.channels,
  cellPagers: state.document.get('cellPagers'),
  focusedCell: state.document.get('focusedCell'),
  cellStatuses: state.document.get('cellStatuses'),
  stickyCells: state.document.get('stickyCells')
});

class Notebook extends _react2.default.Component {

  constructor() {
    super();
    this.shouldComponentUpdate = _reactAddonsPureRenderMixin2.default.shouldComponentUpdate.bind(this);
    this.languageCache = {};
    this.createCellElement = this.createCellElement.bind(this);
    this.createStickyCellElement = this.createStickyCellElement.bind(this);
    this.keyDown = this.keyDown.bind(this);
    this.moveCell = this.moveCell.bind(this);
    this.getCompletions = this.getCompletions.bind(this);
  }

  componentDidMount() {
    document.addEventListener('keydown', this.keyDown);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.focusedCell !== this.props.focusedCell) {
      this.resolveScrollPosition(nextProps.focusedCell);
    }
  }

  componentDidUpdate() {
    // Make sure the document is vertically shifted so the top non-stickied
    // cell is always visible.
    const placeholder = _reactDom2.default.findDOMNode(this.refs['sticky-cells-placeholder']);
    const container = _reactDom2.default.findDOMNode(this.refs['sticky-cell-container']);
    placeholder.style.height = `${ container.clientHeight }px`;
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.keyDown);
  }

  getCompletions(source, cursor) {
    return (0, _completion2.default)(this.props.channels, source, cursor);
  }

  getLanguageMode() {
    // The syntax highlighting language should be set in the language info
    // object.  First try codemirror_mode, then name, and fallback on 'null'.
    let language = this.props.notebook.getIn(['metadata', 'language_info', 'codemirror_mode', 'name'], this.props.notebook.getIn(['metadata', 'language_info', 'codemirror_mode'], this.props.notebook.getIn(['metadata', 'language_info', 'name'], 'text')));

    // TODO: Load the ipython codemirror mode from somewhere
    if (language === 'ipython') {
      language = 'python';
    }

    if (language !== 'text' && !this.languageCache[language]) {
      this.languageCache[language] = true;

      // HACK: This should give you the heeby-jeebies
      // Mostly because language could be ../../../../whatever
      // This is the notebook though, so hands off
      // We'll want to check for this existing later
      // and any other validation
      require(`codemirror/mode/${ language }/${ language }`); // eslint-disable-line global-require
    }
    return language;
  }

  moveCell(sourceId, destinationId, above) {
    this.context.store.dispatch((0, _actions.moveCell)(sourceId, destinationId, above));
  }

  keyDown(e) {
    if (e.keyCode !== 13) {
      return;
    }

    const shiftXORctrl = (e.shiftKey || e.ctrlKey) && !(e.shiftKey && e.ctrlKey);
    if (!shiftXORctrl) {
      return;
    }

    if (!this.props.focusedCell) {
      return;
    }

    e.preventDefault();

    const cellMap = this.props.notebook.get('cellMap');
    const id = this.props.focusedCell;
    const cell = cellMap.get(id);

    if (e.shiftKey) {
      this.context.store.dispatch((0, _actions.focusNextCell)(this.props.focusedCell, true));
    }

    if (cell.get('cell_type') === 'code') {
      this.context.store.dispatch((0, _execute.executeCell)(id, cell.get('source')));
    }
  }

  resolveScrollPosition(id) {
    const viewportHeight = window.innerHeight;
    const viewportOffset = document.body.scrollTop;

    const focusedCell = _reactDom2.default.findDOMNode(this.refs[id]);

    if (focusedCell) {
      const cellTop = focusedCell.offsetTop;
      const cellHeight = focusedCell.offsetHeight;

      const belowFold = cellTop + cellHeight > viewportOffset + viewportHeight;
      const aboveFold = cellTop < viewportOffset;

      if (aboveFold) {
        document.body.scrollTop = cellTop;
      }

      if (belowFold) {
        if (cellHeight > viewportHeight) {
          document.body.scrollTop = cellTop;
        } else {
          const offset = viewportHeight - cellHeight;
          document.body.scrollTop = cellTop - offset;
        }
      }
    }
  }

  createCellProps(id, cell) {
    return {
      id,
      cell,
      language: this.getLanguageMode(),
      getCompletions: this.getCompletions,
      key: id,
      ref: id,
      displayOrder: this.props.displayOrder,
      cellStatus: this.props.cellStatuses.get(id),
      transforms: this.props.transforms,
      moveCell: this.moveCell,
      pagers: this.props.cellPagers.get(id),
      focusedCell: this.props.focusedCell,
      running: this.props.cellStatuses.getIn([id, 'status']) === 'busy',
      // Theme is passed through to let the Editor component know when to
      // tell CodeMirror to remeasure
      theme: this.props.theme
    };
  }

  createCellElement(id) {
    const cellMap = this.props.notebook.get('cellMap');
    const cell = cellMap.get(id);
    const isStickied = this.props.stickyCells.get(id);

    const CellComponent = this.props.CellComponent;

    return _react2.default.createElement(
      'div',
      { key: `cell-container-${ id }`, ref: 'container' },
      isStickied ? _react2.default.createElement(
        'div',
        { className: 'cell-placeholder' },
        _react2.default.createElement('span', { className: 'octicon octicon-link-external' })
      ) : _react2.default.createElement(CellComponent, this.createCellProps(id, cell)),
      _react2.default.createElement(_cellCreator2.default, { key: `creator-${ id }`, id: id, above: false })
    );
  }

  createStickyCellElement(id) {
    const cellMap = this.props.notebook.get('cellMap');
    const cell = cellMap.get(id);
    return _react2.default.createElement(
      'div',
      { key: `cell-container-${ id }`, ref: 'container' },
      _react2.default.createElement(_cell2.default, this.createCellProps(id, cell))
    );
  }

  render() {
    if (!this.props.notebook) {
      return _react2.default.createElement('div', { className: 'notebook' });
    }
    const cellOrder = this.props.notebook.get('cellOrder');
    return _react2.default.createElement(
      'div',
      null,
      _react2.default.createElement(
        'div',
        { className: 'notebook', ref: 'cells' },
        _react2.default.createElement('div', { className: 'sticky-cells-placeholder', ref: 'sticky-cells-placeholder' }),
        _react2.default.createElement(
          'div',
          { className: 'sticky-cell-container', ref: 'sticky-cell-container' },
          cellOrder.filter(id => this.props.stickyCells.get(id)).map(this.createStickyCellElement)
        ),
        _react2.default.createElement(_cellCreator2.default, { id: cellOrder.get(0, null), above: true }),
        cellOrder.map(this.createCellElement)
      ),
      _react2.default.createElement('link', { rel: 'stylesheet', href: `../static/styles/theme-${ this.props.theme }.css` })
    );
  }
}

exports.Notebook = Notebook;
Notebook.propTypes = {
  channels: _react2.default.PropTypes.any,
  displayOrder: _react2.default.PropTypes.instanceOf(_immutable2.default.List),
  notebook: _react2.default.PropTypes.any,
  transforms: _react2.default.PropTypes.instanceOf(_immutable2.default.Map),
  cellPagers: _react2.default.PropTypes.instanceOf(_immutable2.default.Map),
  cellStatuses: _react2.default.PropTypes.instanceOf(_immutable2.default.Map),
  stickyCells: _react2.default.PropTypes.instanceOf(_immutable2.default.Map),
  focusedCell: _react2.default.PropTypes.string,
  theme: _react2.default.PropTypes.string,
  CellComponent: _react2.default.PropTypes.any
};
Notebook.defaultProps = {
  displayOrder: _transforms.defaultDisplayOrder,
  transforms: _transforms.defaultTransforms,
  CellComponent: _draggableCell2.default
};
Notebook.contextTypes = {
  store: _react2.default.PropTypes.object
};
const ConnectedNotebook = exports.ConnectedNotebook = (0, _reactDnd.DragDropContext)(_reactDndHtml5Backend2.default)(Notebook);
exports.default = (0, _reactRedux.connect)(mapStateToProps)(ConnectedNotebook);