#!/usr/bin/python2

import platform, sys, os, time, threading, subprocess, shutil, stat

system = None
debug = False
profile = False
dependencies = False
verbose = False
release = False
targets = []


global todo, headers, object_deps, file_flags, cflags_glib, cflags_gtk, cflags_gtkgl, libs_glib, libs_gtk, libs_gtkgl, lock, stop
global include_paths
todo, headers, object_deps, file_flags = {}, {}, {}, {}
include_paths = []
lock = threading.Lock()
stop = False

installto = None
linkto = ''
print_help = False


# parse command-line:

for arg in sys.argv[1:]:
  if arg == '-help':
    print_help = True
    break
  elif arg == '-verbose': verbose = True
  elif arg == '-debug': debug = True
  elif arg == '-deps': dependencies = True
  elif arg == '-profile': profile = True
  elif arg == '-release': release = True
  elif arg == 'clean': targets = [ 'clean' ]
  elif arg == 'uninstall': targets = [ 'uninstall' ]
  else:
    args = arg.split ('=', 1)
    if args[0] == 'install': 
      if len (args) > 1: installto = args[1]
      else: installto = ''
    elif args[0] == 'linkto': 
      if len (args) > 1: 
        if len (args[1]) == 0: linkto = None
        else: linkto = args[1]
    elif args[0] == '-system': 
      if len (args) != 2: 
        print 'missing argument to option "-system"'
        exit (1)
      system = args[1]
    elif arg[0] == '-':
      print 'unknown command-line option "' + arg + '"'
      sys.stdout.flush()
      sys.exit (1)
    else: 
      targets.append(arg)

if system == None: system = platform.system()
exec 'from sysconf.' + system.lower() + ' import *'


if print_help:
  print """usage: ./build [ options ] [ target ]... 

A target can be any file generated by the compilation process. For example:

$ ./build bin/mrconvert

will compile only the dependencies required to build the mrconvert executable.
Multiple targets can be specified on the command-line if required.

These special targets are also allowed:

  clean              remove all intermediate files generated by a previous
                     invocation of ./build

  doc                generate documentation for each command

  install[=prefix]   install executables and shared library in prefix/bin and
                     prefix/lib respectively. Note that this may require
                     super-user privileges. If no prefix is specified, the
                     default of """ + default_installto + """ will be used. By
                     default, symbolic links will also be created to these
                     executables in a standard location (see "linkto" special
                     target below).

  linkto[=prefix]    When installing using the "install" special target,
                     symbolic links pointing to the executables will be created
                     in the specified folder (default is """ + default_linkto + """). 
                     This can be disabled by specifying an empty path
                     (i.e. "linkto="). A different location can also be
                     specified by specifying a suitable path prefix (e.g.
                     "linkto=/usr/local").

  uninstall          remove all MRtrix-related files. Note that this will not
                     work if the MRtrix executables are not already in the path.

Acceptable options are:
  -verbose          print additional debugging information
  -debug            generate code with debugging symbols
  -deps             print out a list of dependencies for each target
  -profile          generate code with profiling enabled (for use with gprof)
  -system=X         specify the architecture to build
(default is native)
"""
  exit (0)


# check if we are compiling a separate project:

mrtrix_dir = os.path.dirname (os.path.realpath(sys.argv[0]))
if os.path.abspath(mrtrix_dir) == os.path.abspath(os.getcwd()): mrtrix_dir = None
else:
  if verbose: 
    print 'compiling separate project against "' + mrtrix_dir + '"'
    print ''
  lib_dir = os.path.join (mrtrix_dir, lib_dir)
  include_paths = [ misc_dir ]
  misc_dir = os.path.join (mrtrix_dir, misc_dir)
  


# get version info:

f = open (os.path.join (lib_dir, 'mrtrix.h'), 'r')
for line in f:
  line = line.split()
  if len(line) != 3: continue
  if line[0] == '#define':
    if line[1] == 'MRTRIX_MAJOR_VERSION': major = line[2]
    elif line[1] == 'MRTRIX_MINOR_VERSION': minor = line[2]
    elif line[1] == 'MRTRIX_MICRO_VERSION': micro = line[2]
f.close()

libname += '-' + major + '_' + minor + '_' + micro



# other settings:

ld_flags_lib = [ ld_flags_lib_prefix + libname ]
libname = lib_prefix + libname + lib_suffix
ld_path = [ '-L' + lib_dir ]
include_paths += [ lib_dir, misc_dir ]

if release:
  cpp_flags += cpp_flags_release

if debug:
  cpp_flags = cpp_flags_debug
  ld_flags = ld_flags_debug
  ld_lib_flags = ld_lib_flags_debug

if profile:
  cpp_flags = cpp_flags_profile
  ld_flags = ld_flags_profile
  ld_lib_flags = ld_lib_flags_profile



# SVN revision number:
if not os.path.exists (svn_revision_file):
  with open(svn_revision_file, 'wb') as fd:
    fd.write('')
svn_revision = '#define SVN_REVISION 0\n'

try:
  process = subprocess.Popen ([ 'svn', 'info', '--xml' ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  if process.wait() != 0: raise 0
  svn_info = process.stdout.read().translate (None, ' \t\n')
  svn_revision_index = svn_info.find ('revision=')
  svn_revision = 0
  if svn_revision_index > 0:
    svn_revision = svn_info[svn_revision_index:-1].split('"')[1]
  if verbose:
    print svn_revision
  svn_revision = '#define SVN_REVISION ' + svn_revision + '\n'
except:
  pass

if svn_revision != open(svn_revision_file).read():
  with open(svn_revision_file, 'wb') as fd:
    fd.write (svn_revision)




def SVN_last_changed (filename):
  try:
    process = subprocess.Popen ([ 'svn', 'info', filename ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    if process.wait() != 0: raise 0
    for line in process.stdout:
      if line.startswith ('Text Last Updated:'):
        return line.split(':')[1].split()[0].strip() 
  except:
    return None



def pkg (package, target, extras = None):
  cmd = pkgconfig + [ '--' + target, package ]
  if extras: cmd += extras
  process = subprocess.Popen (cmd, stdout=subprocess.PIPE, env=pkgconfig_env)
  if process.wait() != 0:
    print 'WARNING: unable to find', package, 'configuration'
    sys.stdout.flush()
    return None
  return process.stdout.read().split()

cflags_glib = pkg (pkgconfig_glib, 'cflags')
cflags_gtk = pkg (pkgconfig_gtk, 'cflags')
cflags_gtkgl = pkg (pkgconfig_gtk, 'cflags', [ pkgconfig_gl ])
libs_glib = pkg (pkgconfig_glib, 'libs')
libs_gtk = pkg (pkgconfig_gtk, 'libs')
libs_gtkgl = pkg (pkgconfig_gtk, 'libs', [ pkgconfig_gl ]) + ld_flags_gl

if verbose:
  print 'Settings:'
  print '  GLib cflags:  ', ' '.join (cflags_glib)
  print '  GTK cflags:   ', ' '.join (cflags_gtk)
  print '  GTK/GL cflags:', ' '.join (cflags_gtkgl)
  print '  GLib libs:    ', ' '.join (libs_glib)
  print '  GTK libs:     ', ' '.join (libs_gtk)
  print '  GTK/GL libs:  ', ' '.join (libs_gtkgl)



###########################################################################
#                           TODO list Entry
###########################################################################

class Entry:
  def __init__ (self, name):
    global todo
    if name in todo.keys(): return
    todo[name] = self

    self.name = name
    self.cmd = []
    self.deps = set()
    self.action = 'NA'
    self.timestamp = mtime (self.name)
    self.dep_timestamp = self.timestamp
    self.currently_being_processed = False

    if is_executable (self.name): self.set_executable()
    elif is_icon (self.name): self.set_icon()
    elif is_object (self.name): self.set_object()
    elif is_library (self.name): self.set_library()

    [ Entry(item) for item in self.deps ]
    dep_timestamp = [ todo[item].dep_timestamp for item in todo.keys() if item in self.deps and not is_library(item) ]
    if len(dep_timestamp): self.dep_timestamp = max(dep_timestamp)
    

  def set_executable (self):
    self.action = 'LB'
    if len(exe_suffix) > 0: cc_file = self.name[:-len(exe_suffix)]
    else: cc_file = self.name
    cc_file = os.path.join (cmd_dir, os.sep.join (cc_file.split(os.sep)[1:])) + cpp_suffix
    self.deps = list_cmd_deps(cc_file)

    try: windres
    except NameError: pass
    else: self.deps.add (icon)

    if file_flags[cc_file] == 1: gtk = libs_glib
    elif file_flags[cc_file] == 2: gtk = libs_gtk
    else: gtk = libs_gtkgl

    self.cmd = fillin (ld, { '$flags$': ld_flags,
      '$path$': ld_path,
      '$obj$': self.deps,
      '$mrtrix$': ld_flags_lib,
      '$gsl$': ld_flags_gsl,
      '$gtk$': gtk,
      '$lz$': ld_flags_zlib,
      '$bin$': [ self.name ] })

    try:
      if ld_use_shell: self.cmd = [ 'sh', '-c', ' '.join(self.cmd) ]
    except NameError: pass

    if not os.path.isdir (bin_dir): os.mkdir (bin_dir)
    self.deps.add (os.path.join (lib_dir, libname))



  def set_object (self):
    self.action = 'CC'
    cc_file = self.name[:-len(obj_suffix)] + cpp_suffix
    self.deps = set([ cc_file ])
    self.deps = self.deps.union (list_headers (cc_file))

    if file_flags[cc_file] == 1: gtk = cflags_glib
    elif file_flags[cc_file] == 2: gtk = cflags_gtk
    else: gtk = cflags_gtkgl

    self.cmd = fillin (cpp, { '$flags$': cpp_flags + cpp_flags_gsl,
      '$path$': [ '-I' + entry for entry in include_paths ],
      '$obj$': [ self.name ],
      '$gtk$': gtk,
      '$src$': [ cc_file ] })


    
  def set_library (self):
    self.action = 'LD'
    for root, dirs, files in os.walk (os.path.split(self.name)[0], followlinks=True):
      for file in files:
        if file[0] == '.': continue
        if file.endswith (cpp_suffix):
          self.deps.add (os.path.join (root, file[:-len(cpp_suffix)] + obj_suffix))

    self.cmd = fillin (ld_lib, { '$flags$': ld_lib_flags,
      '$obj$': [ item for item in self.deps ] + pkg (pkgconfig_glib, 'libs') + ld_flags_gsl + ld_flags_zlib,
      '$lib$': [ self.name ] })

    try:
      if ld_use_shell: self.cmd = [ 'sh', '-c', ' '.join(self.cmd) ]
    except NameError: pass


  def set_icon (self):
    self.action = 'WR'
    self.deps = [ icon_dep ]
    self.cmd = windres + [ icon_dep, icon ]


  def display (self):
    print '[' + self.action + '] ' + self.name + ' (' + str(self.timestamp) + ' - ' + str(self.dep_timestamp) + ')',
    if not self.timestamp or self.timestamp < self.dep_timestamp: print '[REBUILD]',
    print ':'
    print '  deps: ', ' '.join(self.deps)
    print '  command: ', ' '.join(self.cmd)
    sys.stdout.flush()






###########################################################################
#                         FUNCTION DEFINITIONS
###########################################################################


def default_targets():
  if not os.path.isdir (cmd_dir): 
    print 'ERROR: no "cmd" folder - unable to determine default targets'
    exit (1)
  target_list = []
  for entry in os.listdir (cmd_dir):
    if entry.endswith(cpp_suffix):
      target_list.append (os.path.join (bin_dir, entry[:-len(cpp_suffix)] + exe_suffix))
  return target_list

def is_executable (target):
  return target.split (os.sep)[0] == bin_dir

def is_library (target):
  return target.endswith (lib_suffix) and target.split(os.sep)[-1].startswith (lib_prefix)

def is_object (target):
  return target.endswith (obj_suffix)

def is_icon (target):
  return target == icon

def mtime (target):
  if not os.path.exists (target): return None
  return os.path.getmtime(target)

def fillin (template, keyvalue):
  cmd = []
  for item in template:
    if item in keyvalue: cmd += keyvalue[item]
    else: cmd += [ item ]
  return cmd



def execute (message, cmd):
  print message
  sys.stdout.flush()
  if verbose: 
    print ' '.join(cmd)
    sys.stdout.flush()

  try: 
    process = subprocess.Popen (cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    ( stdoutdata, stderrdata ) = process.communicate()
    if process.returncode != 0:
      print ''
      print 'ERROR:', message
      print ''
      print ' '.join(cmd)
      print ''
      print 'failed with output:'
      print ''
      print stderrdata
      sys.stdout.flush()
      return 1
    if len(stdoutdata):
      print 'STDOUT:', message
      print stdoutdata
      sys.stdout.flush()
    if len(stderrdata):
      print 'STDERR:', message
      print stderrdata
      sys.stdout.flush()

  except OSError:
    print cmd[0] + ': command not found'
    sys.stdout.flush()
    return 1
  except Exception as inst:
    print 'unexpected exception: ' + str(inst)





def list_headers (file):
  global headers, file_flags

  if file not in headers.keys(): 
    headers[file] = set()
    if file == gl: file_flags[file] = 3
    else: file_flags[file] = 1
    if not os.path.exists (file):
      print 'ERROR: cannot find file "' + file + '"'
      sys.stdout.flush()
      exit(1)
    fd = open (file, 'r')
    for line in fd:
      line = line.strip()
      if line.startswith('#include'):
        line = line[8:].strip()
        if line.startswith('<gtk') or line.startswith('<gdk'): file_flags[file] = max (file_flags[file], 2)
        elif line[0] == '"':
          line = line[1:].rstrip('"')
          for path in include_paths:
            if os.path.exists (os.path.join (path, line)):
              line = os.path.join (path, line)
              headers[file].add (line)
              [ headers[file].add(entry) for entry in list_headers(line) ]
              break
          else:
            print 'ERROR: cannot find header file \"' + line + '\" (from file \"' + file + '\")'
            sys.stdout.flush()
            exit(1)
    fd.close()
    flags =  [ file_flags[file] ] + [ file_flags[entry] for entry in headers[file] ]
    if len(flags): file_flags[file] = max (flags)

  return headers[file]






def list_cmd_deps (file_cc):
  global object_deps, file_flags

  if file_cc not in object_deps.keys():
    object_deps[file_cc] = set([ file_cc[:-len(cpp_suffix)] + obj_suffix ])
    for entry in list_headers (file_cc):
      if os.path.abspath(entry).startswith(os.path.abspath(lib_dir)): continue
      entry_cc = entry[:-len(h_suffix)] + cpp_suffix
      if os.path.exists (entry_cc):
        object_deps[file_cc] = object_deps[file_cc].union (list_cmd_deps(entry_cc))
    file_flags[file_cc] = 1
    flags = [ file_flags[entry] for entry in headers[file_cc] ]
    if len(flags): file_flags[file_cc] = max (flags)

  return object_deps[file_cc]





def build_next (id):
  global todo, lock, stop

  try:
    while not stop:
      current = None
      lock.acquire()
      if len(todo):
        for item in todo.keys():
          if todo[item].currently_being_processed: continue
          unsatisfied_deps = set(todo[item].deps).intersection (todo.keys())
          if not len(unsatisfied_deps):
            todo[item].currently_being_processed = True
            current = item
            break
      else: stop = max (stop, 1)
      lock.release()
  
      if stop: return
      if current == None: 
        time.sleep (0.01)
        continue
  
      target = todo[current]
      if execute ('[' + target.action + '] ' + target.name, target.cmd):
        print 'STOP'
        stop = 2
        return

      lock.acquire()
      del todo[current]
      lock.release()

  except:
    stop = 2
    return
    
  stop = max(stop, 1)



def start_html (fid, title, left, up, home, right):
  fid.write ('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1">
  <title>MRtrix #VERSION# documentation</title>
  <link rel="stylesheet" href="../stylesheet.css" type="text/css" media=screen>
</head>

<body>

<table class=nav>
  <tr>
    <td><a href="''' + left + '''.html"><img src="../left.png"></a></td>
    <td><a href="''' + up + '''.html"><img src="../up.png"></a></td>
    <td><a href="''' + home + '''.html"><img src="../home.png"></a></td>
    <th>''' + title + '''</th>
    <td><a href="''' + right + '''.html"><img src="../right.png"></a></td>
  </tr>
</table>

''')




def gen_command_html_help ():
  binaries = os.listdir (bin_dir)
  binaries.sort()
  description = []
  most_recent_cmd = None
  most_recent_timestamp = 0

  # generate each program's individual page:
  for n in range (0, len(binaries)):
    print '[DOC] ' + binaries[n]
    sys.stdout.flush()
     
    env = dict(os.environ)
    if platform.system().lower() == 'windows':
      env['PATH'] = lib_dir + ';' + env['PATH']
    else:
      env['PATH'] = lib_dir + ':' + env['PATH']
    process = subprocess.Popen ([os.path.join(bin_dir,binaries[n]), '__print_full_usage__' ], stdout=subprocess.PIPE, env=env) 
    a = process.communicate()
    if process.returncode != 0:
      print 'ERROR: unable to execute', binaries[n], ' - aborting'
      sys.stdout.flush()
      return 1
    H = a[0]
    H = H.splitlines()

    fid = open (os.path.join (doc_dir, 'commands', binaries[n] + '.html'), 'wb')

    if n == 0: prev = 'index'
    else: prev = binaries[n-1]

    if n == len(binaries)-1: next = 'index'
    else: next = binaries[n+1]

    start_html (fid, binaries[n], prev, 'index', '../index', next)
    fid.write ('<h2>Description</h2>\n')

    line = 0
    while not H[line].startswith ('ARGUMENT') and not H[line].startswith('OPTION'):
      if len(description) <= n: description.append (H[line])
      fid.write ('<p>\n' + H[line] + '\n</p>\n')
      line += 1

    arg = []
    opt = []
    while line < len(H)-2:
      if not H[line].startswith ('ARGUMENT') and not H[line].startswith ('OPTION'):
        print 'ERROR: malformed usage for executable "' + binaries[n] + '" - aborting'
        sys.stdout.flush()
        return 1

      if H[line].startswith ('ARGUMENT'):
        S = H[line].split (None, 5)
        A = [ S[1], int(S[2]), int(S[3]), S[4], H[line+1], H[line+2] ]
        if len(opt) > 0: opt[-1].append (A)
        else: arg.append(A)

      elif H[line].startswith ('OPTION'):
        S = H[line].split (None, 4)
        A = [ S[1], int(S[2]), int(S[3]), H[line+1], H[line+2] ]
        opt.append (A)

      line += 3

    fid.write ('<p class=indented><strong>syntax:</strong> &nbsp; &nbsp; ' + binaries[n] + ' [ options ] ')
    for A in arg:
      if A[1] == 0: fid.write ('[ ')
      fid.write (A[0] + ' ')
      if A[2] == 1: fid.write ('[ ' + A[0] + ' ... ')
      if A[1] == 0 or A[2] == 1: fid.write ('] ')
    fid.write ('</p>\n<h2>Arguments</h2>\n<table class=args>\n')


    for A in arg:
      fid.write ('<tr><td><b>' + A[0] + '</b>')
      if A[1] == 0 or A[2] == 1:
        fid.write (' [ ')
        if A[1] == 0: 
          fid.write ('optional')
          if A[2] == 1: fid.write (', ')
        if A[2] == 1: fid.write ('multiples allowed')
        fid.write (' ]')
      fid.write ('</td>\n<td>' + A[5] + '</td></tr>\n')
    fid.write ('</table>\n')

    fid.write ('<h2>Options</h2>\n<table class=args>\n')
    for O in opt:
      fid.write ('<tr><td nowrap><b>-' + O[0] + '</b>')
      for A in O[5:]: fid.write ('&nbsp;<i>' + A[0] + '</i>')
      fid.write ('</td>\n<td>' + O[4])
      if len(O) > 5:
        fid.write ('\n<table class=opts>\n')
        for A in O[5:]:
          fid.write ('<tr><td><i>' + A[0] + '</i></td>\n<td>' + A[5] + '</td></tr>\n')
        fid.write ('</table>')
      fid.write ('</td></tr>\n')
    fid.write ('</table>\n')

    cmd_file = os.path.join (cmd_dir, binaries[n] + cpp_suffix)
    timestamp = mtime (cmd_file)
    if timestamp > most_recent_timestamp: 
      most_recent_timestamp = timestamp
      most_recent_cmd = cmd_file
    fid.write ('''
<p class=footer>
Donald Tournier<br>
MRtrix version #VERSION#<br>
Last updated #MTIME#
</p>

</body>
</html>
''')

    fid.close()



  fid = open (os.path.join (doc_dir, 'commands', 'index.html'), 'wb')
  start_html (fid, 'list of MRtrix commands', '../faq', '../index', '../index', '../appendix/index')
  fid.write ('<table class=cmdindex width=100%>\n')

  for n in range (0,len(binaries)):
    fid.write ('<tr><td><a href="' + binaries[n] + '.html">' + binaries[n] + '</a></td><td>' + description[n] + '</td></tr>\n')
  fid.write ('''</table>

<p class=footer>
Donald Tournier<br>
MRtrix version #VERSION#<br>
</p>

</body>
</html>
''')
  fid.close()




def gen_doc ():
  shutil.rmtree (doc_dir, True)
  shutil.copytree (os.path.join (misc_dir, doc_dir), doc_dir)
  gen_command_html_help()
  for root, dirs, files in os.walk (doc_dir):
    for entry in files:
      if entry[0] == '.': continue
      if not entry.endswith ('.html'): continue
      fname = os.path.join (root, entry)
      timestamp = SVN_last_changed (os.path.join (misc_dir, fname))
      if not timestamp:
        timestamp = SVN_last_changed (os.path.join (cmd_dir, entry.replace (".html", cpp_suffix)))
      if not timestamp:
        timestamp = '-'
      with open(fname, 'rb') as fd:
        contents = fd.read()
      with open(fname, 'wb') as fd:
        fd.write (contents
           .replace ('#MTIME#', timestamp)
           .replace ('#VERSION#', major + '.' + minor + '.' + micro))





def clean_cmd ():
  files_to_remove = []
  for root, dirs, files in os.walk ('.', followlinks=True):
    for file in files:
      if file[0] == '.': continue
      if file.endswith (obj_suffix) or ( file.startswith (lib_prefix) and file.endswith (lib_suffix) ):
        files_to_remove.append (os.path.join (root, file))
        
  dirs_to_remove = []
  if os.path.isdir (bin_dir):
    for root, dirs, files in os.walk (bin_dir, topdown=False, followlinks=True):
      for file in files: files_to_remove.append (os.path.join (root, file))
      for entry in dirs: dirs_to_remove.append (os.path.join (root, entry))
    dirs_to_remove.append (bin_dir)

  if len(files_to_remove):
    print '[RM] ' + ' '.join(files_to_remove)
    sys.stdout.flush()
    for entry in files_to_remove: os.remove (entry)

  if len(dirs_to_remove):
    print '[RM] ' + ' '.join (dirs_to_remove)
    sys.stdout.flush()
    for entry in dirs_to_remove: os.rmdir (entry)


def apply_recursive (action, source, destination):
  if not os.path.isdir (source):
    if not os.path.isdir (os.path.dirname (destination)):
      os.makedirs (os.path.dirname (destination))
    action (source, destination)
    return

  if not os.path.isdir (destination):
    os.makedirs (destination)

  for entry in os.listdir (source):
    if entry[0] == '.': continue
    src = os.path.join (source, entry)
    dst = os.path.join (destination, entry)
    if os.path.isdir (src): apply_recursive (action, src, dst)
    else: action (src, dst)


def install (source, destination):
  if os.path.exists (destination):
    os.unlink (destination)
  shutil.copy (source, destination)


def create_link (source, destination):
  if os.path.isabs (source): src = source
  else: src = os.path.relpath (source, os.path.dirname(destination))
  try:
    os.symlink (src, destination)
  except OSError as (errno, strerror):
    if errno == 17:
      if verbose: print 'error creating symbolic link "' + destination + '": ' + strerror
    else:
      print 'WARNING: error creating symbolic link: ' + strerror
      raise
  

def which (executables):
  for path in os.environ["PATH"].split(os.pathsep):
    for entry in executables:
      exe_file = os.path.join(path, entry)
      if os.path.exists (exe_file) and os.access (exe_file, os.X_OK):
        return path
  return None



def add_to_uninstall_list (path, executable):
  full_entry = os.path.join (path, executable)
  if not os.path.exists (full_entry): 
    return None
  if os.path.islink (full_entry):
    actual = os.readlink (full_entry)
    if not os.path.isabs (actual):
      actual = os.path.normpath (os.path.join (os.path.dirname (full_entry), actual))
    return [ actual, full_entry ]
  else:
    return [ full_entry, None ]


def sort_uninstall_list (uninstall_list):
  sorted_file_list = {}
  sorted_link_list = {}
  for entry in uninstall_list:
    [ folder, file ] = os.path.split (entry[0])
    if folder not in sorted_file_list.keys():
      sorted_file_list[folder] = []
    sorted_file_list[folder].append (file)
 
    [ folder, link ] = os.path.split (entry[1])
    if folder not in sorted_link_list.keys():
      sorted_link_list[folder] = []
    sorted_link_list[folder].append (link)
 
  return [ sorted_file_list, sorted_link_list ]
    
    
    


def delete (folder, files):
  if verbose: 
    print 'deleting from folder "' + folder + '": ' + ' '.join(files)
  for entry in files:
    try:
      os.remove (os.path.join (folder, entry))
    except OSError as ( errno, strerror ):
      print 'error deleting file "' + os.path.join (folder, entry) + '": ' + strerror
  
  if verbose:
    print 'attempting to delete folder "' + folder + '"...',
  try: 
    os.removedirs (folder)
  except OSError as (errno, strerror):
    if verbose:
      print 'not empty'
  else:
    if verbose: 
      print 'ok'


###########################################################################
#                            START OF PROGRAM
###########################################################################

try: num_processors = os.sysconf('SC_NPROCESSORS_ONLN')
except:
  try: num_processors = int(os.environ['NUMBER_OF_PROCESSORS'])
  except: num_processors = 1
  
if platform.system().lower() == 'windows':
  configfile = 'C:\\mrtrix.conf'
else:
  configfile = '/etc/mrtrix.conf'

# uninstall:

if 'uninstall' in targets:
  executables = [ os.path.basename (entry) for entry in default_targets() ]
  path = which (executables)
  if not path:
    print 'ERROR: could not find executables in path'
    exit (1)
  path = path.rstrip (os.path.sep)

  uninstall_list = [] 
  for entry in executables:
    item = add_to_uninstall_list (path, entry)
    if item: uninstall_list.append (item)

  [ prefix, last ] = os.path.split (path)
  if not last == 'bin':
    print 'ERROR: executables are expected to reside in a folder named "bin"'
    exit (1)

  for entry in os.listdir (os.path.join (prefix, 'lib')):
    if entry == libname:
      item = add_to_uninstall_list (os.path.join (prefix, 'lib'), entry)
      if item: uninstall_list.append (item)

  [ sorted_file_list, sorted_link_list ] = sort_uninstall_list (uninstall_list)

  print 'WARNING: about to delete files:'
  for key in sorted_file_list.keys():
    print '  from folder "' + key + '": ' + ' '.join(sorted_file_list[key])

  print 'WARNING: about to delete symbolic links:'
  for key in sorted_link_list.keys():
    print '  from folder "' + key + '": ' + ' '.join(sorted_link_list[key])

  if os.path.exists (configfile):
    print 'WARNING: about to delete configuration file: ' + configfile


  ans = raw_input ('proceed? y|[N]: ')
  if len(ans) and 'yes'.startswith (ans.lower()):
    for folder in sorted_file_list.keys():
      delete (folder, sorted_file_list[folder])
    for folder in sorted_link_list.keys():
      delete (folder, sorted_link_list[folder])
    if os.path.exists (configfile):
      try:
        os.remove (configfile)
      except OSError as ( errno, strerror ):
        print 'error deleting file "' + configfile + '": ' + strerror
    exit (0)

  print 'aborted'
  exit(1) 




# install:

if installto is not None:
  if len(installto) == 0:
    installto = default_installto
  dest_bin = os.path.join (installto, 'bin')
  dest_lib = os.path.join (installto, 'lib')
  print 'installing executables to "' + dest_bin + '" and dynamic library to "' + dest_lib + '"...',
  apply_recursive (install, bin_dir, dest_bin)
  apply_recursive (install, os.path.join (lib_dir, libname), os.path.join (dest_lib, libname))
  print 'ok'

  if linkto is not None:
    if len (linkto) == 0:
      linkto = default_linkto
    link_bin = os.path.join (linkto, 'bin')
    link_lib = os.path.join (linkto, 'lib')
    print 'creating symbolic links to executables in "' + link_bin + '" and to dynamic library in "' + link_lib + '"...',
    apply_recursive (create_link, dest_bin, link_bin)
    apply_recursive (create_link, os.path.join (dest_lib, libname), os.path.join (link_lib, libname))
    print 'ok'

  if not os.path.exists (configfile):
    print 'creating default system-wide configuration file (setting up multi-threading with ' + str(num_processors) + ' threads)...',
    with open(configfile, 'wb') as fd:
      fd.write ('NumberOfThreads: ' + str(num_processors) + '\n');
    print 'ok'
  else:
    print 'configuration file already exists - leaving as-is'

  exit(0)



# clean:

if 'clean' in targets:
  clean_cmd()
  exit (0)



# doc:

if doc_dir in targets: 
  exit (gen_doc());



# standard targets:

if len(targets) == 0: targets = default_targets()

if verbose:
  print ''
  print 'building targets:',
  for entry in targets:
    print entry,
  print ''
  sys.stdout.flush()

if verbose:
  print '' 
  print 'compiling TODO list...'
[ Entry(item) for item in targets ]
for item in todo.keys():
  if todo[item].action == 'NA' or (todo[item].timestamp and todo[item].timestamp >= todo[item].dep_timestamp):
    del todo[item]
if verbose: 
  print 'TODO list contains ' + str(len(todo)) + ' items'
  print ''

#for entry in todo.values(): entry.display()

if dependencies:
  print '======================================='
  print '  Objects'
  print '======================================='
  for entry in object_deps.keys():
    print entry + ' [' + str(file_flags[entry]) + ']:'
    for item in object_deps[entry]: print '  ' + item 

  print '======================================='
  print '  Headers'
  print '======================================='
  for entry in headers.keys():
    print entry + ' [' + str(file_flags[entry]) + ']:'
    for item in headers[entry]: print '  ' + item + ' [' + str(file_flags[item]) + ']'




if verbose:
  print '' 
  print 'launching ' + str(num_processors) + ' threads'
  print '' 

threads = []
for i in range (1, num_processors):
  t = threading.Thread (target=build_next, args=(i,));
  t.start()
  threads.append (t)

build_next(0)

for t in threads: t.join()

exit (stop > 1)


