'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _throttleDebounce = require('throttle-debounce');

var _parentPosition = require('./utils/parent-position');

var _parentPosition2 = _interopRequireDefault(_parentPosition);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var ANIMATION_TIME = 150;

function wikimedia(x, y, z) {
  var retina = typeof window !== 'undefined' && window.devicePixelRatio >= 2;
  return 'https://maps.wikimedia.org/osm-intl/' + z + '/' + x + '/' + y + (retina ? '@2x' : '') + '.png';
}

// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
var lng2tile = function lng2tile(lon, zoom) {
  return (lon + 180) / 360 * Math.pow(2, zoom);
};
var lat2tile = function lat2tile(lat, zoom) {
  return (1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom);
};

function tile2lng(x, z) {
  return x / Math.pow(2, z) * 360 - 180;
}

function tile2lat(y, z) {
  var n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
  return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}

function getMouseCoords(dom, event) {
  var parent = (0, _parentPosition2.default)(dom);
  return [event.clientX - parent.x, event.clientY - parent.y];
}

var Map = function (_Component) {
  _inherits(Map, _Component);

  function Map(props) {
    _classCallCheck(this, Map);

    var _this = _possibleConstructorReturn(this, (Map.__proto__ || Object.getPrototypeOf(Map)).call(this, props));

    _this.setCenterZoomTarget = function (center, zoom, fromProps) {
      // TODO: if center diff is more than 2 screens, no animation

      if (_this.props.animate) {
        if (_this._isAnimating) {
          window.cancelAnimationFrame(_this._animFrame);

          var _this$animationStep = _this.animationStep(window.performance.now()),
              centerStep = _this$animationStep.centerStep,
              zoomStep = _this$animationStep.zoomStep;

          _this._centerStart = centerStep;
          _this._zoomStart = zoomStep;
        } else {
          _this._isAnimating = true;
          _this._centerStart = [_this.state.center[0], _this.state.center[1]];
          _this._zoomStart = _this.state.zoom;
        }

        _this._animationStart = window.performance.now();
        _this._animationEnd = _this._animationStart + ANIMATION_TIME;

        _this._centerTarget = center;
        _this._zoomTarget = zoom;

        _this._animFrame = window.requestAnimationFrame(_this.animate);
      } else {
        _this.setCenterZoom(center, zoom, fromProps);
      }
    };

    _this.animationStep = function (timestamp) {
      var length = _this._animationEnd - _this._animationStart;
      var progress = Math.max(timestamp - _this._animationStart, 0);
      var percentage = progress / length;

      var zoomStep = _this._zoomStart + (_this._zoomTarget - _this._zoomStart) * percentage;

      var centerStep = [_this._centerStart[0] + (_this._centerTarget[0] - _this._centerStart[0]) * percentage, _this._centerStart[1] + (_this._centerTarget[1] - _this._centerStart[1]) * percentage];

      return { centerStep: centerStep, zoomStep: zoomStep };
    };

    _this.animate = function (timestamp) {
      if (timestamp >= _this._animationEnd) {
        _this._isAnimating = false;
        _this.setCenterZoom(_this._centerTarget, _this._zoomTarget, true);
      } else {
        var _this$animationStep2 = _this.animationStep(timestamp),
            centerStep = _this$animationStep2.centerStep,
            zoomStep = _this$animationStep2.zoomStep;

        _this.setCenterZoom(centerStep, zoomStep, true);

        _this._animFrame = window.requestAnimationFrame(_this.animate);
      }
    };

    _this.setCenterZoom = function (center, zoom) {
      var fromProps = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

      if (Math.round(_this.state.zoom) !== Math.round(zoom)) {
        (function () {
          var tileValues = _this.tileValues(_this.props, _this.state);
          var nextValues = _this.tileValues(_this.props, { center: center, zoom: zoom });
          var oldTiles = _this.state.oldTiles;

          _this.setState({
            oldTiles: oldTiles.filter(function (o) {
              return o.roundedZoom !== tileValues.roundedZoom;
            }).concat(tileValues)
          });

          var loadTracker = {};

          for (var x = nextValues.tileMinX; x <= nextValues.tileMaxX; x++) {
            for (var y = nextValues.tileMinY; y <= nextValues.tileMaxY; y++) {
              var key = x + '-' + y + '-' + nextValues.roundedZoom;
              loadTracker[key] = false;
            }
          }

          _this._loadTracker = loadTracker;
        })();
      }

      _this.setState({ center: center, zoom: zoom });
      if (!fromProps) {
        _this.syncToProps();
      }
    };

    _this.imageLoaded = function (key) {
      if (_this._loadTracker && key in _this._loadTracker) {
        _this._loadTracker[key] = true;

        // all loaded
        if (Object.keys(_this._loadTracker).filter(function (k) {
          return !_this._loadTracker[k];
        }).length === 0) {
          _this.setState({ oldTiles: [] });
        }
      }
    };

    _this.handleTouchStart = function (event) {
      var _this$props = _this.props,
          width = _this$props.width,
          height = _this$props.height;


      if (event.touches.length === 1) {
        var touch = event.touches[0];
        var coords = getMouseCoords(_this._containerRef, touch);

        if (coords[0] >= 0 && coords[1] >= 0 && coords[0] < width && coords[1] < height) {
          _this._touchStartCoords = [[touch.clientX, touch.clientY]];
          event.preventDefault();
        }
        // added second finger and first one was in the area
      } else if (event.touches.length === 2 && _this._touchStartCoords) {
        event.preventDefault();

        if (_this.state.centerDelta || _this.state.zoomDelta) {
          _this.sendDeltaChange();
        }

        var t1 = event.touches[0];
        var t2 = event.touches[1];

        _this._touchStartCoords = [[t1.clientX, t1.clientY], [t2.clientX, t2.clientY]];
        _this._touchStartMidPoint = [(t1.clientX + t2.clientX) / 2, (t1.clientY + t2.clientY) / 2];
        _this._touchStartDistance = Math.sqrt(Math.pow(t1.clientX - t2.clientX, 2) + Math.pow(t1.clientY - t2.clientY, 2));
      }
    };

    _this.handleTouchMove = function (event) {
      if (event.touches.length === 1 && _this._touchStartCoords) {
        event.preventDefault();
        var touch = event.touches[0];

        _this.setState({
          centerDelta: [touch.clientX - _this._touchStartCoords[0][0], touch.clientY - _this._touchStartCoords[0][1]]
        });
      } else if (event.touches.length === 2 && _this._touchStartCoords) {
        var _this$props2 = _this.props,
            width = _this$props2.width,
            height = _this$props2.height;
        var zoom = _this.state.zoom;


        event.preventDefault();

        var t1 = event.touches[0];
        var t2 = event.touches[1];

        var parent = (0, _parentPosition2.default)(_this._containerRef);

        var midPoint = [(t1.clientX + t2.clientX) / 2, (t1.clientY + t2.clientY) / 2];
        var midPointDiff = [midPoint[0] - _this._touchStartMidPoint[0], midPoint[1] - _this._touchStartMidPoint[1]];

        var distance = Math.sqrt(Math.pow(t1.clientX - t2.clientX, 2) + Math.pow(t1.clientY - t2.clientY, 2));

        var zoomDelta = Math.min(18, zoom + Math.log2(distance / _this._touchStartDistance)) - zoom;
        var scale = Math.pow(2, zoomDelta);

        var centerDiffDiff = [(parent.x + width / 2 - midPoint[0]) * (scale - 1), (parent.y + height / 2 - midPoint[1]) * (scale - 1)];

        _this.setState({
          zoomDelta: zoomDelta,
          centerDelta: [centerDiffDiff[0] + midPointDiff[0] * scale, centerDiffDiff[1] + midPointDiff[1] * scale]
        });
      }
    };

    _this.handleTouchEnd = function (event) {
      if (_this._touchStartCoords) {
        event.preventDefault();
        _this.sendDeltaChange();
        if (event.touches.length === 0) {
          _this._touchStartCoords = null;
        } else if (event.touches.length === 1) {
          var touch = event.touches[0];
          _this._touchStartCoords = [[touch.clientX, touch.clientY]];
        }
      }
    };

    _this.handleMouseDown = function (event) {
      var _this$props3 = _this.props,
          width = _this$props3.width,
          height = _this$props3.height;

      var coords = getMouseCoords(_this._containerRef, event);

      if (coords[0] >= 0 && coords[1] >= 0 && coords[0] < width && coords[1] < height) {
        _this._mouseDown = true;
        _this._dragStart = coords;
        event.preventDefault();
      }
    };

    _this.handleMouseMove = function (event) {
      _this._mousePosition = getMouseCoords(_this._containerRef, event);

      if (_this._mouseDown && _this._dragStart) {
        _this.setState({
          centerDelta: [_this._mousePosition[0] - _this._dragStart[0], _this._mousePosition[1] - _this._dragStart[1]]
        });
      }
    };

    _this.handleMouseUp = function (event) {
      if (_this._mouseDown) {
        _this.sendDeltaChange();
        _this._mouseDown = false;
      }
    };

    _this.sendDeltaChange = function () {
      var _this$state = _this.state,
          center = _this$state.center,
          zoom = _this$state.zoom,
          centerDelta = _this$state.centerDelta,
          zoomDelta = _this$state.zoomDelta;


      if (centerDelta || zoomDelta !== 0) {
        var lng = tile2lng(lng2tile(center[1], zoom + zoomDelta) - (centerDelta ? centerDelta[0] / 256.0 : 0), zoom + zoomDelta);
        var lat = tile2lat(lat2tile(center[0], zoom + zoomDelta) - (centerDelta ? centerDelta[1] / 256.0 : 0), zoom + zoomDelta);
        _this.setCenterZoom([lat, lng], zoom + zoomDelta);
      }

      _this.setState({
        centerDelta: null,
        zoomDelta: 0
      });
    };

    _this.syncToProps = function () {
      var onBoundsChanged = _this.props.onBoundsChanged;

      if (onBoundsChanged) {
        onBoundsChanged({ center: _this.state.center, zoom: _this.state.zoom });
      }
    };

    _this.handleWheel = function (event) {
      event.preventDefault();
      _this.handleWheelThrottled(event);
    };

    _this.handleWheelThrottled = (0, _throttleDebounce.throttle)(100, true, function (event) {
      if (event.deltaY < 0) {
        _this.zoomAroundMouse(1);
      } else if (event.deltaY > 0) {
        _this.zoomAroundMouse(-1);
      }
    });

    _this.handleContextMenu = function (event) {
      event.preventDefault();
    };

    _this.pixelToLatLng = function (x, y, zoom) {
      var _this$props4 = _this.props,
          width = _this$props4.width,
          height = _this$props4.height;
      var center = _this.state.center;


      var pointDiff = [(x - width / 2) / 256.0, (y - height / 2) / 256.0];

      var tileX = lng2tile(center[1], zoom) + pointDiff[0];
      var tileY = lat2tile(center[0], zoom) + pointDiff[1];

      return [tile2lat(tileY, zoom), tile2lng(tileX, zoom)];
    };

    _this.latLngToPixel = function (lat, lng, zoom) {
      var _this$props5 = _this.props,
          width = _this$props5.width,
          height = _this$props5.height;
      var center = _this.state.center;


      var tileCenterX = lng2tile(center[1], zoom);
      var tileCenterY = lat2tile(center[0], zoom);

      var tileX = lng2tile(lng, zoom);
      var tileY = lat2tile(lat, zoom);

      return [(tileX - tileCenterX) * 256.0 + width / 2, (tileY - tileCenterY) * 256.0 + height / 2];
    };

    _this.zoomAroundMouse = function (zoomDiff) {
      var _this$state2 = _this.state,
          center = _this$state2.center,
          zoom = _this$state2.zoom;


      if (!_this._mousePosition || zoom + zoomDiff < 1 || zoom + zoomDiff > 18) {
        return;
      }

      var latLngNow = _this.pixelToLatLng(_this._mousePosition[0], _this._mousePosition[1], zoom);

      var latLngZoomed = _this.pixelToLatLng(_this._mousePosition[0], _this._mousePosition[1], zoom + zoomDiff);

      var diffLat = latLngZoomed[0] - latLngNow[0];
      var diffLng = latLngZoomed[1] - latLngNow[1];

      _this.setCenterZoomTarget([center[0] - diffLat, center[1] - diffLng], zoom + zoomDiff);
    };

    _this.setRef = function (dom) {
      _this._containerRef = dom;
    };

    _this._mousePosition = null;
    _this._dragStart = null;
    _this._mouseDown = false;
    _this._touchStartCoords = null;

    _this._isAnimating = false;
    _this._animationStart = null;
    _this._animationEnd = null;
    _this._centerTarget = null;
    _this._zoomTarget = null;

    _this.state = {
      zoom: props.zoom,
      center: props.center,
      zoomDelta: 0,
      centerDelta: null,
      oldTiles: []
    };
    return _this;
  }

  _createClass(Map, [{
    key: 'componentDidMount',
    value: function componentDidMount() {
      var wa = window.addEventListener;
      wa('mousedown', this.handleMouseDown);
      wa('mouseup', this.handleMouseUp);
      wa('mousemove', this.handleMouseMove);

      wa('touchstart', this.handleTouchStart);
      wa('touchmove', this.handleTouchMove);
      wa('touchend', this.handleTouchEnd);
    }
  }, {
    key: 'componentWillUnmount',
    value: function componentWillUnmount() {
      var wr = window.removeEventListener;
      wr('mousedown', this.handleMouseDown);
      wr('mouseup', this.handleMouseUp);
      wr('mousemove', this.handleMouseMove);

      wr('touchstart', this.handleTouchStart);
      wr('touchmove', this.handleTouchMove);
      wr('touchend', this.handleTouchEnd);
    }
  }, {
    key: 'componentWillReceiveProps',
    value: function componentWillReceiveProps(nextProps) {
      if (Math.abs(nextProps.zoom - this.state.zoom) > 0.001 || Math.abs(nextProps.center[0] - this.state.center[0]) > 0.0001 || Math.abs(nextProps.center[1] - this.state.center[1]) > 0.0001) {
        this.setCenterZoomTarget(nextProps.center, nextProps.zoom, true);
      }
    }
  }, {
    key: 'tileValues',
    value: function tileValues(props, state) {
      var width = props.width,
          height = props.height;
      var center = state.center,
          zoom = state.zoom,
          centerDelta = state.centerDelta,
          zoomDelta = state.zoomDelta;


      var roundedZoom = Math.round(zoom + zoomDelta);
      var zoomDiff = zoom + zoomDelta - roundedZoom;

      var scale = Math.pow(2, zoomDiff);
      var scaleWidth = width / scale;
      var scaleHeight = height / scale;

      var tileCenterX = lng2tile(center[1], roundedZoom) - (centerDelta ? centerDelta[0] / 256.0 / scale : 0);
      var tileCenterY = lat2tile(center[0], roundedZoom) - (centerDelta ? centerDelta[1] / 256.0 / scale : 0);

      var halfWidth = scaleWidth / 2 / 256.0;
      var halfHeight = scaleHeight / 2 / 256.0;

      var tileMinX = Math.floor(tileCenterX - halfWidth);
      var tileMaxX = Math.floor(tileCenterX + halfWidth);

      var tileMinY = Math.floor(tileCenterY - halfHeight);
      var tileMaxY = Math.floor(tileCenterY + halfHeight);

      return {
        tileMinX: tileMinX,
        tileMaxX: tileMaxX,
        tileMinY: tileMinY,
        tileMaxY: tileMaxY,
        tileCenterX: tileCenterX,
        tileCenterY: tileCenterY,
        roundedZoom: roundedZoom,
        zoomDelta: zoomDelta,
        scaleWidth: scaleWidth,
        scaleHeight: scaleHeight,
        scale: scale
      };
    }
  }, {
    key: 'renderTiles',
    value: function renderTiles() {
      var _this2 = this;

      var oldTiles = this.state.oldTiles;

      var mapUrl = this.props.provider || wikimedia;

      var _tileValues = this.tileValues(this.props, this.state),
          tileMinX = _tileValues.tileMinX,
          tileMaxX = _tileValues.tileMaxX,
          tileMinY = _tileValues.tileMinY,
          tileMaxY = _tileValues.tileMaxY,
          tileCenterX = _tileValues.tileCenterX,
          tileCenterY = _tileValues.tileCenterY,
          roundedZoom = _tileValues.roundedZoom,
          scaleWidth = _tileValues.scaleWidth,
          scaleHeight = _tileValues.scaleHeight,
          scale = _tileValues.scale;

      var tiles = [];

      for (var i = 0; i < oldTiles.length; i++) {
        var old = oldTiles[i];
        var zoomDiff = old.roundedZoom - roundedZoom;

        if (Math.abs(zoomDiff) > 4 || zoomDiff === 0) {
          continue;
        }

        var pow = 1 / Math.pow(2, zoomDiff);
        var xDiff = -(tileMinX - old.tileMinX * pow) * 256;
        var yDiff = -(tileMinY - old.tileMinY * pow) * 256;

        for (var x = old.tileMinX; x <= old.tileMaxX; x++) {
          for (var y = old.tileMinY; y <= old.tileMaxY; y++) {
            tiles.push({
              key: x + '-' + y + '-' + old.roundedZoom,
              url: mapUrl(x, y, old.roundedZoom),
              left: xDiff + (x - old.tileMinX) * 256 * pow,
              top: yDiff + (y - old.tileMinY) * 256 * pow,
              width: 256 * pow,
              height: 256 * pow,
              active: false
            });
          }
        }
      }

      for (var _x2 = tileMinX; _x2 <= tileMaxX; _x2++) {
        for (var _y = tileMinY; _y <= tileMaxY; _y++) {
          tiles.push({
            key: _x2 + '-' + _y + '-' + roundedZoom,
            url: mapUrl(_x2, _y, roundedZoom),
            left: (_x2 - tileMinX) * 256,
            top: (_y - tileMinY) * 256,
            width: 256,
            height: 256,
            active: true
          });
        }
      }

      var boxStyle = {
        width: scaleWidth,
        height: scaleHeight,
        position: 'absolute',
        top: 0,
        left: 0,
        overflow: 'hidden',
        transform: 'scale(' + scale + ', ' + scale + ')',
        transformOrigin: 'top left'
      };

      var left = -((tileCenterX - tileMinX) * 256 - scaleWidth / 2);
      var top = -((tileCenterY - tileMinY) * 256 - scaleHeight / 2);

      var tilesStyle = {
        position: 'absolute',
        width: (tileMaxX - tileMinX + 1) * 256,
        height: (tileMaxY - tileMinY + 1) * 256,
        left: left,
        top: top
      };

      return _react2.default.createElement(
        'div',
        { style: boxStyle },
        _react2.default.createElement(
          'div',
          { style: tilesStyle },
          tiles.map(function (tile) {
            return _react2.default.createElement('img', { key: tile.key,
              src: tile.url,
              width: tile.width,
              height: tile.height,
              onLoad: function onLoad() {
                return _this2.imageLoaded(tile.key);
              },
              style: { position: 'absolute', left: tile.left, top: tile.top, transform: tile.transform, transformOrigin: 'top left', opacity: 1 } });
          })
        )
      );
    }
  }, {
    key: 'renderOverlays',
    value: function renderOverlays() {
      var _this3 = this;

      var _props = this.props,
          width = _props.width,
          height = _props.height;
      var _state = this.state,
          zoom = _state.zoom,
          centerDelta = _state.centerDelta,
          zoomDelta = _state.zoomDelta;


      var childrenWithProps = _react2.default.Children.map(this.props.children, function (child) {
        var _child$props = child.props,
            position = _child$props.position,
            offset = _child$props.offset;

        if (position) {
          var c = _this3.latLngToPixel(position[0], position[1], zoom + zoomDelta);
          return _react2.default.cloneElement(child, {
            left: c[0] - (offset ? offset[0] : 0) + (centerDelta ? centerDelta[0] : 0),
            top: c[1] - (offset ? offset[1] : 0) + (centerDelta ? centerDelta[1] : 0)
          });
        }
      });

      var childrenStyle = {
        position: 'absolute',
        width: width,
        height: height,
        top: 0,
        left: 0
      };

      return _react2.default.createElement(
        'div',
        { style: childrenStyle },
        childrenWithProps
      );
    }
  }, {
    key: 'render',
    value: function render() {
      var _props2 = this.props,
          width = _props2.width,
          height = _props2.height;


      var containerStyle = {
        width: width,
        height: height,
        position: 'relative',
        display: 'inline-block',
        overflow: 'hidden',
        background: '#dddddd'
      };

      return _react2.default.createElement(
        'div',
        { style: containerStyle,
          ref: this.setRef,
          onContextMenu: this.handleContextMenu,
          onWheel: this.handleWheel },
        this.renderTiles(),
        this.renderOverlays()
      );
    }
  }]);

  return Map;
}(_react.Component);

Map.propTypes = {
  center: _react2.default.PropTypes.array,
  zoom: _react2.default.PropTypes.number,
  width: _react2.default.PropTypes.number,
  height: _react2.default.PropTypes.number,
  provider: _react2.default.PropTypes.func,
  children: _react2.default.PropTypes.node,
  animate: _react2.default.PropTypes.bool,

  onBoundsChanged: _react2.default.PropTypes.func
};
Map.defaultProps = {
  animate: true
};
exports.default = Map;