#  Copyright 2020 Modelling, Simulation and Design Lab (MSDL) at
#  McGill University and the University of Antwerp (http://msdl.cs.mcgill.ca/)
#
#  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.

"""This file contains additional classes w.r.t. spatial properties.

Such properties include:
- Coordinates
- Distance computation
- Multiple axes movement
"""

import numpy as np

class Coordinate:
    """Simple 3-dimensional coordinate.

    A coordinate in 3D will follow a cartesian coorinate system over
    3 axes: x, y and z.

    Args:
        x (numeric):    The x-value of the coordinate.
        y (numeric):    The y-value of the coordinate.
        z (numeric):    The z-value of the coordinate.
    """
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __repr__(self):
        return "Coord(%.5f, %.5f, %.5f)" % (self.x, self.y, self.z)


class distance:
    """Compute the distance between two points."""

    @staticmethod
    def euclidean(A, B):
        """Computes the Euclidean distance between :attr:`A` and :attr:`B`.

        Args:
            A (Coordinate): The first coordinate.
            B (Coordinate): The second coordinate.
        """
        return np.sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2 + (A.z - B.z) ** 2)

    @staticmethod
    def manhattan(A, B):
        """Computes the Manhattan distance between :attr:`A` and :attr:`B`.

        Args:
            A (Coordinate): The first coordinate.
            B (Coordinate): The second coordinate.
        """
        return np.abs(A.x - B.x) + np.abs(A.y - B.y) + np.abs(A.z - B.z)

    @staticmethod
    def chebyshev(A, B):
        """Computes the Chebyshev distance between :attr:`A` and :attr:`B`.

        Args:
            A (Coordinate): The first coordinate.
            B (Coordinate): The second coordinate.
        """
        return np.max(np.abs(B.x - A.x), np.abs(B.y - A.y), np.abs(B.z - A.z))

    @staticmethod
    def minkowski(A, B, p=2):
        """Computes the Minkowski distance between :attr:`A` and :attr:`B`.

        Args:
            A (Coordinate): The first coordinate.
            B (Coordinate): The second coordinate.
            p (int):        The order of the distance. Must be greater than
                            or equal to 1. Defaults to 2 (= :meth:`euclidean`).
        """
        assert p >= 1
        return np.power((np.abs(B.x - A.x) ** p) + (np.abs(B.y - A.y) ** p) + (np.abs(B.z - A.z) ** p), 1.0 / p)
