#!bpy
"""
to run: 
(aAtually I have not been able to run this from command line - the thing
exits without rendering. If I add bpy.ops.render, it alwys renders layers, rather
then compositing output)
blender -b --python this_fnm.py

"""

import bpy
from math import radians
import fnmatch
import os


###################################
def delete_old_stuff():

    # escape edit mode
    if bpy.ops.object.mode_set.poll():
        bpy.ops.object.mode_set(mode='OBJECT')

    # delete all mesh objects
    bpy.ops.object.select_by_type(type='MESH')
    bpy.ops.object.delete()


    # delete all lamps
    bpy.ops.object.select_by_type(type='LAMP')
    bpy.ops.object.delete()

    # delete all font objects
    bpy.ops.object.select_by_type(type='FONT')
    bpy.ops.object.delete()

    # delete all render layers but one
    render_layers = bpy.context.scene.render.layers
    for active_index in range (1,len(render_layers)):
        render_layers.active_index = active_index
        render_layers.remove(render_layers.active)

    # delete all materials
    for i in bpy.data.materials.values():
         bpy.data.materials.remove(i)

    # delete all textures
    for i in bpy.data.textures.values():
        bpy.data.textures.remove(i)

#####################################################################
def makeGlossyTextured (object, image_loaded, material_name):

    material = bpy.data.materials.new(material_name)
    # as soon as we do this we  have Diffuse BSDF and Material Output nodes, linked:
    material.use_nodes = True
    nodes = material.node_tree.nodes
    links = material.node_tree.links

    # uv map node
    uv_node = nodes.new('ShaderNodeUVMap')
    uv_node.uv_map = object.data.uv_textures.active.name

    # image texture node
    image_texture_node = nodes.new(type='ShaderNodeTexImage')
    image_texture_node.image = image_loaded

    links.new(uv_node.outputs['UV'], image_texture_node.inputs['Vector'])

    # diffuse node and Material output are  already generated, and linked
    # so we just need to pipe in the testure into Diffuse BSDF node
    diffuse_node = nodes.get("Diffuse BSDF")
    links.new(image_texture_node.outputs[0], diffuse_node.inputs[0])

    # add a glossy BSDF
    glossy_node =  nodes.new(type='ShaderNodeBsdfGlossy')
    glossy_node.inputs["Color"].default_value = [1.0, 1.0, 1.0, 1.0]
    glossy_node.inputs["Roughness"].default_value = 0.0

    # add a mix node
    mix_node  = nodes.new(type='ShaderNodeMixShader')
    links.new(diffuse_node.outputs[0], mix_node.inputs[1]) # whats mix.inputs[0]?
    links.new(glossy_node.outputs[0], mix_node.inputs[2])

    # output of the mix node into Material Output
    mat_output = nodes.get("Material Output")
    links.new(mix_node.outputs[0], mat_output.inputs[0])

    return material

#####################################################################
def makeEmission (material_name):

    material = bpy.data.materials.new(material_name)
    # as soon as we do this we]'' have Diffuse BSDF and Material Output nodes, linked:
    material.use_nodes = True
    nodes = material.node_tree.nodes
    links = material.node_tree.links

    # add an emission node
    emission_node =  nodes.new(type='ShaderNodeEmission')
    emission_node.inputs["Color"].default_value = [0.335, 0.583, 0.8, 1.0]
    emission_node.inputs["Strength"].default_value = 20.0

    mat_output = nodes.get("Material Output")
    links.new(emission_node.outputs[0], mat_output.inputs[0])

    return material

#####################################################################
def set_camera(scene):
    # Set camera rotation in euler angles
    scene.camera.rotation_mode = 'XYZ'
    scene.camera.rotation_euler[0] = radians(12)
    scene.camera.rotation_euler[1] = 0.0
    scene.camera.rotation_euler[2] = 0.0

    # Set camera translation
    scene.camera.location.x =  0.34
    scene.camera.location.y = -1.2
    scene.camera.location.z =  6.7

#####################################################################
def set_lights(scene):
    # ambient
    scene.world.light_settings.use_ambient_occlusion = True
    scene.world.light_settings.distance = 2
    # spotlight - sun
    bpy.ops.object.lamp_add(type='SUN', location=(-2.0, 0.32, 6.5), rotation=(radians(-21), radians(-5), radians(69)))
    lamp = bpy.context.object
    #lamp.color = (0.43, 0.78,1.0,1.0) # the code does not complain, but not sure if it does anything
    # lamp does not have strength, but the associated rendering node does (sigh)
    # lamp.strength = 5.0 # this does not work
    lamp.data.node_tree.nodes['Emission'].inputs['Strength'].default_value= 5.0
    lamp.data.node_tree.nodes['Emission'].inputs['Color'].default_value= (0.43, 0.78,1.0,1.0)
    lamp.cycles_visibility.shadow = False
    # light emmission plane
    bpy.ops.mesh.primitive_plane_add(location=(-10.0, 3.5, 12.0),
                                     rotation=(radians(-104), radians(-98), radians(80)))
    emission_plane =  bpy.context.object
    emission_plane.scale = (3.3, -5.5, -28.3)
    emission_plane.name = "emission plane"
    emission_plane.data.materials.append (makeEmission ("emission mat"))
    emission_plane.cycles_visibility.shadow = False

#####################################################################
def create_object():
    bpy.ops.mesh.primitive_cube_add(location=(0.0, 0.0, 0.7), enter_editmode=True, layers= [l==0 for l in range(20)])
    bpy.ops.mesh.subdivide(number_cuts=4)

    obj = bpy.context.object
    obj.name = "cube"
    obj.location.z += obj.dimensions.z/4

    obj.modifiers.new("cube_subsurf", "SUBSURF")
    obj.modifiers["cube_subsurf"].subdivision_type = 'CATMULL_CLARK'
    obj.modifiers["cube_subsurf"].render_levels = 4

    mesh = obj.data
    bpy.ops.object.editmode_toggle()
    # show mesh as smooth
    for p in mesh.polygons:
         


    # texture layer: Smart projection
    bpy.ops.mesh.uv_texture_add()
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.uv.cube_project(cube_size=0.1*obj.dimensions.x)
    bpy.ops.object.mode_set(mode='OBJECT')


    return obj

#####################################################################
def compositing(scene, outdir, outfile_base_name):
    
    # let's try to work in some layers
    render_layers = scene.render.layers
    render_layers.active.name = "main"
    render_layers.new("shadow")
    render_layers["main"].layers   = [l==0 for l in range(20)]
    render_layers["shadow"].layers = [l==1 for l in range(20)]
    render_layers["shadow"].use_pass_shadow = True

    scene.layers[0] = True
    scene.layers[1] = True

    # and now ... compositing!
    # I'll want transaprent background
    scene.cycles.film_transparent = True
    
    # switch on nodes and get reference
    scene.use_nodes = True
    tree = scene.node_tree

    # the default nodes are Composite and RenderLayers, that contains out main layer already
    # I better remove them if I am going to run this script repeatedly
    for node in tree.nodes:
         tree.nodes.remove(node)

    links = tree.links
 
    main_layer_node = tree.nodes.new('CompositorNodeRLayers')
    main_layer_node.layer    = "main"
    main_layer_node.location =  200, -100

    shadow_layer_node = tree.nodes.new('CompositorNodeRLayers')
    shadow_layer_node.layer = "shadow"
    shadow_layer_node.location = -400, 100

    # note here: mix, not math
    subtract_node = tree.nodes.new('CompositorNodeMixRGB')
    subtract_node.blend_type = "SUBTRACT" # the default is add
    subtract_node.location  = -200, 200
    # inputs[0] here is 'Fac' (?)
    links.new(shadow_layer_node.outputs['Alpha'],  subtract_node.inputs[1])
    links.new(shadow_layer_node.outputs['Shadow'], subtract_node.inputs[2])

    set_alpha_node = tree.nodes.new('CompositorNodeSetAlpha')
    set_alpha_node.location = 0, 200
    links.new(subtract_node.outputs['Image'], set_alpha_node.inputs['Alpha'])

    blur_node = tree.nodes.new('CompositorNodeBlur')
    blur_node.filter_type = 'FAST_GAUSS'
    blur_node.size_x = 5
    blur_node.size_y = 5
    blur_node.location = 200, 200
    links.new(set_alpha_node.outputs['Image'], blur_node.inputs['Image'])

    alpha_over_node = tree.nodes.new('CompositorNodeAlphaOver')
    alpha_over_node.location = 400, 0
    # inputs[0] here is 'Fac' (?)
    links.new(blur_node.outputs['Image'],  alpha_over_node.inputs[1])
    links.new(main_layer_node.outputs['Image'], alpha_over_node.inputs[2])


    # create output node
    #out_node = tree.nodes.new('CompositorNodeComposite')
    out_node = tree.nodes.new('CompositorNodeOutputFile')
    out_node.base_path = outdir
    out_node.file_slots[0].path = outfile_base_name
    out_node.location = 600,0
    links.new(alpha_over_node.outputs['Image'], out_node.inputs['Image'])
    
    # create Viewer Node
    # viewer_node =  tree.nodes.new('CompositorNodeViewer')
    # viewer_node.location = 600, 200
    # links.new(alpha_over_node.outputs['Image'], viewer_node.inputs['Image'])

###################################
if __name__ == '__main__':

    delete_old_stuff()
    scene = bpy.context.scene
    scene.render.engine="CYCLES"
    scene.unit_settings.system='METRIC'


    image_loaded = bpy.data.images.load(os.path.abspath('Bck_v1.png'))

    set_camera(scene)
    set_lights(scene)
    # floor - that' where the shadows are cast
    # we are placing the floor in the second layer
    bpy.ops.mesh.primitive_plane_add(location=(0.0, 0.0, 0.0), layers=[l==1 for l in range(20)])
    floor = bpy.context.object
    floor.scale = ((4,4,4))
    floor.name  = "floor"
    floor.data.materials.append (bpy.data.materials.new('Plain Diffuse'))

    # cube
    object = create_object()

    # image  as a texture on the curface + gloss
    material = makeGlossyTextured (object, image_loaded, 'Img Txtr Material')

    # bind object and material
    object.data.materials.append(material)

    # do some compositing work to get shadow on transparent background
    scene.render.resolution_y = 512
    scene.render.resolution_x = int (scene.render.resolution_y*(object.dimensions.x/object.dimensions.y))
    compositing(scene, os.getcwd(), "test")
 
    # not working:
    bpy.ops.render.render(use_viewport=True)

p.use_smooth = True