React = require 'react'
_ = require "underscore"

###
Public: EventedIFrame is a thin wrapper around the DOM's standard `<iframe>` element.
You should always use EventedIFrame, because it provides important event hooks that
ensure keyboard and mouse events are properly delivered to the application when
fired within iFrames.

```
<div className="file-frame-container">
  <EventedIFrame src={src} />
  <Spinner visible={!@state.ready} />
</div>
```

Any `props` added to the <EventedIFrame> are passed to the iFrame it renders.

Section: Component Kit
###
class EventedIFrame extends React.Component
  @displayName = 'EventedIFrame'

  render: =>
    <iframe seamless="seamless" {...@props} />

  componentDidMount: =>
    @_subscribeToIFrameEvents()

  componentWillUnmount: =>
    @_unsubscribeFromIFrameEvents()

  ###
  Public: Call this method if you replace the contents of the iframe's document.
  This allows {EventedIframe} to re-attach it's event listeners.
  ###
  documentWasReplaced: =>
    @_unsubscribeFromIFrameEvents()
    @_subscribeToIFrameEvents()

  _unsubscribeFromIFrameEvents: =>
    node = React.findDOMNode(@)
    doc = node.contentDocument
    return unless doc
    doc.removeEventListener('click', @_onIFrameClick)
    doc.removeEventListener('keydown', @_onIFrameKeydown)
    doc.removeEventListener('mousedown', @_onIFrameMouseEvent)
    doc.removeEventListener('mousemove', @_onIFrameMouseEvent)
    doc.removeEventListener('mouseup', @_onIFrameMouseEvent)
    doc.removeEventListener("contextmenu", @_onIFrameContextualMenu)
    if node.contentWindow
      node.contentWindow.removeEventListener('focus', @_onIFrameFocus)
      node.contentWindow.removeEventListener('blur', @_onIFrameBlur)

  _subscribeToIFrameEvents: =>
    node = React.findDOMNode(@)
    doc = node.contentDocument
    _.defer =>
      doc.addEventListener("click", @_onIFrameClick)
      doc.addEventListener("keydown", @_onIFrameKeydown)
      doc.addEventListener("mousedown", @_onIFrameMouseEvent)
      doc.addEventListener("mousemove", @_onIFrameMouseEvent)
      doc.addEventListener("mouseup", @_onIFrameMouseEvent)
      doc.addEventListener("contextmenu", @_onIFrameContextualMenu)
      if node.contentWindow
        node.contentWindow.addEventListener("focus", @_onIFrameFocus)
        node.contentWindow.addEventListener("blur", @_onIFrameBlur)

  _getContainingTarget: (event, options) =>
    target = event.target
    while target? and (target isnt document) and (target isnt window)
      return target if target.getAttribute(options.with)?
      target = target.parentElement
    return null

  _onIFrameBlur: (event) =>
    node = React.findDOMNode(@)
    node.contentWindow.getSelection().empty()

  _onIFrameFocus: (event) =>
    window.getSelection().empty()

  # The iFrame captures events that take place over it, which causes some
  # interesting behaviors. For example, when you drag and release over the
  # iFrame, the mouseup never fires in the parent window.

  _onIFrameClick: (e) =>
    e.stopPropagation()
    target = @_getContainingTarget(e, {with: 'href'})
    if target
      e.preventDefault()
      atom.windowEventHandler.openLink(target: target)

  _onIFrameMouseEvent: (event) =>
    node = React.findDOMNode(@)
    nodeRect = node.getBoundingClientRect()
    node.dispatchEvent(new MouseEvent(event.type, _.extend({}, event, {
      clientX: event.clientX + nodeRect.left
      clientY: event.clientY + nodeRect.top
      pageX: event.pageX + nodeRect.left
      pageY: event.pageY + nodeRect.top
    })))

  _onIFrameKeydown: (event) =>
    return if event.metaKey or event.altKey or event.ctrlKey
    React.findDOMNode(@).dispatchEvent(new KeyboardEvent(event.type, event))

  _onIFrameContextualMenu: (event) =>
    # Build a standard-looking contextual menu with options like "Copy Link",
    # "Copy Image" and "Search Google for 'Bla'"
    event.preventDefault()

    remote = require('remote')
    clipboard = require('clipboard')
    Menu = remote.require('menu')
    MenuItem = remote.require('menu-item')
    NativeImage = require('native-image')
    shell = require('shell')
    path = require('path')
    fs = require('fs')
    menu = new Menu()

    # Menu actions for links
    linkTarget = @_getContainingTarget(event, {with: 'href'})
    if linkTarget
      href = linkTarget.getAttribute('href')
      menu.append(new MenuItem({ label: "Open Link", click:( -> atom.windowEventHandler.openLink({href}) )}))
      menu.append(new MenuItem({ label: "Copy Link", click:( -> clipboard.writeText(href) )}))
      menu.append(new MenuItem({ type: 'separator' }))

    # Menu actions for images
    imageTarget = @_getContainingTarget(event, {with: 'src'})
    if imageTarget
      src = imageTarget.getAttribute('src')
      srcFilename = path.basename(src)
      menu.append(new MenuItem({
        label: "Save Image...",
        click: ->
          atom.showSaveDialog srcFilename, (path) ->
            return unless path
            oReq = new XMLHttpRequest()
            oReq.open("GET", src, true)
            oReq.responseType = "arraybuffer"
            oReq.onload = ->
              buffer = new Buffer(new Uint8Array(oReq.response))
              fs.writeFile path, buffer, (err) ->
                shell.showItemInFolder(path)
            oReq.send()
      }))
      menu.append(new MenuItem({
        label: "Copy Image",
        click: ->
          img = new Image()
          img.addEventListener("load", ->
            canvas = document.createElement("canvas")
            canvas.width = img.width
            canvas.height = img.height
            canvas.getContext("2d").drawImage(imageTarget, 0, 0)
            imageDataURL = canvas.toDataURL("image/png")
            img = NativeImage.createFromDataUrl(imageDataURL)
            clipboard.writeImage(img)
          , false)
          img.src = src
      }))
      menu.append(new MenuItem({ type: 'separator' }))

    # Menu actions for text
    text = ""
    selection = React.findDOMNode(@).contentDocument.getSelection()
    if selection.rangeCount > 0
      range = selection.getRangeAt(0)
      text = range.toString()
    if not text or text.length is 0
      text = (linkTarget ? event.target).innerText
    text = text.trim()

    if text.length > 0
      if text.length > 45
        textPreview = text.substr(0, 42) + "..."
      else
        textPreview = text
      menu.append(new MenuItem({ label: "Copy", click:( -> clipboard.writeText(text) )}))
      menu.append(new MenuItem({ label: "Search Google for '#{textPreview}'", click:( -> shell.openExternal("https://www.google.com/search?q=#{encodeURIComponent(text)}") )}))
      if process.platform is 'darwin'
        menu.append(new MenuItem({ label: "Look Up '#{textPreview}'", click:( -> atom.getCurrentWindow().showDefinitionForSelection() )}))


    if process.platform is 'darwin'
      menu.append(new MenuItem({ type: 'separator' }))
      # Services menu appears here automatically

    menu.popup(remote.getCurrentWindow())

module.exports = EventedIFrame
