import string
from typing import Optional
from typing import List


def normalize_name(character_name: str, allowed_double_names: Optional[List[str]] = None) -> str:
    """
    Ensures consistency for a character's name.  For some scripts, "Jaime Lannister" is listed as "Jaime", "JAIME",
    etc.

    Parameters
    ----------
    character_name : string
        The original name of the character as it appears in the script.

    allowed_double_names : list of strings, optional
        Some characters are allowed to have two names (e.g., "The Hound").  This parameter specifies those names.  If
        ``None``, then uses the default values (see source code).

    Returns
    -------
    character_name : string
        The normalized name of the character.
    """

    # First be consistent and capitalize the first letter in all names of a character.
    # Use `capwords` rather than `title` because `title` capitalizes letters after
    # apostrophes.
    character_name = string.capwords(character_name)

    # There are only a handful of names we (by default) allow to be multiple words.
    if allowed_double_names is None:
        allowed_double_names = [
            "The Hound", "Khal Drogo", "Maester Luwin", "Septa Mordane",
            "Waymar", "Grand Maester Pycelle", "Maester Pycelle", "Street Urchin",
            "King's Landing Baker", "Hot Pie", "Ser Alliser",
            "Maryn Trant", "King Joffrey", "King's Landing Page",
            "Wine Merchant", "Stable Boy", "Old Nan", "Little Bird",
            "The Group", "The Others At The Table", "Gold Cloak", "Crowd",
            "Black Lorren", "The Mountain", "Pyatt Pree", "Eddison Tollett",
            "Kraznys Mo Nakloz", "Grey Worm", "Ser Dontos", "Dying Man", "Old Man",
            "Blone Prostitute", "Black Haired Prostitute", "Sand Snakes", "High Sparrow",
            "Slave Owner", "Night's Watchman", "Khal Moro", "Young Rodrik", "Young Ned",
            "Three-Eyed Raven", "Young Lyanna", "Young Hodor", "Lady Walda", "Lady Crane",
            "Maester Aemon", "Ser Vardis", "Maester Walkan", "Maester Pycelle",
            "High Septon", "Black Walder"
        ]

    # Populate a bunch of <House> <scout/warrior/guards>.
    houses = [
        "Lannister", "Stark", "Tyrell", "Baratheon", "Kings", "Nights Watch",
        "Kings Landing", "Wounded", "Frey"
    ]
    NPC_classes = ["Soldier", "soldier", "Scout", "Warrior", "Guards", "Bannerman", "Bannermen",
                   "Guard", "Boy"]
    random_NPCs = []
    for house in houses:
        for NPC_class in NPC_classes:
            random_NPCs.append(f"{house} {NPC_class}")

    allowed_double_names = allowed_double_names + random_NPCs

    # Now if a character's name is not allowed to be double, we will split it into two and
    # take the first name.
    if character_name not in allowed_double_names:
        character_name = character_name.split()[0]

    # We also map some names explicitly to others...
    name_map = {
        "Three-eyed": "Three-Eyed Raven",
        "Three-Eyed": "Three-Eyed Raven",
        "Three": "Three-Eyed Raven",
        "Eddard": "Ned",
        "Samwell": "Sam",
        "Maester Aemon": "Aemon",
        "Royce": "Waymar",
        "Sandor": "The Hound",
        "Hound": "The Hound",
        "Luwin": "Maester Luwin",
        "Drogo": "Khal Drogo",
        "Grand Maester Pycelle": "Pycelle",
        "Maester Pycelle": "Pycelle",
        "King Joffrey": "Joffrey",
        "Samwell": "Sam",
        "Ser Alliser": "Alliser",
        "Baelish": "Littlefinger",
        "Petyr": "Littlefinger",
        "Mountain": "The Mountain",
        "Gregor": "The Mountain",
        "Sparrow": "High Sparrow",
        "Blackfish": "Brynden",
        "Twyin": "Tywin",  # Spelling lul.
        "Rodrick": "Rodrik",  # Spelling.
        "Oberyon": "Oberyn",
    }

    if character_name in name_map:
        character_name = name_map[character_name]

    return character_name