import bpy
from mathutils import Vector, Euler
import bmesh
import math

def NormalInDirection(normal, direction, limit = 0.99):
    return abs(direction.dot( normal )) > limit

def UpOrDown(normal):
    
    up = NormalInDirection(normal, Vector((0.0, 0.0, 1.0)), limit = 0.99)
    down = NormalInDirection(normal, Vector((0.0, 0.0, -1.0)), limit = 0.99)
    if up or down:
        return True
    return False

def morph_shape(x, L, H, morph_type="linear"):
    
    y = H
    if morph_type == "linear":
        y = (x/L)*H
    elif morph_type == "logistic":
        k = 10.0
        v = 1.0
        y = H/(1.0 + math.exp(-k*(x-L/4.0))**v)
    elif morph_type == "sinusoid":
        y = math.sin((x/L)*math.pi/2.0)
    elif morph_type == "hyperbolic_tan":
        y = math.tanh((x/L)*math.pi/2.0)
    elif morph_type == "circle":
        y = math.sqrt(L*L-(L-x)**2)         
    return y

def generate_pendant_shade(shape_parameters):

    radius = shape_parameters["radius"]
    depth = shape_parameters["height"]
    bpy.ops.mesh.primitive_cylinder_add(radius=radius,
                                        depth=depth)
    
    cone = bpy.data.objects["Cylinder"]
    cone.name = "shade"
    bpy.ops.object.mode_set(mode='EDIT')

    num_subdivisions = 3
    for idx in range(num_subdivisions):
        bpy.ops.mesh.subdivide()
        
    bm = bmesh.from_edit_mesh(cone.data)
    num_verts = len(bm.verts)
    
    for idx in range(num_verts):
        bm.verts.ensure_lookup_table()
        vert = bm.verts[idx]
        vert.co.z = vert.co.z -depth/2.0
        theta = math.atan2(vert.co.x, vert.co.y)
        
        if abs(vert.co.z)<shape_parameters["stem_length"]:
            rad = shape_parameters["fixture_radius"]
        else:
            rad = morph_shape(abs(vert.co.z)-shape_parameters["stem_length"], 
                              depth, radius-shape_parameters["fixture_radius"], 
                              morph_type=shape_parameters["division_pattern"])
            rad += shape_parameters["fixture_radius"]
        vert.co.x = rad*math.sin(theta)
        vert.co.y = rad*math.cos(theta)        
    for face in bm.faces:
        if UpOrDown(face.normal):
            face.select = True
        else:
            face.select = False 
    faces_select = [f for f in bm.faces if f.select] 
    bmesh.ops.delete(bm, geom=faces_select, context=3)
    bmesh.update_edit_mesh(cone.data, True)
      
    # Extrude faces
    bpy.ops.mesh.select_mode( type  = 'FACE'   )
    bpy.ops.mesh.select_all( action = 'SELECT' )
    bpy.ops.mesh.extrude_region_move(
        TRANSFORM_OT_translate={"value":(0, 0, 0.01)} ) 
    bpy.ops.mesh.extrude_region_shrink_fatten(
        TRANSFORM_OT_shrink_fatten={"value":-0.05})
    bpy.ops.object.mode_set(mode='OBJECT')
    return cone

def generate_mesh_shade(shape_parameters):

    radius1 = shape_parameters["radius"]
    depth = shape_parameters["height"]
    radius2 = shape_parameters["fixture_radius"]
    
    bpy.ops.mesh.primitive_cube_add(radius=radius1)
    cube = bpy.data.objects["Cube"]
    cube.name = "shade"
    bpy.ops.object.mode_set(mode='EDIT')

    num_subdivisions = 3
    for idx in range(num_subdivisions):
        bpy.ops.mesh.subdivide()
        
    bm = bmesh.from_edit_mesh(cube.data)
    slat_thickness = 0.05
    for i in range( len( bm.verts ) ):
        bm.verts.ensure_lookup_table()
        vert = bm.verts[i]
        vert.co.z = vert.co.z -radius1/2.0
        vert.co.z *= (depth/radius)
        vert.co.y = vert.co.y*(slat_thickness/radius)
        vert.co.x = vert.co.x*(slat_thickness/radius)
        
        rad = morph_shape(abs(vert.co.z), 
                          depth, 
                          radius1, 
                          morph_type=shape_parameters["division_pattern"])
        vert.co.x += rad

    bpy.ops.object.mode_set(mode='OBJECT')
    
    num_slats = 36
    cube_copy = cube.data.copy()
    for idx in range(num_slats-1):
        ob = bpy.data.objects.new("Cube Copy"+str(idx), cube_copy)
        angle = 2.0*(math.pi/float(num_slats))*float(idx+1)
        ob.rotation_euler = Euler((0.0, 0.0, angle), 'XYZ')
        scene = bpy.context.scene
        scene.objects.link(ob)
        scene.update()    
        
    # Add torus
    bpy.ops.mesh.primitive_torus_add(location=(0.0, 0.0, -2.6*radius1),
                                     major_radius=2.4*radius1, 
                                     minor_radius=0.02)        
        
    for ob in bpy.context.scene.objects:
        if ob.type == 'MESH':
            ob.select = True
            bpy.context.scene.objects.active = ob
        else:
            ob.select = False
    bpy.ops.object.join()

    return cube

def make_square_ring(radius, depth, thickness):
    
    bpy.ops.mesh.primitive_cylinder_add(radius=radius,
                                        depth=depth)
    cone = bpy.data.objects["Cylinder"]
    cone.name = "square_ring"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(cone.data)
    for face in bm.faces:
        if UpOrDown(face.normal):
            face.select = True
        else:
            face.select = False 
    faces_select = [f for f in bm.faces if f.select] 
    bmesh.ops.delete(bm, geom=faces_select, context=3)
    bmesh.update_edit_mesh(cone.data, True)
    
    # Extrude faces
    bpy.ops.mesh.select_mode( type  = 'FACE'   )
    bpy.ops.mesh.select_all( action = 'SELECT' )
    bpy.ops.mesh.extrude_region_move(
        TRANSFORM_OT_translate={"value":(0, 0, 0.01)} ) 
    bpy.ops.mesh.extrude_region_shrink_fatten(
        TRANSFORM_OT_shrink_fatten={"value":-thickness})
    bpy.ops.object.mode_set(mode='OBJECT')
    return bpy.data.objects["square_ring"]   

def generate_led_shade(shape_parameters):

    radius1 = shape_parameters["radius"]
    depth = shape_parameters["height"]
    radius2 = shape_parameters["fixture_radius"]
    
    ring1 = make_square_ring(3.0*radius1, depth/10.0, 0.2)
    bpy.ops.transform.rotate(value=-math.pi/12.0, axis=(1.0,0.0,0.0))
    
    ring2 = make_square_ring(1.5*radius1, depth/10.0, 0.2)
    bpy.ops.transform.rotate(value=math.pi/12.0, axis=(1.0,0.0,0.0))    
    
    ring3 = make_square_ring(1.0*radius1, depth/10.0, 0.2)    
    bpy.ops.transform.rotate(value=-math.pi/8.0, axis=(1.0,0.0,0.0))
    
    for ob in bpy.context.scene.objects:
        if ob.type == 'MESH':
            ob.select = True
            bpy.context.scene.objects.active = ob
        else:
            ob.select = False
    bpy.ops.object.join() 
    
    bpy.context.scene.objects.active = bpy.data.objects["square_ring"]
    bpy.data.objects["square_ring"].name = "shade"
    
    ring1 = make_square_ring(3.0*radius1+0.01, depth/16.0, 0.2)
    bpy.ops.transform.rotate(value=-math.pi/12.0, axis=(1.0,0.0,0.0))
    
    ring2 = make_square_ring(1.5*radius1+0.01, depth/16.0, 0.2)
    bpy.ops.transform.rotate(value=math.pi/12.0, axis=(1.0,0.0,0.0))    
    
    ring3 = make_square_ring(1.1*radius1+0.01, depth/16.0, 0.2)    
    bpy.ops.transform.rotate(value=-math.pi/8.0, axis=(1.0,0.0,0.0))    
    
    return bpy.data.objects["shade"]


