# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python:
import os, sys

# Edit the functions below for the extra functionality, the return should be
# a list of path's to wanted locations
def additional_libs(context, binary, sdk):
  return [
    # Path should be relative either to hl2sdk folder or to build folder
    # 'path/to/lib/example.lib',
  ]

def additional_defines(context, binary, sdk):
  return [
    # 'EXAMPLE_DEFINE=2'
  ]

def additional_includes(context, binary, sdk):
  return [
    # Path should be absolute only!
    # os.path.join(sdk['path'], 'game', 'server'),
    # os.path.join(sdk['path'], 'public', 'entity2'),
    # 'D:/absolute/path/to/include/folder/'
  ]

def ResolveEnvPath(env, folder):
  if env in os.environ:
    path = os.environ[env]
    if os.path.isdir(path):
      return path
  else:
    head = os.getcwd()
    oldhead = None
    while head != None and head != oldhead:
      path = os.path.join(head, folder)
      if os.path.isdir(path):
        return path
      oldhead = head
      head, tail = os.path.split(head)
  return None

def ResolveMMSRoot():
  prenormalized_path = None
  if builder.options.mms_path:
    prenormalized_path = builder.options.mms_path
  else:
    prenormalized_path = ResolveEnvPath('MMSOURCE112', 'mmsource-1.12')
    if not prenormalized_path:
      prenormalized_path = ResolveEnvPath('MMSOURCE111', 'mmsource-1.11')
    if not prenormalized_path:
      prenormalized_path = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10')
    if not prenormalized_path:
      prenormalized_path = ResolveEnvPath('MMSOURCE_DEV', 'metamod-source')
    if not prenormalized_path:
      prenormalized_path = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central')
  if not prenormalized_path or not os.path.isdir(prenormalized_path):
    raise Exception('Could not find a source copy of Metamod:Source')
  
  return os.path.abspath(os.path.normpath(prenormalized_path))

mms_root = ResolveMMSRoot()

def ResolveHL2SDKManifestsRoot():
  prenormalized_path = builder.options.hl2sdk_manifests
  if not prenormalized_path or not os.path.isdir(prenormalized_path):
    raise Exception('Could not find a source copy of HL2SDK manifests')
  
  return os.path.abspath(os.path.normpath(prenormalized_path))

hl2sdk_manifests_root = ResolveHL2SDKManifestsRoot()

SdkHelpers = builder.Eval(os.path.join(hl2sdk_manifests_root, 'SdkHelpers.ambuild'), {
  'Project': 'metamod'
})

class MMSPluginConfig(object):
  def __init__(self):
    self.sdk_manifests = []
    self.sdks = {}
    self.sdk_targets = []
    self.binaries = []
    self.mms_root = mms_root
    self.all_targets = []
    self.target_archs = set()

    if builder.options.plugin_name is not None:
      self.plugin_name = builder.options.plugin_name
    else:
      self.plugin_name = 'sample_mm'

    if builder.options.plugin_alias is not None:
      self.plugin_alias = builder.options.plugin_alias
    else:
      self.plugin_alias = 'sample'

    if builder.options.targets:
      target_archs = builder.options.targets.split(',')
    else:
      target_archs = ['x86', 'x86_64']

    for arch in target_archs:
        try:
            cxx = builder.DetectCxx(target_arch = arch)
            self.target_archs.add(cxx.target.arch)
        except Exception as e:
            # Error if archs were manually overridden.
            if builder.options.targets:
                raise
            print('Skipping target {}: {}'.format(arch, e))
            continue
        self.all_targets.append(cxx)

    if not self.all_targets:
        raise Exception('No suitable C/C++ compiler was found.')

  def findSdkPath(self, sdk_name):
    dir_name = 'hl2sdk-{}'.format(sdk_name)
    if builder.options.hl2sdk_root:
      sdk_path = os.path.abspath(os.path.normpath(os.path.join(builder.options.hl2sdk_root, dir_name)))
      if os.path.exists(sdk_path):
        return sdk_path
    return ResolveEnvPath('HL2SDK{}'.format(sdk_name.upper()), dir_name)

  def detectSDKs(self):
    sdk_list = [s for s in builder.options.sdks.split(',') if s]
    SdkHelpers.find_sdk_path = self.findSdkPath
    SdkHelpers.findSdks(builder, self.all_targets, sdk_list)

    self.sdks = SdkHelpers.sdks
    self.sdk_manifests = SdkHelpers.sdk_manifests
    self.sdk_targets = SdkHelpers.sdk_targets

    if len(self.sdks) > 1:
      raise Exception('Only one sdk at a time is supported, for multi-sdk approach use loader based solution.')

  def configure(self):
    for cxx in self.all_targets:
      if cxx.target.arch not in ['x86', 'x86_64']:
        raise Exception('Unknown target architecture: {0}'.format(arch))

      self.configure_cxx(cxx)

  def configure_cxx(self, cxx):
    if cxx.behavior == 'gcc':
      cxx.defines += [
        'stricmp=strcasecmp',
        '_stricmp=strcasecmp',
        '_snprintf=snprintf',
        '_vsnprintf=vsnprintf',
        'HAVE_STDINT_H',
        'GNUC',
      ]
      cxx.cflags += [
        '-pipe',
        '-fno-strict-aliasing',
        '-Wall',
        '-Werror',
        '-Wno-uninitialized',
        '-Wno-unused',
        '-Wno-switch',
        '-msse',
        '-fPIC',
      ]

      cxx.cxxflags += ['-std=c++17']
      if (cxx.version >= 'gcc-4.0') or cxx.family == 'clang':
        cxx.cflags += ['-fvisibility=hidden']
        cxx.cxxflags += ['-fvisibility-inlines-hidden']
      cxx.cxxflags += [
        '-fno-exceptions',
        '-fno-rtti',
        '-fno-threadsafe-statics',
        '-Wno-non-virtual-dtor',
        '-Wno-overloaded-virtual',
        '-Wno-register',
      ]
      if (cxx.version >= 'gcc-4.7' or cxx.family == 'clang'):
        cxx.cxxflags += ['-Wno-delete-non-virtual-dtor']
      if cxx.family == 'gcc':
        cxx.cflags += ['-mfpmath=sse']
      if cxx.family == 'clang':
        cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch']
        if cxx.version >= 'clang-3.9':
          cxx.cxxflags += ['-Wno-expansion-to-defined']
        if cxx.version >= 'clang-3.6':
          cxx.cxxflags += ['-Wno-inconsistent-missing-override']
        if cxx.version >= 'clang-3.4':
          cxx.cxxflags += ['-Wno-deprecated-register']
        else:
          cxx.cxxflags += ['-Wno-deprecated']
      
      # Work around SDK warnings.
      if cxx.version >= 'clang-10.0':
        cxx.cflags += [
          '-Wno-implicit-int-float-conversion',
          '-Wno-tautological-overlap-compare',
        ]

    elif cxx.like('msvc'):
      if builder.options.debug == '1':
        cxx.cflags += ['/MTd']
        cxx.linkflags += ['/NODEFAULTLIB:libcmt']
      else:
        cxx.cflags += ['/MT']
      cxx.defines += [
        '_CRT_SECURE_NO_DEPRECATE',
        '_CRT_SECURE_NO_WARNINGS',
        '_CRT_NONSTDC_NO_DEPRECATE',
      ]
      cxx.cflags += [
        '/W3',
        '/Zi',
        '/std:c++17',
      ]
      cxx.cxxflags += ['/TP']
      
      cxx.linkflags += [
        '/SUBSYSTEM:WINDOWS',
        'kernel32.lib',
        'user32.lib',
        'gdi32.lib',
        'winspool.lib',
        'comdlg32.lib',
        'advapi32.lib',
        'shell32.lib',
        'ole32.lib',
        'oleaut32.lib',
        'uuid.lib',
        'odbc32.lib',
        'odbccp32.lib',
      ]

    # Optimization
    if builder.options.opt == '1':
      cxx.defines += ['NDEBUG']
      if cxx.behavior == 'gcc':
        cxx.cflags += ['-O3']
      elif cxx.behavior == 'msvc':
        cxx.cflags += ['/Ox', '/Zo']
        cxx.linkflags += ['/OPT:ICF', '/OPT:REF']

    # Debugging
    if builder.options.debug == '1':
      cxx.defines += ['DEBUG', '_DEBUG']
      if cxx.behavior == 'gcc':
        cxx.cflags += ['-g3']
      elif cxx.behavior == 'msvc':
        cxx.cflags += ['/Od', '/RTC1']

    # Don't omit the frame pointer.
    # This needs to be after our optimization flags which could otherwise disable it.
    if cxx.behavior == 'gcc':
      cxx.cflags += ['-fno-omit-frame-pointer']
    elif cxx.behavior == 'msvc':
      cxx.cflags += ['/Oy-']

    # Platform-specifics
    if cxx.target.platform == 'linux':
      cxx.defines += ['LINUX', '_LINUX', 'POSIX', '_FILE_OFFSET_BITS=64']
      if cxx.family == 'gcc':
        cxx.linkflags += ['-static-libgcc']
      elif cxx.family == 'clang':
        cxx.linkflags += ['-lgcc_eh']
      cxx.linkflags += ['-static-libstdc++']
    elif cxx.target.platform == 'windows':
      cxx.defines += ['WIN32', '_WINDOWS']

    # Finish up.
    # Custom defines here
    cxx.defines += [ ]
    # Custom includes here
    cxx.includes += [ ]

  def Library(self, cxx, name):
    binary = cxx.Library(name)
    return binary
  
  def HL2Library(self, context, compiler, name, sdk):
    binary = self.Library(compiler, name)
    mms_core_path = os.path.join(self.mms_root, 'core')
    cxx = binary.compiler

    cxx.cxxincludes += [
      os.path.join(context.currentSourcePath),
      os.path.join(mms_core_path),	
      os.path.join(mms_core_path, 'sourcehook'),
    ]

    defines = []
    for other_sdk in self.sdk_manifests:
      cxx.defines += ['SE_{}={}'.format(other_sdk['define'], other_sdk['code'])]

    if sdk['source2']:
      cxx.defines += ['META_IS_SOURCE2']
      binary.sources += [
        os.path.join(sdk['path'], 'public', 'tier0', 'memoverride.cpp'),
        os.path.join(sdk['path'], 'tier1', 'convar.cpp'),
      ]

    SdkHelpers.configureCxx(context, binary, sdk)

    cxx.linkflags += additional_libs(context, binary, sdk)
    cxx.defines += additional_defines(context, binary, sdk)
    cxx.cxxincludes += additional_includes(context, binary, sdk)

    return binary

MMSPlugin = MMSPluginConfig()
MMSPlugin.detectSDKs()
MMSPlugin.configure()

BuildScripts = [
  'AMBuilder',
  'PackageScript',
]

builder.Build(BuildScripts, { 'MMSPlugin': MMSPlugin })
