// @flow
import { dim } from './spatial'

type TZoomHandlersArgs = {
  d3: Object,
  d3Root: Object,
  root: Object,
  store: Object,
  yAxisOffset: number,
  xAxisOffset: number,
  xAxisLength: number,
  domainWidth: number,
  scale: number,
  svg: Object,
  height: number,
  proteinHeight: number,
  uniqueSelector: string,
  draw: Function,
}
type TZoomHandlersReturn = {
  removeZoomHandlers: Function
}
type TZoomHandlers = (args: TZoomHandlersArgs) => TZoomHandlersReturn
let zoomHandlers: TZoomHandlers = ({
  d3,
  d3Root,
  root,
  store,
  yAxisOffset,
  xAxisOffset,
  xAxisLength,
  domainWidth,
  scale,
  svg,
  height,
  proteinHeight,
  uniqueSelector,
  draw,
} = {}) => {

  let updateTargetChartZoom = ({ zoomX, zoomWidth, offsetX, difference }) => {
    let { min, max } = store.getState()
    let draggingLeft = difference < 0

    let scale = d3.scaleLinear()
      .domain([0, xAxisLength])
      .range([min, max])

    let targetMin = Math.max(
      0,
      (draggingLeft ? scale(offsetX - yAxisOffset) : scale(zoomX - yAxisOffset))
    )

    let targetMax = Math.min(
      domainWidth,
      (draggingLeft ? scale(offsetX + zoomWidth - yAxisOffset) : scale(offsetX - yAxisOffset))
    )

    return [targetMin, targetMax]
  }

  let updateTargetMinimapZoom = ({ zoomX, zoomWidth, offsetX, difference }) => {
    let draggingLeft = difference < 0

    let targetMin = Math.max(
      0,
      (draggingLeft ? (offsetX - yAxisOffset) / scale : (zoomX - yAxisOffset) / scale)
    )

    let targetMax = Math.min(
      domainWidth,
      (draggingLeft ? (offsetX - yAxisOffset + zoomWidth) / scale : (offsetX - yAxisOffset) / scale)
    )

    return [targetMin, targetMax]
  }

  let minimap = root.querySelector(`.minimap`)
  let chart = root.querySelector(`.chart`)
  let chartZoomArea = root.querySelector(`.chart-zoom-area`)
  let slideTarget = root.querySelector(`.minimap-slide-target`)

  let initDrag = ({ selector, y, height, fill }) => (event: Event) => {
    store.update({
      dragging: true,
      zoomStart: event.offsetX,
    })

    svg
      .append(`g`)
      .append(`rect`)
      .attrs({
        class: `${selector}-zoom`,
        'clip-path': `url(#${uniqueSelector}-${selector}-clip)`,
        x: event.offsetX,
        y,
        ...dim(0, height),
        fill,
        cursor: `text`,
        'pointer-events': `none`,
      })
  }

  let minimapMousedownHandler = initDrag({
    selector: `minimap`,
    y: height - xAxisOffset + proteinHeight + 20,
    height: 50,
    fill: `rgba(83, 215, 88, 0.51)`,
  })

  let zoomAreaMousedownHandler = initDrag({
    selector: `chart`,
    y: 0,
    height: height - xAxisOffset,
    fill: `rgba(214, 214, 214, 0.51)`,
  })

  let chartMouseupHandler = (event: Event) => {
    let { dragging, zoomStart, sliding } = store.getState()

    if (sliding) store.update({ sliding: false })

    if (dragging) {
      let difference = event.offsetX - zoomStart

      // do not zoom if insignificant dragging distance
      if (Math.abs(difference) < 5) {
        d3.select(`.minimap-zoom`).remove()
        d3.select(`.chart-zoom`).remove()
        store.update({ dragging: false })
        return
      }

      let zoom = d3Root.select(`.minimap-zoom`)

      if (zoom.empty()) {
        zoom = d3Root.select(`.chart-zoom`)

        let [targetMin, targetMax] = updateTargetChartZoom({
          zoomX: +zoom.attr(`x`),
          zoomWidth: +zoom.attr(`width`),
          offsetX: event.offsetX, difference,
        })

        store.update({ targetMin, targetMax })
      } else {
        let [targetMin, targetMax] = updateTargetMinimapZoom({
          zoomX: +zoom.attr(`x`),
          zoomWidth: +zoom.attr(`width`),
          offsetX: event.offsetX, difference,
        })

        store.update({ targetMin, targetMax })
      }

      // at least one coordinate zoom
      let { targetMin, targetMax } = store.getState()
      if (targetMin === targetMax) store.update({ targetMax: targetMax + 1 })

      store.update({ animating: true, dragging: false })
      draw()
      zoom.remove()
    }
  }

  minimap.addEventListener(`mousedown`, minimapMousedownHandler)
  chartZoomArea.addEventListener(`mousedown`, zoomAreaMousedownHandler)
  chart.addEventListener(`mouseup`, chartMouseupHandler)

  let dragMouse = (selector: string) => (event: Event) => {
    let {
      dragging,
      zoomStart,
      sliding,
      slideStart,
      slideStartMin,
      slideStartMax,
    } = store.getState()

    if (sliding) {
      store.update({
        animating: true,
        targetMin: Math.max(0, slideStartMin + Math.round((event.offsetX - slideStart) / scale)),
        targetMax: Math.min(
          domainWidth,
          slideStartMax + Math.round((event.offsetX - slideStart) / scale)
        ),
      })
      draw()
    }

    if (dragging) {
      let difference = event.offsetX - zoomStart
      let zoom = d3.select(selector)

      zoom.attr(`width`, Math.abs(difference))

      if (difference < 0) {
        zoom.attr(`x`, event.offsetX)
      }
    }
  }

  let handleSlideTargetMousedown = (event: Event) => {
    let { min, max } = store.getState()
    store.update({
      sliding: true,
      slideStart: event.offsetX,
      slideStartMin: min,
      slideStartMax: max,
    })
  }

  slideTarget.addEventListener(`mousedown`, handleSlideTargetMousedown)

  let chartDragHandler = dragMouse(`.minimap-zoom`)
  let zoomAreaDragHandler = dragMouse(`.chart-zoom`)
  chart.addEventListener(`mousemove`, chartDragHandler)
  chartZoomArea.addEventListener(`mousemove`, zoomAreaDragHandler)

  let removeZoomHandlers = () => {
    slideTarget.removeEventListener(`mousedown`, handleSlideTargetMousedown)
    chart.removeEventListener(`mousemove`, chartDragHandler)
    chartZoomArea.removeEventListener(`mousemove`, zoomAreaDragHandler)
    minimap.removeEventListener(`mousedown`, minimapMousedownHandler)
    chartZoomArea.removeEventListener(`mousedown`, zoomAreaMousedownHandler)
    chart.removeEventListener(`mouseup`, chartMouseupHandler)
  }

  return { removeZoomHandlers }
}

/*----------------------------------------------------------------------------*/

export default zoomHandlers
