import os
import bpy
import subprocess
import arm.utils
import arm.assets as assets
import arm.material.mat_utils as mat_utils
import arm.material.mat_state as mat_state
from arm.material.shader import ShaderData
import arm.material.cycles as cycles
import arm.material.make_mesh as make_mesh
import arm.material.make_transluc as make_transluc
import arm.material.make_overlay as make_overlay
import arm.material.make_depth as make_depth
import arm.material.make_decal as make_decal
import arm.material.make_voxel as make_voxel
import arm.api

rpass_hook = None

def build(material, mat_users, mat_armusers):
    mat_state.mat_users = mat_users
    mat_state.mat_armusers = mat_armusers
    mat_state.material = material
    mat_state.nodes = material.node_tree.nodes
    mat_state.data = ShaderData(material)
    mat_state.output_node = cycles.node_by_type(mat_state.nodes, 'OUTPUT_MATERIAL')
    if mat_state.output_node == None:
        # Place empty material output to keep compiler happy..
        mat_state.output_node = mat_state.nodes.new('ShaderNodeOutputMaterial')

    wrd = bpy.data.worlds['Arm']
    rpdat = arm.utils.get_rp()
    rpasses = mat_utils.get_rpasses(material)
    is_emissive = mat_utils.is_emmisive(material)
    if is_emissive and '_Emission' not in wrd.world_defs:
        wrd.world_defs += '_Emission'
    matname = arm.utils.safesrc(arm.utils.asset_name(material))
    rel_path = arm.utils.build_dir() + '/compiled/Shaders/'
    full_path = arm.utils.get_fp() + '/' + rel_path
    if not os.path.exists(full_path):
        os.makedirs(full_path)

    global_elems = []
    if mat_users != None and material in mat_users:
        for bo in mat_users[material]:
            # GPU Skinning
            if arm.utils.export_bone_data(bo):
                global_elems.append({'name': 'bone', 'data': 'short4norm'})
                global_elems.append({'name': 'weight', 'data': 'short4norm'})
            # Instancing
            if bo.arm_instanced != 'Off' or material.arm_particle_flag:
                global_elems.append({'name': 'ipos', 'data': 'float3'})
                if bo.arm_instanced == 'Loc + Rot' or bo.arm_instanced == 'Loc + Rot + Scale':
                    global_elems.append({'name': 'irot', 'data': 'float3'})
                if bo.arm_instanced == 'Loc + Scale' or bo.arm_instanced == 'Loc + Rot + Scale':
                    global_elems.append({'name': 'iscl', 'data': 'float3'})
                
    mat_state.data.global_elems = global_elems

    bind_constants = dict()
    bind_textures = dict()

    for rp in rpasses:

        car = []
        bind_constants[rp] = car
        mat_state.bind_constants = car
        tar = []
        bind_textures[rp] = tar
        mat_state.bind_textures = tar

        con = None

        if rpdat.rp_driver != 'Armory' and arm.api.drivers[rpdat.rp_driver]['make_rpass'] != None:
            con = arm.api.drivers[rpdat.rp_driver]['make_rpass'](rp)

        if con != None:
            pass
            
        elif rp == 'mesh':
            con = make_mesh.make(rp, rpasses)

        elif rp == 'shadowmap':
            con = make_depth.make(rp, rpasses, shadowmap=True)

        elif rp == 'translucent':
            con = make_transluc.make(rp)

        elif rp == 'overlay':
            con = make_overlay.make(rp)

        elif rp == 'decal':
            con = make_decal.make(rp)

        elif rp == 'depth':
            con = make_depth.make(rp, rpasses)

        elif rp == 'voxel':
            con = make_voxel.make(rp)

        elif rpass_hook != None:
            con = rpass_hook(rp)

        write_shaders(rel_path, con, rp, matname)

    arm.utils.write_arm(full_path + '/' + matname + '_data.arm', mat_state.data.get())
    shader_data_name = matname + '_data'
    shader_data_path = arm.utils.get_fp_build() + '/compiled/Shaders/' + shader_data_name + '.arm'
    assets.add_shader_data(shader_data_path)

    return rpasses, mat_state.data, shader_data_name, bind_constants, bind_textures

def write_shaders(rel_path, con, rpass, matname):
    keep_cache = mat_state.material.arm_cached
    write_shader(rel_path, con.vert, 'vert', rpass, matname, keep_cache=keep_cache)
    write_shader(rel_path, con.frag, 'frag', rpass, matname, keep_cache=keep_cache)
    write_shader(rel_path, con.geom, 'geom', rpass, matname, keep_cache=keep_cache)
    write_shader(rel_path, con.tesc, 'tesc', rpass, matname, keep_cache=keep_cache)
    write_shader(rel_path, con.tese, 'tese', rpass, matname, keep_cache=keep_cache)

def write_shader(rel_path, shader, ext, rpass, matname, keep_cache=True):
    if shader == None or shader.is_linked:
        return

    # TODO: blend context
    if mat_state.material.arm_blending and rpass == 'mesh':
        rpass = 'blend'

    file_ext = '.glsl'
    if shader.noprocessing:
        # Use hlsl directly
        hlsl_dir = arm.utils.build_dir() + '/compiled/Hlsl/'
        if not os.path.exists(hlsl_dir):
            os.makedirs(hlsl_dir)
        file_ext = '.hlsl'
        rel_path = rel_path.replace('/compiled/Shaders/', '/compiled/Hlsl/')

    shader_file = matname + '_' + rpass + '.' + ext + file_ext
    shader_path = arm.utils.get_fp() + '/' + rel_path + '/' + shader_file
    assets.add_shader(shader_path)
    if not os.path.isfile(shader_path) or not keep_cache:
        with open(shader_path, 'w') as f:
            f.write(shader.get())

        if shader.noprocessing:
            cwd = os.getcwd()
            os.chdir(arm.utils.get_fp() + '/' + rel_path)
            hlslbin_path = arm.utils.get_sdk_path() + '/lib/armory_tools/hlslbin/hlslbin.exe'
            prof = 'vs_5_0' if ext == 'vert' else 'ps_5_0' if ext == 'frag' else 'gs_5_0'
            # noprocessing flag - gets renamed to .d3d11
            args = [hlslbin_path.replace('/', '\\').replace('\\\\', '\\'), shader_file, shader_file[:-4] + 'glsl', prof]
            if ext == 'vert':
                args.append('-i')
                args.append('pos')
            proc = subprocess.call(args)
            os.chdir(cwd)
