#!/usr/bin/env python

#   Licensed to the Apache Software Foundation (ASF) under one
#   or more contributor license agreements.  See the NOTICE file
#   distributed with this work for additional information
#   regarding copyright ownership.  The ASF licenses this file
#   to you under the Apache License, Version 2.0 (the
#   "License"); you may not use this file except in compliance
#   with the License.  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing,
#   software distributed under the License is distributed on an
#   #  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#   KIND, either express or implied.  See the License for the
#   specific language governing permissions and limitations
#   under the License.

#
# To use this to execute your Eclipse workspace, install as a symbolic
# link in your JRE/bin directory, with any name other than 'viaducc',
# for instance, java_viaducc,, linking back to the base version of
# viaducc in your ducc_runtime.
#
# Then configre your Eclipse's launch JRE to use java_viaducc in place
# of 'java'

import sys
import os
import re

global ducc_mem_size
global ducc_mem_size_set
global ducc_desc_set
global default_jvm
global ducc_home

ducc_class = 'fixed'
java_cmd   = None
ducc_home  = None       # tbd in a minute
ducc_mem_size = None
ducc_mem_size_set = False # whether the user passed a flag to manually set DUCC_MEMORY_SIZE
ducc_desc_set = False # whether the user passed a flag to manually set DUCC_DESCRIPTION
default_jvm = None

# get the default DUCC_HOME right away for message
realpath = os.path.realpath(sys.argv[0])
ndx = realpath.rindex('/')
ndx = realpath.rindex('/', 0, ndx)
ducc_home = realpath[:ndx]

def error(message):
    print "Error:", message
    sys.exit(1)

def usage():

    print "Usage: viaducc [defines] [command and parameters]"
    print "  -or-"
    print "       java_viaducc [defines] [java-class and parameters]"
    print ""
    print "Where defines include:"
    print "   -DDUCC_MEMORY_SIZE=size-in-gb"
    print "       The default is -DDUCC_MEMORY_SIZE="+str(ducc_mem_size)
    print""
    print "   -DDUCC_HOME=alternative-DUCC-runtime"
    print "       The default is -DDUCC_HOME=" + ducc_home
    print ""
    print "   -DDUCC_CLASS=ducc-scheduling-class"
    print "       The default is -DDUCC_CLASS=" + ducc_class
    print ""
    print "   -DDUCC_ENVIRONMENT=environment-settings"
    print "       The default is no additional environment.  The environment string should be"
    print '       blank-delimeted and quoted, for example: -DDUCC_ENVIRONMENT="A=B C=D"'
    if (default_jvm != None ):
        print ""
        print "   -DJAVA_BIN=specific-java-bin-directory"
        print "       For use with java_viaducc.  Details follow."
        print "       The default is -DJAVA_BIN=" + default_jvm
    print ""
    print "   -DDUCC_DESRIPTION=user-description-string"
    print "       The default description is the program name (viaducc or java_viaducc) with arguments"
    print ""
    print "   -DDUCC_NO_CANCEL_ON_INTERRUPT"
    print "       To disable process cancellation when the submitting process terminates."
    print ""
    print "   When invoked as 'viaducc' the command is executed in a DUCC-managed resource"
    print "with the console (bi-directionally) redirected to the submitter's console."
    print ""
    print "   When invokded as 'java_viaducc' an appropriate JRE is found and the class"
    print "is executed as a java class in a DUCC-managed resource, with the console"
    print "bi-directionally redirected to the submitter's console."
    print ""
    print "Notes for java_viaducc:"
    print ""
    print "   If java_viaducc is installed in a JRE/bin directory, it may be invoked as an"
    print "alternative to java from Eclipse, allowing Eclipse users to execute their workspaces"
    print "in a DUCC-managed resource.  JAVA_BIN should NOT be used in this case.  java_viaducc"
    print "will use the JRE it is installed in."
    print ""
    print "   If java_viaducc is used as a 'normal' command, the JRE is searched for in this order:"
    print "      1. Use the java specified by -DJAVA_BIN="
    print "      2. Use the java configured for DUCC"
    print ""
    print "   If -jar is used it must be the last JVM arg before any command line args"
    
    print
    sys.exit(0)

# where should I get java from?
def read_properties():
    ducc_properties = ducc_home + '/resources/ducc.properties'
    if ( not os.path.exists(ducc_properties) ):
        error("Cannot access DUCC_HOME at " + ducc_home)

    ducc_jvm = None
    # first see if i'm executed out of some JAVA_HOME somewhere
    jpath = os.path.abspath(sys.argv[0])    
    ndx = jpath.rindex('/')
    jdir = jpath[:ndx]
    java = jdir + '/java'
    if (os.path.exists(java)):
        ducc_jvm = java

    # nope, we'll use the default ducc java
    props = open(ducc_properties)
    try:
        for line in props:
            line = line.strip()
            mobj = re.search('[ =:]+', line)    # java props allows apace, =, or : as delimeters
            if ( mobj ):
                key = line[:mobj.start()].strip()
                val = line[mobj.end():].strip()
                if ( (ducc_jvm is None) and (key == 'ducc.jvm') ):       # yes!
                    ducc_jvm = val
                    continue
                if ( key == 'ducc.rm.share.quantum' ):       # yes!
                    ducc_mem = 1   # default quantum may not be the same for all classes
                    continue       # and the actual allocation will be rounded up to quantum
    finally:
        props.close();

    return (ducc_jvm, ducc_mem)

def parse_memory_string(text):
    """Given a string (e.g., "5G", "200m"), Returns the memory requested
    in bytes. For example:

        parse_memory_string('500m') should return
            500 * 1024 * 1024
        parse_memory_string('15G') should return
            15 * 1024 * 1024 * 1024
    """

    # this list should cover us for a while
    suffixes = {
        'k': 1024,
        'm': 1024 * 1024,
        'g': 1024 * 1024 * 1024,
        't': 1024 * 1024 * 1024 * 1024,
    }

    text = text.lower()
    if text[-1] in suffixes:
        multiplier = suffixes[text[-1]]
        text = text[:-1] # chop off suffix
    else:
        multiplier = 1

    return multiplier * int(text)

def parse_java_command_line():
    """Parse the command line and returns a dictionary including
    information on the maximum amount of memory the job might need,
    the Java class name, and its arguments."""
    maximum_memory_required = 1024.0 ** 3  # our default if -Xmx not specified
    args = iter(sys.argv[1:])
    skip_next_arg = False

    # scan args until we find the Java class
    while 1:
        arg = next(args).lower()
        if skip_next_arg:
            skip_next_arg = False
            continue

        if arg.startswith('-'):
            # these are the only options that take an argument and require
            # a space between the flag and the argument
            if arg in ('-cp', '-classpath'):
                skip_next_arg = True
            elif arg.startswith('-xmx'): # maximum Java heap size
                maximum_memory_required = parse_memory_string(arg[4:])
            continue

        break

    return dict(maximum_memory_required=maximum_memory_required,
                java_class_name=arg, java_class_args=list(args))

def main():

    global ducc_mem_size
    global ducc_mem_size_set
    global ducc_desc_set
    global default_jvm
    global ducc_home

    if ( len(sys.argv) == 1 ):
        usage()

    ducc_class = 'fixed'
    java_cmd   = None
    ducc_env   = ''
    enable_cancel = True
    ducc_desc  = os.path.basename(sys.argv[0])

    # remember to add the '=' at the end if following value
    p_mem_size  = '-DDUCC_MEMORY_SIZE='
    p_class     = '-DDUCC_CLASS='
    p_jvm_dir   = '-DJAVA_BIN='
    p_ducc_home = '-DDUCC_HOME='
    p_env       = '-DDUCC_ENVIRONMENT='
    p_no_int    = '-DDUCC_NO_CANCEL_ON_INTERRUPT'
    p_desc      = '-DDUCC_DESCRIPTION='

    need_java = False
    if ( os.path.basename(sys.argv[0]) != 'viaducc' ):
        # if invokded as 'java_viaducc we must inject some kind of jvm as the executable
        # this is more liberal: any link other than 'viaducc' caused injection of the
        # jvm, to allow multiple, different symlinks (in case there are multiple,
        # different versions of ducc, not really reccomended, but supported)
        need_java = True

    ducc_args = []
    for arg in sys.argv[1:]:
 
        if (arg in ('-h', '-?', '-help', '--help') ):
            # have to wait for parsing the args to look up DUCC_HOME so we defer emitting the message
            usage()
       
        elif (arg.startswith(p_mem_size) ):
            ducc_mem_size = arg[len(p_mem_size):]
            ducc_mem_size_set = True

        elif (arg.startswith(p_class) ):
            ducc_class = arg[len(p_class):]

        elif (arg.startswith(p_jvm_dir) ):
            java_cmd = arg[len(p_jvm_dir):] + '/java'

        elif (arg.startswith(p_ducc_home) ):
            ducc_home = arg[len(p_ducc_home):]

        elif (arg.startswith(p_env) ):
            ducc_env = ' --environment "' + arg[len(p_env):] + '"'

        elif (arg.startswith(p_desc) ):
            ducc_desc = arg[len(p_desc):]
            ducc_desc_set = True

        elif (arg.startswith(p_no_int) ):
            enable_cancel = False

        else:
            ducc_args.append("'" + arg + "'")

    p = read_properties()
    default_jvm = p[0]
    if (ducc_mem_size is None):
        ducc_mem_size = p[1]

    description = os.path.basename(sys.argv[0])
    if ( need_java and (java_cmd is None) ):
        java_cmd = default_jvm
        if ( (java_cmd is None) or (java_cmd == '') ):
            error("Cannot figure out where java is")

    if need_java:
        java_command_line_info = parse_java_command_line()
        description += ' %s %s' % (java_command_line_info['java_class_name'],
                                    ' '.join(java_command_line_info['java_class_args']))

        import math
        # convert to gigabytes, round up
        maximum_memory_required = java_command_line_info['maximum_memory_required'] / (1024.0 ** 3)
        maximum_memory_required = int(math.ceil(maximum_memory_required))
        if maximum_memory_required > float(ducc_mem_size):
            if ducc_mem_size_set:
                print ("Warning: DUCC memory size specified as %sg but max heap set to %sg.\n"
                       "         Consider increasing -DDUCC_MEMORY_SIZE=<size>\n") % \
                      (ducc_mem_size, maximum_memory_required)
            else:
                print ("Info: DUCC memory size is not specified but asking JVM to set max heap to %sg.\n"
                       "      Setting DUCC memory size to %sg. Use -DDUCC_MEMORY_SIZE=<size> to specify\n") % \
                      (maximum_memory_required, maximum_memory_required)
                ducc_mem_size = maximum_memory_required

    # DUCC_HOME isn't ususally needed but viaducc is slithery and may end up tryng to
    # execute from somebody else's ducc so let's be sure we point at the right one
    os.environ['DUCC_HOME'] = ducc_home
    CMD = ducc_home + '/bin/ducc_process_submit'
    CMD = CMD +       ' --attach_console'
    CMD = CMD +       ' --process_memory_size ' + str(ducc_mem_size)
    CMD = CMD +       ' --scheduling_class ' + ducc_class
    if ducc_desc_set:
        CMD = CMD +       ' --description %r' % ducc_desc
    else:
        if need_java:
            CMD = CMD +       ' --description %r' % description # %r so it will be quoted
        else:
            CMD = CMD +        ' --description viaducc'

    CMD = CMD +       ' --wait_for_completion'
    if (enable_cancel):
        CMD = CMD +       ' --cancel_on_interrupt'
    CMD = CMD +       ' --working_directory ' + os.getcwd()
    CMD = CMD +       ducc_env
 
    if ( need_java ):
        CMD = CMD +   ' --process_executable ' + java_cmd 
        if ( len(ducc_args) > 0):
            CMD = CMD +   ' --process_executable_args "' + ' '.join(ducc_args) + '"'
    else:
        if ( len(ducc_args) == 0 ):
            error("No command specified.")

        CMD = CMD +   ' --process_executable ' + ducc_args[0]
        if ( len(ducc_args[1:]) > 0):
            CMD = CMD +   ' --process_executable_args "' + ' '.join(ducc_args[1:]) + '"'

    print CMD
    sys.stdout.flush()         # UIMA-4168

    rc = os.system(CMD)
    sys.exit(rc);

main()
