#!/usr/bin/env python
########################################
# The contents of this file are subject to the MLX PUBLIC LICENSE version
# 1.0 (the "License"); you may not use this file except in
# compliance with the License.
# 
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
# the License for the specific language governing rights and limitations
# under the License.
# 
# The Original Source Code is "compClust", released 2003 September 03.
# 
# The Original Source Code was developed by the California Institute of
# Technology (Caltech).  Portions created by Caltech are Copyright (C)
# 2002-2003 California Institute of Technology. All Rights Reserved.
########################################

"""Setup script for compClust

The one interesting detail is the command line takes a parameter --notest
to turn off the unit tests.
"""
from getopt import getopt
import glob
import os
import re
import string
import shutil
import sys

from distutils.core import setup
from distutils.core import Extension

from compClust.config import Config

config = Config(installing=True)

class DependenciesUnmet(RuntimeError): pass

# validate dependencies
critical_dependencies_met = True
try:
  import matplotlib
except ImportError, e:
  print >>sys.stderr, "Please install matplotlib <http://matplotlib.sf.net>"
  print >>sys.stderr, "plotting will not work without it"
  print >>sys.stderr, ""
  critical_dependencies_met = False
  
try:
  pyrex = True
  from Pyrex.Distutils import build_ext
except ImportError, e:
  print >>sys.stderr, "Please install pyrex <http://www.cosc.canterbury.ac.nz/~greg/python/Pyrex/>" 
  print >>sys.stderr, "Linear Assignment scoring will not work without it"
  print >>sys.stderr, ""
  critical_dependencies_met = False
  pyrex = False
  
try:
  import quixote
except ImportError, e:
  print >>sys.stderr, "Please install quxiote 2.x from <http://www.mems-exchange.org/software/quixote/>"
  print >>sys.stderr, "if one wants to use the web interface"
  print >>sys.stderr, "You'll also need to either install Twisted or Apache + SCGI"
  print >>sys.stderr, ""
  
try: 
  import Tkinter
except ImportError, e:
  print >>sys.stderr, "Please install Tkinter if one wants to use CompClustTk"
  print >>sys.stderr, "(Consult the python installation instructions for your platform)"
  print >>sys.stderr, ""
    
if not critical_dependencies_met:
  msg = "please install the required software first"
  raise DependenciesUnmet(msg)

# default to running tests
do_tests = True
# don't default to symlinking pyrex extensions into the source tree
# (which is convienent when doing development)
inplace = False
  
########################################
# Setup Configuration
PACKAGES=["compClust",
          "compClust.gui",
          "compClust.gui.tests",
          "compClust.iplot",
          "compClust.iplot.tests",
	  "compClust.iplot.mappers",
	  "compClust.iplot.mappers.tests",
          "compClust.mlx",
          "compClust.mlx.tests",
          "compClust.mlx.DA",
          "compClust.mlx.DA.tests",
          "compClust.mlx.datasets",
          "compClust.mlx.datasets.tests",
          "compClust.mlx.labelings",
          "compClust.mlx.labelings.tests",
          "compClust.mlx.models",
          "compClust.mlx.models.tests",
          "compClust.mlx.views",
          "compClust.mlx.views.tests",
          "compClust.mlx.wrapper",
          "compClust.mlx.wrapper.tests",
          "compClust.score",
          "compClust.score.tests",
          "compClust.util",
          "compClust.util.tests",
# to beta to deal with
#          "compClust.visualize",
#          "compClust.visualize.dataViewer",
#          "bioinformatics",
#          "bioinformatics.schema",
#          "bioinformatics.schema.datasets",
#          "bioinformatics.schema.views",
#          "bioinformatics.util",
#          "bioinformatics.visualize",          
          ]
          
# used to remap source locations
# FIXME: this is an ugly hack
# FIXME: since io, pstat, and stats live in the root directory and
# FIXME: I didn't want to clutter my already heavily cluttered root directory
# FIXME: I moved the root "" package to "extra"
# FIXME: so if new packages get in other places other than compClust 
# FIXME: they'd be added to this.
PACKAGE_DIR={"": "extra", "compClust": "compClust"}

# package data (templates?)
PACKAGE_DATA=[]
# documentation files to include
DATA_FILES=[]
# Individual modules to distribute (as opposed to packages)
MODULES=[]
# compiled modules 
ext_modules = []
         
def findScripts(startDir=None):
  if startDir is None:
    startDir = os.getcwd()
  else:
    startDir = os.path.realpath(startDir)  
  #
  scripts = []
  for dirEntry in filter(lambda x: not re.search("~$", x), os.listdir(startDir)):
    pathname = os.path.join(startDir, dirEntry)
    if re.search("(CVS$)|(.svn$)", pathname):
      continue
    elif os.path.isdir(pathname):
      # recurse through other directories
      scripts.extend(findScripts(pathname))
    elif os.path.isfile(pathname):
      # find files that are scripts
      f = open(pathname, 'r')
      if re.search('#!.*python', f.readline()):
        scripts.append(pathname)
      f.close()
  return scripts

# find all of the non test scripts
SCRIPTS = filter(lambda x: not re.search("/[Tt]est[^/.]*.py$", x),
                 findScripts('compClust'))

# also include the web launch scripts          
SCRIPTS.append(os.path.abspath('compclustweb-scgi.py'))
SCRIPTS.append(os.path.abspath('compclustweb.py'))
          
def make_test_ini(inidir):
  """Make an ini file suitible for running tests before building a distribution
  """
  inifilename = os.path.join(inidir, "compclust.ini")
  from ConfigParser import RawConfigParser as ConfigParser
  parser = ConfigParser()
  parser.add_section('base')
  parser.set('base', 'base_dir', config.base_dir)
  parser.write(open(inifilename, 'w'))
  return inifilename

def run_tests(inplace=False):
  # specify the exact location of the cho data
  # the testIPlot script looks for this environment variable
  # for grabbing the data location
  os.environ['CHO_DATA'] = os.path.join(os.getcwd(), 'compClust', 'gui', 'Examples', 'ChoCellCycling')

  testscript = 'testEverything.py'
  if not inplace:
    curdir = os.getcwd()
    for builddir in glob.glob('build/lib.*'):
      # the stuff after the last - should be the python version number
      version = builddir.split("-")[-1]
      scriptdir = os.path.join(os.getcwd(), builddir, '..', 'scripts-%s'%(version))
      os.environ['PYTHONPATH'] = builddir
      os.environ['COMPCLUST_DIR'] = config.base_dir
      #os.system(string.join([sys.executable, testscript], " "))
      build_testscript = os.path.join(builddir, testscript)
      shutil.copy(testscript, build_testscript)
      os.chdir(builddir)
      os.system(string.join([sys.executable, testscript], " "))
      os.chdir(curdir)
      os.unlink(build_testscript)
  else:
    os.system(string.join([sys.executable, testscript], " "))

def setup_inplace():
  """Link shared libraries to source tree
  """
  builddirs = glob.glob('build/lib.*')
  if len(builddirs) > 1:
    print "can't run in place"
    return
  
  basedir = os.getcwd()
  builddir = os.path.join(basedir, builddirs[0])
  os.chdir(builddir)

  for dirpath, dirnames, filenames in os.walk('.'):
    for filename in filenames:
      path, ext = os.path.splitext(filename)
      if ext == config.get_python_shared_library_extension():
        src_file = os.path.normpath(os.path.join(builddir, dirpath, filename))
        dest_file = os.path.normpath(os.path.join(basedir, dirpath, filename))
        if not os.path.islink(dest_file):
          os.symlink(src_file, dest_file)
  os.chdir(basedir)


# handle the stats/pstat/io stat code
try:
  import io
except ImportError, e:
  MODULES.append('io')
try:
  import pstat
except ImportError, e:
  MODULES.append('pstat')
try:
  import stats
except ImportError, e:
  MODULES.append('stats')


def vc_filter(x):
  """Filter for skipping version control subdirectories
  """
  if x.find('CVS') ==-1 and x.find('.svn') == -1:
    return 1
  else:
    return 0
  
def glob_novc(pattern):
  """Return list of filenames that match pattern excluding CVS and .svn
  """
  return filter(vc_filter, glob.glob(pattern))

docPath = config.data_install_path()

# build the C Code
def have_gnumake(make_command = "make -v"):
  """test to see if we have gnumake  
  this should be extended to allow the user to specify which make to use
  """
  make_version_stream = os.popen(make_command)
  make_version = make_version_stream.readlines()
  make_exit_code = make_version_stream.close()
  if make_exit_code is None and \
     len(make_version) > 0 and \
     re.match("GNU Make", make_version[0]) is not None:
    return True
  else:
    print >>sys.stderr, "GNU make not found can't build c-code"
    return False
  
if os.path.isdir("src") and have_gnumake():    
  log_stream_name = "compclust_src_build.log"
  print >>sys.stderr, "Attempting to build C code,",
  print >>sys.stderr, "look at %s for log" % (log_stream_name)
  #status = os.spawnl(os.P_WAIT, "make", "make", "-C", "src")
  build_stream = os.popen("make -C src")
  log_stream = open(log_stream_name, 'w')
  for l in build_stream:
    log_stream.write(l)
  log_stream.close()
  build_result = build_stream.close()
  if build_result is None:
  #  # we managed to build the C code
    if os.path.exists('src/mls/diagem/diagem'):
      DATA_FILES.append((os.path.join(docPath, 'bin'), 
                         ['src/mls/diagem/diagem']))
      #SCRIPTS.append('src/mls/diagem/diagem')
    if os.path.exists('src/mls/kmeans/kmeans'):
      DATA_FILES.append((os.path.join(docPath, 'bin'), 
                         ['src/mls/kmeans/kmeans']))
    if os.path.exists('src/mls/kmedians/kmedians'):
      DATA_FILES.append((os.path.join(docPath, 'bin'), 
                         ['src/mls/kmedians/kmedians']))
      #SCRIPTS.append('src/mls/kmeans/kmeans')
  else:
    print "----------"
    print "FAILED building C Code", build_result
    print "----------"
else:
  # alas no source code, try the prebuilt binaries
  algorithm_platform = os.path.join(os.getcwd(), 'extra','clustering-alg')
  diagem_command = 'diagem'
  kmeans_command = 'kmeans'
  kmedians_command = 'kmedians'
  if sys.platform == 'linux2':
    # perhaps we should start testing which particular platform of linux?
    algorithm_platform = os.path.join(algorithm_platform, 'linux-x86')
  elif sys.platform == 'darwin':
    algorithm_platform = os.path.join(algorithm_platform, 'osx-ppc')
  elif sys.platform == 'win32':
    algorithm_platform = os.path.join(algorithm_platform, 'win32')    
    diagem_command = 'diagem.exe'
    kmeans_command = 'kmeans.exe'
    kmedians_command = 'kmedians.exe'
  else:
    raise DependenciesUnmet("No clustering algorithms available... "\
                           "this is probably a bad thing.")
  DATA_FILES.append((os.path.join(docPath, 'bin'), 
                     [os.path.join(algorithm_platform, diagem_command)]))
  DATA_FILES.append((os.path.join(docPath, 'bin'), 
                     [os.path.join(algorithm_platform, kmeans_command)]))
  DATA_FILES.append((os.path.join(docPath, 'bin'), 
                     [os.path.join(algorithm_platform, kmedians_command)]))
  
#CompClustTk Tutorial
# FIXME: need to properly figure out where the tutorial should go
#tutPath = os.path.join(docPath, 'compclusttk_tutorial')
#TUTORIAL_TUPLE=(tutPath,
#                glob_novc('compClust/gui/Docs/compclusttk_tutorial/*'))
#
#DATA_FILES.append(TUTORIAL_TUPLE)

#CompClustTk Example Data
examplePath = os.path.join(docPath, 'Examples', 'ChoCellCycling')
EXAMPLE_DATA_TUPLE=(examplePath,
                    glob_novc('compClust/gui/Examples/ChoCellCycling/*'))

#CompClustTk Data
DATA_FILES.append(EXAMPLE_DATA_TUPLE)

# web interface templates
template_files = glob_novc('compClust/gui/*.xml')
template_files.extend(glob_novc('compClust/gui/*.css'))
template_files.extend(glob_novc('compClust/gui/*.js'))
DATA_FILES.append((config.template_dir, template_files))

# Pyrex extensions
if pyrex:
  graphmatching_dir = os.path.join(os.getcwd(), "compClust/mlx/graphmatching")
  graphmatching_modules = [ os.path.join(graphmatching_dir, x) for x in
                            ["graphmatching.pyx", "wmatch.c", "glib.c"]]
  graphmatching_package = "compClust.mlx.graphmatching"
  ext_modules.append(Extension(graphmatching_package, graphmatching_modules ))

ext_modules.append(Extension("compClust.mlx.DA.cDA", [os.path.join(os.getcwd(), "compClust/mlx/DA/DAmodule.c")]))

      
# Allow user to override testingg::
for i in xrange(len(sys.argv)-1,-1,-1):
  if re.match("--?notest",sys.argv[i]):
    do_tests = False
    del sys.argv[i]
  elif re.match("--?inplace", sys.argv[i]):
    del sys.argv[i]
    inplace = True
    PACKAGES=None
    SCRIPTS=None
    DATA_FILES=None

d= setup(name="compClust",
      version="1.2",
      description="Comp Clust is a suite of tools for clustering  " \
      "and data mining. ",
      maintainer="Diane Trout",
      maintainer_email="diane@caltech.edu",
      url="http://woldlab.caltech.edu/compClust/",
      packages=PACKAGES,
      scripts=SCRIPTS,
      package_dir=PACKAGE_DIR,
#      package_data={'compClust':['gui/*.xml']},
      py_modules=MODULES,
      ext_modules=ext_modules,
      data_files=DATA_FILES,
      cmdclass = {'build_ext': build_ext})

if inplace: setup_inplace()
if do_tests: run_tests(inplace)
