### Tendious ###
# In a static language this could be done in a similar way
class Animal(object):
    def __init__(self):
        self.hp = 5
        self.fly = False
        
    def eat(self, food):
        if type(food) == Apple:
            self.hp += 1
        elif type(food) == Spike:
            self.hp -= 1
        elif type(food) == Mushroom:
            self.fly = True
        else:
            raise TypeError("Can't handle %s" % (type(food)))
    

### More dynamic, it is hard to add new methods though ###
class Animal(object):
    def __init__(self):
        self.hp = 5
        self.fly = False

    def eat_apple(self):
        self.hp += 1

    def eat_pear(self):
        self.hp -= 1

    def eat_mushroom(self):
        self.fly = True
        
    def eat(self, food):
        function = food.__class__.__name__.lower()
        eval("s.eat_%s()" % (function), {"s": self})

### Introducing double dispatch (aka the visitor pattern) ###
class Animal(object):
    def __init__(self):
        self.hp = 5
        self.fly = False

    def eat(self, food):
        return food.eaten(self)


class Apple(object):
    def eaten(self, eater):
        eater.hp += 1

class Spike(object):
    def eaten(self, eater):
        eater.hp += 1

class Mushroom(object):
    def eaten(self, eater):
        eater.fly = True


a = Apple()
s = Spike()
m = Mushroom()

bo = Animal()

bo.eat(a)
bo.eat(m)

print bo.fly, bo.hp


### implementing a simple interpreter for a fictive AST ###
# 
# var(var(X)) --> [X], {atom(X)}.
# num(num(X)) --> [X], {number(X)}.

# assign(assign(X, Y)) --> var(X), [:=], expr(Y).
# expr(X)		     --> term(X).
# expr(add(X, Y))	     --> term(X), [+], expr(Y).
# expr(sub(X, Y))	     --> term(X), [-], expr(Y).

# term(mul(X, Y))	 --> factor(X), [*], term(Y).
# term(divv(X, Y)) --> factor(X), [/], term(Y).
# term(X)		 --> factor(X).
# factor(X)	 --> ['('], expr(X), [')'].
# factor(X)	 --> num(X).
# 
# bnf
# 
class Node(object):
    def visit(self, visitor):
        class_name = "visit_%s" % (self.__class__.__name__)
        if hasattr(visitor.__class__, class_name):
            return eval("visitor.%s(self)" % (class_name))
        else:
            return visitor.generic_visit(self)

class Expr(Node):
    def __init__(self, lhs, op=None, rhs=None):
        self.lhs = lhs
        self.rhs = rhs
        self.op = op

class Term(Node):
    def __init__(self, lhs, op=None, rhs=None):
        self.lhs = lhs
        self.op = op
        self.rhs = rhs

class Num(Node):
    def __init__(self, n):
        self.num = n

class Var(Node):
    def __init__(self, v, exp="Load"):
        self.var = v
        self.exp = exp

tree = Expr(lhs=Term(lhs=Num(10), 
                     op="*", 
                     rhs=Term(lhs=Num(10))), 
            op="+", 
            rhs=Expr(lhs=Term(lhs=Num(10))))

class NodeVisitor(object):
    def generic_visit(self, node):
        print node
        return node

    def visit(self, node):
        if node is not None:
            return node.visit(self)
        else: return None

    def visit_Num(self, node):
        return node.num

    def visit_Var(self, node):
        return node.var

    def visit_Term(self, node):
        lhs = self.visit(node.lhs)
        rhs = self.visit(node.rhs)
        if(rhs is not None):
            return lhs * rhs
        else:
            return lhs

    def visit_Expr(self, node):
        lhs = self.visit(node.lhs)
        rhs = self.visit(node.rhs)
        if(rhs is not None):
            return lhs + rhs
        else:
            return lhs


v = NodeVisitor().visit(tree)
print "visited, giving back: ", v

# Lookup the interpreter pattern in GOF, interesting read...


#
# Using python parser to parse a python string and then iterpret it
#
#

import ast


scope = {}
class ExprEval(ast.NodeVisitor):
    def __init__(self):
        self.operator = {ast.Add: lambda l, r: l+r,
                         ast.Sub: lambda l, r: l-r,
                         ast.Mult: lambda l, r: l*r,
                         ast.Div: lambda l, r: l/r}

    def visit_Module(self, node):
        return self.visit(node.body[0])

    def visit_Num(self, node):
        return node.n

    def visit_Name(self, node):
        if type(node.ctx) == ast.Load:
            return scope[node.id]
        else:
            return node.id

    def visit_Assign(self, node):
        name = self.visit(node.targets[0])
        value = self.visit(node.value)
        scope[name] = value

    def visit_Expr(self, node):
        return self.visit(node.value)

    def visit_BinOp(self, node):
        l = self.visit(node.left)
        r = self.visit(node.right)
        return self.operator[type(node.op)](l, r)

def eval(code):
    return ExprEval().visit(ast.parse(code))
        


class Maze(object):
    def __init__(self):
        self.rooms = []

    def add_room(self, room):
        self.rooms.append(room)

    def get_room(self, no):
        return self.rooms[no]

class Part(object):
    def enter(self):
        pass

class Room(Part):
    def __init__(self):
        self.walls = {}

    def set_side(self, side, part):
        self.walls[side] = part

class Wall(Part):
    pass

class Door(Part):
    def __init__(self, r1=None, r2=None):
        self.room1 = r1
        self.room2 = r2
        self.is_open = False


# This function is pretty complicated, considering that all it does is
# create a maze with two rooms. There are obvious ways to make it
# simpler. For example, the Room constructor could initialize the
# sides with walls ahead of time. But that just moves the code
# somewhere else. The real problem with this member function isn't its
# size but its inflexibility. It hard-codes the maze layout. Changing
# the layout means changing this member function, either by overriding
# it which means reimplementing the whole thing or by changing parts
# of it which is error prone and doesn't promote reuse.
def create_maze():
    m = Maze()
    r1 = Room()
    r2 = Room()
    
    d = Door(r1, r2)

    m.add_room(r1)
    m.add_room(r2)

    r1.set_side("north", Wall())
    r1.set_side("south", Wall())
    r1.set_side("east", d)
    r1.set_side("west", Wall())
    
    r2.set_side("north", Wall())
    r2.set_side("south", Wall())
    r2.set_side("east", Wall())
    r2.set_side("west", d)

    return m

# Abstract factory patter to the rescue!

class MazeFactory(object):
    def make_maze(self):
        return Maze()
    def make_room(self):
        return Room()
    def make_door(self, r1, r2):
        return Door(r1, r2)
    def make_wall(self):
        return Wall()


def make_maze(factory):
    m = factory.make_maze()
    r1 = factory.make_room()
    r2 = factory.make_room()
    
    d = factory.make_door(r1, r2)

    m.add_room(r1)
    m.add_room(r2)

    r1.set_side("north", factory.make_wall())
    r1.set_side("south", factory.make_wall())
    r1.set_side("east", d)
    r1.set_side("west", factory.make_wall())
    
    r2.set_side("north", factory.make_wall())
    r2.set_side("south", factory.make_wall())
    r2.set_side("east", factory.make_wall())
    r2.set_side("west", d)


# What have we gained?
# we can create mazes...
maze = make_maze(MazeFactory())

# but we can also define, a new maze type:

class TransparentWall(Part):
    pass

class TransparentFactory(MazeFactory):
    def make_wall(self):
        return TransparentWall()

# make a maze with transparent walls.
maze = make_maze(TransparentFactory())

# I have talked about 2 patterns that I find my self using OFTEN,
# but there are so many, READ GOF!

### DESCRIPTOR LOGGER ###
import time
def trace(f):
    """
    Simple logger that wraps a function call
    in a logging wrapper if enabled is true
    """
    def log_wrapper(*args, **kv):
        call = "%s(%s)" % (f.func_name, 
                           ", ".join(map(
                    lambda a: a.__class__.__name__, args)))

        print "TRACE:\t Start call: '%s'" % (call)

        start = time.time()
        ret = f(*args, **kv)
        elapsed = time.time() - start
        print "TRACE:\t End call  : '%s' \n\t Took %s sek" % (call, elapsed)
        
    log_wrapper.__name__= f.__name__
    log_wrapper.__doc__ = f.__doc__
    return log_wrapper

def log(m):
    print "LOG  :\t %s" % (m)


if __name__ == "__main__":
    @trace
    def test(s):
        """ Documentation ...."""
        print s

    class Isak(object):
        @trace
        def class_test(self, x):
            print x
    
    test("isak")
    Isak().class_test("isak")
    
    help(test)



## Observer ##
class Button(object):
    def __init__(self, text):
        self.text = text
        self.listeners = []
    def add_listener(self, listener):
        self.listeners.append(listener)

    def click(self):
        self.fire_click_event()

    def fire_click_event(self):
        for l in self.listeners:
            l(self)


@trace
def hej_click(sender):
    print sender.text, " is clicked"

hej = Button("Hello world")
hej.add_listener(hej_click)
hej.click()


#### AST REWRITE ####


## Rename all occurences of occ with rep
## in the code, eg. if var = 1 then rename var, foo
## would transform the code into foo = 1
import ast
class RenameLookup(ast.NodeTransformer):
    def __init__(self, occ, rep):
        self.occ = occ
        self.rep = rep

    def visit_Name(self, node):
        if self.occ == node.id:
            return ast.copy_location(ast.Name(id=self.rep, ctx=node.ctx), node)
        else: 
            return node


## Rewrite import statements to always allow for an alias
## eg. import isak would be import isak as i
class ShorterImport(ast.NodeTransformer):
    def visit_Import(self, node):
        for alias in node.names:
            if alias.asname == None:
                alias.asname = alias.name[0]
            
        return ast.copy_location(ast.Import(names=node.names), node)

    
xx = ShorterImport().visit(ast.parse("import isak"))
print ast.dump(xx)
print ast.dump(RenameLookup("a", "x").visit(ast.parse("a + foo")))

code = compile(xx, "<test>", "exec")
exec code

def test():
    pass

def code_test():
    print "This is a test"


test.__code__ = code_test.__code__
c = test.__code__
exec c

c = compile("print 1+1", "<test>", "exec")
test.__code__ = c

test()
print c
