#!/usr/bin/env python

# python3 status: started

# ---------------------------------------------------------------------------
# These are routines for checking whether libraries can be imported
# (and getting python version).
#
# Consider starting a python file (as library or main program) with
# something like the following:
#
#       import sys
#       from afnipy import module_test_lib
#       g_testlibs = ['numpy', 'wx', 'matplotlib', 'scipy']
#       if module_test_lib.num_import_failures(g_testlibs): sys.exit(1)
#
# --------------------------------------------------
#
# main functions:
#
#   get_py_ver()          : return list of version indices: like ['2','4','1']
#                           e.g. verlist = get_py_ver()
#
#   get_py_ver_float()    : return python version as float: like 2.4
#                           e.g. if get_py_ver_float() < 2.5: PANIC()
#
#   num_import_failures() : return the number of failures importing libraries
#                           e.g. num_errs = test_libs(['sys','os','R','scipy'])
#
#   test_import()         : test a single library
#                           e.g. if test_import('numpy'): print '** failure'
#
# R Reynolds 28 Oct 2008
# ---------------------------------------------------------------------------


# ---------------------------------------------------------------------------
# todo:
#
# - fromlibs = { 'scipy': ['linalg', 'signal', 'stats'], 'fish': ['tuna'] }
# - add to 

import os, sys
IL = None

# add 'R' if needed
genlibs  = ['os', 'sys', 'string', 'glob', 'copy', 'gc', 'time', 'webbrowser']
mathlibs = ['math', 'random', 'numpy', 'scipy']
guilibs  = ['PyQt4', 'wx', 'matplotlib']

alllibs  = []
alllibs.extend(genlibs)
alllibs.extend(mathlibs)
alllibs.extend(guilibs)


# ---------------------------------------------------------------------------
# library failure message list (for select library packages)
# 
# These install descriptions are old.  Just mention the libraries for now.
#                                                       16 Sep 2016
g_mesglist = [
             [ 'numpy', '' ],
#"""
#     -> consider downloading 'numpy' from www.scipy.org
#           or on Linux: "sudo yum install numpy"
#           or on OS X:  "sudo fink install numpy-py25"
#""" ],
             [ 'matplotlib', '' ],
#"""
#     -> consider downloading 'matplotlib' from matplotlib.sourceforge.net
#           or on Linux: "sudo yum install python-matplotlib"
#           or on OS X:  "sudo fink install matplotlib-py25"
#""" ],
             [ 'R', '' ],
#"""
#     -> consider downloading 'R' from a site in the mirror list:
#        http://cran.r-project.org/mirrors.html
#           or on Linux: "sudo yum install R"
#
#        consider also installing the developer sub-package 'R-devel'
#           "sudo yum install R-devel"
#""" ],
             [ 'scipy', '' ],
#"""
#     -> consider downloading 'scipy' from www.scipy.org
#           or on Linux: "sudo yum install scipy"
#           or on OS X:  "sudo fink install scipy-py25"
#""" ],
             [ 'PyQt4','' ],
#"""
#     -> please see the output of "uber_subject.py -help_install"
#""" ],
             [ 'wx', '' ],
#"""
#     -> consider downloading 'wxpython' from www.wxpython.org
#           or on Linux: "sudo yum install wxPython"
#           or on OS X:  "sudo fink install wxpython-py25"
#                        Note: wxpython-py25 is not available on OS X 10.4
#""" ]
]



# ---------------------------------------------------------------------------
# main functions

def get_py_ver():
   "return the python version as a list, allow for older versions"
   vlist = ['0', '0']
   try:
      import platform
      vlist = platform.python_version_tuple()
   except:
      if sys.version[0] == '1':
         import string
         ver = string.split(sys.version)
         vlist = string.split(ver[0], '.')
      else:
         ver = sys.version.split()
         vlist = ver[0].split('.')

   return vlist

def get_py_ver_float():
   pv = get_py_ver()
   if pv[0] == '1': return float('%c.%c' % (pv[0],pv[1]))
   else:            return float('%s.%s' % (pv[0],pv[1]))

def compare_py_ver_to_given(vstr):
   """return -1, 0, 1 comparing the current version to input vstr
   """
   # get current and input version lists, as ints
   pvc = [int(v) for v in list(get_py_ver())]
   pvi = [int(v) for v in vstr.split('.')]

   lenc = len(pvc)
   leni = len(pvi)
   
   dmin = min(lenc,leni)

   # check up to where they are equal
   for dind in range(dmin):
      if pvc[dind] < pvi[dind]: return -1
      if pvc[dind] > pvi[dind]: return  1

   # if still equal return the longer list
   if lenc < leni: return -1
   if lenc > leni: return  1

   # else equal
   return 0

# not for general use: return message for libname
def _get_mesg(libname):
   for mpair in g_mesglist:
      if mpair[0] == libname:
         return mpair[1]
   return ''


# function definition string for if we are running python 3.12+
# (actually okay for 3.4+)
import_find_test_312_def =       \
"""
def import_find_test_312(libname, details=1, verb=1):
   # return loaded library or None (on failure)
   import importlib

   if verb > 3: print("-- running test_312 on library %s" % libname)

   try:    mod = sys.modules[libname]
   except: pass
   else:
      if verb>1: print("++ module already loaded: %s" % libname)
      return mod

   try:
      spec = importlib.util.find_spec(libname)
   except:
      if verb > 0:
         if details: mesg = _get_mesg(libname)
         else:       mesg = ''
         if mesg:
            print("---------------------------------------------------------")
         print("** python module not found: %s" % libname)
         if mesg: print(mesg)
            
      return None

   if verb>2: print("++ module '%s' found at %s" % (libname, spec.origin))

   # continue and try to load it (just use import)
   mod = None
   try: mod = importlib.import_module(libname)
   except:
      if verb>0: print("** failed to load module: %s" % libname)
      mod = None  # be sure of return value
   else:
      if verb>1: print("++ module loaded: %s" % (libname))

   return mod
"""


# function definition string for if we are running python 2.5 +
# (this version produces a syntax error in 2.4.x python)
# (old string method, forget python 2.4) import_find_test_25_def =     \
import_find_test_25_def =       \
"""
def import_find_test_25(libname, details=1, verb=1):
   # return loaded library or None (on failure)
   import imp

   try:    mod = sys.modules[libname]
   except: pass
   else:
      if verb>1: print("++ module already loaded: %s" % libname)
      return mod

   try: fp, pname, desc = imp.find_module(libname)
   except:
      if verb > 0:
         if details: mesg = _get_mesg(libname)
         else:       mesg = ''
         if mesg:
            print("---------------------------------------------------------")
         print("** python module not found: %s" % libname)
         if mesg: print(mesg)
            
      return None

   if verb>2: print("++ module '%s' found at %s" % (libname, pname))

   # continue and try to load it
   mod = None
   try: mod = imp.load_module(libname, fp, pname, desc)
   except:
      if verb>0: print("** failed to load module: %s" % libname)
      mod = None  # be sure of return value
   else:
      if verb>1: print("++ module loaded: %s" % (libname))
   finally:
      if fp:
         if verb>3: print("-- close file for module: %s" % libname)
         fp.close()
   return mod
"""


# function definition string for if we are running python 2.4.x
# (the 2.5 version is a syntax error in 2.4.x python)
import_find_test_24_def =       \
"""
def import_find_test_24(libname, details=1, verb=1):
   # return loaded library or None (on failure)

   import imp

   try:    mod = sys.modules[libname]
   except: pass
   else:
      if verb>1: print("++ module already loaded: %s" % libname)
      return mod

   try: fp, pname, desc = imp.find_module(libname)
   except:
      if verb > 0:
         if details: mesg = _get_mesg(libname)
         else:       mesg = ''
         if mesg:
            print("---------------------------------------------------------")
         print("** python module not found: %s" % libname)
         if mesg: print(mesg)
      return None

   if verb>2: print("++ module '%s' found at %s" % (libname, pname))

   # continue and try to load it
   mod = None
   try:
      try: mod = imp.load_module(libname, fp, pname, desc)
      except:
         if verb>0: print("** failed to load module: %s" % libname)
         mod = None  # be sure of return value
      else:
         if verb>1: print("++ module loaded: %s" % (libname))
   finally:
      if fp:
         if verb>3: print("-- close file for module '%s'" % libname)
         fp.close()

   return mod
"""

def simple_import(libname, details=1, verb=1):
   # return loaded library or None (on failure)

   global IL

   old_python = (get_py_ver_float() < 2.7)

   if IL is None:
      try:
         import importlib as LLL
         IL = LLL
      except:
         # failure, unless this is old python
         if not old_python:
            print("** simple_import_test: failed to get importlib to test %s" \
                  % libname)
            return None

   # exec waaaay simple backup plan for older python without importlib
   # --> simply attempt to import
   if old_python:
      try:
         exec('import %s as mod' % libname)
         if verb > 1:
            print("++ module loaded: %s" % libname)
      except:
         if verb:
             print("** failed to load module %s" % libname)
         return None
   else:
      try:
         mod = IL.import_module(libname)
         if verb > 1:
            print("++ module loaded: %s" % libname)
      except:
         if verb:
             print("** failed to load module %s" % libname)
         return None

   if details and verb > 1:
      if hasattr(mod, '__file__'):
         print("   module file : %s" % mod.__file__)
      elif hasattr(mod, '__path__'):
         print("   module path : %s" % mod.__path__)

   return mod

def get_version(libname, vfield='__version__',verb=0):
   """if this is an importable library, return the LIB.vfield element
      return 'NONE' on error
   """
   rstr = 'NONE'
   if verb > 0: details = 1
   else:        details = 0
   mod = simple_import(libname, details=details, verb=verb)
   if mod is None:
      return rstr

   # return the given attribute
   if hasattr(mod, vfield):
      return getattr(mod, vfield)

   return rstr

def simple_import_test(libname, details=1, verb=1):
   """test whether the library can be imported, return 0 on success
   """
   mod = simple_import(libname, details=details, verb=verb)
   if mod is None: return 1  # failure
   else:           return 0  # success

def test_import(libname, details=1, verb=1):
   """try to import a single library, specified as a string
        e.g. if test_import('numpy'): print('** failure')

        details: if libname is in g_mesglist, print(message)
      return 0 on success, 1 on failure"""

   # run test based on python version
   # break as less than 2.5, less than 3.10, and otherwise
   if compare_py_ver_to_given('2.5') < 0: # use 2.4 version (might fail)
      exec(import_find_test_24_def)
      imptest = eval('import_find_test_24')
   elif compare_py_ver_to_given('3.10') < 0: # might go up to 3.12, but overlap
      exec(import_find_test_25_def)
      imptest = eval('import_find_test_25')
   else:
      exec(import_find_test_312_def)
      imptest = eval('import_find_test_312')

   if imptest(libname, details, verb): return 0
   else:                               return 1

def num_import_failures(liblist=[], details=1, verb=1):
   """try to import a list of library names as strings

        liblist: e.g. ['wx', 'numpy']
        details: if libname is in g_mesglist, print(message)
      return the number of failures (0=success)"""

   # note python version
   pv = get_py_ver_float()

   if verb > 2: print('-- python version %s' % pv)

   if pv < 2.5: # use 2.4 version (might fail)
      exec(import_find_test_24_def)
      imptest = eval('import_find_test_24')
   else:        # use 2.5 version
      
      if liblist: libs = liblist
      else:       libs = alllibs
   
      errs = 0
      for lib in libs:
         # use simple_import to handle A.B form
         rlib = simple_import(lib, details, verb)
         if rlib == None:
            errs += 1

   return errs

