import numpy as np
import random
import time
import threading
from pyproj import Proj, transform
import ast
# from src.services.monitor.interpolator import Interpolator # Pycharm
from interpolator import Interpolator # Prod

class Satellite:

    ELIPSE_TRAJECTORY = "elipse"
    PARABOLIC_TRAJECTORY = "parabola"
    P2P_TRAJECTORY = "p2p"

    # Constantes de la Tierra
    # R = 6378137.0  # Radio ecuatorial de la Tierra en metros
    # e = 0.08181919  # Excentricidad de la Tierra

    def __init__(self,
                 trajectory,
                 a_km=500,
                 b_km=300,
                 time_interval=1,
                 focal_distance=10,
                 flight_height=1000,
                 flight_time=3600,
                 elipse_center_geo=None,
                 parabolic_vertix=None,
                 num_points=100,
                 random_center=False,
                 cities=None,
                 interpolate_factor=2000):
        self.trajectory = trajectory # Type of traectory the satellite will follow
        self.a_km = a_km # Lenght of a axis of the elipsis in meters
        self.b_km = b_km # length of b axis of the elipsis in meters
        self.time_interval = time_interval # Time of position refresh
        self.focal_distance = focal_distance # Focal distance of the parabolic trajectory
        self.flight_height = flight_height # Elevation of the satellite in meters
        self.flight_time = flight_time # Time the trajectory will last
        self.elipse_center_geo = elipse_center_geo # Center of the elipsoidal trajectory in geografic coordinates [Lon, Lat]
        self.parabolic_vertix = parabolic_vertix # Vertix of the parabolical trajectory in geografic coordinates [Lon, Lat]
        self.num_points = num_points # number of points the trajectory will have. The more points the more precision of the movement
        self.random_center = random_center # True if you want the center of the elipsoidal trajectory to be centered in any random place of spain
        self.cities = cities
        self.interpolate_factor = interpolate_factor

        if self.trajectory == self.ELIPSE_TRAJECTORY:
            if self.elipse_center_geo is None:
                self.elipse_center_geo = [-3.7, 40.4]
            self.zone = int(((self.elipse_center_geo[0] + 180) / 6) + 1)
            self.wgs84 = Proj(proj='latlon', datum='WGS84')
            self.utm_zone = Proj(proj='utm', zone=self.zone, datum='WGS84')
            self.__init__elipse(
                center_geo=self.elipse_center_geo,
                a_km=self.a_km,
                b_km=self.b_km,
                time_interval=self.time_interval,
                num_points=self.num_points,
                random_center=self.random_center
            )
        elif self.trajectory == self.PARABOLIC_TRAJECTORY:
            if self.parabolic_vertix is None:
                self.parabolic_vertix = [-3.7, 40.4]
            self.zone = int(((self.parabolic_vertix[0] + 180) / 6) + 1)
            self.wgs84 = Proj(proj='latlon', datum='WGS84')
            self.utm_zone = Proj(proj='utm', zone=self.zone, datum='WGS84')
            self.__init__parabolic(
                self.parabolic_vertix,
                self.focal_distance,
                flight_time=self.flight_time,
                flight_height=self.flight_height,
                num_points=self.num_points,
                time_interval=self.time_interval
            )
        elif trajectory == self.P2P_TRAJECTORY:
            self.__init__p2p(cities, flight_height=self.flight_height, time_interval=self.time_interval,
                             interpolate_factor=self.interpolate_factor)
        else:
            return False
        self.trajectory = trajectory

    def __init__elipse(self,
                       center_geo=None,
                       a_km=500,
                       b_km=300,
                       flight_height=1000,
                       time_interval=1,
                       random_center=False,
                       num_points=100):

        if center_geo is None:
            center_geo = [-3.7, 40.4]

        if random_center:
            # Definir el rango de latitud y longitud para España (aproximadamente)
            min_lat, max_lat = 36.0, 43.0  # Latitud aproximada de España
            min_lon, max_lon = -9.5, 3.5  # Longitud aproximada de España

            # Generar centro aleatorio de la órbita dentro de España
            self.lat_center = random.uniform(min_lat, max_lat)
            self.lon_center = random.uniform(min_lon, max_lon)
        else:
            self.lat_center = center_geo[1]
            self.lon_center = center_geo[0]

        # Parámetros de la elipse (semiejes en kilómetros)
        self.a_km = a_km  # Semieje mayor en kilómetros
        self.b_km = b_km  # Semieje menor en kilómetros

        # Conversión de kilómetros a grados
        km_per_degree_lat = 111  # Aproximadamente 111 km por grado de latitud
        km_per_degree_lon = 111  # Aproximado para España

        # Convertir los semiejes de la elipse de kilómetros a grados
        self.a_deg = self.a_km / km_per_degree_lon
        self.b_deg = self.b_km / km_per_degree_lat

        # Crear array de ángulos para parametrizar la elipse
        self.theta = np.linspace(0, 2 * np.pi, num_points)  # 100 puntos en la órbita
        self.latitudes = self.lat_center + self.b_deg * np.sin(self.theta)
        self.longitudes = self.lon_center + self.a_deg * np.cos(self.theta)
        self.heights = np.ones(shape=self.latitudes.shape) * flight_height

        # Variables para almacenar la posición actual del satélite
        self.current_lat = self.lat_center
        self.current_lon = self.lon_center
        self.i = 0  # Índice del punto actual en la órbita

        # Intervalo de tiempo entre movimientos
        self.time_interval = time_interval

        # Variable para controlar el hilo
        self._active_movement = False


    def __init__parabolic(self, vertix_geo, focal_distance, flight_time=60, flight_height=1000, num_points=100, time_interval=0.5):
        self.times = np.linspace(0, flight_time, num_points)
        ver_x_car, ver_y_car, ver_h_car = self.geo_to_cartesian(vertix_geo[1], vertix_geo[0], flight_height)
        c1 = 1 / focal_distance
        c2 = (-2 * ver_x_car) / focal_distance
        c3 = ver_y_car + (ver_x_car**2)/focal_distance
        init_point = (ver_x_car - focal_distance, c1*(ver_x_car - focal_distance)**2 + c2*(ver_x_car - focal_distance) + c3)
        final_point = (ver_x_car + focal_distance, c1*(ver_x_car + focal_distance)**2 + c2*(ver_x_car + focal_distance) + c3)
        self.x_car = init_point[0] + (final_point[0] - init_point[0])*self.times/flight_time
        self.y_car = c1*self.x_car**2 + c2*self.x_car + c3
        self.heights = np.ones(shape=self.x_car.shape) * flight_height
        self.i = 0

        self.latitudes, self.longitudes, self.heights = self.cartesian_to_geo(self.x_car, self.y_car, self.heights)

        self.time_interval = time_interval
        self._active_movement = False

    def __init__p2p(self,
                    cities,
                    flight_height=1000,
                    time_interval=1,
                    interpolate_factor=2000
                    ):
        for city, coords_str in cities.items():
            cities[city] = ast.literal_eval(coords_str)
        longitudes = [coords[0] for coords in cities.values()]
        latitudes = [coords[1] for coords in cities.values()]

        # Interpolación spline cúbica de la trayectoria
        self.longitudes, self.latitudes = Interpolator.CubicTrajectoryInterpolator(
            longitudes,
            latitudes,
            interpolate_factor=interpolate_factor,
            circular=True
        )
        self.heights = np.ones(shape=self.longitudes.shape) * flight_height
        self.i = 0
        self.time_interval = time_interval
        self._active_movement = False

    def mover_satelite(self):
        self._active_movement = True
        num_puntos = len(self.latitudes)  # Cantidad de puntos en la órbita

        while self._active_movement:
            # Actualizar la posición del satélite
            self.current_lat = self.latitudes[self.i]
            self.current_lon = self.longitudes[self.i]
            self.current_h = self.heights[self.i]

            # Avanzar al siguiente punto de la órbita (cíclico)
            self.i = (self.i + 1) % num_puntos

            # Esperar el intervalo de tiempo antes de mover al siguiente punto
            time.sleep(self.time_interval)

    def get_position(self):
        return self.current_lat, self.current_lon, self.current_h

    def iniciar_hilo(self):
        self.hilo_satelite = threading.Thread(target=self.mover_satelite)
        self.hilo_satelite.start()

    def detener_hilo(self):
        self._active_movement = False
        self.hilo_satelite.join()  # Esperar a que el hilo termine

    def geo_to_cartesian(self, lat, lon, h):
        x, y, h = transform(self.wgs84, self.utm_zone, lon, lat, h)
        return x, y, h

    def cartesian_to_geo(self, x, y, z):
        lon, lat, z = transform(self.utm_zone, self.wgs84, x, y, z)
        return lat, lon, z
