#
#  Module contains a number of functions useful
#  for conducting 2D random walks
#
import random
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle  # Used to draw rectangle
random.seed(None)        # Seed generator, None => system clock

def rand_angle():
    """  
    Performs unit 2D random step at random angle ( 0 to 2*PI)
    Returns (x,y) co-ordinates of unit step at random angle
     """
    rand_angle = random.uniform(0,2*np.pi)
    xdelta = np.sin(rand_angle)
    ydelta = np.cos(rand_angle)
    return xdelta, ydelta

def setup_box( box_loc , width, height):
    """  
    Sets shape of rectangle. Returns upper and lower corners 
    """ 
    xloc,yloc = box_loc
    low_left  = (xloc - .5 * width, yloc -.5 * height)
    top_right = (xloc + .5 * width, yloc +.5 * height)    
    return (low_left, top_right)


def check_inside_box(xvalue, yvalue, box):
    """  
    Checks if point is inside a rectangle. Returns True if inside.
    False if not      
    """ 
    xmin, ymin = box[0]
    xmax, ymax = box[1]
    if xmin < xvalue < xmax  and  ymin < yvalue < ymax:
        return True
    else:
        return False


def random_walk_box(num_steps, start_loc, box):
    """ Function conducts 2D random walk inside a box """
#    Start point for walk
    xpos = start_loc[0]
    ypos = start_loc[1]
    xwalk = [xpos]
    ywalk = [ypos]
    steps_taken = 0     
    while steps_taken < num_steps :
        # Create trial move
        xdelta, ydelta = rand_angle()
        xtrial = xpos + xdelta
        ytrial = ypos + ydelta
        # Check if trial move is inside boundary. If so, accept it
        if check_inside_box(xtrial, ytrial, box) :
            xpos = xtrial
            ypos = ytrial
            xwalk.append(xpos)
            ywalk.append(ypos)
            steps_taken += 1  
    return xwalk,ywalk


def random_walk_target(max_steps,start_loc, boundary,target):
    """  Performs a random walk inside a boundary with target """
    #  Start point for walk
    xpos = start_loc[0]
    ypos = start_loc[1]
    xwalk = [xpos]
    ywalk = [ypos]
    steps_taken = 0
    was_target_hit = False 
    #  Conduct walk until target reached or max steps exceeded     
    while steps_taken < max_steps :
        # Create trial move
        xdelta, ydelta = rand_angle()
        xtrial = xpos + xdelta
        ytrial = ypos + ydelta
        #  Check if target reached. if so, end walk
        if check_inside_box(xtrial, ytrial,target):
            xpos = xtrial
            ypos = ytrial
            xwalk.append(xpos)
            ywalk.append(ypos)
            steps_taken += 1
            was_target_hit = True 
            break         
        # Check if still inside boundary. If so, accept move and continue 
        if check_inside_box(xtrial, ytrial,boundary):
            xpos = xtrial
            ypos = ytrial
            xwalk.append(xpos)
            ywalk.append(ypos)
            steps_taken += 1 
    return xwalk,ywalk,steps_taken, was_target_hit


def draw_boundary(start_loc,boundary):
    """  
    Draw boundary on graph and set size of graph (xlim,ylim)
    """
    xmin, ymin = boundary[0]
    xmax, ymax = boundary[1]
    plt.gca().add_patch(Rectangle((xmin,ymin), xmax-xmin, ymax-ymin,  fill=False, edgecolor='b',lw=4))
    plt.xlim([xmin-3,xmax+3])
    plt.ylim([ymin-3,ymax+3])
#  Create bold axis lines through walk starting point
    plt.axvline(start_loc[0], color='grey', lw=2)
    plt.axhline(start_loc[1], color='grey', lw=2)  
    return

def draw_target(target):
    """  
    Draw target on graph 
    """
    xmin, ymin = target[0]
    xmax, ymax = target[1]
    plt.gca().add_patch(Rectangle((xmin,ymin), xmax-xmin, ymax-ymin,  fill=True, facecolor='r'))


