"use strict";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Big map functionality, interactive map of the Prague Castle area which can be opened
 * by clicking at the on-screen GUI button with map image.
 *
 * @author smrceant (Antonin Smrcek)
 *
 * @param {jQuery}        mapElement            SVG element with map
 * @param {jQuery}        mapImageElement       SVG image of the map
 * @param {jQuery}        markerParentElement   sticky figure
 * @param {WindowDialog}  mapDialog             dialog which display the big map
 * @constructor
 */
var Bigmap = function (mapElement, mapImageElement, markerParentElement, mapDialog/*zoomCallback, resizeCallback*/)
{
  var _this = this;

  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

  /**
   * SVG element with map.
   *
   * @type {jQuery}
   */
  this.mapElement = mapElement;

  /**
   * For position computations etc.
   *
   * @type {MapGui}
   */
  this.underlyingMapGui = new MapGui (this);

  /**
   * Sticky figure.
   *
   * @type {jQuery}
   */
  this.marker = $('<img class="markerBigmap" src="data/gui/avatar_icon.svg" langcaption_title="MAP_MARKER_TELEPORT" />');

  /**
   * SVG image of the map.
   *
   * @type {jQuery}
   */
  this.mapImageElement = mapImageElement;

  markerParentElement.append (this.marker);

  ///////////////////////////////////////////////////////////////
  // MAP DRAGGING VARIABLES
  ///////////////////////////////////////////////////////////////

  /**
   * Container used for values associated with dragging the map.
   */
  this.mapDragging =
  {
    /**
     * Value of scrollLeft of the (map) dialog element at the time of drag start.
     *
     * @type {number}
     */
    scrollLeftOnDragStart: 0,

    /**
     * Value of scrollTop of the (map) dialog element at the time of drag start.
     *
     * @type {number}
     */
    scrollTopOnDragStart: 0,

    markerLeftOnDragStart: 0,
    markerTopOnDragStart: 0,

    /**
     * TRUE if we are currently dragging the map (i.e. drag event on the dialog element is in the process).
     *
     * @type {boolean}
     */
    dragging: false
  };

  ///////////////////////////////////////////////////////////////
  // MAP DRAGGING FUNCTIONALITY
  ///////////////////////////////////////////////////////////////

  mapDialog.getDialog().drag (function (ev, dd)
  {
    mapDialog.getDialog().scrollLeft (_this.mapDragging.scrollLeftOnDragStart - dd.deltaX);
    mapDialog.getDialog().scrollTop (_this.mapDragging.scrollTopOnDragStart - dd.deltaY);
  });

  mapDialog.getDialog().drag ("init", function ()
  {
    // we have to remember these scroll values so we can later change them by delta drag distance
    _this.mapDragging.scrollLeftOnDragStart = mapDialog.getDialog()[0].scrollLeft;
    _this.mapDragging.scrollTopOnDragStart  = mapDialog.getDialog()[0].scrollTop;

    _this.mapDragging.dragging = true;

    mapElement.addClass("mapDialogMapDragging");
  });

  mapDialog.getDialog().drag ("end", function ()
  {
    _this.mapDragging.dragging = false;

    mapElement.removeClass("mapDialogMapDragging");
  });

  ///////////////////////////////////////////////////////////////
  // MAP MARKER DRAGGING FUNCTIONALITY
  ///////////////////////////////////////////////////////////////

  // register drag event from plugin, but only for setting the dragging flag and changing the tooltip hint
  this.marker.drag ("init", function (ev, dd)
  {
    _this.mapDragging.dragging = true;
  });

  this.marker.drag ("end", function (ev, dd)
  {
    _this.mapDragging.dragging = false;
  });

  // use jQuery for dragging the marker, because we can specify containment (element in which the marker can be moved)
  this.marker.draggable(
      {
        containment: mapDialog.getDialog(),
        scroll: false,
        revertDuration: 0,

        start: function()
        {

          _this.mapDragging.markerLeftOnDragStart = _this.marker.offset().left;
          _this.mapDragging.markerTopOnDragStart  = _this.marker.offset().top;
        },

        // using revert instead of stop, so we can easily specify whether the marker should revert to its starting position
        revert: function()
        {
          // how far we have moved the marker (in image coordinates)
          var deltaX = Math.round (_this.marker.offset().left - _this.mapDragging.markerLeftOnDragStart);
          var deltaY = Math.round (_this.marker.offset().top - _this.mapDragging.markerTopOnDragStart);

          // compute new coordinates of the marker in the map (in image coordinates)
          var newPlayer2dCoordinatesInMap = new THREE.Vector3();
          // note: the reason for weird indexing is the unprojectInplaceXZ() function
          newPlayer2dCoordinatesInMap.x = _this.underlyingMapGui.getLastCoordinatesInMap().x + deltaX; // (plus, because in both image and map coordinates, the X increases to the right)
          newPlayer2dCoordinatesInMap.z = _this.underlyingMapGui.getLastCoordinatesInMap().y - deltaY; // minus because deltaY is in image coordinates, and image coordinates have flipped Y axis in comparison with the map (GL) coordinates

          // safety check
          var areaName;
          for (var cntRetries = 0, retryLimit = 5; cntRetries < retryLimit; cntRetries++)
          {
            areaName = gui.minimap.findAreaAt2dCoordinatesInMap (newPlayer2dCoordinatesInMap.x, newPlayer2dCoordinatesInMap.z);

            // ok, found a valid area
            if (Util.isPropertyDefined(areaName))
            {
              break;
            }
            // obtained undefined = anti-aliased pixel in the SVG, search for area again, this time one pixel to the left
            else
            {
              newPlayer2dCoordinatesInMap.x -= 1;
            }
          }

          if (areaName)
          {
            newPlayer2dCoordinatesInMap.y = heightmapWorld.getMain().getWorldHeightAt(newPlayer2dCoordinatesInMap.x, newPlayer2dCoordinatesInMap.z, appMain.getCurrentControllingEntity());

            _this.getUnderlyingMap().unprojectInplaceXZ (newPlayer2dCoordinatesInMap);

            // it can happen that player will move the avatar to the area which is defined in the Area map, but there is no data in the heightmap (e.g. Stag Moat currently)
            // this will throw player to the minimum heightmap height;
            // but we want to teleport player to the highest heightmap position :)
            var maxHeightmapHeight = heightmapWorld.getMain().getWorldHeightForDepth (0.0);
            if (Math.abs (newPlayer2dCoordinatesInMap.y - maxHeightmapHeight) <= 0.1)
            {
              newPlayer2dCoordinatesInMap.y = heightmapWorld.getMain().getWorldHeightForDepth (0.05); // safer to teleport to near maximum
            }

            appMain.getCurrentControllingEntity().setPosition (newPlayer2dCoordinatesInMap);
            mapDialog.close();

            // no revert of dragged element (marker)
            return false;
          }
          // we are at "no area" place
          else
          {
            // revert dragged element (marker) to its original location
            return true;
          }
        }
      });

  // there is a bug in the drag jQuery plugin: when we perform a mouse click (so we do not move the mouse at all),
  // the drag end event of the map is not fired; by this, we ensure it is fired every time when the mouse button is released.. little hacky, but whatever!
  mapDialog.getDialog().mouseup( function()
  {
    mapDialog.getDialog().drag ("end");
  });
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// EVENTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Called upon window resize.
 *
 * @private
 */
Bigmap.prototype._resizeCallback = function()
{
  this.underlyingMapGui._resizeCallback();
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// UPDATING THE MINIMAP IMAGE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Updates minimap image.
 *
 * @param {Entity} entity
 */
Bigmap.prototype.updateMinimap = function (entity)
{
  var imgCoord = this.underlyingMapGui.computeCoordinatesInMapImage (entity, false);

  // several things happening there:
  // 1) Alter imgCoord position a little bit - we want the origin of the marker image to be somewhere at the marker feet so it is where the computed coordinates are; we do not want it in standard upper-left corner
  // 2) Divide this altered position by size of the map image element to get percentage sizes; this ensures automatically correct scaling when window dimensions change, when the map is scaled etc.
  this.marker[0].style.left = ((imgCoord.x - this.marker.width() * 0.5)  / 1280) * 100  + "%";
  this.marker[0].style.top  = ((imgCoord.y - this.marker.height()* 0.5) / 659) * 100 + "%";
};


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// GETTERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Retrieves underlying map object used e.g. for projection of 3D coordinates into 2D.
 *
 * @returns {Map}
 */
Bigmap.prototype.getUnderlyingMap = function()
{
  return this.underlyingMapGui.underlyingMap;
};

/**
 *
 * @returns {boolean}   TRUE if we are currently dragging the map (and moving it) in the Map dialog
 */
Bigmap.prototype.isMapDragging = function()
{
  return this.mapDragging.dragging;
};
