# Notes from Monday, November 13

# A simple rule-based system for natural language processing

import string

#----------------------------------------------------------------------------------

class Movie:

    def __init__(self, title, year, director, actors):
        self.title = title
        self.year = year
        self.director = director
        self.actors = actors

    def __str__(self):
        return "%s (%d)" % (self.title, self.year)

    def info(self):
        print "%s (%d)" % (self.title, self.year)
        print "Directed by" , self.director
        print "Starring", string.join(self.actors, ", ")

#----------------------------------------------------------------------------------
# cleanup removes punctuation characters from a string

def cleanup(phrase):
    chars = [ch for ch in phrase if ch not in string.punctuation]
    return string.join(chars, "")

#----------------------------------------------------------------------------------

class Rule:

    def __init__(self, pattern, action):
        assert pattern.count('...') <= 1, "no more than one '...' symbol allowed"
        self.pattern = pattern
        self.action = action

    def __str__(self):
        return "%s" % self.pattern

    def match(self, phrase):
        phrase = cleanup(phrase)
        wordsLeft = string.split(phrase)
        symbolsLeft = self.pattern
        substitutions = []
        while len(symbolsLeft) > 0 and len(wordsLeft) > 0:
            firstSymbol = symbolsLeft[0]
            symbolsLeft = symbolsLeft[1:]
            if firstSymbol == '...':
                numMatched = len(wordsLeft) - len(symbolsLeft)
                wordsMatched = wordsLeft[:numMatched]
                substitutions.append(string.join(wordsMatched))
                wordsLeft = wordsLeft[numMatched:]
            elif firstSymbol == '#':
                try:
                    substitutions.append(eval(wordsLeft[0]))
                    wordsLeft = wordsLeft[1:]
                except:
                    return None
            elif type(firstSymbol) is list and wordsLeft[0] in firstSymbol:
                substitutions.append(wordsLeft[0])
                wordsLeft = wordsLeft[1:]
            elif firstSymbol == '_':
                substitutions.append(wordsLeft[0])
                wordsLeft = wordsLeft[1:]
            elif firstSymbol == wordsLeft[0]:
                wordsLeft = wordsLeft[1:]
            else:
                return None
        if len(symbolsLeft) > 0 or len(wordsLeft) > 0:
            return None
        else:
            return substitutions

    def apply(self, phrase):
        subs = self.match(phrase)
        if subs is not None:
            result = self.action(*subs)
            return result

#----------------------------------------------------------------------------------

class MovieMeister:

    def __init__(self):
        self.database = []
        self.rules = []

    def addRule(self, pattern, action):
        rule = Rule(pattern, action)
        self.rules.append(rule)

    def addMovie(self, title, year, director, actors):
        movie = Movie(title, year, director, actors)
        self.database.append(movie)

    def process(self, phrase):
        for rule in self.rules:
            if rule.match(phrase):
                result = rule.apply(phrase)
                return result
        return None
            
    def interact(self):
        print "Welcome to the Movie Meister!"
        while True:
            input = raw_input("May I help you? ")
            if input in ['quit', 'bye', 'goodbye']:
                break
            results = self.process(input)
            if results is None:
                print "I don't understand."
            elif results == []:
                print "I don't know."
            else:
                for x in results:
                    print x
        print "Come back again soon!"

#----------------------------------------------------------------------------------
# The knowledge base

mm = MovieMeister()

mm.addMovie("Amarcord", 1974, "Federico Fellini",
            ["Nagali Noel", "Bruno Zanin", "Pupella Maggio", "Armando Drancia"])

mm.addMovie("The Godfather", 1972, "Francis Ford Coppola",
            ["Marlon Brando", "Al Pacino", "James Caan", "Robert Duvall",
             "Diane Keaton"])

mm.addMovie("The Big Easy", 1987, "Jim McBride",
            ["Dennis Quaid", "Ellen Barkin", "Ned Beatty", "Lisa Jane Persky",
             "John Goodman", "Charles Ludlam"])

mm.addMovie("Boyz in the Hood", 1991, "John Singleton",
            ["Cuba Gooding Jr.", "Ice Cube", "Larry Fishburne", "Tyra Ferrell",
             "Morris Chestnut"])

mm.addMovie("Psycho", 1960, "Alfred Hitchcock",
            ["Anthony Perkins", "Janet Leigh", "Vera Miles"])

mm.addMovie("Unforgiven", 1992, "Clint Eastwood",
            ["Clint Eastwood", "Gene Hackman", "Morgan Freeman", "Richard Harris"])

mm.addMovie("High Plains Drifter", 1973, "Clint Eastwood",
            ["Clint Eastwood", "Verna Bloom", "Marianna Hill"])

mm.addMovie("Dirty Harry", 1971, "Don Siegel",
            ["Clint Eastwood", "Harry Guardino", "Reni Santoni", "John Vernon"])

mm.addMovie("Annie Hall", 1977, "Woody Allen",
            ["Woody Allen", "Diane Keaton", "Tony Roberts"])

mm.addMovie("Star Wars", 1977, "George Lucas",
            ["Mark Hamill", "Harrison Ford", "Carrie Fisher", "Alec Guinness"])

#----------------------------------------------------------------------------------
# Pattern/action production rules

pattern1 = ['What', 'movies', 'were', 'made', 'between', '#', 'and', '#']

def action1(year1, year2):
    return [movie.title for movie in mm.database if year1 <= movie.year <= year2]

mm.addRule(pattern1, action1)

#----------------------------------------------------------------------------------
pattern2 = ['What', 'movies', 'did', '...', ['act', 'star', 'appear'], 'in']

def action2(actor, verb):
    return [movie.title for movie in mm.database if actor in movie.actors]

mm.addRule(pattern2, action2)

#----------------------------------------------------------------------------------
pattern3 = ['What', 'movies', 'were', 'made', ['in', 'before', 'after', 'since'], '#']

def action3(prep, year):
    if prep == 'in':
        return [movie.title for movie in mm.database if movie.year == year]
    elif prep == 'before':
        return [movie.title for movie in mm.database if movie.year < year]
    elif prep == 'after' or prep == 'since':
        return [movie.title for movie in mm.database if movie.year > year]
    else:
        return None

mm.addRule(pattern3, action3)

#----------------------------------------------------------------------------------
pattern4 = ['Who', ['directed', 'wrote'], '...']

def action4(verb, title):
    if verb == 'wrote':
        return []
    elif verb == 'directed':
        return [movie.director for movie in mm.database if movie.title == title]

mm.addRule(pattern4, action4)

#----------------------------------------------------------------------------------
pattern5 = ['Who', ['starred', 'acted', 'appeared'], 'in', '...']

def action5(verb, title):
    for movie in mm.database:
        if movie.title == title:
            return movie.actors
    return []

mm.addRule(pattern5, action5)

#----------------------------------------------------------------------------------
pattern6 = ['When', 'was', '...', 'made']

def action6(title):
    return [movie.year for movie in mm.database if movie.title == title]

mm.addRule(pattern6, action6)

#----------------------------------------------------------------------------------
pattern7 = ['What', 'movies', 'did', '...', 'direct']

def action7(director):
    return [movie.title for movie in mm.database if movie.director == director]

mm.addRule(pattern7, action7)

#----------------------------------------------------------------------------------
# Example
# 
# >>> mm.interact()
# Welcome to the Movie Meister!
# May I help you? What movies did Francis Ford Coppola direct?
# The Godfather
# May I help you? What movies did Clint Eastwood direct?
# Unforgiven
# High Plains Drifter
# May I help you? What movies did Clint Eastwood star in?
# Unforgiven
# High Plains Drifter
# Dirty Harry
# May I help you? Who directed Dirty Harry?
# Don Siegel
# May I help you? What year was Dirty Harry made?
# I don't understand.
# May I help you? When was Dirty Harry made?
# 1971
# May I help you? What movies were made between 1970 and 1980?
# Amarcord
# The Godfather
# High Plains Drifter
# Dirty Harry
# Annie Hall
# Star Wars
# May I help you? Who acted in The Godfather?
# Marlon Brando
# Al Pacino
# James Caan
# Robert Duvall
# Diane Keaton
# May I help you? What movies did Diane Keaton appear in?
# The Godfather
# Annie Hall
# May I help you? Who directed Annie Hall?
# Woody Allen
# May I help you? Who wrote Annie Hall?
# I don't know.
# May I help you? What movies were made after 1980?
# The Big Easy
# Boyz in the Hood
# Unforgiven
# May I help you? goodbye
# Come back again soon!

