# Now we're going to make a pattern that describes what a line of dialogue
#   looks like in this movie script
#
# Visualize this regular expression at https://www.debuggex.com/r/iLYFPobXckKrlx4l
dialogue_pattern = re.compile(r'''(?x)
          (?P<character_name>  # Create a capturing group called `character_name`
              [A-Z,\s]+)       #   Where movie character names contain either ALL CAPS letters or spaces
          :                    # followed by a colon,
          \s*                  # followed by some whitespace,
          (?P<what_they_say>   # Then create a capturing group for what the movie character actually says
              .*               #   Where what they actually say can be any characters 0 or more,
              $)               #   followed by the end of the line
    '''
)


"""
Explanation: Does Pirates of the Caribbean: Dead Man's Chest pass the Bechdel Test?
Criteria

Is there a scene with at least two women in it?
Are those women talking to each other?
Are they talking about something other than a man?

What's the pattern of text that indicates a dialogue line?
End of explanation
"""


def create_dialogue_element(line, pattern=dialogue_pattern):
    """
    Keyword Arguments:
    line -- a line from the script
    pattern -- a regular expression pattern that recognizes dialogue
    
    Output:
    If the line matches the pattern, a Python dictionary.
    Otherwise, None
    """
    dialogue_element = re.match(dialogue_pattern, line)
    if dialogue_element is not None:
        return dialogue_element.groupdict()
    else:
        return None

# add a new column of data
pirates_of_the_caribbean = pirates_of_the_caribbean.assign(
    dialogue_element = pirates_of_the_caribbean["line_of_script"].apply(create_dialogue_element)    
)



"""
Explanation: How can we extract dialogue lines using this pattern?
End of explanation
"""


dialogue_filter = pd.notnull(pirates_of_the_caribbean["dialogue_element"]) # True/False array
pirates_of_the_caribbean[dialogue_filter].head()

"""
Explanation: How can we exclude data rows that aren't dialogue?
End of explanation
"""


def extract_character_name(dialogue_element):
    """
    Get a character's name from a dialogue_element dictionary
    """
    if dialogue_element is not None:
        return dialogue_element["character_name"]
    else:
        return None

def extract_what_character_says(dialogue_element):
    if dialogue_element is not None:
        return dialogue_element["what_they_say"]
    else:
        return None
    
def add_character_and_speech_columns(pirates_dataframe):
    return (
        pirates_of_the_caribbean.assign(
            character = pirates_of_the_caribbean["dialogue_element"].apply(extract_character_name),
            what_they_say = pirates_of_the_caribbean["dialogue_element"].apply(extract_what_character_says)
        )
    )
    
pirates_of_the_caribbean = add_character_and_speech_columns(pirates_of_the_caribbean)
pirates_of_the_caribbean.head()    

"""
Explanation: How do we extract character names?
End of explanation
"""


def print_all_characters_with_speaking_parts(pirates_data):
    characters_with_speaking_parts = pirates_data["character"].unique()
    for character in characters_with_speaking_parts:
        print(character)
    
print_all_characters_with_speaking_parts(pirates_of_the_caribbean)

def sort_characters_by_number_of_lines_of_dialogue(pirates_data):
    return (
        pirates_of_the_caribbean
            .groupby("character")
            .count()
            .sort_values(by=['what_they_say'], ascending=False)
    )

sort_characters_by_number_of_lines_of_dialogue(pirates_of_the_caribbean)["what_they_say"].plot(kind='bar')
sort_characters_by_number_of_lines_of_dialogue(pirates_of_the_caribbean)

scene_pattern = re.compile(r'''(?x)
    ^Scene # The line begins with scene,
         : # Followed by a colon,
        .* # Followed by any characters,
         $ # Until the end of the line.
    ''')


def create_scene_filter(pattern, pirates_data):
    return pd.notnull(
        pirates_data["line_of_script"].apply(lambda x: re.match(pattern, x))
    )

scene_filter = create_scene_filter(scene_pattern, pirates_of_the_caribbean)
pirates_of_the_caribbean[scene_filter]
pirates_of_the_caribbean[scene_filter].count()

def add_scene_numbers_to_data(pattern, pirates_data):
    scenes = []
    scene_number = 0
    for line in pirates_data["line_of_script"]:
        if (re.match(pattern, line)):
            scene_number = scene_number + 1
        scenes.append(scene_number)
    return pirates_data.assign(scene=scenes)

add_scene_numbers_to_data(scene_pattern, pirates_of_the_caribbean)

def detect_male_pronouns(line_of_dialogue):
    """
    Returns True if the line contains male pronouns; False if it doesn't
    """
    male_pronouns = ["he", "him"]
    contains_male_pronoun = False
    if pd.notnull(line_of_dialogue):
        tokenized_dialogue = word_tokenize(line_of_dialogue.lower())
        for pronoun in male_pronouns:
            if (pronoun in tokenized_dialogue):
                contains_male_pronoun = True
    return contains_male_pronoun

def infer_gender_of_character(character_name):
    """
    Uses the gender_map to look up the gender of a character based on their name.
    If the character's gender is defined in the map, this returns that gender.
    If a character name/gender is NOT defined in the map, it returns "unknown"
    
    You can add characters to the character map using "(name_of_character)": "(gender)"
    """
    gender_map = {
        "ELIZABETH SWANN": "female",
        "LORD CUTLER BECKETT": "male"
    }
    character_gender = "unknown"
    if character_name in gender_map.keys():
        character_gender = gender_map[character_name]
    return character_gender

"""
Explanation: What's the distribution of speaking lines by gender
End of explanation
"""


pirates_of_the_caribbean["line_of_script"].apply(print)

# Putting it all together for the Bechdel Test
def add_gender_columns_to_data(pirates_data):
    return pirates_of_the_caribbean.assign(
        gender_of_character = pirates_of_the_caribbean["character"].apply(infer_gender_of_character),
        contains_male_pronouns = pirates_of_the_caribbean["what_they_say"].apply(detect_male_pronouns)
    )

(
    add_gender_columns_to_data(pirates_of_the_caribbean)
        .query("gender_of_character == 'female' and contains_male_pronouns == False")
        [["character", "gender_of_character", "what_they_say"]]
)

# [element for element in enumerate(list(lines_per_character))]


# re.match(r'(foo)(bar)', "foobar").group(3)

"""
Explanation: Printing out the values of a column
End of explanation
"""


