from __future__ import division 
import pygame, math, vect, ui, random, physics, simobject, constants
            
def sphere_collide(object1, object2):
    if  sphere_test(object1, object2):
        """ assumes hard bodies that mostly conserve energy and momentum"""
        relative_velocity = vect.add(1,object1.velocity,-1,object2.velocity)
        rel_speed = vect.length(relative_velocity)
        if rel_speed > 0:
            direction_to = vect.direction_to(object2.location, object1.location)
            projection = vect.dot(direction_to,relative_velocity)
            if projection  > 0:
                angle = vect.angle_to(object2.location, object1.location)
                restitution = object1.attrs['physics']['elasticity']*object2.attrs['physics']['elasticity']
                mass_factor = object2.attrs['physics']['mass']*object1.attrs['physics']['mass']/(object1.attrs['physics']['mass']+object2.attrs['physics']['mass'])
                damping = 0.5
                momentum_transfer = (1+restitution)*mass_factor*projection*damping
                impulse1  = momentum_transfer/object1.physics_dt
                impulse2  = momentum_transfer/object2.physics_dt
                """we apply forces to both objects in case object1 moves quickly and object2 never gets to affect object1"""
                physics.temp_force(object1,impulse1/2,angle+math.pi)
                physics.temp_force(object2,impulse2/2,angle) 

def sphere_test(object1, object2):
    collision_distance = object1.radius+object2.radius
    dist = vect.distance_to(object1.location, object2.location)
    if  dist < collision_distance:
        return True
    else:
        return False
    
def square_test(object1, object2, radius = None):
    # test for collision between round object1 and square object2
    # yes, i realise that this is actually between two square objects currently
    bool = False
    if radius == None:
        r = object1.radius
    else:
        r = radius
    dx,dy = object2.lengthx/2, object2.lengthy/2
    if math.fabs(object1.location[0]-object2.location[0]) < r+dx:
        if math.fabs(object1.location[1]-object2.location[1]) < r+dy:
            bool = True
    return bool

def square_projection(object1, object2, radius = None):
    """method to back track to find point of entry and can move sim there"""
    if square_test(object1, object2):
        vx,vy = object1.velocity
        lx,ly = object1.location
        
        if radius == None:
            r = object1.radius
        else:
            r = radius
            
        dx,dy = object2.lengthx/2, object2.lengthy/2
        x,y = object2.location
        
        time = {}
        Time = None
        """setup time/direction dictionary"""
        if not vx:
            time[(1,0)] = 0
            time[(-1,0)] = 0
        else:
            time[(1,0)] = (x-dx-r-lx)/vx
            time[(-1,0)] = (x+dx+r-lx)/vx
        if not vy:
            time[(0,1)] = 0
            time[(0,-1)] = 0
        else:
            time[(0,1)] = (y-dy-r-ly)/vy
            time[(0,-1)] = (y+dy+r-ly)/vy
        """ find the shortest historical intersection with a square boundary """
        for key, val in time.items():
            if Time:
                if val < 0 and val > Time:
                    Time = val
                    dir = key
            else:
                if val < 0:
                    Time = val
                    dir = key
        """ if vx = vy = 0 have a problem """
        if not Time:
            Time = 0
            dir = (0,0)
        """ reset object1 location at the edge"""
        if object1.penetrating:
            # if it is penetrating, we might not want to reset its position, so we store the potential position for later usage
            # we remove its latest position so that it doesnt draw itself inside the solid object
            if len(object1.physics_locations)>0:
                object1.physics_locations.pop(-1)
            object1.physics_locations.append((lx+Time*vx , ly+Time*vy ))
        else:
            object1.location = lx+Time*vx , ly+Time*vy

        return dir

def repulse(object1, object2):
    ''' keeps objects from occupying the same space'''
    collision_distance = 1.1*(object1.radius+object2.radius)
    distance = vect.distance_to(object1.location, object2.location)
    penetration = collision_distance-distance
    if penetration>0:
        angle = vect.angle_to(object1.location, object2.location)
        ##repulse1 = 4*(penetration+1)/(distance+0.01)*object1.attrs['physics']['mass']/object1.world.dt
        repulse1 = max(object1.acc+0.1,object1.attrs['physics']['friction_0'])*(object1.attrs['physics']['mass']+0.001)
        if object2.mobile:
            frac = 1.2
        else:
            frac = 2
        ##fraction =(max(frac,collision_distance/max(distance,0.001))-frac)*object2.attrs['physics']['mass']/object1.attrs['physics']['mass']
        ##object1.health -= fraction/object1.max_health*1000
        physics.temp_force(object1,repulse1,angle)
        
def circle_projection(object1, object2):
    ''' keeps objects from occupying the same space'''
    collision_distance = (object1.radius+1*object2.radius)
    distance = vect.distance_to(object1.location, object2.location)
    penetration = max(collision_distance-distance,0)
    if penetration>0:
        if len(object2.physics_locations)==0:
            prev_location = object2.location
        else: prev_location = object2.physics_locations[-1]
        direction_to = vect.direction_to(prev_location, object1.location)
        angle = vect.angle_to(object1.location, prev_location)
        object2.location = vect.add(1,object2.location, 1.01*penetration+0.01,direction_to)
        object2.world.grid.moveSim(object2)
     
def bullet_strike(object1, object2):
    ''' bullet collisions with objects
    '''
    debug_color = False
    direction_to = None
    if object2.attrs['physics']['shape'] == "square":
        if square_test(object1, object2, 0):
            direction_to = square_projection(object1, object2,0) 
    elif object2.attrs['physics']['shape'] == "circle":
        if sphere_test(object1, object2):
            direction_to = vect.direction_to(object2.location, object1.location)
    if direction_to:
        relative_velocity = vect.add(1,object1.velocity,-1,object2.velocity)
        projection = vect.dot(direction_to,relative_velocity)
        if projection > 0:
            # we draw some blood
            if (object2.sim_cat is "biped") or (object2.sim_type is "zombie_hive"):
                object1.world.blood = object1.world.add_sim("blood", object1.location, object1.direction, sim_cat = "effect")
                ##pygame.draw.circle(object1.world.screen, (0,0,0), ui.screen_pos(object1.world, object1.location),10)
                # we let zombies know they've been shot. might use this for others. might change this to ref ai.
                if (object2.sim_cat is "biped"):
                    object2.hit = True
                    object2.hit_direction = object1.direction+math.pi
                    
            # we calculate how the bullet interacts with target            
            directed_energy = 1/2*object1.attrs['physics']['mass']*projection**2
            embed_bullet = False
            ##print directed_energy/object1.attrs['physics']['reference_area']
            if (directed_energy > object1.attrs['physics']['reference_area']*object2.attrs['physics']['hardness']):# or (object2.block and object1.currently_penetrating):
                #object1 mostly penetrates
                if object1.attrs['physics']['elasticity'] < random.random()*random.random():
                    """ bullet gets stuck in target """
                    angle = object1.vel_dir
                    mass_factor = object2.attrs['physics']['mass']*object1.attrs['physics']['mass']/(object1.attrs['physics']['mass']+object2.attrs['physics']['mass'])
                    embed_bullet = True
                    energy = 0.5*(projection*mass_factor)**2/object1.attrs['physics']['mass']
                    object2.damage(energy, "kinetic")
                    momentum_transfer = mass_factor*projection
                    #physics.temp_force(object1,momentum_transfer/object1.physics_dt,angle+math.pi)
                    physics.temp_force(object2,momentum_transfer/object2.physics_dt,angle)
                    object1.velocity = object2.velocity
                    physics.get_polar_speed(object1)
                    object1.health = -1
                    object1.physics_time_remaining = 0
                    if debug_color:
                        object1.color = (0,0,255)
                else:
                    ## bullet squarely hits target and passes cleanly through
                    mass_factor = 0
                    #angle = object1.vel_dir
                    if debug_color:
                        object1.color = (0,255,0)
                    object2.damage(object1.attrs['physics']['mass']*projection**2/2, "kinetic")
                    ## we do not want the bullet to reflect off blocks for a while, or else
                    ## it can bounce between two adjacent blocks, which is quite computationally slow
                    if object2.block:
                        object1.currently_penetrating = True


            else:
                ## object1 mostly reflects
                if debug_color:
                    object1.color = (255,0,0)
                if len(object1.physics_locations)>0:
                    ## restore object1 location to exterior of object2 
                    object1.location = object1.physics_locations[-1]
                ## draw a ricochet
                if object2.block:
                    object1.world.ricochet = object1.world.add_sim("ricochet", object1.location, object1.direction, sim_cat = "effect")
                
                ##calculate physics
                restitution = object1.attrs['physics']['elasticity']*object2.attrs['physics']['elasticity']
                mass_factor = (1+restitution)*object2.attrs['physics']['mass']*object1.attrs['physics']['mass']/(object1.attrs['physics']['mass']+object2.attrs['physics']['mass'])
                angle = vect.angle_to(direction_to)
                momentum_transfer = mass_factor*projection
                ## enact physics
                physics.temp_force(object1,momentum_transfer/object1.physics_dt,angle+math.pi)
                physics.temp_force(object2,momentum_transfer/object2.physics_dt,angle)
                object1.direction = object1.vel_dir
                ## cause some damage
                energy = 0.5*(projection*mass_factor)**2/object1.attrs['physics']['mass']
                object2.damage(energy, "kinetic")


def laser_strike(object1, object2):
    ''' laser collisions wih objects'''
    collision_distance = 0.9*(object1.radius+object2.radius)
    if vect.distance_to(object1.location, object2.location) < collision_distance:
        direction_to = vect.direction_to(object2.location, object1.location)
        relative_velocity = vect.add(1,object1.velocity,-1,object2.velocity)
        projection = vect.dot(direction_to,relative_velocity)
        if projection >= 0:
            object1.velocity = object2.velocity
            physics.get_polar_speed(object1)
            object1.health = -1
            object1.physics_time_remaining = 0
            object1.world.Heat.add_cell(object2.grid_location, constants.LASER_PULSE_TEMP)
            object2.damage(constants.LASER_PULSE_DAMAGE, "burn")