##############################################################################
# Copyright 2004-2008, Geoffrey Irving, Frank Losasso, Andrew Selle.
# This file is party of PhysBAM whose distribution is governed by the license contained in the accompanying file PHYSBAM_COPYRIGHT.txt.
##############################################################################
import sys
import os
import glob
#import commands
import re
import functools

### variables and base environment
variables=Variables('SConstruct.options')
variables.AddVariables(
    ('CXX','C++ compiler','g++'),
    ('YACC','which grammar parser to use','yacc'),
    ('LEX','which lexer to use','lex'),
    EnumVariable('PLATFORM','Architecture (e.g. linux, osx)','linux',allowed_values=('linux','osx')),
    EnumVariable('COMPILER_TYPE','(gcc,clang)','gcc',allowed_values=('gcc','clang')),
    EnumVariable('TYPE','Type of build (release, debug, profile, optdebug)','release',allowed_values=('release','debug','profile','optdebug')),
    ('cache','Cache directory to use',''),
    BoolVariable('SHARED','Build shared libraries',1),
    BoolVariable('USE_LEX_YACC','Use lex and yacc to allow parsing for symbolics',0),
    BoolVariable('USE_SYMBOLIC','Use symbolics',0),
    BoolVariable('INSTALL_PROGRAMS','install programs into source directories',1),
    BoolVariable('USE_RPATH','use rpaths for dynamic libraries',1),
    ('CXXFLAGS_EXTRA','',[]),
    ('LINKFLAGS_EXTRA','',[]),
    ('CPPPATH_EXTRA','',[]),
    ('LIBPATH_EXTRA','',[]),
    ('RPATH_EXTRA','',[]),
    ('LIBS_EXTRA','',[]))

### Choose libraries
external_libraries={
    'zlib': {'enable':1,'libs':['z']},
    'libjpeg': {'enable':1,'defines':['USE_LIBJPEG'],'libs':['jpeg']},
    'libpng': {'enable':1,'defines':['USE_LIBPNG'],'libs':['png']},
    'fftw': {'enable':1,'defines':['USE_FFTW'],'libs':['fftw3f','fftw3']},
    'nlopt': {'defines':['USE_NLOPT'],'libs':['nlopt']},
    'colamd': {'defines':['USE_COLAMD'],'libs':['colamd']},
    'lapack': {'enable':1,'defines':['USE_LAPACK'],'libs':['lapack','lapacke','blas']},
    'mkl': {'defines':['USE_MKL'],'libs':['mkl_rt']},
    'partio': {'defines':['USE_PARTIO'],'libs':['partio']},
    'OpenGL': {'enable':1,'libs':['GL','GLU','glut']},
    'lam': {'defines':['USE_MPI'],'libs':['lammpio','lammpi++','mpi','lam','util','dl'],'linkflags':['-pthread']},
    'openmpi': {'defines':['USE_MPI'],'libs':['mpi_cxx','open-rte','mpi','open-pal','util','dl','nsl'],'linkflags':['-pthread']},
    'openmp': {'enable':1,'defines':['USE_OPENMP'],'flags':['-fopenmp'],'linkflags':['-fopenmp']}}

lib_keys={
    'enable':(0,"definitions"),
    'flags':([],"compiler flags"),
    'defines':([],"compiler defines"),
    'includes':([],"include paths"),
    'linkflags':([],"linker flags"),
    'libpath':([],"link paths"),
    'libs':([],"libraries"),
    'rpath':([],"dynamic library path")}

for name,lib in external_libraries.items():
    for key,value in lib_keys.items():
        lib.setdefault(key,value[0])
        variables.Add(name+'_'+key,value[1]+' for '+name,lib[key])

### parse variables
env=Environment(variables=variables,tools=['default','lex','yacc'])
env.Replace(ENV=os.environ) # do this here to allow SConstruct.options to change environment
Help(variables.GenerateHelpText(env))
variant_build=os.path.join('build',env['TYPE'])

build_directory=Dir('.').srcnode().abspath

for name,lib in external_libraries.items():
    for key in lib_keys.keys():
        lib[key]=env[name+'_'+key]

if env['SHARED']: object_builder=env.SharedObject
else: object_builder=env.StaticObject
if env['SHARED']: library_builder=env.SharedLibrary
else: library_builder=env.StaticLibrary

### improve performance
if 'Decider' in dir(env): # if Decider exists, use it to avoid deprecation warnings
    env.Decider('MD5-timestamp')
else:
    env.TargetSignatures('build')
env.SetOption('max_drift',100)
env.SetDefault(CPPDEFINES=[])

### avoid annoying bug in previous versions of scons
env.EnsureSConsVersion(0,96,92)

### avoid deprecation warnings about env.Copy
if 'AddMethod' in dir(env) and 'Clone' in dir(env):
    AddMethod(Environment,Environment.Clone,'Copy')

## override platform specific library names
if env['PLATFORM']=='osx':
    opengl=external_libraries['OpenGL']
    opengl['linkflags']='-framework OpenGL -framework GLUT'
    opengl['libpath']=[]
    opengl['libs']=[]
    lapack=external_libraries['lapack']
    lapack['linkflags']='-framework Accelerate'
    lapack['libpath']=[]
    lapack['libs']=['openblas']
    env.Replace(LDMODULESUFFIX='.so')
    env.Replace(RPATH=[])
    env.Append(SHLINKFLAGS = ['-install_name',build_directory+'/$TARGET'])
else:
    if env['SHARED'] and False:
        if env['USE_RPATH']==0: env.Replace(RPATH=[])
    else: env.Append(LINKFLAGS='-rdynamic')

for name,lib in external_libraries.items():
    if lib['enable']:
        env.Append(CXXFLAGS=lib['flags'])
        env.Append(LINKFLAGS=lib['linkflags'])
        env.Append(CPPPATH=lib['includes'])
        env.Append(LIBPATH=lib['libpath'])
        env.Append(RPATH=lib['rpath'])
        env.Append(LIBS=lib['libs'])
        env.Append(CPPDEFINES=lib['defines'])

### build cache
if env['cache']!='': CacheDir(env['cache'])

if env['PLATFORM']!='osx': env.Append(RPATH=os.path.join(build_directory,variant_build,'Public_Library'))
program_suffix=''
if env['TYPE']!='release': program_suffix+='_'+env['TYPE']
library_suffix=''
if not env['SHARED']: library_suffix='_static'
env.Append(LIBPATH=os.path.join('#'+variant_build,'Public_Library'))

### extra flag options
env.Append(CXXFLAGS=env['CXXFLAGS_EXTRA'])
env.Append(LINKFLAGS=env['LINKFLAGS_EXTRA'])
env.Append(CPPPATH=env['CPPPATH_EXTRA'])
env.Append(LIBPATH=env['LIBPATH_EXTRA'])
env.Append(RPATH=env['RPATH_EXTRA'])
env.Append(LIBS=env['LIBS_EXTRA'])

# Set compile flags
env.Append(CXXFLAGS=[
    '-march=native','-g3','-std=c++2a','-Wall','-Werror','-Winit-self',
    '-Woverloaded-virtual','-Wstrict-aliasing=2','-Wno-unknown-pragmas',
    '-Wno-strict-overflow','-Wno-sign-compare','-Wno-register',
    '-Wno-unused-local-typedefs','-Wno-sign-compare','-Wno-strict-overflow',
    '-I/usr/include/eigen3','-Wno-ignored-attributes'])
env.Append(LINKFLAGS=['-g','-gdwarf-2'])

if env["COMPILER_TYPE"]=="gcc":
    env.Append(CXXFLAGS=['-Wno-int-in-bool-context']);
if env["COMPILER_TYPE"]=="clang":
    env.Append(CXXFLAGS=['-Wno-c99-designator', '-Wno-ambiguous-reversed-operator', '-Wno-unused-function', '-Wno-unused-lambda-capture']);

if env['TYPE']=='release' or env['TYPE']=='optdebug' or env['TYPE']=='profile':
    env.Append(CXXFLAGS=['-O3','-funroll-loops','-fno-math-errno','-fno-signed-zeros'])
    env.Append(CPPDEFINES=['NDEBUG'])

if env['TYPE']=='profile':
    env.Append(CXXFLAGS=['-pg'],LINKFLAGS=['-pg'])

if env['USE_LEX_YACC']: env.Append(CPPDEFINES=['USE_LEX_YACC'])
if env['USE_SYMBOLIC']: env.Append(CPPDEFINES=['USE_SYMBOLIC'])

### library configuration
env.Append(CPPPATH=['#/Public_Library'])

if env['PLATFORM']=='osx':
    env.Append(LINKFLAGS=['-undefined','dynamic_lookup','-bind_at_load'])

rpath_save=env['RPATH']
if env['INSTALL_PROGRAMS'] and env['SHARED'] and False: env.Replace(RPATH=[])

pb={}
pb_name={}


### find SConscript files two levels down (for Projects and Tools)
def Find_SConscripts(env,dir):
    for g in ['SConscript','*/SConscript','*/*/SConscript']:
        for c in glob.glob(os.path.join(dir,g)):
            v=os.path.join(variant_build,os.path.dirname(c))
            env.SConscript(c, variant_dir=v, duplicate=0)

### find all .cpp files below current directory
def Find_Sources(dirs):
    sources=[]
    build_directory=Dir('.').srcnode().abspath
    for d in dirs:
        for root,_,files in os.walk(os.path.join(build_directory,d)):
            r=os.path.relpath(root,build_directory)
            for f in files:
                if f.endswith('.cpp'):
                    sources.append(os.path.join(r,f))
    return sources

def Automatic_Objects(env,sources):
    list=[]
    for s in sources:
        if type(s)==type(""):
            list.append(object_builder(s))
        else:
            list.append(s)
    return list
            
### automatic generation of library targets
def Automatic_Library(env,name,dirs=['.'],extra_objects=[]):
    objects=Automatic_Objects(env,Find_Sources(dirs))+extra_objects
    return library_builder(name+library_suffix,source=objects)

### automatic generation of program targets
def Automatic_Program(env,name,sources,level):
    objects=Automatic_Objects(env,sources)
    pb_libs=[pb_name[l] for l in level]
    install_name=os.path.basename(name)+program_suffix
    executable_target_path=os.path.join(Dir('.').srcnode().abspath,install_name)
    program=env.Program(name,objects,LIBS=env['LIBS']+pb_libs)
    ### Mac OS X does not support -rdynamic
    if env['INSTALL_PROGRAMS']:
        env.InstallAs(executable_target_path,program)

### build everything
Export('env Automatic_Program Automatic_Library Find_Sources pb pb_name')
Find_SConscripts(env,'Public_Library')
Find_SConscripts(env,'Projects')
Find_SConscripts(env,'Tests')
Find_SConscripts(env,'Tools')
