import sys
import os
import imp
from glob import glob
import distutils.sysconfig

#######################################################
# reusable functions and data structures
#######################################################
def LoadTool(name, env, **kw):
    config_path = GetBuildPath('#/Build/Tools/SCons')
    file, path, desc = imp.find_module(name, [config_path])
    module = imp.load_module(name, file, path, desc)
    module.generate(env, **kw)

def GlobSources(dir, patterns):
    root = GetBuildPath('#'+SOURCE_ROOT)+'/'+dir+'/'
    files = []
    for pattern in patterns:
        files += glob(root+pattern)
    return [dir+'/'+os.path.basename(x) for x in  files]

def GetDirPath(dir):
    return '#'+SOURCE_ROOT+'/'+dir
    
def DeclareBuildDir(dir):
    env.BuildDir(dir, GetDirPath(dir), duplicate=0)

def GetModule(name):
    return Modules[name]
    
def GetIncludeDirs(modules, exclude=None):
    dirs = []
    for module in Split(modules):
        if Modules.has_key(module) and not module == exclude:
            dirs += Modules[module].GetIncludeDirs()
        else:
            dirs += [GetDirPath(module)]
    return dirs

def GetLibraries(modules):
    return [GetModule(module).GetLibraries() for module in Split(modules)]

Modules = {}
class Module:
    def __init__(self, name, included_modules = [], linked_modules = []):
        self.included_modules = included_modules
        self.linked_modules   = linked_modules
        self.product          = []

    def GetLibraries(self):
        return self.product+GetLibraries(self.linked_modules)
        
    def GetIncludeDirs(self):
        return GetIncludeDirs(self.included_modules, self.name)
    
class LibraryModule(Module):
    def __init__(self, name, 
                 build_source_dirs, 
                 build_source_pattern=['*.c', '*.cpp'], 
                 build_include_dirs = [], 
                 included_modules = [], 
                 linked_modules = []) :
        Module.__init__(self, name, Split(included_modules)+Split(build_source_dirs), linked_modules)
        self.env                = env.Clone()
        self.name               = name
        self.build_source_dirs  = build_source_dirs
        self.build_include_dirs = build_include_dirs
        
        # store this new object in the module dictionary
        Modules[name] = self
                
        # for each source dir to build, create a BuildDir
        # to say where we want the object files to be built,
        # and compute the list of source files to build
        sources = []
        for dir in Split(self.build_source_dirs):
            DeclareBuildDir(dir)
            sources += GlobSources(dir, build_source_pattern)
            
        # calculate our build include path
        cpp_path = GetIncludeDirs(Split(build_include_dirs) + Split(build_source_dirs) + Split(included_modules))

        # calculate our preprocessor defines for this module
        cpp_defines={}
        
        # the product is a library
        self.env.AppendUnique(CPPDEFINES=cpp_defines)
        self.env.AppendUnique(CPPPATH=cpp_path)
        self.product = self.env.Library(target=name, source=sources)
        Alias(name, self.product)
        
def Executable(name,
               source_dir,
               extra_deps=[],
               shared_lib=False,
               source_pattern=['*.cpp'],
               extra_includes=[],
               environment = None,
               lowercase = True):
    if environment is None:
        environment = env
    DeclareBuildDir(source_dir)
    sdk = ['Bento4'] + Split(extra_deps)
    libs = GetLibraries(sdk)
    cpp_path = GetIncludeDirs(sdk) + extra_includes
    sources = GlobSources(source_dir, source_pattern)
    
    if lowercase:
        target_name = name.lower()
    else:
        target_name = name
        
    if shared_lib:
        if env['target'] == 'universal-apple-macosx':
            func = environment.LoadableModule
        else:
            func = environment.SharedLibrary
        exe = func(target_name, sources,
                   LIBS=libs, CPPPATH=cpp_path, SHLIBPREFIX='')
    else:
        exe = environment.Program(target_name, 
                                  sources + environment['AP4_EXTRA_EXECUTABLE_OBJECTS'], 
                                  LIBS=libs, CPPPATH=cpp_path)
    #environment.Alias(name, exe)
    
#######################################################
# Main Build
#######################################################
Import("env")
SOURCE_ROOT='Source'
env['AP4_EXTRA_EXECUTABLE_OBJECTS'] = []

### try to read in any target specific configuration
target_config_file = env.GetBuildPath('#/Build/Targets/'+env['target']+'/Config.scons')
if os.path.exists(target_config_file):
    # Load the target-specific config file    
    execfile(target_config_file)

#######################################################
# modules
#######################################################
LibraryModule(name               = 'Bento4', 
              build_source_dirs  = ['C++/'+dir for dir in ['Core', 'Crypto', 'MetaData', 'System/StdC', 'Codecs']],
              included_modules   = 'Config')
           
for name in ['Mp4Dump', 'Mp4Info', 'Mp4Edit', 'Mp4Encrypt', 'Mp4Decrypt', 'Mp4Tag', 'Mp4Extract', 'Mp4RtpHintInfo', 'Mp42Aac', 'Mp42Ts', 'Mp4DcfPackager']:       
    Executable(name, source_dir='C++/Apps/'+name)

Executable('Aac2Mp4', source_dir='C++/Apps/Aac2Mp4')
Executable('CryptoTest', source_dir='C++/Test/Crypto')
Executable('AvcTrackWriterTest', source_dir='C++/Test/Avc')
Executable('PassthroughWriterTest', source_dir='C++/Test/PassthroughWriter')
Executable('TracksTest', source_dir='C++/Test/Tracks')
Executable('BenchmarksTest', source_dir='C++/Test/Benchmarks')
Executable('libBento4C.so', source_dir='C++/CApi', shared_lib=True, lowercase=False)
