/**
 * Frame for builder
 * @class ApBuilderFrame
 */

'use strict'

import React, {PropTypes as types} from 'react'
import classnames from 'classnames'
import {ApFrame} from 'apeman-react-frame'
import {ApSpinner} from 'apeman-react-spinner'
import uuid from 'uuid'
import equal from 'deep-equal'
import defineDebug from 'debug'
import asleep from 'asleep'

const debug = defineDebug('ap:builder:frame')

/** @lends ApBuilderFrame */
const ApBuilderFrame = React.createClass({
  // --------------------
  // Specs
  // --------------------
  propTypes: {
    /** Source string */
    src: types.string,
    /** Handler for load event */
    onLoad: types.func,
    /** Handler for script error */
    onScriptError: types.func,
    /** Global variables inside iframe */
    globals: types.object,
    /** Script to execute when loaded */
    script: types.string,
    /** Compile script before executing */
    compile: types.func,
    /** Version number */
    vr: types.number
  },

  getDefaultProps () {
    return {
      globals: {},
      compile: (script) => script,
      id: `ap-builder-frame-${uuid.v4()}`
    }
  },

  getInitialState () {
    return {
      spinning: false
    }
  },

  mixins: [],

  render () {
    const s = this
    let { state, props, id } = s
    let { spinning } = state
    return (
      <div id={ id }
           className={ classnames('ap-builder-frame', props.className) }>
        <ApSpinner enabled={ spinning }
                   theme={ props.spinner }
                   size='xx-large'
                   className='ap-builder-spinner'
        >
        </ApSpinner>
        <ApFrame id={ `${id}-frame` }
                 name={ `${id}-frame` }
                 full={ true }
                 onLoad={ s.handleLoad }
                 iframeRef={ s.registerFrame }/>
      </div>
    )
  },

  // --------------------
  // Lifecycle
  // --------------------

  componentWillReceiveProps (nextProps) {
    const s = this
    let { props, iframe } = s

    let changed = (props.src !== nextProps.src) ||
      (props.script !== nextProps.script) ||
      (props.vr !== nextProps.vr) ||
      (!equal(props.globals, nextProps.globals))

    if (changed) {
      s.loadSrc(nextProps.src)
    }
  },

  componentWillUnmount () {
    const s = this
    clearTimeout(s._loadTimer)
  },

  // --------------------
  // Custom
  // --------------------

  handleLoad (e) {
    debug('Loaded')
    const s = this
    let { props } = s
    if (props.onLoad) {
      props.onLoad(e)
    }
  },

  registerFrame (iframe) {
    const s = this
    let { props } = s
    s.iframe = iframe
    s.loadSrc(props.src)
  },

  getFrameWindow () {
    const s = this
    let { iframe } = s
    if (!iframe) {
      return null
    }
    let { contentWindow } = iframe
    if (contentWindow) {
      return contentWindow && contentWindow.window || contentWindow
    }
    let { frame } = window.frames[ `${s.getDOMId()}-frame` ]
    return frame && frame.window || frame
  },

  loadSrc (src) {
    const s = this
    clearTimeout(s._loadTimer)
    s._loadTimer = setTimeout(() => {
      debug('Loading src')
      let { props } = s
      let { iframe } = s
      if (!iframe) {
        return
      }
      let { parentNode } = iframe
      iframe.src = ''

      let tasks = [
        () => parentNode.removeChild(iframe),
        () => asleep(10), // Wait for flush
        () => parentNode.appendChild(iframe),
        () => asleep(10), // Wait for flush
        () => new Promise((resolve, reject) => {
          let window = s.getFrameWindow()
          if (!window) {
            resolve(true)
            return
          }
          let { document } = window
          let writeout = () => {
            Object.assign(window, props.globals)
            document.open()
            document.write(src)
            document.close()
            Object.assign(window, props.globals)
            resolve(true)
          }
          let completed = document.readyState === 'complete'
          if (completed) {
            writeout()
          } else {
            window.addEventListener('load', writeout)
          }
        }),
        () => asleep(10), // Wait for flush
        () => {
          let window = s.getFrameWindow()
          let { script, compile } = props
          if (window && script) {
            s.setState({ spinning: true })
            return Promise.resolve(compile(script))
          } else {
            return null
          }
        },
        (script) => {
          s.setState({ spinning: false }, () => {
            let window = s.getFrameWindow()
            if (window && script) {
              try {
                window.eval(script)
              } catch (err) {
                if (props.onScriptError) {
                  props.onScriptError(err)
                } else {
                  console.error(err)
                }
              }
            }
          })
        }
      ]
      tasks.reduce((promise, task) => promise.then(task), Promise.resolve(null))
    }, 100)
  },

  // --------------------
  // Private
  // --------------------

  _loadTimer: null

})
export default ApBuilderFrame
