import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js'
import ol_control_Control from 'ol/control/Control.js'
import ol_ext_element from '../util/element.js'
import ol_ext_getMapCanvas from '../util/getMapCanvas.js'

/** Image line control
 *
 * @constructor
 * @extends {ol.control.Control}
 * @fires select
 * @fires collapse
 * @param {Object=} options Control options.
 *	@param {String} options.className class of the control
 *	@param {Array<ol.source.Vector>|ol.source.Vector} options.source vector sources that contains the images
 *	@param {Array<ol.layer.Vector>} options.layers A list of layers to display images. If no source and no layers, all visible layers will be considered.
 *	@param {function} options.getImage a function that gets a feature and return the image url or false if no image to Show, default return the img propertie
 *	@param {function} options.getTitle a function that gets a feature and return the title, default return an empty string
 *	@param {boolean} options.collapsed the line is collapse, default false
 *	@param {boolean} options.collapsible the line is collapsible, default false
 *	@param {number} options.maxFeatures the maximum image element in the line, default 100
 *	@param {number} options.useExtent only show feature in the current extent
 *	@param {boolean} options.hover select image on hover, default false
 *	@param {string|boolean} options.linkColor link color or false if no link, default false
 */
var ol_control_Imageline = class olcontrolImageline extends ol_control_Control {
  constructor(options) {

    var element = ol_ext_element.create('DIV', {
      className: (options.className || '') + ' ol-imageline'
        + (options.target ? '' : ' ol-unselectable ol-control')
        + (options.collapsed && options.collapsible ? 'ol-collapsed' : '')
    });

    // Initialize
    super({
      element: element,
      target: options.target
    });

    if (!options.target && options.collapsible) {
      ol_ext_element.create('BUTTON', {
        type: 'button',
        click: function () {
          this.toggle();
        }.bind(this),
        parent: element
      });
    }

    // Source 
    if (options.source)
      this._sources = options.source.forEach ? options.source : [options.source];
    if (options.layers) {
      this.setLayers(options.layers);
    }

    // Scroll imageline
    this._setScrolling();
    this._scrolldiv.addEventListener("scroll", function () {
      if (this.getMap())
        this.getMap().render();
    }.bind(this));

    // Parameters
    if (typeof (options.getImage) === 'function')
      this._getImage = options.getImage;
    if (typeof (options.getTitle) === 'function')
      this._getTitle = options.getTitle;

    this.set('maxFeatures', options.maxFeatures || 100);
    this.set('linkColor', options.linkColor || false);
    this.set('hover', options.hover || false);
    this.set('useExtent', options.useExtent || false);

    this.refresh();
  }
  /**
   * Remove the control from its current map and attach it to the new map.
   * @param {ol.Map} map Map.
   * @api stable
   */
  setMap(map) {
    if (this._listener) {
      this._listener.forEach(function (l) {
        ol_Observable_unByKey(l);
      }.bind(this));
    }
    this._listener = null;

    super.setMap(map);

    if (map) {
      this._listener = [
        map.on('postcompose', this._drawLink.bind(this)),
        map.on('moveend', function () {
          if (this.get('useExtent'))
            this.refresh();
        }.bind(this))
      ];
      this.refresh();
    }
  }
  /** Set layers to use in the control
   * @param {Array<ol.Layer>} layers
   */
  setLayers(layers) {
    this._sources = this._getSources(layers);
  }
  /** Get source from a set of layers
   * @param {Array<ol.Layer>} layers
   * @returns {Array<ol.source.Vector>}
   * @private
   */
  _getSources(layers) {
    var sources = [];
    layers.forEach(function (l) {
      if (l.getVisible()) {
        if (l.getSource() && l.getSource().getFeatures)
          sources.push(l.getSource());
        else if (l.getLayers)
          this._getSources(l.getLayers());
      }
    }.bind(this));
    return sources;
  }
  /** Set useExtent param and refresh the line
   * @param {boolean} b
   */
  useExtent(b) {
    this.set('useExtent', b);
    this.refresh();
  }
  /** Is the line collapsed
   * @return {boolean}
   */
  isCollapsed() {
    return this.element.classList.contains('ol-collapsed');
  }
  /** Collapse the line
   * @param {boolean} b
   */
  collapse(b) {
    if (b)
      this.element.classList.add('ol-collapsed');
    else
      this.element.classList.remove('ol-collapsed');
    if (this.getMap()) {
      setTimeout(function () {
        this.getMap().render();
      }.bind(this), this.isCollapsed() ? 0 : 250);
    }
    this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() });
  }
  /** Collapse the line
   */
  toggle() {
    this.element.classList.toggle('ol-collapsed');
    if (this.getMap()) {
      setTimeout(function () {
        this.getMap().render();
      }.bind(this), this.isCollapsed() ? 0 : 250);
    }
    this.dispatchEvent({ type: 'collapse', collapsed: this.isCollapsed() });
  }
  /** Default function to get an image of a feature
   * @param {ol.Feature} f
   * @private
   */
  _getImage(f) {
    return f.get('img');
  }
  /** Default function to get an image title
   * @param {ol.Feature} f
   * @private
   */
  _getTitle( /* f */) {
    return '';
  }
  /**
   * Get features
   * @return {Array<ol.Feature>}
   */
  getFeatures() {
    var map = this.getMap();
    if (!map)
      return [];
    var features = [];
    var sources = this._sources || this._getSources(map.getLayers());
    sources.forEach(function (s) {
      if (features.length < this.get('maxFeatures')) {
        if (!this.get('useExtent') || !map) {
          features.push(s.getFeatures());
        } else {
          var extent = map.getView().calculateExtent(map.getSize());
          features.push(s.getFeaturesInExtent(extent));
        }
      }
    }.bind(this));
    return features;
  }
  /** Set element scrolling with a acceleration effect on desktop
   * (on mobile it uses the scroll of the browser)
   * @private
   */
  _setScrolling() {
    var elt = this._scrolldiv = ol_ext_element.create('DIV', {
      parent: this.element
    });

    ol_ext_element.scrollDiv(elt, {
      // Prevent selection when moving
      onmove: function (b) {
        this._moving = b;
      }.bind(this)
    });
    elt.addEventListener('scroll', this._updateScrollBounds.bind(this));
    this._updateScrollBounds();
  }
  /** Set element scrolling with a acceleration effect on desktop
   * (on mobile it uses the scroll of the browser)
   * @private
   */
  _updateScrollBounds() {
    var elt = this._scrolldiv;
    if (elt.scrollLeft < 5) {
      this.element.classList.add('ol-scroll0');
    } else {
      this.element.classList.remove('ol-scroll0');
    }
    if (elt.scrollWidth - elt.scrollLeft - elt.offsetWidth < 5) {
      this.element.classList.add('ol-scroll1');
    } else {
      this.element.classList.remove('ol-scroll1');
    }
  }
  /**
   * Refresh the imageline with new data
   */
  refresh() {
    this._scrolldiv.innerHTML = '';
    var allFeatures = this.getFeatures();
    var current = this._select ? this._select.feature : null;

    if (this._select)
      this._select.elt = null;
    this._iline = [];
    if (this.getMap())
      this.getMap().render();

    // Add a new image
    var addImage = function (f) {
      if (this._getImage(f)) {
        var img = ol_ext_element.create('DIV', {
          className: 'ol-image',
          parent: this._scrolldiv
        });
        ol_ext_element.create('IMG', {
          src: this._getImage(f),
          parent: img
        }).addEventListener('load', function () {
          this.classList.add('ol-loaded');
        });
        ol_ext_element.create('SPAN', {
          html: this._getTitle(f),
          parent: img
        });
        // Current image
        var sel = { elt: img, feature: f };
        // On click > dispatch event
        img.addEventListener('click', function () {
          if (!this._moving) {
            this._scrolldiv.scrollLeft = img.offsetLeft
            + ol_ext_element.getStyle(img, 'width') / 2
            - ol_ext_element.getStyle(this.element, 'width') / 2;
            if (this._select) this._select.elt.classList.remove('select');
            this._select = sel;
            if (this._select) this._select.elt.classList.add('select');
            this.dispatchEvent({ type: 'select', feature: f });
          }
        }.bind(this));
        // Show link
        img.addEventListener('mouseover', function (e) {
          if (this.get('hover')) {
            if (this._select)
              this._select.elt.classList.remove('select');
            this._select = sel;
            this._select.elt.classList.add('select');
            this.getMap().render();
            e.stopPropagation();
          }
        }.bind(this));
        // Remove link
        img.addEventListener('mouseout', function (e) {
          if (this.get('hover')) {
            if (this._select)
              this._select.elt.classList.remove('select');
            this._select = false;
            this.getMap().render();
            e.stopPropagation();
          }
        }.bind(this));
        // Prevent image dragging
        img.ondragstart = function () { return false; };
        // Add image
        this._iline.push(sel);
        if (current === f) {
          this._select = sel;
          sel.elt.classList.add('select');
        }
      }
    }.bind(this);

    // Add images 
    var nb = this.get('maxFeatures');
    allFeatures.forEach(function (features) {
      for (var i = 0, f; f = features[i]; i++) {
        if (nb-- < 0)
          break;
        addImage(f);
      }
    }.bind(this));
    // Add the selected one
    if (this._select && this._select.feature && !this._select.elt) {
      addImage(this._select.feature);
    }
    this._updateScrollBounds();
  }
  /** Center image line on a feature
   * @param {ol.feature} feature
   * @param {boolean} scroll scroll the line to center on the image, default true
   * @api
   */
  select(feature, scroll) {
    this._select = false;
    // Find the image
    this._iline.forEach(function (f) {
      if (f.feature === feature) {
        f.elt.classList.add('select');
        this._select = f;
        if (scroll !== false) {
          this._scrolldiv.scrollLeft = f.elt.offsetLeft
            + ol_ext_element.getStyle(f.elt, 'width') / 2
            - ol_ext_element.getStyle(this.element, 'width') / 2;
        }
      } else {
        f.elt.classList.remove('select');
      }
    }.bind(this));
  }
  /** Draw link on the map
   * @private
   */
  _drawLink(e) {
    if (!this.get('linkColor') | this.isCollapsed())
      return;
    var map = this.getMap();
    if (map && this._select && this._select.elt) {
      var ctx = e.context || ol_ext_getMapCanvas(this.getMap()).getContext('2d');
      var ratio = e.frameState.pixelRatio;

      var pt = [
        this._select.elt.offsetLeft
        - this._scrolldiv.scrollLeft
        + ol_ext_element.getStyle(this._select.elt, 'width') / 2,
        parseFloat(ol_ext_element.getStyle(this.element, 'top')) || this.getMap().getSize()[1]
      ];
      var geom = this._select.feature.getGeometry().getFirstCoordinate();
      geom = this.getMap().getPixelFromCoordinate(geom);

      ctx.save();
      ctx.fillStyle = this.get('linkColor');
      ctx.beginPath();
      if (geom[0] > pt[0]) {
        ctx.moveTo((pt[0] - 5) * ratio, pt[1] * ratio);
        ctx.lineTo((pt[0] + 5) * ratio, (pt[1] + 5) * ratio);
      } else {
        ctx.moveTo((pt[0] - 5) * ratio, (pt[1] + 5) * ratio);
        ctx.lineTo((pt[0] + 5) * ratio, pt[1] * ratio);
      }
      ctx.lineTo(geom[0] * ratio, geom[1] * ratio);
      ctx.fill();
      ctx.restore();
    }
  }
}

export default ol_control_Imageline