/*	Copyright (c) 2015-2018 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 {transform as ol_proj_transform} from 'ol/proj.js'
import ol_ext_element from '../util/element.js'
import { toLonLat as ol_geohash_toLonLat } from '../geom/geohash.js'
import { fromLonLat as ol_geohash_fromLonLat } from '../geom/geohash.js'

/**
 * Set an hyperlink that will return the user to the current map view.
 * Just add a `permalink`property to layers to be handled by the control (and added in the url). 
 * The layer's permalink property is used to name the layer in the url.
 * The control must be added after all layer are inserted in the map to take them into acount.
 *
 * @constructor
 * @extends {ol_control_Control}
 * @param {Object=} options
 *  @param {boolean} [options.urlReplace=true] replace url or not, default true
 *  @param {boolean|string} [options.localStorage=false] save current map view in localStorage, if 'position' only store map position
 *  @param {boolean} [options.geohash=false] use geohash instead of lonlat, default false
 *  @param {integer} [options.fixed=6] number of digit in coords, default 6
 *  @param {boolean} [options.anchor] use "#" instead of "?" in href
 *  @param {boolean} [options.visible=true] hide the button on the map, default true
 *  @param {boolean} [options.hidden] hide the button on the map, default false DEPRECATED: use visible instead
 *  @param {function} [options.onclick] a function called when control is clicked
 *  @param {number} [options.refreshDelay=500] 
 */
var ol_control_Permalink = class olcontrolPermalink extends ol_control_Control {
  constructor(opt_options) {
    var options = opt_options || {}

    var element = document.createElement('div')
    super({
      element: element,
      target: options.target
    })
    
    var self = this
    var button = document.createElement('button')
    ol_ext_element.create('I', { parent: button })
    this.replaceState_ = (options.urlReplace !== false)
    this.fixed_ = options.fixed || 6
    this.hash_ = options.anchor ? "#" : "?"
    this._localStorage = options.localStorage
    if (!this._localStorage) {
      try {
        localStorage.removeItem('ol@permalink')
      } catch (e) { console.warn('Failed to access localStorage...')} 
    }

    function linkto() {
      if (typeof (options.onclick) == 'function'){
        options.onclick(self.getLink())
      } else {
        self.setUrlReplace(!self.replaceState_)
      }
    }
    button.addEventListener('click', linkto, false)
    button.addEventListener('touchstart', linkto, false)

    element.className = (options.className || "ol-permalink") + " ol-unselectable ol-control"
    element.appendChild(button)
    if (options.hidden || options.visible === false) ol_ext_element.hide(element)

    this.set('geohash', options.geohash)
    this.set('initial', false)

    this.on('change', this.viewChange_.bind(this))

    // Save search params
    this.search_ = {}
    var init = {}
    var hash = document.location.hash || document.location.search || ''
    //  console.log('hash', hash)
    if (this.replaceState_ && !hash && this._localStorage) {
      try {
        hash = localStorage['ol@permalink']
      } catch (e) { console.warn('Failed to access localStorage...')} 
    }
    if (hash) {
      hash = hash.replace(/(^#|^\?)/, "").split("&")
      for (var i = 0; i < hash.length; i++) {
        var t = hash[i].split("=")
        switch (t[0]) {
          case 'lon':
          case 'lat':
          case 'z':
          case 'r': {
            init[t[0]] = t[1]
            break
          }
          case 'gh': {
            const ghash = t[1].split('-')
            const lonlat = ol_geohash_toLonLat(ghash[0])
            init.lon = lonlat[0]
            init.lat = lonlat[1]
            init.z = ghash[1]
            break
          }
          case 'l': break
          default: this.search_[t[0]] = t[1]
        }
      }
    }
    if (init.hasOwnProperty('lon')) {
      this.set('initial', init)
    }
    this.set('refreshDelay', options.refreshDelay || 500)

    // Decode permalink
    if (this.replaceState_) this.setPosition()
  }

  /**
   * Get the initial position passed by the url
   */
  getInitialPosition() {
    return this.get('initial')
  }

  /**
   * Set the map instance the control associated with.
   * @param {ol.Map} map The map instance.
   */
  setMap(map) {
    if (this._listener) {
      ol_Observable_unByKey(this._listener.change)
      ol_Observable_unByKey(this._listener.moveend)
    }
    this._listener = null

    super.setMap.call(this, map)

    // Get change 
    if (map) {
      this._listener = {
        change: map.getLayerGroup().on('change', this.layerChange_.bind(this)),
        moveend: map.on('moveend', this.viewChange_.bind(this))
      }
      this.setPosition()
    }
  }
  /** Get layer given a permalink name (permalink propertie in the layer)
  *	@param {string} the permalink to search for
  *	@param {Array<ol.layer>|undefined} an array of layer to search in
  *	@return {ol.layer|false}
  */
  getLayerByLink(id, layers) {
    if (!layers && this.getMap())
      layers = this.getMap().getLayers().getArray()
    for (var i = 0; i < layers.length; i++) {
      if (layers[i].get('permalink') == id)
        return layers[i]
      // Layer Group
      if (layers[i].getLayers) {
        var li = this.getLayerByLink(id, layers[i].getLayers().getArray())
        if (li)
          return li
      }
    }
    return false
  }
  /** Set coordinates as geohash
   * @param {boolean}
   */
  setGeohash(b) {
    this.set('geohash', b)
    this.setUrlParam()
  }
  /** Set map position according to the current link
   * @param {boolean} [force=false] if true set the position even if urlReplace is disabled
   */
  setPosition(force) {
    var map = this.getMap()
    if (!map)
      return

    var hash = (this.replaceState_ || force) ? document.location.hash || document.location.search : ''
    if (!hash && this._localStorage) {
      try {
        hash = localStorage['ol@permalink']
      } catch (e) { console.warn('Failed to access localStorage...')} 
    }
    if (!hash)
      return

    var i, t, param = {}
    hash = hash.replace(/(^#|^\?)/, "").split("&")
    for (i = 0; i < hash.length; i++) {
      t = hash[i].split("=")
      param[t[0]] = t[1]
    }
    if (param.gh) {
      var ghash = param.gh.split('-')
      var lonlat = ol_geohash_toLonLat(ghash[0])
      param.lon = lonlat[0]
      param.lat = lonlat[1]
      param.z = ghash[1]
    }
    var c = ol_proj_transform([Number(param.lon), Number(param.lat)], 'EPSG:4326', map.getView().getProjection())
    if (c[0] && c[1])
      map.getView().setCenter(c)
    if (param.z)
      map.getView().setZoom(Number(param.z))
    if (param.r)
      map.getView().setRotation(Number(param.r))

    // Reset layers visibility
    function resetLayers(layers) {
      if (!layers)
        layers = map.getLayers().getArray()
      for (var i = 0; i < layers.length; i++) {
        if (layers[i].get('permalink')) {
          layers[i].setVisible(false)
          // console.log("hide "+layers[i].get('permalink'));
        }
        if (layers[i].getLayers) {
          resetLayers(layers[i].getLayers().getArray())
        }
      }
    }

    if (param.l) {
      resetLayers()

      var l = param.l.split("|")
      for (i = 0; i < l.length; i++) {
        t = l[i].split(":")
        var li = this.getLayerByLink(t[0])
        var op = Number(t[1])
        if (li) {
          li.setOpacity(op)
          li.setVisible(true)
        }
      }
    }
  }
  /**
   * Get the parameters added to the url. The object can be changed to add new values.
   * @return {Object} a key value object added to the url as &key=value
   * @api stable
   */
  getUrlParams() {
    return this.search_
  }
  /**
   * Set a parameter to the url.
   * @param {string} key the key parameter
   * @param {string|undefined} value the parameter's value, if undefined or empty string remove the parameter
   * @api stable
   */
  setUrlParam(key, value) {
    if (key) {
      if (value === undefined || value === '')
        delete (this.search_[encodeURIComponent(key)])
      else
        this.search_[encodeURIComponent(key)] = encodeURIComponent(value)
    }
    this.viewChange_()
  }
  /**
   * Get a parameter url.
   * @param {string} key the key parameter
   * @return {string} the parameter's value or empty string if not set
   * @api stable
   */
  getUrlParam(key) {
    return decodeURIComponent(this.search_[encodeURIComponent(key)] || '')
  }
  /**
   * Has a parameter url.
   * @param {string} key the key parameter
   * @return {boolean}
   * @api stable
   */
  hasUrlParam(key) {
    return this.search_.hasOwnProperty(encodeURIComponent(key))
  }
  /** Get the permalink
   * @param {boolean|string} [search=false] false: return full link | true: return the search string only | 'position': return position string
   * @return {permalink}
   */
  getLink(search) {
    var map = this.getMap()
    var c = ol_proj_transform(map.getView().getCenter(), map.getView().getProjection(), 'EPSG:4326')
    var z = Math.round(map.getView().getZoom() * 10) / 10
    var r = map.getView().getRotation()
    var l = this.layerStr_

    // Change anchor
    var anchor = (r ? "&r=" + (Math.round(r * 10000) / 10000) : "") + (l ? "&l=" + l : "")
    if (this.get('geohash')) {
      var ghash = ol_geohash_fromLonLat(c, 8)
      anchor = "gh=" + ghash + '-' + z + anchor
    } else {
      anchor = "lon=" + c[0].toFixed(this.fixed_) + "&lat=" + c[1].toFixed(this.fixed_) + "&z=" + z + anchor
    }

    if (search === 'position')
      return anchor

    // Add other params
    for (var i in this.search_) {
      anchor += "&" + i + (typeof (this.search_[i]) !== 'undefined' ? "=" + this.search_[i] : '')
    }
    if (search)
      return anchor

    //return document.location.origin+document.location.pathname+this.hash_+anchor;
    return document.location.protocol + "//" + document.location.host + document.location.pathname + this.hash_ + anchor
  }
  /** Check if urlreplace is on
   * @return {boolean}
   */
  getUrlReplace() {
    return this.replaceState_
  }
  /** Enable / disable url replacement (replaceSate)
   *	@param {bool}
   */
  setUrlReplace(replace) {
    try {
      this.replaceState_ = replace
      if (!replace) {
        var s = ""
        for (var i in this.search_) {
          s += (s == "" ? "?" : "&") + i + (typeof (this.search_[i]) !== 'undefined' ? "=" + this.search_[i] : '')
        }
        this.replaceUrl_(document.location.origin + document.location.pathname + s, true)
      } else {
        this.replaceUrl_(this.getLink(), true)
      }
    } catch (e) { /* ok */ }
    /*
    if (this._localStorage) {
      localStorage['ol@permalink'] = this.getLink(true);
    }
    */
  }
  /** Refresh the url
   * @private
   */
  replaceUrl_(url, force) {
    clearTimeout(this.refreshTout_)
    if (force) {
      window.history.replaceState(null, null, url)
    } else {
      this.refreshTout_ = setTimeout(function() {
        window.history.replaceState(null, null, url)
      }, this.get('refreshDelay'))
    }
  }
  /**
   * On view change refresh link
   * @param {ol.event} The map instance.
   * @private
   */
  viewChange_() {
    try {
      if (this.replaceState_) {
        this.replaceUrl_(this.getLink())
      }
    } catch (e) { /* ok */ }
    if (this._localStorage) {
      try {
        localStorage['ol@permalink'] = this.getLink(this._localStorage)
      } catch (e) { console.warn('Failed to access localStorage...')} 
    }
  }
  /**
   * Layer change refresh link
   * @private
   */
  layerChange_() {
    // Prevent multiple change at the same time
    if (this._tout) {
      clearTimeout(this._tout)
      this._tout = null
    }
    this._tout = setTimeout(function () {
      this._tout = null
      // Get layers
      var l = ""
      function getLayers(layers) {
        for (var i = 0; i < layers.length; i++) {
          if (layers[i].getVisible() && layers[i].get("permalink")) {
            if (l)
              l += "|"
            l += layers[i].get("permalink") + ":" + layers[i].get("opacity")
          }
          // Layer Group
          if (layers[i].getLayers)
            getLayers(layers[i].getLayers().getArray())
        }
      }
      getLayers(this.getMap().getLayers().getArray())
      this.layerStr_ = l

      this.viewChange_()
    }.bind(this), 200)
  }
}

export default ol_control_Permalink
