from os.path import join, exists, basename
from shutil import copyfile, rmtree
from copy import copy
import json
from types import ListType

from workspace_tools.utils import mkdir
from workspace_tools.toolchains import TOOLCHAIN_CLASSES, Resources

"""
src_path: the path of the source directory
build_path: the path of the build directory
target: ['LPC1768', 'LPC11U24', 'LPC2368']
toolchain: ['ARM', 'uARM', 'GCC_ARM', 'GCC_CS', 'GCC_CR']
library_paths: List of paths to additional libraries
clean: Rebuild everything if True
notify: Notify function for logs
verbose: Write the actual tools command lines if True
"""
def build_library(src_paths, build_path, target, toolchain_name,
        libraries_paths=None, name=None, clean=False, notify=None, verbose=False):
    if type(src_paths) != ListType: src_paths = [src_paths]
    
    for src_path in src_paths:
        if not exists(src_path):
            raise Exception("The library source folder does not exist: %s", src_path)
    
    # Toolchain instance
    toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, notify)
    toolchain.VERBOSE = verbose
    toolchain.build_all = clean
    
    # Options
    # By convention the first path should contain the build configuration
    main_src = src_paths[0]
    options_file = join(main_src, 'options.json')
    if exists(options_file):
        try:
            options = json.load(open(options_file))
        except Exception, e:
            raise Exception("Error parsing option file: %s\n\t%s" % (options_file, e))
    else:
        options = {}
    toolchain.set_options(options)
    
    # Library name can be set by: function parameter, option file, or directory name (in that order)
    if name is None:
        if 'lib_name' in options:
            name = options['lib_name']
        else:
            name = basename(main_src)
    
    toolchain.info("\n>>> BUILD LIBRARY %s (%s, %s)" % (name.upper(), target.name, toolchain_name))
    
    # Scan Resources
    resources = []
    for src_path in src_paths:
        resources.append(toolchain.scan_resources(src_path))
    
    # Just for the include paths
    lib_resources = Resources()
    if libraries_paths is not None:
        for path in libraries_paths:
            lib_resources.add(toolchain.scan_resources(path))
    
    # Create the desired build directory structure
    bin_path = join(build_path, toolchain.obj_path)
    mkdir(bin_path)
    
    tmp_path = join(build_path, '.temp', toolchain.obj_path)
    mkdir(tmp_path)
    
    # Copy Files
    for resource in resources:
        files_to_be_copied = resource.headers
        if resource.linker_script is not None:
            # Linker script
            files_to_be_copied.append(resource.linker_script)
        toolchain.copy_files(resource.base_path, build_path, files_to_be_copied)
    
    # Compile sources
    objects = []
    for resource in resources:
        objects.extend(toolchain.compile_sources(resource, tmp_path, lib_resources.inc_dirs))
    
    # Same objects need to remain stand alone
    if 'objects' in options:
        for object in copy(objects):
            filename = basename(object)
            if filename in options['objects']:
                objects.remove(object)
                target = join(bin_path, filename)
                if toolchain.need_update(target, [object]):
                    toolchain.info("Copy: %s" % filename)
                    copyfile(object, target)
    
    # Archive objects in a library
    if objects:
        toolchain.build_library(objects, bin_path, name)


def build_project(src_path, build_path, target, toolchain_name,
        libraries_paths=None, clean=False, notify=None, verbose=False, name=None):
    # Toolchain instance
    toolchain = TOOLCHAIN_CLASSES[toolchain_name](target, notify)
    toolchain.VERBOSE = verbose
    toolchain.build_all = clean
    
    if name is None:
        name = basename(src_path)
    toolchain.info("\n>>> BUILD PROJECT: %s (%s, %s)" % (name.upper(), target.name, toolchain_name))
    
    # Scan src_path and libraries_paths for resources
    resources = toolchain.scan_resources(src_path)
    if libraries_paths is not None:
        for path in libraries_paths:
            resources.add(toolchain.scan_resources(path))
    
    # Build Directory
    if clean:
        if exists(build_path):
            rmtree(build_path)
    mkdir(build_path)
    
    # Build Program
    return toolchain.build_program(resources, build_path, name)


# Core mbed libraries 
from workspace_tools.libraries import Library


def build_lib(lib_id, target, toolchain, verbose=False):
    lib = Library(lib_id)
    if lib.is_supported(target, toolchain):
        build_library(lib.source_dir, lib.build_dir, target, toolchain, lib.dependencies, lib.name, verbose=verbose)
    else:
        print '\n\nLibrary "%s" is not yet supported on target %s with toolchain %s' % (lib_id, target.name, toolchain)


def build_mbed_libs(target, toolchain, verbose=False):
    for lib_name in ["%s_cmsis" % target.vendor, "%s_mbed" % target.vendor]:
        build_lib(lib_name, target, toolchain, verbose=verbose)
