from __future__ import division
import pygame, math, random

import constants
import ui
import vect

def get_cartesian(object):

    vx = object.speed * math.cos(object.vel_dir)
    vy = object.speed * math.sin(object.vel_dir)
    object.velocity  = (vx,vy)

    ax = object.acc * math.cos(object.acc_dir)
    ay = object.acc * math.sin(object.acc_dir)
    object.acceleration  = (ax,ay)

    temp_ax = object.temp_acc * math.cos(object.temp_acc_dir)
    temp_ay = object.temp_acc * math.sin(object.temp_acc_dir)
    object.temp_acceleration  = (temp_ax,temp_ay)

    physics_ax = object.physics_acc * math.cos(object.physics_acc_dir)
    physics_ay = object.physics_acc * math.sin(object.physics_acc_dir)
    object.physics_acceleration  = (physics_ax,physics_ay)

def get_polar_speed(object):
    (vx,vy) = object.velocity
    object.vel_dir = math.atan2(vy, vx)
    object.speed = math.hypot(vx, vy)

def get_polar_acc(object):
    (ax,ay) = object.acceleration
    object.acc_dir = math.atan2(ay, ax)
    object.acc = math.hypot(ax, ay)

def get_polar_temp_acc(object):
    (temp_ax,temp_ay) = object.temp_acceleration
    object.temp_acc_dir = math.atan2(temp_ay, temp_ax)
    object.temp_acc = math.hypot(temp_ax, temp_ay)

def get_polar_phys_acc(object):
    (physics_ax,physics_ay) = object.physics_acceleration
    object.physics_acc_dir = math.atan2(physics_ay, physics_ax)
    object.physics_acc = math.hypot(physics_ax, physics_ay)

##def get_max_speed(object):
##    if object.drag_term_2 == 0:
##        max_speed = 10000
##    else:
##        max_speed = (1-object.drag_term_1/object.attrs['physics']['mass']*object.physics_dt)/(2*object.drag_term_2/object.attrs['physics']['mass']*object.physics_dt)+ \
##        math.sqrt(((1-object.drag_term_1/object.attrs['physics']['mass']*object.physics_dt)/(2*object.drag_term_2/object.attrs['physics']['mass']*object.physics_dt))**2-object.friction_1/(object.drag_term_2/object.attrs['physics']['mass']))
##    return 0.99*max_speed

def limit_speed(object):
##    if object.speed > object.max_speed:
##        object.speed = object.max_speed
##        get_cartesian(object)
##
    if math.fabs(object.speed)< 0.001:
        """ this eliminates the need to calculate speed < 0.001, has no real effect on physics """
        object.speed = 0
        get_cartesian(object)

def force(object, amount, angle):
    ''' takes a force and returns the new acceleration of object'''
    fx = amount * math.cos(angle)
    fy = amount * math.sin(angle)

    object.acceleration = vect.add(1,object.acceleration,1/object.attrs['physics']['mass'],(fx,fy))
    get_polar_acc(object)

def force_cart(object, force):
    object.acceleration = vect.add(1,object.acceleration,1/object.attrs['physics']['mass'],force)
    get_polar_acc(object)

def temp_force(object, amount, angle):
    ''' takes a force and returns the new acceleration of object, used by friction as we
    want to apply new frictions multiple physics ticks per game tick,
    while unaltering the "external" acceleration at the start of each game tick'''
    fx = amount * math.cos(angle)
    fy = amount * math.sin(angle)

    object.temp_acceleration = vect.add(1,object.temp_acceleration,1/object.attrs['physics']['mass'],(fx,fy))
    get_polar_temp_acc(object)

def update_friction(object):
    if object.speed == 0:
        temp_force(object, min(object.attrs['physics']['friction_0']*constants.GRAVITY, object.physics_acc)*object.attrs['physics']['mass'], object.physics_acc_dir+math.pi)
    else:
        # add kinetic frinction and drag to oppose motion
        # we take the minimum because it is force opposing the motion, taking the larger one could cause the object to "overshoot"
        drag_term = object.attrs['physics']['drag_term_1']*object.speed+object.attrs['physics']['drag_term_2']*object.speed**2
        friction_term = object.attrs['physics']['friction_1']*constants.GRAVITY*object.attrs['physics']['mass']
        min_friction_term = object.speed*object.attrs['physics']['mass']/object.physics_dt
        temp_force(object, min(friction_term+drag_term,min_friction_term), object.vel_dir+math.pi)

def update_velocity(object):
    object.velocity = vect.add(1,object.velocity, object.physics_dt,object.physics_acceleration)
    get_polar_speed(object)
    limit_speed(object)

def update_location(object):
    object.location = vect.add(1,object.location, object.physics_dt,object.velocity)
    object.check_world_boundary()
    object.world.grid.moveSim(object)

def get_physics_dt(object):
    """ find the physics time appropriate to use for the object so it doesn't "skip" any space as it moves"""
    if object.speed != 0:
        time_speed = object.radius*2/object.speed
    else:
        time_speed = object.physics_dt
    if object.physics_acc != 0:
        time_acc = math.sqrt(object.radius*4/object.physics_acc)
    else:
        time_acc = object.physics_dt
    object.physics_dt = min(time_speed, time_acc, object.world.physics_dt)

def append_physics_locations(object):
    object.physics_locations.append(object.location)

    """ here we add the location to the draw location list for bullets """
    if object.sim_cat == "bullet":
        if object.world.player:
            if object.grid_location in object.world.player.visible_tiles or object.world.player.health <0:
                object.draw_locations.append(object.location)
        else:
            object.draw_locations.append(object.location)

def update_kinematics(object):
    object.physics_time_remaining += object.world.dt
    '''need to update acceleration here, to get things started. this retrieves "self" accelerations
     from the object and adds them to external accelerations, like from, say, wind'''
    object.turn()
    object.update_acceleration()
    """objects may be touching without moving at start of loop"""
    if object.collidable:
        #if random.randint(1,5)==1:
            object.world.grid.check_collision_grid(object)
            object.collision()
    object.physics_acceleration = vect.add(1,object.acceleration,1,object.temp_acceleration)
    get_polar_phys_acc(object)
    if object.speed == 0 and object.physics_acc == 0:
        object.physics_time_remaining = 0
    else:
        get_physics_dt(object)
        object.physics_locations = []
        object.draw_locations = []
        append_physics_locations(object)
        while object.physics_time_remaining >= object.physics_dt:
            """innards of main physics loop here"""
            """ we might aldready have a temp_acc from a collision"""
            object.physics_acceleration = vect.add(1,object.acceleration,1,object.temp_acceleration)
            get_polar_phys_acc(object)
            """ update friction using physics_acc"""
            update_friction(object)
            """ friction adds a new_temp_acc to temp_acc, so recompute physics_acc"""
            object.physics_acceleration = vect.add(1,object.acceleration,1,object.temp_acceleration)
            update_velocity(object)
            update_location(object)
            """reset temp_acceleration so that temp forces are only applied once the physics loop """
            object.temp_acc = 0
            get_cartesian(object)
            """ the objects rect has just updated it's position, so check if it is colliding with anything
             - this might add new temp_acc so make sure temp_acc is already zeroed out"""
            if object.collidable:
                object.world.grid.check_collision_grid(object)
                object.collision()
            append_physics_locations(object)
            """ remove the physics time from the main loop time"""
            object.physics_time_remaining -=object.physics_dt
            """ in case the object has slowed down we recalc its physics_dt, hoping for a speed increase"""
            get_physics_dt(object)

    '''resets acceleration so that the only forces applied next main loop tick are new ones'''
    object.acc = 0
    get_cartesian(object)


