/*	Copyright (c) 2015 Jean-Marc VIGLINO, 
  released under the CeCILL-B license (French BSD license)
  (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
*/
import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js'
import ol_control_Control from 'ol/control/Control.js'
import ol_layer_Tile from 'ol/layer/Tile.js'
import ol_layer_Vector from 'ol/layer/Vector.js'
import ol_layer_VectorTile from 'ol/layer/VectorTile.js'
// ol < 6 compatibility VectorImage is not defined
// import ol_layer_VectorImage from 'ol/layer/VectorImage.js'
import ol_layer_Image from 'ol/layer/Image.js'
import ol_layer_Heatmap from 'ol/layer/Heatmap.js'
import {intersects as ol_extent_intersects} from 'ol/extent.js'

import ol_ext_element from '../util/element.js'

/** Layer Switcher Control.
 * @fires select
 * @fires drawlist
 * @fires toggle
 * @fires reorder-start
 * @fires reorder-end
 * @fires layer:visible
 * @fires layer:opacity
 * @fires layer:keydown
 * 
 * @constructor
 * @extends {ol_control_Control}
 * @param {Object=} options
 *  @param {boolean} options.selection enable layer selection when click on the title
 *  @param {function} options.displayInLayerSwitcher function that takes a layer and return a boolean if the layer is displayed in the switcher, default test the displayInLayerSwitcher layer attribute
 *  @param {boolean} options.show_progress show a progress bar on tile layers, default false
 *  @param {boolean} options.mouseover show the panel on mouseover, default false
 *  @param {boolean} options.reordering allow layer reordering, default true
 *  @param {boolean} options.trash add a trash button to delete the layer, default false
 *  @param {function} options.oninfo callback on click on info button, if none no info button is shown DEPRECATED: use on(info) instead
 *  @param {boolean} options.extent add an extent button to zoom to the extent of the layer
 *  @param {function} options.onextent callback when click on extent, default fits view to extent
 *  @param {number} options.drawDelay delay in ms to redraw the layer (usefull to prevent flickering when manipulating the layers)
 *  @param {boolean} options.collapsed collapse the layerswitcher at beginning, default true
 *  @param {ol.layer.Group} options.layerGroup a layer group to display in the switcher, default display all layers of the map
 *  @param {boolean} options.noScroll prevent handle scrolling, default false
 *  @param {boolean} options.counter layer counter, default false
 *  @param {function} options.onchangeCheck optional callback on click on checkbox, you can call this method for doing operations after check/uncheck a layer
 *
 * Layers attributes that control the switcher
 *	- allwaysOnTop {boolean} true to force layer stay on top of the others while reordering, default false
 *	- displayInLayerSwitcher {boolean} display the layer in switcher, default true
 *	- noSwitcherDelete {boolean} to prevent layer deletion (w. trash option = true), default false
 */
var ol_control_LayerSwitcher = class olcontrolLayerSwitcher extends ol_control_Control {
  constructor(options) {
    options = options || {}
    var element = ol_ext_element.create('DIV', {
      className: options.switcherClass || 'ol-layerswitcher'
    })
    super({
      element: element,
      target: options.target
    })
    
    var self = this
    this.dcount = 0
    this.show_progress = options.show_progress
    this.oninfo = (typeof (options.oninfo) == 'function' ? options.oninfo : null)
    this.onextent = (typeof (options.onextent) == 'function' ? options.onextent : null)
    this.hasextent = options.extent || options.onextent
    this.hastrash = options.trash
    this.reordering = (options.reordering !== false)
    this._layers = []
    this._layerGroup = (options.layerGroup && options.layerGroup.getLayers) ? options.layerGroup : null
    this.onchangeCheck = (typeof (options.onchangeCheck) == "function" ? options.onchangeCheck : null)

    // displayInLayerSwitcher
    if (typeof (options.displayInLayerSwitcher) === 'function') {
      this.displayInLayerSwitcher = options.displayInLayerSwitcher
    }

    // Insert in the map
    if (!options.target) {
      element.classList.add('ol-unselectable')
      element.classList.add('ol-control')
      element.classList.add(options.collapsed !== false ? 'ol-collapsed' : 'ol-forceopen')
      if (options.counter) element.classList.add('ol-counter')

      this.counter = ol_ext_element.create('SPAN', {
        class: 'ol-counter',
        text: 0,
        parent: element
      });
      this.button = ol_ext_element.create('BUTTON', {
        type: 'button',
        parent: element
      })
      this.button.addEventListener('touchstart', function (e) {
        element.classList.toggle('ol-forceopen')
        element.classList.add('ol-collapsed')
        self.dispatchEvent({ type: 'toggle', collapsed: element.classList.contains('ol-collapsed') })
        e.preventDefault()
        self.overflow()
      })
      this.button.addEventListener('click', function () {
        element.classList.toggle('ol-forceopen')
        element.classList.add('ol-collapsed')
        self.dispatchEvent({ type: 'toggle', collapsed: !element.classList.contains('ol-forceopen') })
        self.overflow()
      })

      if (options.mouseover) {
        element.addEventListener('mouseleave', function () {
          element.classList.add("ol-collapsed")
          self.dispatchEvent({ type: 'toggle', collapsed: true })
        })
        element.addEventListener('mouseover', function () {
          element.classList.remove("ol-collapsed")
          self.dispatchEvent({ type: 'toggle', collapsed: false })
        })
      }

      if (options.minibar)
        options.noScroll = true
      if (!options.noScroll) {
        this.topv = ol_ext_element.create('DIV', {
          className: 'ol-switchertopdiv',
          parent: element,
          click: function () {
            self.overflow("+50%")
          }
        })

        this.botv = ol_ext_element.create('DIV', {
          className: 'ol-switcherbottomdiv',
          parent: element,
          click: function () {
            self.overflow("-50%")
          }
        })
      }
      this._noScroll = options.noScroll
    }

    this.panel_ = ol_ext_element.create('UL', {
      className: 'panel',
    })
    this.panelContainer_ = ol_ext_element.create('DIV', {
      className: 'panel-container',
      html: this.panel_,
      parent: element
    })
    // Handle mousewheel
    if (!options.target && !options.noScroll) {
      ol_ext_element.addListener(this.panel_, 'mousewheel DOMMouseScroll onmousewheel', function (e) {
        if (self.overflow(Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail))))) {
          e.stopPropagation()
          e.preventDefault()
        }
      })
    }

    this.header_ = ol_ext_element.create('LI', {
      className: 'ol-header',
      parent: this.panel_
    })

    this.set('drawDelay', options.drawDelay || 0)
    this.set('selection', options.selection)

    if (options.minibar) {
      // Wait init complete (for child classes)
      setTimeout(function () {
        var mbar = ol_ext_element.scrollDiv(this.panelContainer_, {
          mousewheel: true,
          vertical: true,
          minibar: true
        })
        this.on(['drawlist', 'toggle'], function () {
          mbar.refresh()
        })
      }.bind(this))
    }
  }
  /** Test if a layer should be displayed in the switcher
   * @param {ol.layer} layer
   * @return {boolean} true if the layer is displayed
   */
  displayInLayerSwitcher(layer) {
    return (layer.get('displayInLayerSwitcher') !== false)
  }
  /**
   * Set the map instance the control is associated with.
   * @param {_ol_Map_} map The map instance.
   */
  setMap(map) {
    super.setMap(map)
    this.drawPanel()

    if (this._listener) {
      for (var i in this._listener){
        ol_Observable_unByKey(this._listener[i])
      }
    }
    this._listener = null

    // Get change (new layer added or removed)
    if (map) {
      this._listener = {
        moveend: map.on('moveend', this.viewChange.bind(this)),
        size: map.on('change:size', this.overflow.bind(this))
      }
      // Listen to a layer group
      if (this._layerGroup) {
        this._listener.change = this._layerGroup.getLayers().on('change:length', this.drawPanel.bind(this))
      } else {
        //Listen to all layers
        this._listener.change = map.getLayerGroup().getLayers().on('change:length', this.drawPanel.bind(this))
      }
    }
  }
  /** Show control
   */
  show() {
    this.element.classList.add('ol-forceopen')
    this.overflow()
    this.dispatchEvent({ type: 'toggle', collapsed: false })
  }
  /** Hide control
   */
  hide() {
    this.element.classList.remove('ol-forceopen')
    this.overflow()
    this.dispatchEvent({ type: 'toggle', collapsed: true })
  }
  /** Toggle control
   */
  toggle() {
    this.element.classList.toggle("ol-forceopen")
    this.overflow()
    this.dispatchEvent({ type: 'toggle', collapsed: !this.isOpen() })
  }
  /** Is control open
   * @return {boolean}
   */
  isOpen() {
    return this.element.classList.contains("ol-forceopen")
  }
  /** Add a custom header
   * @param {Element|string} html content html
   */
  setHeader(html) {
    ol_ext_element.setHTML(this.header_, html)
  }
  /** Calculate overflow and add scrolls
   * @param {Number} dir scroll direction -1|0|1|'+50%'|'-50%'
   * @private
   */
  overflow(dir) {
    if (this.button && !this._noScroll) {
      // Nothing to show
      if (ol_ext_element.hidden(this.panel_)) {
        ol_ext_element.setStyle(this.element, { height: 'auto' })
        return
      }
      // Calculate offset
      var h = ol_ext_element.outerHeight(this.element)
      var hp = ol_ext_element.outerHeight(this.panel_)
      var dh = this.button.offsetTop + ol_ext_element.outerHeight(this.button)
      //var dh = this.button.position().top + this.button.outerHeight(true);
      var top = this.panel_.offsetTop - dh
      if (hp > h - dh) {
        // Bug IE: need to have an height defined
        ol_ext_element.setStyle(this.element, { height: '100%' })
        var li = this.panel_.querySelectorAll('li.ol-visible .li-content')[0]
        var lh = li ? 2 * ol_ext_element.getStyle(li, 'height') : 0
        switch (dir) {
          case 1: top += lh; break
          case -1: top -= lh; break
          case "+50%": top += Math.round(h / 2); break
          case "-50%": top -= Math.round(h / 2); break
          default: break
        }
        // Scroll div
        if (top + hp <= h - 3 * dh / 2) {
          top = h - 3 * dh / 2 - hp
          ol_ext_element.hide(this.botv)
        } else {
          ol_ext_element.show(this.botv)
        }
        if (top >= 0) {
          top = 0
          ol_ext_element.hide(this.topv)
        } else {
          ol_ext_element.show(this.topv)
        }
        // Scroll ?
        ol_ext_element.setStyle(this.panel_, { top: top + "px" })
        return true
      } else {
        ol_ext_element.setStyle(this.element, { height: "auto" })
        ol_ext_element.setStyle(this.panel_, { top: 0 })
        ol_ext_element.hide(this.botv)
        ol_ext_element.hide(this.topv)
        return false
      }
    }
    else
      return false
  }
  /** Set the layer associated with a li
   * @param {Element} li
   * @param {ol.layer} layer
   * @private
   */
  _setLayerForLI(li, layer) {
    var listeners = []
    if (layer.getLayers) {
      listeners.push(layer.getLayers().on('change:length', this.drawPanel.bind(this)))
    }
    if (li) {
      // Handle opacity change
      listeners.push(layer.on('change:opacity', (function () {
        this.setLayerOpacity(layer, li)
      }).bind(this)))
      // Handle visibility chage
      listeners.push(layer.on('change:visible', (function () {
        this.setLayerVisibility(layer, li)
      }).bind(this)))
    }
    // Other properties
    listeners.push(layer.on('propertychange', (function (e) {
      if (e.key === 'displayInLayerSwitcher'
        || e.key === 'openInLayerSwitcher'
        || e.key === 'title'
        || e.key === 'name') {
        this.drawPanel(e)
      }
    }).bind(this)))
    this._layers.push({ li: li, layer: layer, listeners: listeners })
  }
  /** Set opacity for a layer
   * @param {ol.layer.Layer} layer
   * @param {Element} li the list element
   * @private
   */
  setLayerOpacity(layer, li) {
    var i = li.querySelector('.layerswitcher-opacity-cursor')
    if (i){
      i.style.left = (layer.getOpacity() * 100) + "%"
    }
    this.dispatchEvent({ type: 'layer:opacity', layer: layer })
  }
  /** Set visibility for a layer
   * @param {ol.layer.Layer} layer
   * @param {Element} li the list element
   * @api
   */
  setLayerVisibility(layer, li) {
    var i = li.querySelector('.ol-visibility')
    if (i) {
      i.checked = layer.getVisible()
    }
    if (layer.getVisible()){
      li.classList.add('ol-visible')
    } else{
      li.classList.remove('ol-visible')
    }
    this.dispatchEvent({ type: 'layer:visible', layer: layer })
  }
  /** Clear layers associated with li
   * @private
   */
  _clearLayerForLI() {
    this._layers.forEach(function (li) {
      li.listeners.forEach(function (l) {
        ol_Observable_unByKey(l)
      })
    })
    this._layers = []
  }
  /** Get the layer associated with a li
   * @param {Element} li
   * @return {ol.layer}
   * @private
   */
  _getLayerForLI(li) {
    for (var i = 0, l; l = this._layers[i]; i++) {
      if (l.li === li) {
        return l.layer
      }
    }
    return null
  }
  /**
   * On view change hide layer depending on resolution / extent
   * @private
   */
  viewChange() {
    this.panel_.querySelectorAll('li').forEach(function (li) {
      var l = this._getLayerForLI(li)
      if (l) {
        if (this.testLayerVisibility(l)) {
          li.classList.remove('ol-layer-hidden')
        } else {
          li.classList.add('ol-layer-hidden')
        }
      }
    }.bind(this))
  }
  /** Get control panel
   * @api
   */
  getPanel() {
    return this.panelContainer_
  }
  /** Draw the panel control (prevent multiple draw due to layers manipulation on the map with a delay function)
   * @api
   */
  drawPanel() {
    if (!this.getMap())
      return
    var self = this
    // Multiple event simultaneously / draw once => put drawing in the event queue
    this.dcount++
    setTimeout(function () { self.drawPanel_() }, this.get('drawDelay') || 0)
  }
  /** Delayed draw panel control
   * @private
   */
  drawPanel_() {
    if (--this.dcount || this.dragging_) {
      return
    }
    var scrollTop = this.panelContainer_.scrollTop

    // Remove existing layers
    this._clearLayerForLI()
    this.panel_.querySelectorAll('li').forEach(function (li) {
      if (!li.classList.contains('ol-header')) li.remove()
    }.bind(this))
    // Draw list
    if (this._layerGroup) {
      this.drawList(this.panel_, this._layerGroup.getLayers())
    } else if (this.getMap()) {
      this.drawList(this.panel_, this.getMap().getLayers())
    }
    // Reset scrolltop
    this.panelContainer_.scrollTop = scrollTop
    // Counter
    if (this.counter) {
      this.counter.innerHTML = this.panel_.parentNode.querySelectorAll('ul.panel > li:not(.ol-header)').length;
    }
  }
  /** Change layer visibility according to the baselayer option
   * @param {ol.layer}
   * @param {Array<ol.layer>} related layers
   * @private
   */
  switchLayerVisibility(l, layers) {
    if (!l.get('baseLayer')) {
      l.setVisible(!l.getVisible())
    } else {
      if (!l.getVisible()) {
        l.setVisible(true)
      }
      layers.forEach(function (li) {
        if (l !== li && li.get('baseLayer') && li.getVisible()) {
          li.setVisible(false)
        }
      })
    }
  }
  /** Check if layer is on the map (depending on resolution / zoom and extent)
   * @param {ol.layer}
   * @return {boolean}
   * @private
   */
  testLayerVisibility(layer) {
    if (!this.getMap())
      return true
    var res = this.getMap().getView().getResolution()
    var zoom = this.getMap().getView().getZoom()
    // Calculate visibility on resolution or zoom
    if (layer.getMaxResolution() <= res || layer.getMinResolution() >= res) {
      return false
    } else if (layer.getMinZoom && (layer.getMinZoom() >= zoom || layer.getMaxZoom() < zoom)) {
      return false
    } else {
      // Check extent
      var ex0 = layer.getExtent()
      if (ex0) {
        var ex = this.getMap().getView().calculateExtent(this.getMap().getSize())
        return ol_extent_intersects(ex, ex0)
      }
      return true
    }
  }
  /** Start ordering the list
  *	@param {event} e drag event
  *	@private
  */
  dragOrdering_(e) {
    e.stopPropagation()
    e.preventDefault()
    // Get params
    var self = this
    var elt = e.currentTarget.parentNode.parentNode
    var start = true
    var panel = this.panel_
    var pageY
    var pageY0 = e.pageY
      || (e.touches && e.touches.length && e.touches[0].pageY)
      || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY)
    var target, dragElt
    var layer, group
    elt.parentNode.classList.add('drag')

    // Stop ordering
    function stop() {
      if (target) {
        // Get drag on parent
        var drop = layer
        var isSelected = self.getSelection() === drop
        if (drop && target) {
          var collection
          if (group) {
            collection = group.getLayers()
          } else {
            collection = self._layerGroup ? self._layerGroup.getLayers() : self.getMap().getLayers()
          }
          var layers = collection.getArray()
          // Switch layers
          for (var i = 0; i < layers.length; i++) {
            if (layers[i] == drop) {
              collection.removeAt(i)
              break
            }
          }
          for (var j = 0; j < layers.length; j++) {
            if (layers[j] === target) {
              if (i > j) {
                collection.insertAt(j, drop)
              } else {
                collection.insertAt(j + 1, drop)
              }
              break
            }
          }
        }
        if (isSelected) self.selectLayer(drop)

        self.dispatchEvent({ type: "reorder-end", layer: drop, group: group })
      }

      elt.parentNode.querySelectorAll('li').forEach(function (li) {
        li.classList.remove('dropover')
        li.classList.remove('dropover-after')
        li.classList.remove('dropover-before')
      })
      elt.classList.remove("drag")
      elt.parentNode.classList.remove("drag")
      self.element.classList.remove('drag')
      if (dragElt)
        dragElt.remove()

      ol_ext_element.removeListener(document, 'mousemove touchmove', move)
      ol_ext_element.removeListener(document, 'mouseup touchend touchcancel', stop)
    }

    // Ordering
    function move(e) {
      // First drag (more than 2 px) => show drag element (ghost)
      pageY = e.pageY
        || (e.touches && e.touches.length && e.touches[0].pageY)
        || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageY)
      if (start && Math.abs(pageY0 - pageY) > 2) {
        start = false
        elt.classList.add("drag")
        layer = self._getLayerForLI(elt)
        target = false
        group = self._getLayerForLI(elt.parentNode.parentNode)
        // Ghost div
        dragElt = ol_ext_element.create('LI', {
          className: 'ol-dragover',
          html: elt.innerHTML,
          style: {
            position: "absolute",
            "z-index": 10000,
            left: elt.offsetLeft,
            opacity: 0.5,
            width: ol_ext_element.outerWidth(elt),
            height: ol_ext_element.getStyle(elt, 'height'),
          },
          parent: panel
        })
        self.element.classList.add('drag')
        self.dispatchEvent({ type: "reorder-start", layer: layer, group: group })
      }
      // Start a new drag sequence
      if (!start) {
        e.preventDefault()
        e.stopPropagation()

        // Ghost div
        ol_ext_element.setStyle(dragElt, { top: pageY - ol_ext_element.offsetRect(panel).top + panel.scrollTop + 5 })

        var li
        if (!e.touches) {
          li = e.target
          
          // Get the HTML node within web component on click drag
          if(e.target.shadowRoot){
            li = e.composedPath()[0]
          }
        } else {
          li = document.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY);
          
          //Get actual HTML node within web component on touch drag
          while(li.shadowRoot){
            li = li.shadowRoot.elementFromPoint(e.touches[0].clientX, e.touches[0].clientY)
          }
        }
        if (li.classList.contains("ol-switcherbottomdiv")) {
          self.overflow(-1)
        } else if (li.classList.contains("ol-switchertopdiv")) {
          self.overflow(1)
        }
        // Get associated li
        while (li && li.tagName !== 'LI') {
          li = li.parentNode
        }
        if (!li || !li.classList.contains('dropover')) {
          elt.parentNode.querySelectorAll('li').forEach(function (li) {
            li.classList.remove('dropover')
            li.classList.remove('dropover-after')
            li.classList.remove('dropover-before')
          })
        }
        if (li && li.parentNode.classList.contains('drag') && li !== elt) {
          target = self._getLayerForLI(li)
          // Don't mix layer level
          if (target && !target.get('allwaysOnTop') == !layer.get('allwaysOnTop')) {
            li.classList.add("dropover")
            li.classList.add((elt.offsetTop < li.offsetTop) ? 'dropover-after' : 'dropover-before')
          } else {
            target = false
          }
          ol_ext_element.show(dragElt)
        } else {
          target = false
          if (li === elt)
            ol_ext_element.hide(dragElt)
          else
            ol_ext_element.show(dragElt)
        }

        if (!target)
          dragElt.classList.add("forbidden")
        else
          dragElt.classList.remove("forbidden")
      }
    }

    // Start ordering
    ol_ext_element.addListener(document, 'mousemove touchmove', move)
    ol_ext_element.addListener(document, 'mouseup touchend touchcancel', stop)
  }
  /** Change opacity on drag
  *	@param {event} e drag event
  *	@private
  */
  dragOpacity_(e) {
    e.stopPropagation()
    e.preventDefault()
    var self = this
    // Register start params
    var elt = e.target
    var layer = this._getLayerForLI(elt.parentNode.parentNode.parentNode)
    if (!layer)
      return
    var x = e.pageX
      || (e.touches && e.touches.length && e.touches[0].pageX)
      || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX)
    var start = ol_ext_element.getStyle(elt, 'left') - x
    self.dragging_ = true

    // stop dragging
    function stop() {
      ol_ext_element.removeListener(document, "mouseup touchend touchcancel", stop)
      ol_ext_element.removeListener(document, "mousemove touchmove", move)
      self.dragging_ = false
    }
    // On draggin
    function move(e) {
      var x = e.pageX
        || (e.touches && e.touches.length && e.touches[0].pageX)
        || (e.changedTouches && e.changedTouches.length && e.changedTouches[0].pageX)
      var delta = (start + x) / ol_ext_element.getStyle(elt.parentNode, 'width')
      var opacity = Math.max(0, Math.min(1, delta))
      ol_ext_element.setStyle(elt, { left: (opacity * 100) + "%" })
      elt.parentNode.nextElementSibling.innerHTML = Math.round(opacity * 100)
      layer.setOpacity(opacity)
    }
    // Register move
    ol_ext_element.addListener(document, "mouseup touchend touchcancel", stop)
    ol_ext_element.addListener(document, "mousemove touchmove", move)
  }
  /** Render a list of layer
   * @param {Elemen} element to render
   * @layers {Array{ol.layer}} list of layer to show
   * @api stable
   * @private
   */
  drawList(ul, collection) {
    var self = this
    var layers = collection.getArray()

    // Change layer visibility
    var setVisibility = function (e) {
      e.stopPropagation()
      e.preventDefault()
      var l = self._getLayerForLI(this.parentNode.parentNode)
      self.switchLayerVisibility(l, collection)
      if (self.get('selection') && l.getVisible()) {
        self.selectLayer(l)
      }
      if (self.onchangeCheck) {
        self.onchangeCheck(l)
      }
    }
    // Info button click
    function onInfo(e) {
      e.stopPropagation()
      e.preventDefault()
      var l = self._getLayerForLI(this.parentNode.parentNode)
      self.oninfo(l)
      self.dispatchEvent({ type: "info", layer: l })
    }
    // Zoom to extent button
    function zoomExtent(e) {
      e.stopPropagation()
      e.preventDefault()
      var l = self._getLayerForLI(this.parentNode.parentNode)
      if (self.onextent) {
        self.onextent(l)
      } else {
        self.getMap().getView().fit(l.getExtent(), self.getMap().getSize())
      }
      self.dispatchEvent({ type: "extent", layer: l })
    }
    // Remove a layer on trash click
    function removeLayer(e) {
      e.stopPropagation()
      e.preventDefault()
      var li = this.parentNode.parentNode.parentNode.parentNode
      var layer, group = self._getLayerForLI(li)
      // Remove the layer from a group or from a map
      if (group) {
        layer = self._getLayerForLI(this.parentNode.parentNode)
        group.getLayers().remove(layer)
        if (group.getLayers().getLength() == 0 && !group.get('noSwitcherDelete')) {
          removeLayer.call(li.querySelectorAll('.layerTrash')[0], e)
        }
      } else {
        li = this.parentNode.parentNode
        self.getMap().removeLayer(self._getLayerForLI(li))
      }
    }

    // Create a list for a layer
    function createLi(layer) {
      if (!this.displayInLayerSwitcher(layer)) {
        this._setLayerForLI(null, layer)
        return
      }

      var li = ol_ext_element.create('LI', {
        className: (layer.getVisible() ? "ol-visible " : " ") + (layer.get('baseLayer') ? "baselayer" : ""),
        parent: ul
      })
      this._setLayerForLI(li, layer)
      if (this._selectedLayer === layer) {
        li.classList.add('ol-layer-select')
      }

      var layer_buttons = ol_ext_element.create('DIV', {
        className: 'ol-layerswitcher-buttons',
        parent: li
      })

      // Content div
      var d = ol_ext_element.create('DIV', {
        className: 'li-content',
        parent: li
      })

      // Visibility
      var input = ol_ext_element.create('INPUT', {
        type: layer.get('baseLayer') ? 'radio' : 'checkbox',
        className: 'ol-visibility',
        checked: layer.getVisible(),
        click: function(e) {
          setVisibility.bind(this)(e)
          setTimeout(function() { e.target.checked = layer.getVisible(); });
        },
        on: {
          // Set opacity on keydown
          keydown: function(e) {
            switch (e.key) {
              // Change opacity on arrow
              case 'ArrowLeft':
              case 'ArrowRight': {
                e.preventDefault();
                e.stopPropagation();
                var delta = (e.key==='ArrowLeft' ? -0.1 : 0.1);
                var opacity = Math.min(1, Math.max(0, layer.getOpacity() + delta))
                layer.setOpacity(opacity)
                break;
              }
              // Select on enter
              case 'Enter': {
                if (self.get('selection')) {
                  e.preventDefault();
                  e.stopPropagation();
                  self.selectLayer(layer)
                }
                break;
              }
              // Expend 
              case '-': 
              case '+': {
                if (layer.getLayers) {
                  this._focus = layer;
                  layer.set("openInLayerSwitcher", !layer.get("openInLayerSwitcher"))
                }
              }
              // Move up dans down
              // fallthrough
              case 'ArrowUp':
              case 'ArrowDown': {
                if (e.ctrlKey && this.reordering) {
                  e.preventDefault();
                  e.stopPropagation();
                  var pos = collection.getArray().indexOf(layer);
                  if (pos > -1) {
                    if (e.key === 'ArrowDown') {
                      if (pos > 0) {
                        collection.remove(layer);
                        collection.insertAt(pos-1, layer)
                        self._focus = layer
                        self.dispatchEvent({ type: "reorder-end", layer: layer })
                      }
                    } else {
                      if (pos < collection.getLength()-1) {
                        collection.remove(layer);
                        collection.insertAt(pos+1, layer)
                        self._focus = layer
                        self.dispatchEvent({ type: "reorder-end", layer: layer })
                      }
                    }
                  }
                }
                break;
              }
              default: {
                var group = this._getLayerForLI(ul.parentNode)
                this.dispatchEvent({ type: 'layer:keydown', key: e.key, group: group, li: li, layer: layer, originalEvent: e })
              }
            }
          }.bind(this)
        },
        parent: d
      })
      // Focus on element
      if (layer === self._focus) {
        input.focus();
        self.overflow()
      }
      // Label
      var label = ol_ext_element.create('LABEL', {
        title: layer.get('title') || layer.get('name'),
        click: setVisibility,
        style: {
          userSelect: 'none'
        },
        parent: d
      })
      label.addEventListener('selectstart', function () { return false })
      ol_ext_element.create('SPAN', {
        html: layer.get('title') || layer.get('name'),
        click: function (e) {
          if (this.get('selection')) {
            e.stopPropagation()
            this.selectLayer(layer)
          }
        }.bind(this),
        parent: label
      })
      
      //  up/down
      if (this.reordering) {
        if ((i < layers.length - 1 && (layer.get("allwaysOnTop") || !layers[i + 1].get("allwaysOnTop")))
          || (i > 0 && (!layer.get("allwaysOnTop") || layers[i - 1].get("allwaysOnTop")))) {
          ol_ext_element.create('DIV', {
            className: 'layerup ol-noscroll',
            title: this.tip.up,
            on: { 'mousedown touchstart': function (e) { self.dragOrdering_(e) } },
            parent: layer_buttons
          })
        }
      }

      // Show/hide sub layers
      if (layer.getLayers) {
        var nb = 0
        layer.getLayers().forEach(function (l) {
          if (self.displayInLayerSwitcher(l))
            nb++
        })
        if (nb) {
          ol_ext_element.create('DIV', {
            className: layer.get("openInLayerSwitcher") ? "collapse-layers" : "expend-layers",
            title: this.tip.plus,
            click: function () {
              var l = self._getLayerForLI(this.parentNode.parentNode)
              l.set("openInLayerSwitcher", !l.get("openInLayerSwitcher"))
            },
            parent: layer_buttons
          })
        }
      }

      // Info button
      if (this.oninfo) {
        ol_ext_element.create('DIV', {
          className: 'layerInfo',
          title: this.tip.info,
          click: onInfo,
          parent: layer_buttons
        })
      }
      // Layer remove
      if (this.hastrash && !layer.get("noSwitcherDelete")) {
        ol_ext_element.create('DIV', {
          className: 'layerTrash',
          title: this.tip.trash,
          click: removeLayer,
          parent: layer_buttons
        })
      }
      // Layer extent
      if (this.hasextent && layers[i].getExtent()) {
        var ex = layers[i].getExtent()
        if (ex.length == 4 && ex[0] < ex[2] && ex[1] < ex[3]) {
          ol_ext_element.create('DIV', {
            className: 'layerExtent',
            title: this.tip.extent,
            click: zoomExtent,
            parent: layer_buttons
          })
        }
      }

      // Progress
      if (this.show_progress && layer instanceof ol_layer_Tile) {
        var p = ol_ext_element.create('DIV', {
          className: 'layerswitcher-progress',
          parent: d
        })
        this.setprogress_(layer)
        layer.layerswitcher_progress = ol_ext_element.create('DIV', { parent: p })
      }

      // Opacity
      var opacity = ol_ext_element.create('DIV', {
        className: 'layerswitcher-opacity',
        // Click on the opacity line
        click: function (e) {
          if (e.target !== this)
            return
          e.stopPropagation()
          e.preventDefault()
          var op = Math.max(0, Math.min(1, e.offsetX / ol_ext_element.getStyle(this, 'width')))
          self._getLayerForLI(this.parentNode.parentNode).setOpacity(op)
          this.parentNode.querySelectorAll('.layerswitcher-opacity-label')[0].innerHTML = Math.round(op * 100)
        },
        parent: d
      })
      // Start dragging
      ol_ext_element.create('DIV', {
        className: 'layerswitcher-opacity-cursor ol-noscroll',
        style: { left: (layer.getOpacity() * 100) + "%" },
        on: {
          'mousedown touchstart': function (e) { self.dragOpacity_(e) }
        },
        parent: opacity
      })
      // Percent
      ol_ext_element.create('DIV', {
        className: 'layerswitcher-opacity-label',
        html: Math.round(layer.getOpacity() * 100),
        parent: d
      })

      // Layer group
      if (layer.getLayers) {
        li.classList.add('ol-layer-group')
        if (layer.get("openInLayerSwitcher") === true) {
          var ul2 = ol_ext_element.create('UL', {
            parent: li
          })
          this.drawList(ul2, layer.getLayers())
        }
      }
      li.classList.add(this.getLayerClass(layer))

      // Dispatch a dralist event to allow customisation
      this.dispatchEvent({ type: 'drawlist', layer: layer, li: li })
    }

    // Add the layer list
    for (var i = layers.length - 1; i >= 0; i--) {
      createLi.call(this, layers[i])
    }

    this.viewChange()

    if (ul === this.panel_) {
      this.overflow('')
      // Remove focus
      this._focus = null;
    }

  }
  /** Select a layer
   * @param {ol.layer.Layer} layer
   * @returns {string} the layer classname
   * @api
   */
  getLayerClass(layer) {
    if (!layer)
      return 'none'
    if (layer.getLayers)
      return 'ol-layer-group'
    if (layer instanceof ol_layer_Vector)
      return 'ol-layer-vector'
    if (layer instanceof ol_layer_VectorTile)
      return 'ol-layer-vectortile'
    if (layer instanceof ol_layer_Tile)
      return 'ol-layer-tile'
    if (layer instanceof ol_layer_Image)
      return 'ol-layer-image'
    if (layer instanceof ol_layer_Heatmap)
      return 'ol-layer-heatmap'
    /* ol < 6 compatibility VectorImage is not defined */
    // if (layer instanceof ol_layer_VectorImage) return 'ol-layer-vectorimage';
    if (layer.getFeatures)
      return 'ol-layer-vectorimage'
    /* */
    return 'unknown'
  }
  /** Select a layer
   * @param {ol.layer.Layer} layer
   * @api
   */
  selectLayer(layer, silent) {
    if (!layer) {
      if (!this.getMap())
        return
      layer = this.getMap().getLayers().item(this.getMap().getLayers().getLength() - 1)
    }
    this._selectedLayer = layer
    // Has focus ?
    if (this.element.querySelector('input.ol-visibility:focus')) {
      this._focus = layer;
    }
    // Draw
    this.drawPanel()
    if (!silent) {
      this.dispatchEvent({ type: 'select', layer: layer })
    }
  }
  /** Get selected layer
   * @returns {ol.layer.Layer}
   */
  getSelection() {
    return this._selectedLayer
  }
  /** Handle progress bar for a layer
  *	@private
  */
  setprogress_(layer) {
    if (!layer.layerswitcher_progress) {
      var loaded = 0
      var loading = 0
      var draw = function () {
        if (loading === loaded) {
          loading = loaded = 0
          ol_ext_element.setStyle(layer.layerswitcher_progress, { width: 0 }) // layer.layerswitcher_progress.width(0);
        } else {
          ol_ext_element.setStyle(layer.layerswitcher_progress, { width: (loaded / loading * 100).toFixed(1) + '%' }) // layer.layerswitcher_progress.css('width', (loaded / loading * 100).toFixed(1) + '%');
        }
      }
      layer.getSource().on('tileloadstart', function () {
        loading++
        draw()
      })
      layer.getSource().on('tileloadend', function () {
        loaded++
        draw()
      })
      layer.getSource().on('tileloaderror', function () {
        loaded++
        draw()
      })
    }
  }
}

/** List of tips for internationalization purposes
*/
ol_control_LayerSwitcher.prototype.tip = {
  up: "up/down",
  down: "down",
  info: "informations...",
  extent: "zoom to extent",
  trash: "remove layer",
  plus: "expand/shrink"
};

export default ol_control_LayerSwitcher
