"use strict";

function TileCollisionManager() {
}

TileCollisionManager.prototype.continuousMapNodeVecToContinuousObjLayerVec = function(withTiledMapNode, continuousMapNodeVec) {
  const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap);

  const mapOrientation = tiledMapIns.getMapOrientation();
  const mapTileRectilinearSize = tiledMapIns.getTileSize();

  switch (mapOrientation) {
    case cc.TiledMap.Orientation.ORTHO:
      return continuousMapNodeVec;

    case cc.TiledMap.Orientation.ISO:
      const tileSizeUnifiedLength = Math.sqrt(mapTileRectilinearSize.width * mapTileRectilinearSize.width * 0.25 + mapTileRectilinearSize.height * mapTileRectilinearSize.height * 0.25);
      const isometricObjectLayerPointOffsetScaleFactor = (tileSizeUnifiedLength / mapTileRectilinearSize.height);
      const inverseIsometricObjectLayerPointOffsetScaleFactor = 1 / isometricObjectLayerPointOffsetScaleFactor;

      const cosineThetaRadian = (mapTileRectilinearSize.width * 0.5) / tileSizeUnifiedLength;
      const sineThetaRadian = (mapTileRectilinearSize.height * 0.5) / tileSizeUnifiedLength;

      const inverseTransMat = [
        [inverseIsometricObjectLayerPointOffsetScaleFactor * 0.5 * (1 / cosineThetaRadian), -inverseIsometricObjectLayerPointOffsetScaleFactor * 0.5 * (1 / sineThetaRadian)],
        [-inverseIsometricObjectLayerPointOffsetScaleFactor * 0.5 * (1 / cosineThetaRadian), -inverseIsometricObjectLayerPointOffsetScaleFactor * 0.5 * (1 / sineThetaRadian)]
      ];
      const convertedVecX = inverseTransMat[0][0] * continuousMapNodeVec.x + inverseTransMat[0][1] * continuousMapNodeVec.y;
      const convertedVecY = inverseTransMat[1][0] * continuousMapNodeVec.x + inverseTransMat[1][1] * continuousMapNodeVec.y;

      return cc.v2(convertedVecX, convertedVecY);

    default:
      return null;
  }
}

TileCollisionManager.prototype.continuousObjLayerVecToContinuousMapNodeVec = function(withTiledMapNode, continuousObjLayerVec) {
  var tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap);

  var mapOrientation = tiledMapIns.getMapOrientation();
  var mapTileRectilinearSize = tiledMapIns.getTileSize();

  switch (mapOrientation) {
    case cc.TiledMap.Orientation.ORTHO:
      return cc.v2(continuousObjLayerVec.x, -continuousObjLayerVec.y);

    case cc.TiledMap.Orientation.ISO:
      const tileSizeUnifiedLength = Math.sqrt(mapTileRectilinearSize.width * mapTileRectilinearSize.width * 0.25 + mapTileRectilinearSize.height * mapTileRectilinearSize.height * 0.25);
      const isometricObjectLayerPointOffsetScaleFactor = (tileSizeUnifiedLength / mapTileRectilinearSize.height);

      const cosineThetaRadian = (mapTileRectilinearSize.width * 0.5) / tileSizeUnifiedLength;
      const sineThetaRadian = (mapTileRectilinearSize.height * 0.5) / tileSizeUnifiedLength;

      const transMat = [
        [isometricObjectLayerPointOffsetScaleFactor * cosineThetaRadian, -isometricObjectLayerPointOffsetScaleFactor * cosineThetaRadian],
        [-isometricObjectLayerPointOffsetScaleFactor * sineThetaRadian, -isometricObjectLayerPointOffsetScaleFactor * sineThetaRadian]
      ];
      const convertedVecX = transMat[0][0] * continuousObjLayerVec.x + transMat[0][1] * continuousObjLayerVec.y;
      const convertedVecY = transMat[1][0] * continuousObjLayerVec.x + transMat[1][1] * continuousObjLayerVec.y;

      return cc.v2(convertedVecX, convertedVecY);

    default:
      return null;
  }
}

TileCollisionManager.prototype.continuousObjLayerOffsetToContinuousMapNodePos = function(withTiledMapNode, continuousObjLayerOffset) {
  const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap);
  const mapOrientation = tiledMapIns.getMapOrientation();

  let layerOffset = null;
  switch (mapOrientation) {
    case cc.TiledMap.Orientation.ORTHO:
      layerOffset = cc.v2(-(withTiledMapNode.getContentSize().width * 0.5), +(withTiledMapNode.getContentSize().height * 0.5));
      break;
    case cc.TiledMap.Orientation.ISO:
      layerOffset = cc.v2(0, +(withTiledMapNode.getContentSize().height * 0.5));
      break;
    default:
      return null;
  }
  return layerOffset.add(this.continuousObjLayerVecToContinuousMapNodeVec(withTiledMapNode, continuousObjLayerOffset));
}

TileCollisionManager.prototype.continuousMapNodePosToContinuousObjLayerOffset = function(withTiledMapNode, continuousMapNodePos) {
  const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap);
  const mapOrientation = tiledMapIns.getMapOrientation();
  let layerOffset = null;
  switch (mapOrientation) {
    case cc.TiledMap.Orientation.ORTHO:
      layerOffset = cc.v2(+(withTiledMapNode.getContentSize().width * 0.5), +(withTiledMapNode.getContentSize().height * 0.5));
      return cc.v2(continuousMapNodePos.x + layerOffset.x, continuousMapNodePos.y + layerOffset.y);
    case cc.TiledMap.Orientation.ISO:
      // The immediately following statement takes a magic assumption that the anchor of `withTiledMapNode` is (0.5, 0.5) which is NOT NECESSARILY true.
      layerOffset = cc.v2(0, +(withTiledMapNode.getContentSize().height * 0.5));
      const calibratedVec = continuousMapNodePos.sub(layerOffset); // TODO: Respect the real offsets!
      return this.continuousMapNodeVecToContinuousObjLayerVec(withTiledMapNode, calibratedVec);
    default:
      return null;
  }
}

/**
 * Note that `TileCollisionManager.extractBoundaryObjects` returns everything with coordinates local to `withTiledMapNode`!
 */
window.battleEntityTypeNameToGlobalGid = {};
TileCollisionManager.prototype.extractBoundaryObjects = function(withTiledMapNode) {
  let toRet = {
    playerStartingPositions: [],
    barriers: [],
    npcStartingPositions: [],
    npcPatrolCues: [],
  };
  const tiledMapIns = withTiledMapNode.getComponent(cc.TiledMap); // This is a magic name.

  const allObjectGroups = tiledMapIns.getObjectGroups();
  for (let i = 0; i < allObjectGroups.length; ++i) {
    var objectGroup = allObjectGroups[i];
    var allObjects = objectGroup.getObjects();
    switch (objectGroup.getGroupName()) {
      case "PlayerStartingPos":
        for (let j = 0; j < allObjects.length; ++j) {
          const cccMaskedX = allObjects[j].x,
            cccMaskedY = allObjects[j].y;
          const origX = cccMaskedX,
            origY = withTiledMapNode.getContentSize().height - cccMaskedY; // FIXME: I don't know why CocosCreator did this, it's stupid and MIGHT NOT WORK IN ISOMETRIC orientation!   
          let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
          toRet.playerStartingPositions.push(wpos);
        }
        break;
      case "NpcPatrolCue":
        for (let j = 0; j < allObjects.length; ++j) {
          const cccMaskedX = allObjects[j].x,
            cccMaskedY = allObjects[j].y;
          const origX = cccMaskedX,
            origY = withTiledMapNode.getContentSize().height - cccMaskedY;
          let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
          const cue = {
            x: wpos.x,
            y: wpos.y,
            flAct: parseInt(allObjects[j].flAct),
            frAct: parseInt(allObjects[j].frAct),
          };
          toRet.npcPatrolCues.push(cue);
        }
        break;
      case "NpcStartingPos":
        for (let j = 0; j < allObjects.length; ++j) {
          const cccMaskedX = allObjects[j].x,
            cccMaskedY = allObjects[j].y;
          const origX = cccMaskedX,
            origY = withTiledMapNode.getContentSize().height - cccMaskedY;
          let wpos = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, cc.v2(origX, origY));
          const npc = {
            x: wpos.x,
            y: wpos.y,
            speciesId: parseInt(allObjects[j].speciesId),
            dirX: parseInt(allObjects[j].dirX),
          };
          toRet.npcStartingPositions.push(npc);
        }
        break;
      case "Barrier":
        for (let j = 0; j < allObjects.length; ++j) {
          let object = allObjects[j];
          let gid = object.gid;
          if (0 < gid) {
            continue;
          }
          const boundaryType = object.boundary_type;
          let toPushBarrier = [];
          toPushBarrier.boundaryType = boundaryType;
          let polylinePoints = object.polylinePoints;
          if (null == polylinePoints) {
            polylinePoints = [{
              x: 0,
              y: 0
            }, {
              x: object.width,
              y: 0
            }, {
              x: object.width,
              y: -object.height
            }, {
              x: 0,
              y: -object.height
            }];
          }
          for (let k = 0; k < polylinePoints.length; ++k) {
            /* Since CocosCreatorv2.1.3, the Y-coord of object polylines is inverted compared to that of the tmx file. */
            toPushBarrier.push(this.continuousObjLayerVecToContinuousMapNodeVec(withTiledMapNode, cc.v2(polylinePoints[k].x, -polylinePoints[k].y)));
          }
          toPushBarrier.anchor = this.continuousObjLayerOffsetToContinuousMapNodePos(withTiledMapNode, object.offset); // DON'T use "(object.x, object.y)" which are wrong/meaningless! 
          toRet.barriers.push(toPushBarrier);
        }
        break;
    }
  }

  return toRet;
}

TileCollisionManager.prototype.isOutOfMapNode = function(tiledMapNode, continuousPosLocalToMap) {
  const tiledMapIns = tiledMapNode.getComponent(cc.TiledMap); // This is a magic name.

  const mapOrientation = tiledMapIns.getMapOrientation();
  const mapTileRectilinearSize = tiledMapIns.getTileSize();

  const mapContentSize = cc.size(tiledMapIns.getTileSize().width * tiledMapIns.getMapSize().width, tiledMapIns.getTileSize().height * tiledMapIns.getMapSize().height);

  switch (mapOrientation) {
    case cc.TiledMap.Orientation.ORTHO:
    case cc.TiledMap.Orientation.ISO:
      const continuousObjLayerOffset = this.continuousMapNodePosToContinuousObjLayerOffset(tiledMapNode, continuousPosLocalToMap); // Already took care of both orientations
      return 0 > continuousObjLayerOffset.x || 0 > continuousObjLayerOffset.y || mapContentSize.width < continuousObjLayerOffset.x || mapContentSize.height < continuousObjLayerOffset.y;

    default:
      return true;
  }
  return true;
};

TileCollisionManager.prototype.initMapNodeByTiledBoundaries = function(mapScriptIns, mapNode, extractedBoundaryObjs) {
  const tiledMapIns = mapNode.getComponent(cc.TiledMap);

  mapScriptIns.barrierColliders = [];
  for (let boundaryObj of extractedBoundaryObjs.barriers) {
    const newBarrier = cc.instantiate(mapScriptIns.polygonBoundaryBarrierPrefab);
    const newBoundaryOffsetInMapNode = cc.v2(boundaryObj.anchor.x, boundaryObj.anchor.y);
    newBarrier.setPosition(newBoundaryOffsetInMapNode);
    newBarrier.setAnchorPoint(cc.v2(0, 0));
    const newBarrierColliderIns = newBarrier.getComponent(cc.PolygonCollider);
    newBarrierColliderIns.points = [];
    for (let p of boundaryObj) {
      newBarrierColliderIns.points.push(p);
    }
    mapScriptIns.barrierColliders.push(newBarrierColliderIns);
    mapScriptIns.node.addChild(newBarrier);
  }
}

window.tileCollisionManager = new TileCollisionManager();
