# Phase 2: Vehicle Selection
# Data Minimization techniques - Vehicle cloaking and clustering
# sklearn library - https://pypi.org/project/scikit-learn/
# matplotlib library - https://pypi.org/project/matplotlib/
# other libraries are installed with sklearn
# Author: Norbert Lovinger; xlovin00@vutbr.cz
# Date: 16th May 2023

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import random
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler

#
# Step 1: Vehicle database generation
#

# Setting a random seed for reproducibility
random.seed(123)

# Generate 100 rows of random vehicle data with name, license number and location in range of Vienna city
data = pd.DataFrame(columns=["brand_name", "vehicle_number", "latitude", "longitude"])
for i in range(100):
    data.loc[i] = [  # type: ignore
        random.choice(["BMW", "Mercedes", "Audi", "Volkswagen", "Skoda", "Kia", "Hyundai", "Ford", "Fiat", "Peugeot"]),
        f"vehicle_{i+1}", # license number
        random.uniform(48.1, 48.3), # latitude range for Vienna
        random.uniform(16.3, 16.6)  # longitude range for Vienna
    ]

# Save the data to a CSV file
data.to_csv("vehicle_locations.csv", index=False)

#
# Step 2: Vehicle cloaking
#

# Load data from CSV file
vehicle_db = pd.read_csv("vehicle_locations.csv")

# Perturb the latitude and longitude data in the vehicle database by adding small random noise
vehicle_db["latitude"] = vehicle_db["latitude"] + np.random.normal(scale=0.01, size=len(vehicle_db))
vehicle_db["longitude"] = vehicle_db["longitude"] + np.random.normal(scale=0.01, size=len(vehicle_db))

# Scale the latitude and longitude data using StandardScaler
scaler = StandardScaler()
vehicle_db[["latitude", "longitude"]] = scaler.fit_transform(vehicle_db[["latitude", "longitude"]])

# Find the optimal number of clusters using Silhouette Score
sil_scores = []
for k in range(2, 21):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(vehicle_db[["latitude", "longitude"]])
    sil_score = silhouette_score(vehicle_db[["latitude", "longitude"]], kmeans.labels_)
    sil_scores.append(sil_score)

# Choose the number of clusters with the highest silhouette score
n_clusters = np.argmax(sil_scores) + 2
print(f"Optimal number of clusters: {n_clusters}")

#
# Step 3: Vehicle clustering 
#

# Cluster the vehicle locations using K-means
kmeans = KMeans(n_clusters=n_clusters, random_state=42)
kmeans.fit(vehicle_db[["latitude", "longitude"]])
vehicle_db["cluster"] = kmeans.labels_

# Reverse the scaling on the latitude and longitude data
vehicle_db[["latitude", "longitude"]] = scaler.inverse_transform(vehicle_db[["latitude", "longitude"]])

# Save the vehicle data with cluster labels to csv file
vehicle_db.to_csv("vehicle_clusters.csv", index=False)

#
# Step 4: Displaying results 
#

# Print out the list of vehicles in each cluster
for cluster in range(n_clusters):
    print(f"Cluster {cluster}:")
    vehicles = vehicle_db[vehicle_db['cluster'] == cluster]
    center_lat = vehicles["latitude"].mean()
    center_lon = vehicles["longitude"].mean()
    print(f"Center location: ({center_lat}, {center_lon})")
    for _, row in vehicles.iterrows():
        print(f"Vehicle {row['vehicle_number']}: {row['brand_name']} at ({row['latitude']}, {row['longitude']})")
    print("--------------------")

# Plot the clusters with background color
plt.figure(figsize=(10, 8))
cluster_colors = {cluster: np.random.rand(3,) for cluster in range(n_clusters)}
for cluster in range(n_clusters):
    color = cluster_colors[cluster]
    vehicles = vehicle_db[vehicle_db['cluster'] == cluster]
    center_lat = vehicles["latitude"].mean()
    center_lon = vehicles["longitude"].mean()
    plt.scatter(vehicles["longitude"], vehicles["latitude"], s=50, alpha=0.5, c=[color], label=f"Cluster {cluster}")
    plt.plot(center_lon, center_lat, marker='x', markersize=10, color=color)
plt.legend(loc="lower right")
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.title(f"{n_clusters} Clusters of Vehicle Locations in Vienna")
plt.show()