import nanoid from 'nanoid'
import olVectorTile from 'ol/layer/VectorTile'
import OpenLayersParser from 'geostyler-openlayers-parser'
import olStyleStyle from 'ol/style/Style'
import olStyleFill from 'ol/style/Fill'
import olStyleStroke from 'ol/style/Stroke'
import olStyleCircle from 'ol/style/Circle'
import olGeomPoint from 'ol/geom/Point'
import olGeomLinestring from 'ol/geom/LineString'
import olGeomMultiPoint from 'ol/geom/MultiPoint'
import olGeomMultiLinestring from 'ol/geom/MultiLineString'

/**
 * VectorTileLayer class extends olLayerVector {@link https://openlayers.org/en/latest/apidoc/module-ol_layer_Vector-VectorTileLayer.html}
 * @function
 * @category Classes
 * @since 1.4.0
 * @param {Object} [opts] - Object of optional params for olLayerVector
 */
class VectorTileLayer extends olVectorTile {
  constructor (opts) {
    if (!opts?.className) opts.className = `_ol_kit_vector_layer_${nanoid()}`
    super(opts)

    this.parser = new OpenLayersParser()
    this.userStyles = []
    this.defaultStyles = []
    this._defaultStylesCache = []
    this.isVectorTileLayer = true
    if (!opts?.style) this._setInitialStyle()
    this.setDefaultVectorStyles()

    return this
  }

  /**
   * A function that returns the VectorTileLayers attributes
   * @function
   * @since 1.4.0
   * @returns {Array} VectorTileLayer attributes
   */
  getAttributes () {
    // Vector tile layers don't have the convention that all features have to share the same attributes so we have to get all of the features and summarize their attributes.
    return this.getSource()
      .getFeaturesInExtent([-Infinity, -Infinity, Infinity, Infinity]) // get all available features
      .reduce((attributes, feature) => {
        return [...new Set([...attributes, ...Object.keys(feature.getProperties())]).values()]
      }, [])
  }

  /**
   * A function that returns the VectorTileLayers values of a specific attribute
   * @function
   * @since 1.4.0
   * @param {String} - olFeature property
   * @returns {Array} VectorTileLayer values of a specific attribute
   */
  fetchValuesForAttribute (attribute) {
    const dupes = this.getSource().getFeaturesInExtent([-Infinity, -Infinity, Infinity, Infinity]).map(feature => feature.getProperties()[`${attribute}`])

    return [...new Set(dupes)]
  }

  /**
   * A function that sets custom styles from ManageLayer
   * @function
   * @since 1.4.0
   * @param {Object} - Geostyler OpenLayers Parser rules object {@link https://github.com/geostyler/geostyler-openlayers-parser}
   */
  setUserVectorStyles (styles) {
    this.userStyles = styles

    this._applyVectorStyles()
  }

  /**
   * A function that returns the VectorTileLayers custom user styles
   * @function
   * @since 1.4.0
   * @returns {Object} Geostyler rules object
   */
  getUserVectorStyles () {
    return this.userStyles
  }

  /**
   * A function that sets default styles of a VectorTileLayer
   * @function
   * @since 1.4.0
   */
  setDefaultVectorStyles () {
    return this.parser.readStyle(this.getStyle()()).then(style => {
      this._defaultStylesCache = style.rules
      this.defaultStyles = style.rules
    })
  }

  /**
   * A function that returns the VectorTileLayers default VectorTileLayer styles
   * @function
   * @since 1.4.0
   * @returns {Object} Geostyler rules object
   */
  getDefaultVectorStyles () {
    return this.defaultStyles
  }

  /**
   * A function that updates default styles of a VectorTileLayer
   * @function
   * @since 1.4.0
   * @param {Object} - Geostyler OpenLayers Parser rules object {@link https://github.com/geostyler/geostyler-openlayers-parser}
   */
  updateDefaultVectorStyles (styles) {
    this.defaultStyles = styles

    this._applyVectorStyles()
  }

  /**
   * A function that resets default styles of a VectorTileLayer back to its original default
   * @function
   * @since 1.4.0
   */
  resetDefaultVectorStyles () {
    this.defaultStyles = this._defaultStylesCache

    this._applyVectorStyles()
  }

  _applyVectorStyles () {
    const filteredUserStyles = this.getUserVectorStyles().filter(style => {
      // do a safe check for the filter key
      if (!Array.isArray(style.filter)) return true
      const attributeValue = style.filter[1][2]

      return attributeValue !== ''
    })

    const style = {
      name: this.get('title') || 'Custom Vector Style',
      rules: [
        ...this.getDefaultVectorStyles(),
        ...filteredUserStyles
      ]
    }

    return this.parser
      .writeStyle(style)
      .then(olStyle => {
        // update the sld_body which is what geoserver uses to style the layer
        this.setStyle(olStyle)
      })
  }

  _setInitialStyle () {
    let style = {}
    const hasFeatures = this.getSource().getFeaturesInExtent([0, 0, Infinity, Infinity]).length
    const geomType = hasFeatures ? this.getSource()
      .getFeaturesInExtent([0, 0, Infinity, Infinity])[0].getGeometry() : null

    if (geomType instanceof olGeomPoint || geomType instanceof olGeomMultiPoint) {
      style = [new olStyleStyle({
        image: new olStyleCircle({
          fill: new olStyleFill({ color: 'rgba(255,255,255,0.5)' }),
          stroke: new olStyleStroke({ color: '#3399CC', width: 2 }),
          radius: 5
        })
      })]
    } else if (geomType instanceof olGeomLinestring || geomType instanceof olGeomMultiLinestring) {
      style = [new olStyleStyle({
        stroke: new olStyleStroke({ color: '#3399CC', width: 2 })
      })]
    } else {
      style = [new olStyleStyle({
        fill: new olStyleFill({ color: 'rgba(255,255,255,0.5)' }),
        stroke: new olStyleStroke({ color: '#3399CC', width: 2 })
      })]
    }

    this.setStyle(() => style)
  }
}

export default VectorTileLayer