import numpy as np

def euclidean_distance(p1, p2):
    print(p1, p2)
    return np.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)


def maximize_score(places, max_distance, fixed_entry, top_k=3):
    """
    Maximizes the total score of visited places while staying below the maximum distance.
    
    Parameters:
    places (list of tuples): Each tuple contains (score, (x, y), location).
    max_distance (float): The maximum distance that can be traveled.
    fixed_entry (tuple): The place that needs to be visited independently of its score.
    top_k (int): Number of top candidates to consider in each iteration.
    
    Returns:
    list of tuples: The visited places.
    float: The total score of the visited places.
    """
    # Initialize total distance and score
    total_distance = 0
    total_score = 0
    visited_places = []

    # Add the fixed entry to the visited list
    score, (x, y), _ = fixed_entry
    visited_places.append(fixed_entry)
    total_score += score

    # Remove the fixed entry from the list of places
    remaining_places = [place for place in places if place != fixed_entry]

    # Sort remaining places by score-to-distance ratio
    
    remaining_places.sort(key=lambda p: p[0] / euclidean_distance((x, y), (p[1][0], p[1][1])), reverse=True)

    # Add places to the visited list if they don't exceed the maximum distance
    current_location = (x, y)
    while remaining_places and total_distance < max_distance:
        # Consider top_k candidates
        candidates = remaining_places[:top_k]
        best_candidate = None
        best_score_increase = -np.inf

        for candidate in candidates:
            score, (cx, cy), location = candidate
            distance = euclidean_distance(current_location, (cx, cy))
            if total_distance + distance <= max_distance:
                score_increase = score / distance
                if score_increase > best_score_increase:
                    best_score_increase = score_increase
                    best_candidate = candidate

        if best_candidate:
            visited_places.append(best_candidate)
            total_distance += euclidean_distance(current_location, best_candidate[1])
            total_score += best_candidate[0]
            current_location = best_candidate[1]
            remaining_places.remove(best_candidate)
        else:
            break

    return visited_places, total_score

# Example usage
places = [
    (10, (0, 0), 'A'),
    (8, (4, 2), 'B'),
    (15, (6, 4), 'C'),
    (7, (5, 6), 'D'),
    (12, (1, 8), 'E'),
    (14, (34, 10), 'F'),
    (15, (65, 12), 'G'),
    (12, (3, 14), 'H'),
    (12, (15, 1), 'I'),
    (7, (17, 4), 'J'),
    (12, (3, 3), 'K'),
    (4, (21, 22), 'L'),
    (12, (23, 24), 'M'),
    (4, (25, 26), 'N'),
    (2, (27, 28), 'O'),
]
fixed_entry = (10, (0, 0), 'A')
max_distance = 50

visited_places, total_score = maximize_score(places, max_distance, fixed_entry)
print("Visited Places:", visited_places)
print("Total Score:", total_score)

import matplotlib.pyplot as plt

# Plot the route
def plot_route(visited_places):
    x_coords = [place[1][0] for place in visited_places]
    y_coords = [place[1][1] for place in visited_places]
    labels = [place[2] for place in visited_places]

    plt.figure(figsize=(10, 6))
    plt.plot(x_coords, y_coords, marker='o', linestyle='-', color='b')
    for i, label in enumerate(labels):
        plt.text(x_coords[i], y_coords[i], label, fontsize=12, ha='right')

    plt.title('Route of Visited Places')
    plt.xlabel('X Coordinate')
    plt.ylabel('Y Coordinate')
    plt.grid(True)
    plt.savefig('route.png')

plot_route(visited_places)