/* eslint no-use-before-define: 0 */

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import stringifyObject from 'stringify-object';
import _ from 'lodash';

import style from './style';
import { renderIf, limitEval } from '../../helpers';

class TestRunner extends Component {
  constructor(props) {
    super(props);
    this.getIframeDoc = this.getIframeDoc.bind(this);
    this.runTests = this.runTests.bind(this);
    this.handleStartClick = this.handleStartClick.bind(this);
    this.handleInfiniteLoop = this.handleInfiniteLoop.bind(this);
    this.state = { consoleLogs: [], autoRunEnabled: props.autoRun, hasRun: false };
  }

  componentWillReceiveProps(nextProps) {
    if ((this.props.codeToTest !== nextProps.codeToTest) && this.state.autoRunEnabled) {
      limitEval(nextProps.codeToTest, (success) => {
        if (success) {
          this.setState({ consoleLogs: [] });
          this.iframe.onload = this.runTests.bind(this, nextProps);
          // Need to clear Mocha tests
          this.iframe.contentWindow.location.reload();
        } else {
          this.handleInfiniteLoop();
        }
      }, nextProps.infiniteLoopTimeout);
    }
  }

  componentWillUnmount() {
    ReactDOM.unmountComponentAtNode(this.iframe);
  }

  handleInfiniteLoop() {
    this.props.onError({ message: 'Your Code is taking too long to run. Maybe you wrote an infinite loop.' });
    this.setState({ autoRunEnabled: false });
  }

  toggleTestRunner(callback) {
    this.setState(({ autoRunEnabled: wasEnabled }) => {
      return { autoRunEnabled: !wasEnabled, hasRun: true };
    }, callback);
  }

  getIframeDoc() {
    if (!this.iframe) return;
    const iFrame = this.iframe;
    let iFrameDoc = iFrame.contentDocument;
    if (!iFrameDoc && iFrame.contentWindow) {
      iFrameDoc = iFrame.contentWindow.document;
    }
    return iFrameDoc;
  }

  runTests({ codeToTest, testSpec }) {
    const { testHandlerUrl } = this.props;
    // Set up test results handler
    this.iframe.contentWindow.testHandler = this.props.onTestResults;
    // Set up console scrapper
    const nativeLog = this.iframe.contentWindow.console.log;
    const nativeErrorLog = this.iframe.contentWindow.console.error;

    this.iframe.contentWindow.console.log = (...args) => {
      const log = args.reduce((log, arg) =>
        `${log}\n${stringifyObject(arg)}`,
      '');
      this.setState({
        consoleLogs: this.state.consoleLogs.concat(log)
      }, () => {
        this.props.onConsoleLog(this.state.consoleLogs);
      });
      nativeLog.apply(this.iframe.contentWindow.console, args);
    }

    const doc = this.getIframeDoc();
    // Remove previous <body> elements
    while (doc.body.firstChild) {
      doc.body.removeChild(doc.body.firstChild);
    }
    const scripts = [
      // { type: 'script', content: codeToTest, url: 'https://cdnjs.cloudflare.com/ajax/libs/mocha/3.4.2/mocha.js' },
      // { type: 'script', content: codeToTest, url: 'https://cdnjs.cloudflare.com/ajax/libs/chai/4.0.1/chai.min.js' },
      // { type: 'script', content: 'mocha.setup("bdd")', url: '' },
      { type: 'script', content: codeToTest, url: '' },
      { type: 'script', content: testSpec, url: '' },
      // Append script to handle Mocha results
      { type: 'script', url: testHandlerUrl },
    ];
    const mochaDiv = document.createElement('div');
    mochaDiv.id = 'mocha';
    doc.body.appendChild(mochaDiv);
    scripts.forEach((el) => {
      const domElement = document.createElement(el.type);
      if (el.type === 'script' && el.url.length) {
        domElement.src = el.url;
      } else {
        domElement.innerHTML = el.content;
      }
      doc.body.appendChild(domElement);
    });
  }

  handleStartClick() {
    this.toggleTestRunner(() => {
      if (this.state.autoRunEnabled) {
        limitEval(this.props.codeToTest, (success) => {
        if (success) {
          this.setState({ consoleLogs: [] });
          this.iframe.onload = this.runTests.bind(this, this.props);
          // Need to clear Mocha tests
          this.iframe.contentWindow.location.reload();
        } else {
          this.handleInfiniteLoop();
        }
      }, this.props.infiniteLoopTimeout);
      }
    });
  }

  render() {
    const { autoRunEnabled, hasRun } = this.state;
    const { testRunnerContainer, testRunnerInstructions, testRunnerOverlay } = style;
    return (
      <div className="test-runner-container" style={testRunnerContainer}>
        <button onClick={this.handleStartClick} style={testRunnerOverlay}>{autoRunEnabled ? 'Stop' : 'Start'}</button>
        <iframe
          title="test-runner"
          src={this.props.iFrameUrl}
          ref={(iframe) => { this.iframe = iframe; }}
          style={style.iframe}
          frameBorder="0"
          id="iframe"
        />
        { renderIf(!autoRunEnabled && !hasRun)(
            <div className="text-center" style={testRunnerInstructions}>
              Click "Start" to run the tests.
            </div>
          )
        }
      </div>
    );
  }
}

TestRunner.propTypes = {
  autoRun: PropTypes.bool.isRequired,
  testSpec: PropTypes.string.isRequired,
  codeToTest: PropTypes.string,
  onTestResults: PropTypes.func,
  iFrameUrl: PropTypes.string.isRequired,
  testHandlerUrl: PropTypes.string.isRequired,
  onError: PropTypes.func,
  infiniteLoopTimeout: PropTypes.number,
};


TestRunner.defaultProps = {
  scriptUrl: '',
  codeToTest: '',
  onTestResults: (passCount, failCount, testDuration) => {},
  testHandlerUrl: 'get_results.js',
  onError: (err) => {
    const message = _.get(err, 'message') ? err.message : 'Unknown';
    console.error('There was an error running your tests: ', message);
  },
};

export default TestRunner;
