import math


class Punto:
    # Atributos estáticos
    ORG_X = 0
    ORG_Y = 0

    # Obj static
    ORG = None

    @staticmethod
    def GET_ORG():
        if not Punto.ORG:
            Punto.ORG = Punto(Punto.ORG_X, Punto.ORG_Y)

        return Punto.ORG

    def __init__(self, x, y):
        # Atributos normales
        self._x = x
        self._y = y

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y

    @x.setter
    def x(self, v):
        self._x = v

    @y.setter
    def y(self, v):
        self._y = v

    def __add__(self, p2):
        return Punto(self.x + p2.x, self.y + p2.y)

    def __eq__(self, p2):
        return self.x == p2.x and self.y == p2.y

    def __str__(self):
        return f"{self._x}, {self._y}"


class Persona:
    def __init__(self, nombre, edad):
        self._nombre = nombre
        self._edad = edad

    @property
    def nombre(self):
        return self._nombre

    @property
    def edad(self):
        return self._edad

    def __str__(self):
        return f"{self.nombre} ({self.edad} años)"


class Empleado(Persona):
    def __init__(self, nombre, edad, empresa, salario):
        super().__init__(nombre, edad)
        self._empresa = empresa
        self._salario = salario

    @property
    def empresa(self):
        return self._empresa

    @property
    def salario(self):
        return self._salario

    def __str__(self):
        return super().__str__() + ": " + self.empresa + " = " + str(self.salario)


# Lambdas

doble = lambda x: 2 * x
car = lambda l: l[0] if len(l) > 0 else None
cdr = lambda l: l[1:] if len(l) > 1 else []

reverse = lambda l: ([] if l == [] else
                    [car(l)] if len(l) == 1 else
                    reverse(cdr(l)) + [car(l)])

# Write a lambda that finds the minimum element in a list
find_min_aux = lambda l, mn: (
                    mn if l == [] else
                    find_min_aux(cdr(l), car(l)) if car(l) < mn else
                    find_min_aux(cdr(l), mn))

find_min = lambda l: (find_min_aux(cdr(l), car(l))
                        if len(l) > 1 else None)

# Map, filter y reduce
# Map(f, l) -> [f(x) for x in l]
map = lambda f, l:(
                    [] if l == [] else
                    [f(car(l))] + map(f, cdr(l)))

# Filter(f, l) -> [x for x in l if f(x)]
filter = lambda f, l: (
                    [] if l == [] else
                    filter(f, cdr(l)) if not f(car(l)) else
                    [car(l)] + filter(f, cdr(l)))

# Reduce(f, l) -> reduce(lambda x, y: x + y, [1,2,3]) == 6
reduce = lambda f, l, default=lambda x: 0 if x is None else x: (
                    default(None) if l == [] else
                    default(car(l)) if len(l) == 1 else
                    f(default(car(l)), default(reduce(f, cdr(l), default))))


print("doble(5) =", doble(5))
print("reverse([1, 2, 3]) =", reverse([1, 2, 3]))
print("find_min([5, 2, 1, 3, 9]) =", find_min([5, 2, 1, 3, 9]))
print("map(lambda x: x * 2, [1, 2, 3, 4, 5, 6]) =",
      map(lambda x: x * 2, [1, 2, 3, 4, 5, 6]))
print("filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6, 7 ,8]) =",
      filter(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6, 7 ,8]))
print("reduce(lambda x, y: x + y, [1, 2, 3, 4, 5, 6, 7, 8]) =",
      reduce(lambda x, y: x + y, [1, 2, 3, 4, 5, 6, 7, 8]))
print("reduce(lambda x, y: x * y, [1, 2, 3, 4, 5]) =",
      reduce(lambda x, y: x * y, [1, 2, 3, 4, 5]))
print("reduce(lambda x, y: x * y, []) =",
      reduce(lambda x, y: x * y, [], default=lambda x: 1 if x is None else x))

lps = [Punto(0, 0), Punto(5, 6), Punto(7, 8), Punto(11, 22)]

# Show points in lps with a distance to origin < 30
lps_dist_menor = map(str,
                     filter(
                        lambda p: math.sqrt((p.x ** 2) + (p.y ** 2)) < 10,
                        lps))

print(str.join(" - ", [str(p) for p in lps]))
print("Puntos dist origen < 10:", lps_dist_menor)

# Jubilación anticipada
# How much money will we save if we retire people
# with age > 60?
lista_empleados = [
    Empleado("Baltasar", 21, "UVigo", 1500),
    Empleado("Lourdes", 61, "UVigo", 1500),
    Empleado("Pedro", 57, "UVigo", 1500),
    Empleado("Nanny", 48, "UVigo", 1500),
    Empleado("Rosalía", 63, "UVigo", 1500),
    Empleado("Reyes", 60, "UVigo", 1500),
    Empleado("Juan Carlos", 64, "UVigo", 1500),
]

print("Ahorro retiro:",
      reduce(lambda x, y: x + y,
        filter(lambda e: e.edad > 60, lista_empleados),
        default=lambda e: e.salario if isinstance(e, Empleado) else e))
