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 MergeListUnique(item_list, items):
    for item in items: 
        if not item in item_list: item_list.append(item)

def MergeItemUnique(item_list, item):
    if not item in item_list: item_list.append(item)

def GlobSources(drct, patterns, excluded_files=[]):
    root = GetBuildPath('#'+drct)
    files = []
    for pattern in Split(patterns):
        files += glob(root+'/'+pattern)
    return [drct+'/'+os.path.basename(x) for x in  files if os.path.basename(x) not in excluded_files]

def GetDirPath(dir):
    return '#/'+dir
    
def DeclareBuildDir(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.name             = name
        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.build_include_dirs, 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      = [], 
                 included_only_modules = [],
                 linked_modules        = [],
                 environment           = None,
                 excluded_files        = [],
                 extra_cpp_defines     = [],
                 shared                = False) :
        build_source_dirs = [source_root+'/'+drct for drct in build_source_dirs]
        Module.__init__(self, 
                        name, 
                        Split(included_modules)+Split(included_only_modules)+Split(build_source_dirs), 
                        Split(linked_modules)+Split(included_modules))
        self.build_include_dirs = build_include_dirs
        if environment is None:
            self.env = env.Clone()
        else:
            self.env = environment.Clone()
        self.env.AppendUnique(CPPDEFINES = extra_cpp_defines)
        
        # store this new object in the module dictionary
        Modules[name] = self
                
        # for each source drct 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 drct in Split(build_source_dirs):
            DeclareBuildDir(drct)
            sources += GlobSources(drct, build_source_pattern, excluded_files)
            
        # add cherry-picked files
        for drct in build_source_files.keys():
            pattern = build_source_files[drct]
            drct_path = source_root+'/'+drct
            DeclareBuildDir(drct_path)
            sources += GlobSources(drct_path, pattern)

        # calculate our build include path
        cpp_path = GetIncludeDirs(Split(self.build_include_dirs) + Split(build_source_dirs) + self.included_modules + self.linked_modules)

        # the product is a library
        self.env.AppendUnique(CPPPATH=cpp_path)
        if shared is False:
            self.product = self.env.Library(target=name, source=sources)
        else:
            libs = GetLibraries(Split(linked_modules))
            self.product = self.env.SharedLibrary(target=name, LIBS=libs, source=sources)
        self.env.Alias(name, self.product)
        
        
def Application(name, dir):
    DeclareBuildDir(dir)
    libs = GetLibraries('Neptune')
    cpp_path = GetIncludeDirs('Neptune')
    
    prog = env.Program(name, 
                       GlobSources(dir, ['*.c', '*.cpp']) + env['NPT_EXTRA_EXECUTABLE_OBJECTS'],
                       LIBS=libs, CPPPATH=cpp_path)
    #env.Alias(name, prog)
    if env.has_key('NPT_EXECUTABLE_POST_PROCESSOR'):
        env.AddPostAction(prog, env['NPT_EXECUTABLE_POST_PROCESSOR'])
    
#######################################################
# Main Build
#######################################################
Import("env")

### defaults
env['NPT_EXTRA_LIBS'] = []
env['NPT_EXTRA_EXECUTABLE_OBJECTS'] = []

if (env['build_config'] == 'Debug'):
    env.AppendUnique(CPPDEFINES=['NPT_DEBUG', 'NPT_CONFIG_ENABLE_LOGGING'])

### 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                  = 'Zlib',
              source_root           = '.',
              build_source_dirs     = ['ThirdParty/zlib-1.2.3'])


extra_cpp_flags = []
if not env.has_key('NPT_CONFIG_NO_ZIP'):
    extra_cpp_flags = ['NPT_CONFIG_ENABLE_ZIP']
    
LibraryModule(name                  = 'Neptune',
              build_source_dirs     = ['Core'],
              build_source_files    = env['NPT_SYSTEM_SOURCES'],
              extra_cpp_defines     = extra_cpp_flags,
              linked_modules        = env['NPT_EXTRA_LIBS']+['Zlib'])
           
for app in ['NetPump', 'NetConfig', 'NetPing', 'NetEcho', 'MiniHttpServer']:
    Application(app, 'Source/Apps/'+app)
    
for test in ['Arrays1', 'BufferedStreams1', 'HttpClient1', 'HttpServer1', 'Lists1', 'Maps1', 'Misc1', 'RingBuffer1', 'Strings1', 'Xml1', 'Udp1', 'Threads1', 'Queue1', 'Messages1', 'Messages2', 'Url1', 'Zip1', 'Time1', 'Sockets1', 'FileTest1', 'FileTest2']:
    Application(test+'Test', 'Source/Tests/'+test)

