"""chiang_mai_map.py — draw a real map of Chiang Mai and put places on it.

You need folium first:

    pip3 install folium

Then run it:

    python3 chiang_mai_map.py

It makes a file called chiang_mai_map.html. Open that file in your browser.
You can drag the map, zoom in, and click the pins.

Every coordinate below was checked against OpenStreetMap.
"""
import math
import webbrowser
from pathlib import Path

import folium

# ─────────────────────────────────────────────────────────────
# Settings.
# ─────────────────────────────────────────────────────────────
CENTRE = (18.7883, 98.9853)          # the middle of the old city
START_ZOOM = 13
OUT_FILE = Path(__file__).resolve().parent / "chiang_mai_map.html"

# Each kind of place gets its own colour and its own little picture.
COLOURS = {
    "temple": "purple",
    "market": "orange",
    "travel": "blue",
    "landmark": "red",
    "school": "green",
    "nature": "darkgreen",
    "far": "cadetblue",
}
ICONS = {
    "temple": "place-of-worship",
    "market": "cart-shopping",
    "travel": "plane",
    "landmark": "landmark",
    "school": "graduation-cap",
    "nature": "tree",
    "far": "star",
}


# ─────────────────────────────────────────────────────────────
# One place on the map.
#
# Same class every time. Different numbers and words each time.
# That is what makes every pin different.
# ─────────────────────────────────────────────────────────────
class Place:
    def __init__(self, name, thai, lat, lon, kind, note):
        self.name = name        # what we call it in English
        self.thai = thai        # the Thai name
        self.lat = lat          # how far north  (18.x for Chiang Mai)
        self.lon = lon          # how far east   (98.x or 99.x here)
        self.kind = kind        # "temple", "market", "travel", "landmark", "school", "nature", "far"
        self.note = note        # one line for the popup

    def colour(self):
        """The pin colour for this kind of place."""
        return COLOURS.get(self.kind, "gray")

    def icon(self):
        """The little picture for this kind of place."""
        return ICONS.get(self.kind, "circle-info")

    def popup(self):
        """What the box says when you click the pin."""
        return f"<b>{self.name}</b><br>{self.thai}<br><i>{self.note}</i>"

    def add_to(self, the_map):
        """Put this one place on the map."""
        folium.Marker(
            location=[self.lat, self.lon],
            popup=folium.Popup(self.popup(), max_width=260),
            tooltip=self.name,
            icon=folium.Icon(color=self.colour(), icon=self.icon(), prefix="fa"),
        ).add_to(the_map)


# ─────────────────────────────────────────────────────────────
# The places. Fourteen real spots, checked on OpenStreetMap.
# ─────────────────────────────────────────────────────────────
PLACES = [
    Place("Wat Phra That Doi Suthep", "วัดพระธาตุดอยสุเทพ", 18.80501, 98.92218,
          "temple", "On the mountain. 306 steps to the top."),
    Place("Wat Phra Singh", "วัดพระสิงห์", 18.78821, 98.98138,
          "temple", "The biggest temple inside the old city."),
    Place("Wat Chedi Luang", "วัดเจดีย์หลวง", 18.78714, 98.98678,
          "temple", "A huge old chedi, broken by an earthquake."),
    Place("Wat Don Chan", "วัดดอนจั่น", 18.76021, 99.03257,
          "temple", "Temple and school for children in Tha Sala."),
    Place("Tha Phae Gate", "ประตูท่าแพ", 18.78776, 98.99327,
          "landmark", "The east gate of the old city wall."),
    Place("Three Kings Monument", "อนุสาวรีย์สามกษัตริย์", 18.79023, 98.98735,
          "landmark", "The three kings who founded the city in 1296."),
    Place("Warorot Market", "ตลาดวโรรส", 18.79024, 99.00053,
          "market", "Big day market. Food, cloth, everything."),
    Place("Night Bazaar", "ไนท์บาซาร์", 18.78444, 99.00038,
          "market", "Open in the evening, near the river."),
    Place("Nimmanhaemin Road", "ถนนนิมมานเหมินท์", 18.79813, 98.96887,
          "market", "Cafes, shops and students."),
    Place("Maya Shopping Centre", "เมญ่า", 18.80243, 98.96730,
          "market", "Shopping mall at the top of Nimman."),
    Place("Chiang Mai Railway Station", "สถานีรถไฟเชียงใหม่", 18.78232, 99.01696,
          "travel", "Trains to Bangkok take about 12 hours."),
    Place("Chiang Mai Airport", "ท่าอากาศยานเชียงใหม่", 18.76684, 98.96488,
          "travel", "Only 4 km from the old city."),
    Place("Chiang Mai University", "มหาวิทยาลัยเชียงใหม่", 18.80093, 98.95216,
          "school", "Opened in 1964. The first university in the north."),
    Place("Huay Tung Tao Lake", "ทะเลสาบห้วยตึงเฒ่า", 18.86789, 98.94010,
          "nature", "A lake under the mountain. Good for a swim."),
]

# Two places a long way from Chiang Mai.
#
# Look at Casa Bonita's longitude: it is NEGATIVE. Numbers go up as you
# travel east and down as you travel west, so everywhere in the Americas
# has a minus sign. The same Place class handles it without any changes.
FAR_AWAY = [
    Place("Casa Bonita", "คาซาโบนิต้า", 39.74194, -105.07103,
          "far", "A very large Mexican restaurant in Lakewood, Colorado."),
    Place("Beijing", "ปักกิ่ง", 39.90571, 116.39130,
          "far", "The capital of China. Almost the same latitude as Casa Bonita."),
]


# The four corners of the old city moat. These are close, not exact.
OLD_CITY = [
    (18.7949, 98.9791),      # north west
    (18.7949, 98.9933),      # north east
    (18.7807, 98.9933),      # south east
    (18.7807, 98.9791),      # south west
]


def find(name):
    """Get one place out of the lists by its name."""
    for place in PLACES + FAR_AWAY:
        if place.name == name:
            return place
    return None


def distance_km(a, b):
    """How far apart are two places, in kilometres?

    The earth is round, so we cannot just subtract the numbers. This is
    the haversine formula. 6371 is the radius of the earth in kilometres.

    This is the straight line, like a bird flies. The road can be much
    longer. The road up to Doi Suthep bends all the way up the mountain,
    so it is about twice this number.
    """
    radius = 6371.0
    lat1, lon1, lat2, lon2 = map(math.radians, [a.lat, a.lon, b.lat, b.lon])
    d_lat = lat2 - lat1
    d_lon = lon2 - lon1
    h = math.sin(d_lat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(d_lon / 2) ** 2
    return 2 * radius * math.asin(math.sqrt(h))


def add_old_city(the_map):
    """Draw a square around the old city."""
    folium.Polygon(
        locations=OLD_CITY,
        color="#b03030",
        weight=3,
        fill=True,
        fill_opacity=0.08,
        popup="The old city moat (roughly)",
    ).add_to(the_map)


def add_temple_walk(the_map):
    """Draw a line between three temples you can walk between."""
    stops = ["Wat Phra Singh", "Wat Chedi Luang", "Tha Phae Gate"]
    points = [(find(name).lat, find(name).lon) for name in stops]

    folium.PolyLine(
        locations=points,
        color="#1e6660",
        weight=5,
        opacity=0.8,
        tooltip="A short temple walk",
    ).add_to(the_map)

    total = 0.0
    for first, second in zip(stops, stops[1:]):
        total += distance_km(find(first), find(second))
    return total


def add_airport_circle(the_map):
    """Draw a 5 km circle around the airport."""
    airport = find("Chiang Mai Airport")
    folium.Circle(
        location=[airport.lat, airport.lon],
        radius=5000,                       # metres
        color="#2a6cb0",
        weight=2,
        fill=True,
        fill_opacity=0.06,
        popup="5 km from the airport",
    ).add_to(the_map)


def build_map():
    """Make the whole map and give it back."""
    the_map = folium.Map(location=CENTRE, zoom_start=START_ZOOM, tiles="OpenStreetMap")

    # A second map style. The little button top right switches between them.
    folium.TileLayer("CartoDB positron", name="Plain", show=False).add_to(the_map)

    for place in PLACES:
        place.add_to(the_map)

    # These two are on the other side of the world. Zoom out to find them.
    for place in FAR_AWAY:
        place.add_to(the_map)

    add_old_city(the_map)
    add_airport_circle(the_map)
    walk_km = add_temple_walk(the_map)

    folium.LayerControl().add_to(the_map)
    return the_map, walk_km


def main():
    the_map, walk_km = build_map()
    the_map.save(str(OUT_FILE))

    print(f"Put {len(PLACES)} places in Chiang Mai on the map, "
          f"and {len(FAR_AWAY)} far away.")
    print(f"The temple walk is {walk_km:.1f} km.")

    doi_suthep = find("Wat Phra That Doi Suthep")
    gate = find("Tha Phae Gate")
    print(f"Doi Suthep is {distance_km(doi_suthep, gate):.1f} km from Tha Phae Gate")
    print("  ...but that is the straight line. The road up the mountain is much longer.")

    print()
    for place in FAR_AWAY:
        print(f"{place.name} is {distance_km(place, gate):,.0f} km away.")

    print(f"\nSaved: {OUT_FILE}")
    print("Open that file in your browser.")

    try:
        webbrowser.open(OUT_FILE.as_uri())
    except Exception:
        pass          # no browser here — the file is saved anyway


if __name__ == "__main__":
    main()
