import sys
import os
import imp
from glob import glob

#######################################################
# 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('#/'+dir)
    files = []
    for pattern in Split(patterns):
        files += glob(root+'/'+pattern)
    return [dir+'/'+os.path.basename(x) for x in  files]

def GetDirPath(dir):
    return '#/'+dir
    
def DeclareBuildDir(env, dir):
    env.BuildDir(dir, GetDirPath(dir), duplicate=0)
    
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):
    libs = []
    for module in Split(modules):
        if Modules.has_key(module):
            libs += Modules[module].GetLibraries()
        else:
            libs += [module]
    return libs

def BuildApp(source_dir, name, deps):
    DeclareBuildDir(source_dir)
    libs = GetLibraries(deps)
    cpp_path = GetIncludeDirs(deps)
    prog = env.Program(target=name,
                       source=GlobSources(source_dir, ['*c', '*.cpp']),
                       LIBS=libs, CPPPATH=cpp_path)
    env.Alias(name, prog)

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_files = {},
                 source_root = 'Source',
                 build_source_pattern=['*.c', '*.cpp'], 
                 build_include_dirs = [], 
                 included_modules = [], 
                 include_dirs     = [],
                 linked_modules = []) :
        build_source_dirs = [source_root+'/'+dir for dir in Split(build_source_dirs)]
        include_dirs      = [source_root+'/'+dir for dir in Split(include_dirs)]
        Module.__init__(self, name, Split(included_modules)+Split(include_dirs)+Split(build_source_dirs), linked_modules)
        self.env                = env.Clone()
        self.name               = name
        self.build_include_dirs = [source_root+'/'+dir for dir in Split(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(build_source_dirs):
            DeclareBuildDir(self.env, dir)
            sources += GlobSources(dir, build_source_pattern)

        # add cherry-picked files
        for dir in build_source_files.keys():
            pattern = build_source_files[dir]
            dir_path = source_root+'/'+dir
            DeclareBuildDir(self.env, dir_path)
            sources += GlobSources(dir_path, pattern)

        # calculate our build include path
        cpp_path = GetIncludeDirs(Split(self.build_include_dirs) + Split(build_source_dirs) + Split(self.included_modules))
                
        # the product is a library
        self.env.AppendUnique(CPPPATH=cpp_path)
        self.product = self.env.Library(target=name, source=sources)
        self.env.Alias(name, self.product)
        
def Application(name, dir):
    DeclareBuildDir(env, dir)
    libs = GetLibraries('Atomix')
    cpp_path = GetIncludeDirs('Atomix')
    
    prog = env.Program(name, 
                       GlobSources(dir, ['*.c']), 
                       LIBS=libs, CPPPATH=cpp_path)
    #env.Alias(name, prog)
    if env.has_key('ATX_EXECUTABLE_POST_PROCESSOR'):
        env.AddPostAction(prog, env['ATX_EXECUTABLE_POST_PROCESSOR'])
        
#######################################################
# Main Build
#######################################################
Import("env")

### defaults
env['ATX_EXTRA_LIBS'] = ''
if (env['build_config'] == 'Debug'):
    env.Append(CPPDEFINES=['ATX_DEBUG', 'ATX_CONFIG_ENABLE_LOGGING'])

env['ATX_EXTRA_LIBS'] = ''


### 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               = 'Atomix', 
              build_source_dirs  = ['Core'],
              build_source_files = env['ATX_SYSTEM_SOURCES'],
              linked_modules     = env['ATX_EXTRA_LIBS'])
            
Application('NetPump', 'Source/Apps/NetPump')
for test in ['Strings', 'Misc', 'Properties', 'RingBuffer', 'Http', 'Logging', 'Containers', 'Files']:
    Application(test+'Test', 'Source/Tests/'+test)
