import React from 'react'
import PropTypes from 'prop-types'
import moment from 'moment'
import debounce from 'lodash.debounce'
import Event from 'ol/events/Event'

import TimeSliderBase from './TimeSliderBase'
import { datesDiffDay, datesSameDay } from './utils'
import { connectToContext } from 'Provider'

class SelectEvent extends Event {
  constructor (type, selected, deselected, mapBrowserEvent) {
    super(type)
    this.selected = selected
    this.deselected = deselected
    this.mapBrowserEvent = mapBrowserEvent
  }
}

/** TimeSlider component used to display/filter data with time attributes
 * @component
 * @category TimeSlider
 * @since 0.12.0
 */
class TimeSlider extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
      tabs: [],
      index: 0,
      show: true
    }

    this.moveHandler = debounce(e => this.state.show && this.setTabsFromExtent(), 100)
  }

  componentDidMount = () => {
    const { map } = this.props
    const layers = map.getLayers()

    // kicks off the process of fetching features for the current extent
    this.setTabsFromExtent()

    // bind the event listener
    this.layerListener = layers.on('change:length', this.moveHandler)
    map.on('moveend', this.moveHandler)
  }

  componentWillUnmount = () => {
    const { map } = this.props
    const layers = map.getLayers()

    // unbind the listener
    layers.unset(this.layerListener)
    map.un('moveend', this.moveHandler)
  }

  fetchFeaturesForCurrentExtent = async layer => {
    const { map } = this.props
    const extent = map.getView().calculateExtent()

    let dates = []

    if (layer.isGeoserverLayer && !!layer.getTimeAttribute()) {
      // use the geoserver methods to request intersection features -- then pull their dates off them
      const res = await layer.fetchFeaturesIntersectingExtent(extent, { featureTypes: [] })

      // we convert all dates to JS dates for easier use
      dates = res.map(f => new Date(f.get(layer.getTimeAttribute())))
    } else if (layer.get('_ol_kit_time_key')) {
      // this key must be set on a layer to enable time slider
      const source = layer.getSource()
      const featuresInExtent = source?.getFeaturesInExtent(extent) || []

      // we convert all dates to JS dates for easier use
      dates = featuresInExtent.map(f => new Date(f.get(layer.get('_ol_kit_time_key'))))
    }

    const sortedDates = dates
      .sort((a, b) => a - b) /* the sort must happen before the filter in order to remove dup dates */
      .filter((d, i, a) => datesDiffDay(a[i], a[i - 1])) /* this removes dup dates (precision is down to the day) */

    return sortedDates
  }

  setTabsFromExtent = async () => {
    const { map } = this.props
    const timeEnabledLayers = map.getLayers().getArray().filter(l => !!l.get('_ol_kit_time_key') || (l.isGeoserverLayer && !!l.getTimeAttribute()))
    const tabs = []

    await timeEnabledLayers.forEach(async layer => {
      const dates = await this.fetchFeaturesForCurrentExtent(layer)
      const tickColor = null // fetch style off layer: layer.getStyle()

      tabs.push({
        dates,
        id: layer.ol_uid,
        tickColor,
        title: layer.get('title')
      })
    })

    this.setState({ tabs })
  }

  onDatesChange = ({ id, selectedDate, selectedDateRange }) => {
    const { map, selectInteraction } = this.props
    const extent = map.getView().calculateExtent()
    const layer = map.getLayers().getArray().find(l => l.ol_uid === id)
    const source = layer?.getSource()

    if (selectedDate) {
      // select the date selected
      const deselected = selectInteraction.getFeatures().getArray()
      const features = source.getFeatures().filter(f => datesSameDay(new Date(f.get(layer.get('_ol_kit_time_key'))), selectedDate))
      const selected = [...features]
      const event = new SelectEvent('select', selected, deselected)

      // clear the previously selected feature before adding newly selected feature so only one feature is "selected" at a time
      selectInteraction.getFeatures().clear()
      features.forEach(feature => selectInteraction.getFeatures().push(feature))
      selectInteraction.dispatchEvent(event)

      if (layer.isGeoserverLayer) {
        // update the layer to reflect the time extent selected
        layer.getWMSLayer().getSource().updateParams({
          TIME: `${(new Date(selectedDate)).toISOString().split('T')[0]}/${(new Date(selectedDate)).toISOString().split('T')[0]}`
        })
      }
    } else if (selectedDateRange && selectedDateRange.length) {
      // logic for drag select
      if (layer.isGeoserverLayer) {
        layer.getWMSLayer().getSource().updateParams({
          TIME: `${(new Date(selectedDateRange[0])).toISOString().split('T')[0]}/${(new Date(selectedDateRange[1])).toISOString().split('T')[0]}`
        })
      } else {
        const allFeatures = source.getFeaturesInExtent(extent)
        const features = allFeatures.filter(f => moment(new Date(f.get(layer.get('_ol_kit_time_key')))).isBetween(selectedDateRange[0], selectedDateRange[1]))
        const deselected = selectInteraction.getFeatures().getArray()
        const selected = [...features]
        const event = new SelectEvent('select', selected, deselected)

        // clear the previously selected feature before adding newly selected feature so only one feature is "selected" at a time
        selectInteraction.getFeatures().clear()
        features.forEach(feature => selectInteraction.getFeatures().push(feature))
        selectInteraction.dispatchEvent(event)
      }
    } else if (!selectedDate) {
      // reset filter

      // clear select
      selectInteraction.getFeatures().clear()

      if (layer.isGeoserverLayer) {
        // update the layer to reflect the time extent selected
        layer.getWMSLayer().getSource().updateParams({ TIME: null })
        // refresh the layer source to update the map
        layer.getWMSLayer().getSource().refresh()
      }
    }
  }

  onClose = () => {
    this.setState({ show: false })
    this.props.onClose()
  }

  render () {
    const { show: propShow } = this.props
    const { show: stateShow, tabs } = this.state
    const show = typeof propShow === 'boolean' ? propShow : stateShow // keep show prop as source of truth over state

    return (
      !show
        ? null
        : (
          <TimeSliderBase
            onClose={this.onClose}
            onDatesChange={this.onDatesChange}
            tabs={tabs} />
        )
    )
  }
}

TimeSlider.defaultProps = {
  onClose: () => {},
  show: undefined
}

TimeSlider.propTypes = {
  /** callback fired when TimeSlider 'x' is clicked */
  onClose: PropTypes.func,
  /** a reference to openlayers map object */
  map: PropTypes.object.isRequired,
  /** reference to openlayers select interaction */
  selectInteraction: PropTypes.object,
  /** boolean that is respected over internal state */
  show: PropTypes.bool
}

export default connectToContext(TimeSlider)