#!/usr/bin/env python
import glob
import os
import shlex
import sys
import gyp
script_dir = os.path.dirname(__file__)
root = os.path.normpath(script_dir)
sys.path.insert(0, os.path.join(root, 'tools/'))


import config_builder

# Directory within which we want all generated files (including Makefiles)
# to be written.
output_dir = os.path.join(os.path.abspath(root), 'out')

def CheckThreads(builder, args) :
  std_thread = builder.CheckHeader(False, ['thread', 'tr1/thread'], '')
  stds = {};
  boosts = {};
  if not std_thread[0] :
    result = builder.CheckLibAndHeader(True, [
    {
      'header' : ['boost/thread.hpp'],
      'lib' : ['boost_thread-vc100-mt-1_47.lib', 'boost_thread-vc100-mt-gd-1_47.lib', 'libboost_thread-mt.a']
    }
    ], 'boost/thread is required.')
    args.append('-Dboost_thread_lib_name=' + result[1])
    
  stds['promise'] = builder.CheckStruct(False, [
      {
        'name' : 'std::promiss',
        'header' : ['thread', 'future'],
        'struct': 'std::promise<int>'
      }
      ], 'promise required.')

  boosts['promise'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::promiss',
        'header' : ['boost/thread.hpp'],
        'struct': 'boost::promise<int>'
        }      
      ], 'promise required.')


  stds['future'] = builder.CheckStruct(False, [
      {
        'name' : 'std::future',
        'header' : ['thread.hpp', 'future'],
        'struct': 'std::future<int>'
      }
      ], 'future required.')

  boosts['future'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::unique_future',
        'header' : ['boost/thread.hpp'],
        'struct': 'boost::unique_future<int>'
        }
      ], 'future required.')

  stds['packaged_task'] = builder.CheckStruct(False, [
      {
        'name' : 'std::packaged_task',
        'header' : ['thread.hpp', 'future'],
        'struct': 'std::packaged_task<int>'
      }
      ], 'packaged_task required.')

  boosts['packaged_task'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::packaged_task',
        'header' : ['boost/thread.hpp'],
        'struct': 'boost::packaged_task<int>'
      }
      ], 'packaged_task required.')

  stds['mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'std::mutex',
        'header' : ['thread.hpp', 'mutex'],
        'struct': 'std::mutex'
      }
      ], 'mutex required.')

  boosts['mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::mutex',
        'header' : ['boost/thread.hpp', 'boost/thread/condition.hpp'],
        'struct': 'boost::mutex'
        }
      ], 'mutex required.')

  stds['recursive_mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'std::recursive_mutex',
        'header' : ['thread.hpp', 'mutex'],
        'struct': 'std::recursive_mutex'
      }
      ], 'recursive_mutex required.')

  boosts['recursive_mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::recursive_mutex',
        'header' : ['boost/thread.hpp', 'boost/thread/condition.hpp'],
        'struct': 'boost::recursive_mutex'
        }
      ], 'recursive_mutex required.')

  stds['timed_mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'std::timed_mutex',
        'header' : ['thread.hpp', 'mutex'],
        'struct': 'std::timed_mutex'
      }
      ], 'timed_mutex required.')

  boosts['timed_mutex'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::timed_mutex',
        'header' : ['boost/thread.hpp', 'boost/thread/condition.hpp'],
        'struct': 'boost::timed_mutex'
        }
      ], 'timed_mutex required.')

  stds['unique_lock'] = builder.CheckStruct(False, [
      {
        'name' : 'std::unique_lock',
        'header' : ['thread.hpp', 'mutex.hpp'],
        'struct': 'std::unique_lock<std::mutex>'
      }
      ], 'unique_lock required.')

  boosts['unique_lock'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::unique_lock',
        'header' : ['boost/thread.hpp', 'boost/thread/condition.hpp'],
        'struct': 'boost::unique_lock<boost::mutex>'
        }
      ], 'unique_lock required.')

  stds['lock_guard'] = builder.CheckStruct(False, [
      {
        'name' : 'std::lock_guard',
        'header' : ['thread'],
        'struct': 'std::lock_guard<std::mutex>'
      }
      ], 'lock_guard required.')

  boosts['lock_guard'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::lock_guard',
        'header' : ['boost/thread.hpp', 'boost/thread/condition.hpp'],
        'struct': 'boost::lock_guard<boost::mutex>'
        }
      ], 'lock_guard required.')

  stds['condition_variable'] = builder.CheckStruct(False, [
      {
        'name' : 'std::condition_variable',
        'header' : ['thread', 'condition_variable'],
        'struct': 'std::condition_variable'
      }
      ], 'condition_variable required.')

  boosts['condition_variable'] = builder.CheckStruct(False, [
      {
        'name' : 'boost::condition_variable',
        'header' : ['boost/thread/condition.hpp'],
        'struct': 'boost::condition_variable'
        }
      ], 'condition_variable required.')

  builder.CheckHeader(True, ['boost/thread/tss.hpp'], 'boost/thread/tss.hpp is required.')

  builder.CheckStruct(True, [
      {
        'name' : 'boost::thread_specific_ptr',
        'header' : ['boost/thread/tss.hpp'],
        'struct' : 'boost::thread_specific_ptr<int>'
        }
      ], 'boost::thread_specific_ptr is required.')
  
  if std_thread[0] :
    for std in stds :
      if std[0] == False :
        print 'std::' + std[1] + ' is required. Change a compiler or install boost library.'
        sys.exit(1)

  else :
    for boost in boosts :
      if boost[0] == False :
        print 'boost::' + boost[1] + ' is required. You need to install boost library1.49.0 or later.'
        sys.exit(1)

def BuildConfig(args) :
  builder = config_builder.ConfigBuilder('include/libos_config.h', always_build=True)
  builder.CheckHeader(False, ['stdint.h'], '')
  builder.CheckHeader(True, ["unordered_map", "tr1/unordered_map", "boost/unordered_map.hpp"], 'unordered_map required.')
  builder.CheckStruct(True, [
      {
        'name' : 'std::bind',
        'header' : ['functional'],
        'function' : 'std::bind(fopen, std::placeholders::_1, "rb")'
        }, 
      {
        'name' : 'boost::bind',
        'header' : ['boost/bind.hpp'],
        'function' : 'boost::bind(fopen, _1, "rb")'
        }], 'bind required.')
  builder.CheckHeader(True, ['type_traits', 'boost/type_traits.hpp'], 'type_traits required.')
  CheckThreads(builder, args)
  builder.CheckHeader(True, ['tuple', 'tr1/tuple', 'boost/tuple'], 'tuple required.')
  builder.CheckHeader(True, ['boost/foreach.hpp'], 'boost/foreach is required.')
  builder.CheckStruct(True, [
      {
        'name' : 'std::function',
        'header' : ['functional'],
        'struct': 'std::function<int (int)>'
      },
      {
        'name' : 'boost::function',
        'header' : ["boost/function.hpp"],
        'struct' : 'boost::function<int (int)>'
      }
    ], 'function required.')
  builder.CheckStruct(True, [
      {
        'name' : 'std::shared_ptr',
        'header' : ['memory'],
        'struct': 'std::shared_ptr<const char>'
      },
      {
        'name' : 'tr1::shared_ptr',
        'header' : ['memory'],
        'struct': 'std::tr1::shared_ptr<const char>'
      },
      {
        'name' : 'boost::shared_ptr',
        'header' : ["boost/shared_ptr.hpp"],
        'struct' : 'boost::shared_ptr<const char>'
      }
    ], 'shared_ptr required.')
  builder.CheckStruct(True, [
      {
        'name' : 'std::allocate_shared',
        'header' : ['memory'],
        'function': 'std::allocate_shared<char>(std::allocator<char>())'
      },
      {
        'name' : 'boost::allocate_shared',
        'header' : ["memory", "boost/shared_ptr.hpp", "boost/make_shared.hpp"],
        'function' : 'boost::allocate_shared<char>(std::allocator<char>())'
      }
    ], 'allocate_shared required.')

  builder.CheckStruct(True, [
      {
        'name' : 'std::make_shared',
        'header' : ['memory'],
        'function': 'std::make_shared<char>(5)'
      },
      {
        'name' : 'boost::make_shared',
        'header' : ["memory", "boost/shared_ptr.hpp", "boost/make_shared.hpp"],
        'function' : 'boost::make_shared<char>(5)'
      }
    ], 'allocate_shared required.')

  builder.CheckStruct(True, [
      {
        'name' : 'std::unique_ptr',
        'header' : ['memory'],
        'struct': 'std::unique_ptr<int>'
      },
      {
        'name' : 'boost::unique_ptr',
        'header' : ["boost/interprocess/smart_ptr/unique_ptr.hpp", 'boost/checked_delete.hpp'],
        'struct' : 'boost::interprocess::unique_ptr<int, boost::checked_deleter<int> >'
      }
    ], 'unique_ptr is required.')

  builder.CheckStruct(True, [
      {
        'name' : 'std::ref',
        'header' : ['functional'],
        'function': 'std::ref(printf)'
      },
      {
        'name' : 'boost::ref',
        'header' : ["boost/ref.hpp"],
        'function' : 'boost::ref(printf)'
      }
    ], 'ref is required.')

  builder.CheckHeader(True, ["boost/preprocessor/repetition/repeat.hpp"], 'boost/preprocessor/repetition/repeat.hpp required.')
  builder.CheckHeader(True, ["boost/preprocessor/repetition/enum_params.hpp"], 'boost/preprocessor/repetition/enum_params.hpp required.')
  builder.CheckHeader(True, ["boost/preprocessor/repetition/enum_binary_params.hpp"], 'boost/preprocessor/repetition/enum_binary_params.hpp required.')
  builder.CheckHeader(True, ["boost/detail/atomic_count.hpp"], 'boost/preprocessor/repetition/repeat.hpp required.')
  builder.Build()

def run_gyp(args):
  rc = gyp.main(args)
  if rc != 0:
    print 'Error running GYP'
    sys.exit(rc)

if __name__ == '__main__':
  args = sys.argv[1:]
  # GYP bug.
  # On msvs it will crash if it gets an absolute path.
  # On Mac/make it will crash if it doesn't get an absolute path.
  if sys.platform == 'win32':
    args.append(os.path.join(root, 'os.gyp'))
    common_fn  = os.path.join(root, 'common.gypi')
  else:
    args.append(os.path.join(os.path.abspath(root), 'os.gyp'))
    common_fn  = os.path.join(os.path.abspath(root), 'common.gypi')

  if os.path.exists(common_fn):
    args.extend(['-I', common_fn])

  args.append('--depth=' + root)
  
  additional_include = os.getenv("INCLUDE")
  additional_lib = os.getenv("LIB")
  if  additional_include :
    args.append('-Dadditional_include=' + additional_include)
  else :
    additional_include = ""
  
  if additional_lib :
    args.append('-Dadditional_lib=' + additional_lib)
  else :
    additional_lib = ''

  # There's a bug with windows which doesn't allow this feature.
  if sys.platform != 'win32':
    # Tell gyp to write the Makefiles into output_dir
    args.extend(['--generator-output', output_dir])
    # Tell make to write its output into the same dir
    args.extend(['-Goutput_dir=' + output_dir])
    # Create Makefiles, not XCode projects
    args.extend('-f make'.split())

  args.append('-Dtarget_arch=ia32')
  args.append('-Dcomponent=static_library')
  args.append('-Dlibrary=static_library')  
  args.append('-Dcurrent_dir=' + os.getcwd().replace('\\', '/'))
  BuildConfig(args)
  gyp_args = list(args)
  print gyp_args
  run_gyp(gyp_args)

