import laspy
import datetime
from collections import defaultdict
from laspy.vlrs.known import WktCoordinateSystemVlr
import os
import folium
from pyproj import Transformer

# Dictionnaire des classes standard ASPRS
class_names = {
    1: "Non classé",
    2: "Sol",
    3: "Végétation basse",
    4: "Végétation moyenne",
    5: "Végétation haute",
    6: "Bâtiment",
    9: "Eau",
    17: "Pont",
    64: "Sursol pérenne",
    65: "Artefact",
    66: "Points virtuels (modélisation)",
}

# Demander à l'utilisateur de saisir le chemin et le nom du fichier
file_path = input("Veuillez entrer le chemin complet et le nom du fichier .laz à analyser : ")
file_name = os.path.basename(file_path)

# Ouvrir le fichier .laz et afficher les informations à son sujet
with laspy.open(file_path, laz_backend=laspy.LazBackend.Lazrs) as las_file:
    header = las_file.header
    point_format = header.point_format
    num_points = header.point_count
    point_attributes = point_format.dimension_names

    file_name_info = f"<b>Nom du fichier :</b> {file_name}"
    version_info = f"<b>Version :</b> {header.version}"
    num_points_info = f"<b>Nombre de points :</b> {num_points}"
    point_format_info = f"<b>ID du format de point :</b> {point_format.id}"
    point_attributes_info = "<b>Attributs des points :</b><br>" + "<br>".join([f"- {attribute}" for attribute in point_attributes])
    scales_info = f"<b>Facteurs d'échelle :</b> {header.scales}"
    offsets_info = f"<b>Offsets :</b> {header.offsets}"
    limits_info = (f"<b>Limites :</b><br>"
                   f"  X : [{header.mins[0]}, {header.maxs[0]}]<br>"
                   f"  Y : [{header.mins[1]}, {header.maxs[1]}]<br>"
                   f"  Z : [{header.mins[2]}, {header.maxs[2]}]")

    # Examiner les VLR pour des informations de projection
    wkt_projection = None
    projection_info = "Projection : Non disponible"
    projection_info_full = ""
    vlrs = header.vlrs
    for vlr in vlrs:
        if isinstance(vlr, WktCoordinateSystemVlr):
            projection_info_full = vlr.string
            if len(projection_info_full) > 50:  # Tronquer si trop long
                projection_info = "Projection : " + projection_info_full[:50] + "..."
            else:
                projection_info = "Projection : " + projection_info_full
            wkt_projection = vlr.string
        elif vlr.description.lower().find("projection") != -1 or vlr.description.lower().find("coordinate") != -1:
            projection_info_full = vlr.description
            if len(projection_info_full) > 50:  # Tronquer si trop long
                projection_info = "Projection : " + projection_info_full[:50] + "..."
            else:
                projection_info = "Projection : " + projection_info_full

    # Lire les points
    points = las_file.read()
    gps_times = points.gps_time
    min_gps_time = min(gps_times)
    max_gps_time = max(gps_times)

    # Référence pour le temps GPS
    gps_epoch = datetime.datetime(2011, 9, 14, 0, 0, 0)
    start_time = gps_epoch + datetime.timedelta(seconds=min_gps_time)
    end_time = gps_epoch + datetime.timedelta(seconds=max_gps_time)

    start_time_info = f"<b>Heure de début d'acquisition :</b> {start_time}"
    end_time_info = f"<b>Heure de fin d'acquisition :</b> {end_time}"

    x_coords = points.x
    y_coords = points.y

    # Calculer les coins de la dalle
    x_min, x_max = min(x_coords), max(x_coords)
    y_min, y_max = min(y_coords), max(y_coords)

    # Calculer les dimensions de la dalle
    width = header.maxs[0] - header.mins[0]
    height = header.maxs[1] - header.mins[1]
    altitude_range = header.maxs[2] - header.mins[2]

    # Calculer la surface couverte
    surface_area = width * height

    # Calculer la densité de points
    point_density = num_points / surface_area

    # Trouver les points les plus bas et les plus hauts
    min_z = min(points.z)
    max_z = max(points.z)

    width_info = f"<b>Largeur de la dalle (m) :</b> {width}"
    height_info = f"<b>Hauteur de la dalle (m) :</b> {height}"
    altitude_range_info = f"<b>Plage d'altitude de la dalle (m) :</b> {altitude_range}"
    surface_area_info = f"<b>Surface de la dalle (m²) :</b> {surface_area}"
    point_density_info = f"<b>Densité de points (points/m²) :</b> {point_density}"
    min_z_info = f"<b>Point le plus bas (Z) :</b> {min_z}"
    max_z_info = f"<b>Point le plus haut (Z) :</b> {max_z}"

    # Compter les points par classe
    classifications = points.classification
    class_counts = defaultdict(int)
    for classification in classifications:
        class_counts[classification] += 1

    class_info = "<b>Classes et nombre de points associés :</b><br>"
    for class_number in sorted(class_counts):
        class_name = class_names.get(class_number, "User-defined or Reserved")
        count = class_counts[class_number]
        class_info += f"  Classe {class_number} ({class_name}): {count} points<br>"

    # Afficher les informations
    print(file_name_info.replace("<b>", "").replace("</b>", ""))
    print(version_info.replace("<b>", "").replace("</b>", ""))
    print(num_points_info.replace("<b>", "").replace("</b>", ""))
    print(point_format_info.replace("<b>", "").replace("</b>", ""))
    print(point_attributes_info.replace("<b>", "").replace("</b>", "").replace("<br>", "\n"))
    print(scales_info.replace("<b>", "").replace("</b>", ""))
    print(offsets_info.replace("<b>", "").replace("</b>", ""))
    print(limits_info.replace("<b>", "").replace("</b>", "").replace("<br>", "\n"))
    print(projection_info_full)
    print(start_time_info.replace("<b>", "").replace("</b>", ""))
    print(end_time_info.replace("<b>", "").replace("</b>", ""))
    print(width_info.replace("<b>", "").replace("</b>", ""))
    print(height_info.replace("<b>", "").replace("</b>", ""))
    print(altitude_range_info.replace("<b>", "").replace("</b>", ""))
    print(surface_area_info.replace("<b>", "").replace("</b>", ""))
    print(point_density_info.replace("<b>", "").replace("</b>", ""))
    print(min_z_info.replace("<b>", "").replace("</b>", ""))
    print(max_z_info.replace("<b>", "").replace("</b>", ""))
    print(class_info.replace("<b>", "").replace("</b>", "").replace("<br>", "\n"))

    # Transformation des coordonnées des coins de la dalle
    if wkt_projection:
        transformer = Transformer.from_crs(wkt_projection, "EPSG:4326", always_xy=True)
        bottom_left = transformer.transform(x_min, y_min)
        bottom_right = transformer.transform(x_max, y_min)
        top_left = transformer.transform(x_min, y_max)
        top_right = transformer.transform(x_max, y_max)

        # Créer la carte avec folium
        m = folium.Map(location=[(bottom_left[1] + top_right[1]) / 2, (bottom_left[0] + top_right[0]) / 2], zoom_start=13)

        # Informations à afficher dans l'infobulle
        popup_content = (
            file_name_info + "<br>" +
            version_info + "<br>" +
            num_points_info + "<br>" +
            point_format_info + "<br>" +
            point_attributes_info + "<br>" +
            scales_info + "<br>" +
            offsets_info + "<br>" +
            limits_info + "<br>" +
            projection_info + "<br>" +
            start_time_info + "<br>" +
            end_time_info + "<br>" +
            width_info + "<br>" +
            height_info + "<br>" +
            altitude_range_info + "<br>" +
            surface_area_info + "<br>" +
            point_density_info + "<br>" +
            min_z_info + "<br>" +
            max_z_info + "<br>" +
            class_info
        )

        # Ajouter les coins de la dalle à la carte
        folium.Polygon(
            locations=[bottom_left[::-1], bottom_right[::-1], top_right[::-1], top_left[::-1], bottom_left[::-1]],
            color='blue', weight=2.5, fill=True, fill_color='blue', fill_opacity=0.2,
            popup=folium.Popup(popup_content, max_width=300)
        ).add_to(m)

        # Déterminer le répertoire de sortie pour le fichier HTML
        output_dir = os.path.dirname(file_path)
        output_path = os.path.join(output_dir, 'lidar_extent_map.html')

        # Enregistrer la carte
        m.save(output_path)
        print(f"Carte interactive créée : {output_path}")
