# Copyright 2018 The TensorFlow Authors All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""Adapted from rllab maze_env_utils.py."""
import numpy as np
import math

default_desired_goal_distribution = {
  "Maze_U": np.array([[-3.5, 12.5], [3.5, 19.5]], dtype=np.float32),
  "Maze_S": np.array([[12.5, 28.5], [19.5, 35.5]], dtype=np.float32)
}

default_fixed_desired_goal = {
  "Maze_U": np.array([0., 16.], dtype=np.float32),
  "Maze_S": np.array([16., 32.], dtype=np.float32)
}


class Move(object):
  X = 11
  Y = 12
  Z = 13
  XY = 14
  XZ = 15
  YZ = 16
  XYZ = 17
  SpinXY = 18


def can_move_x(movable):
  return movable in [Move.X, Move.XY, Move.XZ, Move.XYZ,
                     Move.SpinXY]


def can_move_y(movable):
  return movable in [Move.Y, Move.XY, Move.YZ, Move.XYZ,
                     Move.SpinXY]


def can_move_z(movable):
  return movable in [Move.Z, Move.XZ, Move.YZ, Move.XYZ]


def can_spin(movable):
  return movable in [Move.SpinXY]


def can_move(movable):
  return can_move_x(movable) or can_move_y(movable) or can_move_z(movable)


def construct_maze(maze_id='Maze'):
  if maze_id == 'Maze_U' or maze_id == "Maze":
    structure = [
        [1, 1, 1, 1, 1],
        [1, 'r', 0, 0, 1],
        [1, 1, 1, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1],
    ]
  elif maze_id == 'Maze_S':
    structure = [
      [1, 1, 1, 1, 1],
      [1, 'r', 0, 0, 1],
      [1, 1, 1, 0, 1],
      [1, 0, 0, 0, 1],
      [1, 0, 1, 1, 1],
      [1, 0, 0, 0, 1],
      [1, 1, 1, 1, 1]
    ]
  elif maze_id == 'Push':
    structure = [
        [1, 1,  1,  1,   1],
        [1, 0, 'r', 1,   1],
        [1, 0,  Move.XY, 0,  1],
        [1, 1,  0,  1,   1],
        [1, 1,  1,  1,   1],
    ]
  elif maze_id == 'Fall':
    structure = [
        [1, 1,   1,  1],
        [1, 'r', 0,  1],
        [1, 0,   Move.YZ,  1],
        [1, -1, -1,  1],
        [1, 0,   0,  1],
        [1, 1,   1,  1],
    ]
  elif maze_id == 'Block':
    O = 'r'
    structure = [
        [1, 1, 1, 1, 1],
        [1, O, 0, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1],
        [1, 1, 1, 1, 1],
    ]
  elif maze_id == 'BlockMaze':
    O = 'r'
    structure = [
        [1, 1, 1, 1],
        [1, O, 0, 1],
        [1, 1, 0, 1],
        [1, 0, 0, 1],
        [1, 1, 1, 1],
    ]
  else:
      raise NotImplementedError('The provided MazeId %s is not recognized' % maze_id)

  return structure


def line_intersect(pt1, pt2, ptA, ptB):
  """
  Taken from https://www.cs.hmc.edu/ACM/lectures/intersections.html
  this returns the intersection of Line(pt1,pt2) and Line(ptA,ptB)
  """

  DET_TOLERANCE = 0.00000001

  # the first line is pt1 + r*(pt2-pt1)
  # in component form:
  x1, y1 = pt1
  x2, y2 = pt2
  dx1 = x2 - x1
  dy1 = y2 - y1

  # the second line is ptA + s*(ptB-ptA)
  x, y = ptA
  xB, yB = ptB
  dx = xB - x
  dy = yB - y

  DET = (-dx1 * dy + dy1 * dx)

  if math.fabs(DET) < DET_TOLERANCE: return (0, 0, 0, 0, 0)

  # now, the determinant should be OK
  DETinv = 1.0 / DET

  # find the scalar amount along the "self" segment
  r = DETinv * (-dy * (x - x1) + dx * (y - y1))

  # find the scalar amount along the input line
  s = DETinv * (-dy1 * (x - x1) + dx1 * (y - y1))

  # return the average of the two descriptions
  xi = (x1 + r * dx1 + x + s * dx) / 2.0
  yi = (y1 + r * dy1 + y + s * dy) / 2.0
  return (xi, yi, 1, r, s)


def ray_segment_intersect(ray, segment):
  """
  Check if the ray originated from (x, y) with direction theta intersects the line segment (x1, y1) -- (x2, y2),
  and return the intersection point if there is one
  """
  (x, y), theta = ray
  # (x1, y1), (x2, y2) = segment
  pt1 = (x, y)
  len = 1
  pt2 = (x + len * math.cos(theta), y + len * math.sin(theta))
  xo, yo, valid, r, s = line_intersect(pt1, pt2, *segment)
  if valid and r >= 0 and 0 <= s <= 1:
    return (xo, yo)
  return None


def point_distance(p1, p2):
  x1, y1 = p1
  x2, y2 = p2
  return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5