#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
#% $Id$ 
#
#
# Copyright (C) 2002-2011
# The MeqTree Foundation & 
# ASTRON (Netherlands Foundation for Research in Astronomy)
# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>,
# or write to the Free Software Foundation, Inc., 
# 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
import sys
import os.path
import re
import time
import traceback

# required to import Pyxis under Python3 (see comments in Pyxis/__init__.py). Do not remove!
PYXIS_ROOT_NAMESPACE = True

try: # check installation, don't remove
    import Pyxides
except:
    import traceback
    sys.stderr.write("Cannot find Pyxides\n")
    sys.stderr.write(traceback.format_exc())
    sys.exit(1)

from Pyxis.Commands import _info as info
from Pyxis.Commands import _verbose as verbose
from Pyxis.Commands import _verbose as error

from Pyxis.Internals import assign

# defer config until later
PYXIS_LOAD_CONFIG = False
# disable auto-import -- meant for interactive sessions only
PYXIS_AUTO_IMPORT_MODULES = False

def report_elapsed (ts0):
  ts0 = time.time() - ts0;
  h = ts0//3600;
  m = (ts0-h*3600)//60;
  s = ts0%60;     
  info("elapsed time is %dh%dm%.2fs"%(h,m,s));

if __name__ == "__main__":

  # setup some standard command-line option parsing
  #
  import optparse
  parser = optparse.OptionParser(usage="""%prog: [options] [receipes and config files] a=value command1 b=value c=value command2 ...""",
    description="Runs a sequence of reduction commands",add_help_option=True);
  parser.add_option("-v","--verbose",type="int",default=None,metavar="LEVEL",
                    help="verbosity level, default is 1. Equivalent to VERBOSE=LEVEL");
  parser.add_option("-q","--quiet",action="store_true",
                    help="quiet mode: non-critical internal messages are not sent to console "+
                    "when logging to file. Implies --verbose 0 unless explicitly set.");
  parser.add_option("-D","--doc",type="string",metavar="NAME",
                    help="prints documentation on function or module NAME, then exits");
  parser.add_option("-i","--interactive",action="store_true",
                    help="interactive mode: ignore assignments to LOG and send all output to console, disable screen sessions and pausing on exit.\n"
                    "Equivalent to LOG_DISABLE=True SPAWN_SCREEN=False PAUSE_ON_EXIT=False");
  parser.add_option("--log",type="string",metavar="FILENAME",
                    help="log output to file. Equivalent to LOG=FILENAME. Overrides any config file LOG settings.");
  parser.add_option("-l","--default-log",dest="default_log",action="store_true",
                    help="log output to default log file default.log. Overrides any config file LOG settings.");
  parser.add_option("-f","--flush-log",dest="flush_log",action="store_true",
                    help="flush old logfiles. Default is to append to existing logs.\n"
                    "Equivalent to LOG_FLUSH=True.");
  parser.add_option("--running-in-screen",action="store_true",
                    help=optparse.SUPPRESS_HELP);
  parser.add_option("-s","--scripts-dir",type="string",metavar="SCRIPTS_DIR",default='.',
                    help="Loads Pyxis recipes & configs from alternate directory, default is current directory.");
  parser.add_option("-A","--no-auto-load",action="store_true",
                    help="Disables automatic loading of recipes and configs from SCRIPTS_DIR.");
  parser.add_option("-P","--path",type="string",action="append",
                    help="Add PATH to PYTHONPATH when loading scripts.");
  parser.add_option("-j","--jobs",type="int",metavar="N",
                    help="run up to N jobs in parallel. Equivalent to JOBS=N.");
  parser.add_option("-p","--persist",action="store_true",
                    help="Persist with per-lists as far as possible on error. Equivalent to PERSIST=1.");
  parser.add_option("-o","--outdir",type="string",metavar="DIR",
                    help="redirect all outputs to subdirectory. Equivalent to OUTDIR=DIR. Overrides any config file OUTDIR settings.");
  parser.add_option("-w","--wrapup",type="string",metavar="COMMAND",action="append",
                    help="executes command on exit, regardless of success or failure. Can be specified multiple times for multiple commands. Equivalent to PYXIS_WRAPUP.append(\"command\").");
  parser.add_option("-x","--no-x11",action="store_true",
                    help="explicitly disable X11 connections. Equivalent to NO_X11=True.\n"
                    "HIGHLY RECOMMENDED for offline jobs, as some tools (e.g. matplolib) like\n"
                    "to open needless X11 connections, then crash if the X server becomes\n"
                    "unavailable. This is the default when running under a 'screen' session.");
  parser.add_option("-X","--x11",action="store_true",
                    help="do not disable X11 connections when running under a screen or tmux session.");
  parser.add_option("-S","--screen",action="store_true",
                    help="automatically spawn a screen session to run the commands. Equivalent to SPAWN_SCREEN=True in config.");
  parser.add_option("--no-screen",action="store_true",
                    help="enforces SPAWN_SCREEN=False, overriding any config settings.");
  parser.add_option("-T","--screen-title",type="string",metavar="TITLE",
                    help="title of screen session. Equivalent to SPAWN_SCREEN_TITLE=TITLE. Default is formed from current directory name.");                    
  parser.add_option("--detach",action="store_true",
                    help="detach screen session immediately. Equivalent to SPAWN_SCREEN_DETACH=True in config. Default is to stay attached to the session."); 
  parser.add_option("--no-detach",action="store_true",
                    help="enforces SPAWN_SCREEN_DETACH=False, overriding any config settings."); 
  parser.add_option("--no-screen-log",action="store_true",
                      help="disable logging of screen sessions (normally screen is run with -L to enable logging). Equivalent to SPAWN_SCREEN_LOG=False.");
  parser.add_option("--pause-on-exit",action="store_true",
                    help="pauses before exiting. Equivalent to PAUSE_ON_EXIT=True. Useful when automatically spawning a screen session.");
  parser.add_option("--no-pause-on-exit",action="store_true",
                    help="Enforces PAUSE_ON_EXIT=False, overriding any config settings.");
                    

  (options,args) = parser.parse_args();
  # set up variables from options before import
  if options.doc:
    options.quiet = True;
  LOG_DISABLE = options.interactive or options.doc;
  if options.outdir:
    OUTDIR = options.outdir;
  else:
    OUTDIR = False
  if not options.doc and not options.interactive:
    if options.log:
      LOG = options.log;
#    Pyxis.assign("LOG",options.log);
    elif options.default_log:
      LOG = os.path.join(globals().get("OUTDIR","."),"default.log");
  if options.screen_title:
    SPAWN_SCREEN_TITLE = options.screen_title;

  if options.path:
    sys.path += options.path;
  
  in_screen = os.environ.get("STY") or os.environ.get("TMUX");

  NO_X11 = not options.x11 and ( options.no_x11 or in_screen );
  if NO_X11:
    os.environ.pop('DISPLAY',None);
  JOBS = options.jobs or 1;
  QUIET = options.quiet; 
  PERSIST = options.persist;
  SPAWN_SCREEN = options.screen;
  SPAWN_SCREEN_LOG = not options.no_screen_log;
  SPAWN_SCREEN_DETACH = options.detach;
  PAUSE_ON_EXIT = options.pause_on_exit;
  PYXIS_WRAPUP = options.wrapup or []
  PYXIS_INTERACTIVE = options.interactive
  
  if options.verbose is None:
    if options.quiet:
      VERBOSE = -1;
  else:
    VERBOSE = options.verbose;
    
  # protect these variables down below from being reassigned in configs, if set from command line
  presets = set([varname for varname in 
                 ('OUTDIR','LOG','SPAWN_SCREEN_TITLE')
    if varname in globals() ])

  import Pyxis
  import Pyxis.Internals
  import Pyxis.Commands

  # sort remaining arguments into recipes, configs, commands and MSs
  mslist = []
  commands = []
  recipes = []
  
  for arg in args:
    if Pyxis.Internals._re_assign.match(arg):
      commands.append(arg);
    elif re.match(".*\\.(MS|ms)/?$",arg):
      if arg.endswith("/"):
        arg = arg[:-1];
#      assign("MS",arg);
      mslist.append(arg);
    elif re.match(".*\\.(py|conf)$",arg):
      recipes.append(arg);
    else:
      commands.append(arg);
      
  if options.interactive:
    verbose(-99,"running with --interactive: assignments to LOG will be disabled and all output will go to the console");
  if in_screen and not options.no_x11:
    verbose(-99,"screen/tmux session detected: auto-disabling X11 connections. Run with the -X option to reenable.")

  LOG_HEADER = cmdline = time.strftime("## %c:\n")+" ".join(sys.argv);  
  LOG_FLUSH = options.flush_log;

  # protect preset variables while config is being loaded
  __pyxis_protected_variables = set(presets);

  # init configuration. This preloads pyxis-* fro SCRIPTS_DIR, and any files specified on the command line
  PYXIS_LOAD_CONFIG = True
  # Allow loading of recipes from different directories
  if options.no_auto_load:
    Pyxis.Internals.initconf(files=recipes,directory=None);
  else:
    scripts_dirs = options.scripts_dir or ['.']
    for scripts_dir in scripts_dirs:
       Pyxis.Internals.initconf(files=recipes,directory=scripts_dir);

  # unprotect variables
  __pyxis_protected_variables -= presets;

  # print docs and exit, if -d option
  if options.doc:
    Pyxis.Internals.print_doc(options.doc);
    sys.exit(0);
  
  # re-apply command-line options that take priority over config default
  if options.persist:
    PERSIST = True;
  if options.screen:
    SPAWN_SCREEN = True
  if options.no_screen:
    SPAWN_SCREEN = False
  if options.interactive:
    SPAWN_SCREEN and verbose(-99,"--interactive specified, ignoring SPAWN_SCREEN setting.")
    PAUSE_ON_EXIT and verbose(-99,"--interactive specified, ignoring PAUSE_ON_EXIT setting.")
    SPAWN_SCREEN = PAUSE_ON_EXIT = False;
  if options.no_screen_log:
    SPAWN_SCREEN_LOG = False;
  if options.pause_on_exit:
    PAUSE_ON_EXIT = True;
  if options.no_pause_on_exit:
    PAUSE_ON_EXIT = False;
  if options.detach:
    SPAWN_SCREEN_DETACH = True
  if options.no_detach:
    SPAWN_SCREEN_DETACH = False
  if options.verbose is not None:
    VERBOSE = options.verbose;
  elif options.quiet:
    VERBOSE = -1;

  # make output directory
  if OUTDIR:
    makedir(OUTDIR,no_interpolate=True);
  if not options.running_in_screen:
    pyxislog = (OUTDIR or '.')+"/pyxis.log"
    try:
      open(pyxislog,'a').write(cmdline+"\n");
    except:
      verbose(-99,"error writing to $pyxislog, perhaps no write permissions? Proceeding anyway.")
    
  # respawn in screen, if asked to, and not already running under screen
  if commands and SPAWN_SCREEN and not options.running_in_screen:
      import distutils.spawn
      if isinstance(SPAWN_SCREEN,str):
        if os.path.isfile(screenpath) and os.access(screenpath,os.X_OK):
          screenpath = SPAWN_SCREEN;
          verbose(-99,"using explicitily set $SPAWN_SCREEN to run a screen session.");
        else:
          screenpath = None;
          verbose(-99,"SPAWN_SCREEN=$SPAWN_SCREEN does not refer to a valid executable, running without screen.");
      else:
        screenpath = distutils.spawn.find_executable("screen")
        if not screenpath:
          verbose(-99,"screen binary not found in system path. Install screen, or set SPAWN_SCREEN to an explicit path.");
      if screenpath:
        cwd = os.getcwd();
        homedir = os.path.expanduser("~");
        if cwd.startswith(homedir):
          cwd = cwd[len(homedir):];
        title = globals().get("SPAWN_SCREEN_TITLE","pyxis"+cwd.replace("/","-"));
        args = [ "screen","-m","-S",title ];
        verbose(-99,"launching %sscreen session: $title"%("detached " if SPAWN_SCREEN_DETACH else""));
        if SPAWN_SCREEN_DETACH:
          verbose(-99,"use screen -r $title to attach to the session");
          args.append("-d");
        if SPAWN_SCREEN_LOG:
          args.append("-L");
        args += [ "bash", "-ci" ] 
        pyxis_args = sys.argv 
        pyxis_args.append("--running-in-screen");
        if PAUSE_ON_EXIT and "--pause-on-exit" not in pyxis_args:
          pyxis_args.append("--pause-on-exit");
        # now turn the pyxis args list into a something that bash -c will process
        # quote the single quotes in arguments
        pyxis_args = [ re.sub(r'(["\'$` ])',r'\\\1',x) for x in pyxis_args ]
        args.append(' '.join(pyxis_args)) 
        os.execv(screenpath,args);

  Pyxis.Internals.saveconf();
 
  # MS list from command line overrides defaults
  if mslist:
    info("setting MS list to",*mslist);
    globals().pop("MS_List_Template",None);
    assign('MS_List',mslist);
    if len(mslist) == 1:
      info("setting MS=%s"%mslist[0]);
      assign('MS',mslist[0]);
    
  ts0 = time.time();
  
  retcode = 0;
  # run commands or print documentation
  if not commands:
    info("no commands given");
  else:
    try:
      Pyxis.Internals.run(*commands);
      retcode = 0;
    except Exception:
      if sys.stdout is not sys.__stdout__:
        traceback.print_exc();
        error("Exception raised, aborting");
        Pyxis.Internals.flush_log();
      sys.stdout = sys.__stdout__;
      sys.stderr = sys.__stderr__;
      traceback.print_exc();
      error("Exception raised, aborting");
      retcode = 1;
    except SystemExit as exc:
      retcode = exc.code;
    except KeyboardInterrupt:
      if Pyxis.Commands._subprocess_id is None:
        error("aborted with Ctrl+C");
      retcode = 130;
 
  wrapfail = None;
  # check that we're not in a subprocess -- exit quietly if so, else go wrap things up
  if Pyxis.Commands._subprocess_id is None:
    # execute wrapup commands
    if PYXIS_WRAPUP:
      info("executing wrapup commands")
      if type(PYXIS_WRAPUP) is str:
        PYXIS_WRAPUP = [ PYXIS_WRAPUP ]
      if not isinstance(PYXIS_WRAPUP,(list,tuple)):
        wrapfail = "PYXIS_WRAPUP must be set to a list or tuple of commands, or a single command"
      else:
        try:
          Pyxis.Internals.run(*PYXIS_WRAPUP);
        except Exception:
          if sys.stdout is not sys.__stdout__:
            traceback.print_exc();
            error("Exception raised during wrapup command");
            Pyxis.Internals.flush_log();
          sys.stdout = sys.__stdout__;
          sys.stderr = sys.__stderr__;
          traceback.print_exc();
          error("Exception raised during wrapup command");
          wrapfail = "failed with an exception";
        except SystemExit as exc:
          wrapfail = "exited with code %d"%exc.code;
        except KeyboardInterrupt:
          if Pyxis.Commands._subprocess_id is None:
            error("Wrapup command aborted with Ctrl+C");
          wrapfail = "been interrupted with Ctrl+C";
    
    # print status
    if not retcode:
      info("all commands have executed successfully");
      if wrapfail:
        info("  (though wrapup commands have $wrapfail)");
    else:
      error("exiting with error code %d, check the logs for errors"%retcode);
      if wrapfail:
        info("  (and wrapup commands have $wrapfail");

    # close log
    logobj,logfile = Pyxis.Internals.get_logfile();
    if logobj:
      Pyxis.Internals.set_logfile(None,quiet=True);
      info("last logfile was %s"%logfile);
    
    report_elapsed(ts0);
  
    # pause on exit?
    if PAUSE_ON_EXIT:
      verbose(-99,"--pause-on-exit or PAUSE_ON_EXIT set. Press ENTER to exit.")
      sys.stdin.readline();

  sys.exit(retcode);
      
      
