from pandas import DataFrame
import pyproj
from shapely.geometry import Point, Polygon, MultiPolygon
from shapely.ops import unary_union

def change_crs_polygon(polygon, crs_fuente, crs_final):
    src_origen = pyproj.CRS(crs_fuente)
    src_destino = pyproj.CRS(crs_final)
    transformador = pyproj.Transformer.from_crs(src_origen, src_destino, always_xy=True)

    transformed_exterior = [transformador.transform(v[0], v[1]) for v in polygon.exterior.coords]
    transformed_interiors = [[transformador.transform(v[0], v[1]) for v in hole.coords] for hole in polygon.interiors]

    return Polygon(transformed_exterior, transformed_interiors)

def change_crs_tuple_point(punto, crs_in, crs_out):
    src_origen = pyproj.CRS(crs_in)
    src_destino = pyproj.CRS(crs_out)
    transformador = pyproj.Transformer.from_crs(src_origen, src_destino, always_xy=True)
    
    x=punto[0]
    y=punto[1]
    
    return transformador.transform(x, y)

def change_crs_point(punto, crs_in, crs_out):
    src_origen = pyproj.CRS(crs_in)
    src_destino = pyproj.CRS(crs_out)
    transformador = pyproj.Transformer.from_crs(src_origen, src_destino, always_xy=True)
    
    return Point(transformador.transform(punto.x, punto.y))


def change_crs_multipolygon(multi_polygon, crs_fuente, crs_final):
    src_origen = pyproj.CRS(crs_fuente)
    src_destino = pyproj.CRS(crs_final)
    transformador = pyproj.Transformer.from_crs(src_origen, src_destino, always_xy=True)

    multi_new = []
    
    for pol in multi_polygon.geoms:
        transformed_exterior = [transformador.transform(v[0], v[1]) for v in pol.exterior.coords]
        transformed_interiors = [[transformador.transform(v[0], v[1]) for v in hole.coords] for hole in pol.interiors]
        
        transformed_polygon = Polygon(transformed_exterior, transformed_interiors)
        multi_new.append(transformed_polygon)
    
    return unary_union(multi_new)

def change_crs(item, src_crs, dst_crs):
    if isinstance(item, Point):
        return change_crs_point(item, src_crs, dst_crs)
    if isinstance(item, Polygon):
        return change_crs_polygon(item, src_crs, dst_crs)
    if isinstance(item, MultiPolygon):
        return change_crs_multipolygon(item, src_crs, dst_crs)
    if isinstance(item, tuple) and len(item) == 2:
        return change_crs_tuple_point(item, src_crs, dst_crs)
    
    if isinstance(item, list):
        new_list = item.copy()
        for i, sub_item in enumerate(item):
            new_list[i] = change_crs(sub_item, src_crs, dst_crs)
        
        return new_list
    
    if isinstance(item, dict):
        new_dict = item.copy()
        for key, sub_item in item.items():
            new_dict[key] = change_crs(sub_item, src_crs, dst_crs)
        return new_dict
    
    if isinstance(item, DataFrame):
        new_df = item.copy()
        return new_df.applymap(lambda x: change_crs(x, src_crs, dst_crs))
    
    return item