#!/usr/bin/env python
#
# Copyright (C) 2013-2016 DNAnexus, Inc.
#
# This file is part of dx-toolkit (DNAnexus platform client libraries).
#
#   Licensed 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.

from __future__ import (print_function, unicode_literals)

import sys, collections
import os, shutil, subprocess, tempfile
import json
import argparse
import datetime
import platform
from dxpy.utils.printing import *
from dxpy.utils.describe import job_output_to_str, io_val_to_str
from dxpy.cli import prompt_for_yn, INTERACTIVE_CLI
from dxpy.cli.exec_io import *
from dxpy.exceptions import err_exit
from dxpy.scripts import dx_build_app
import dxpy.utils.local_exec_utils as local_exec_utils
from dxpy.compat import open, wrap_env_var_handlers

wrap_env_var_handlers()

parser = argparse.ArgumentParser(description='Takes a local app directory and runs it locally for testing purposes.  This directory should have a valid dxapp.json file and can be built using dx build.')
parser.add_argument('path', help='Local path to an app directory')
parser.add_argument('--function', help='The name of an entry point to run', default='main')
parser.add_argument('--workspace-path', help="Local directory in which jobs will have their home directories (default is a directory called 'test-job-workspaces' within the app directory)")
parser.add_argument('-i', '--input', help=fill('An input to be used in calling the app "<input name>[:<class>]=<input value>", where class can be any job IO class, e.g. "string", "array:string", or "array".  If class is "array" or not specified, the value will be attempted to be parsed as JSON and is otherwise treated as a string', width_adjustment=-24), action='append')
parser.add_argument('-j', '--input-json', help=fill('The full input JSON (keys=input field names, values=input field values)', width_adjustment=-24))
parser.add_argument('-f', '--input-json-file', dest='filename', help=fill('Load input JSON from FILENAME ("-" to use stdin)'))
parser.add_argument('-x', help='Adds the -x flag for bash apps so that each command is echoed to stderr', action='store_true')
parser.add_argument('-y', '--yes', dest='confirm', help='Do not ask for confirmation', action='store_false')
args = parser.parse_args()

args.path = os.path.abspath(args.path)

if dxpy.AUTH_HELPER is None:
    err_exit("You must be logged in to the DNAnexus Platform to run this command; e.g. please run 'dx login'", code=3)

# Do the equivalent of "dx build --dry-run" to do some basic checking
with tempfile.TemporaryFile() as fd:
    print("About to run 'dx build --dry-run " + args.path + "'")
    import logging
    logger = logging.getLogger('dxpy')
    logger.propagate = False
    sys.stdout = fd
    sys.stderr = fd
    build_error = None
    try:
        dx_build_app.build_and_upload_locally(args.path, 'app', dry_run=True)
    except BaseException as e:
        build_error = e
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__
    logger.propagate = True
    if build_error:
        fd.seek(0)
        print(fd.read())
        sys.exit(build_error)
    else:
        print('dx build dry run successful')

path_to_dxapp_json = os.path.join(args.path, 'dxapp.json')

with open(path_to_dxapp_json, 'r') as fd:
    dxapp_json = json.load(fd)

interpreter = dxapp_json['runSpec']['interpreter']

if interpreter not in ('python2.7', 'bash'):
    err_exit('Unknown interpreter {} in {}'.format(interpreter, path_to_dxapp_json), code=3)

running_main = args.function == 'main'

job_inputs = ExecutableInputs(input_spec=(dxapp_json.get('inputSpec') if running_main else None))
job_inputs.update_from_args(args)

# Extend job_inputs.inputs using "default" values in inputSpec
if 'inputSpec' in dxapp_json:
    for param in dxapp_json['inputSpec']:
        if param['name'] not in job_inputs.inputs and 'default' in param:
            job_inputs.inputs[param['name']] = param['default']

if args.workspace_path is None:
    args.workspace_path = os.path.join(args.path, 'test-job-workspaces')

if not os.path.exists(args.workspace_path):
    os.mkdir(args.workspace_path)
if not os.path.isdir(args.workspace_path):
    parser.exit(1,
                fill('Error: The path to be used for job home directories is not a directory') + '\n')

job_homedirs = os.path.join(args.workspace_path, datetime.datetime.now().strftime('%Y-%m-%d-%H%M%S-%f'))
os.mkdir(job_homedirs)
all_job_outputs_path = os.path.join(job_homedirs, 'job_outputs.json')
with open(all_job_outputs_path, 'w') as fd:
    fd.write('{}\n')

job_queue_path = os.path.join(job_homedirs, 'job_queue.json')
with open(job_queue_path, 'w') as fd:
    fd.write('[]\n')

# Snapshot the script used in a file in job_homedirs
# For Python: also make sure it's in dxapp_json['runSpec'] to cache it for later
code_path = os.path.join(job_homedirs, 'code.' + ('sh' if interpreter == 'bash' else 'py'))
if 'code' in dxapp_json['runSpec']:
    with open(code_path, 'w') as fd:
        fd.write(dxapp_json['runSpec']['code'])
else:
    shutil.copy(os.path.join(args.path, dxapp_json['runSpec']['file']), code_path)
    if interpreter == 'python2.7':
        with open(code_path, 'r') as fd:
            dxapp_json['runSpec']['code'] = fd.read()

resources_path = os.path.join(args.path, 'resources')
if os.path.isdir(resources_path):
    os.environ['PATH'] = os.path.join(resources_path, 'bin') + os.pathsep + \
        os.path.join(resources_path, 'usr', 'local', 'bin') + os.pathsep + \
        os.path.join(resources_path, 'usr', 'bin') + os.pathsep + os.environ['PATH']
    if platform.system() == 'Linux':
        old_ld_library_path = (os.pathsep + os.environ['LD_LIBRARY_PATH']) if 'LD_LIBRARY_PATH' in os.environ else ''
        os.environ['LD_LIBRARY_PATH'] = os.path.join(resources_path, 'lib') + os.pathsep + \
            os.path.join(resources_path, 'usr', 'lib') + old_ld_library_path

if platform.system() == 'Darwin':
    mac_resources_path = os.path.join(args.path, 'mac_resources')
    if os.path.isdir(mac_resources_path):
        os.environ['PATH'] = os.path.join(mac_resources_path, 'usr', 'bin') + os.pathsep + \
            os.environ['PATH']
        old_dyld_library_path = (os.pathsep + os.environ['DYLD_LIBRARY_PATH']) if 'DYLD_LIBRARY_PATH' in os.environ else ''
        os.environ['DYLD_LIBRARY_PATH'] = os.path.join(mac_resources_path, 'usr', 'lib') + old_dyld_library_path

os.environ['DX_FS_ROOT'] = resources_path
os.environ['DX_TEST_CODE_PATH'] = code_path
os.environ['DX_TEST_JOB_HOMEDIRS'] = job_homedirs
os.environ['DX_TEST_DXAPP_JSON'] = path_to_dxapp_json

if args.x:
    os.environ['DX_TEST_X_FLAG'] = '1'

print()
print('Using input JSON:')
print(json.dumps(job_inputs.inputs, indent=4))
print()

# Ask for confirmation if a tty and if input was not given as a
# single JSON.
if args.confirm and INTERACTIVE_CLI:
    if not prompt_for_yn('Confirm running the executable with this input', default=True):
        parser.exit(0)

main_job_id = local_exec_utils.queue_entry_point(function=args.function,
                                                 input_hash=job_inputs.inputs)
local_exec_utils.run_entry_points(run_spec=dxapp_json['runSpec'])

print('App finished successfully')
with open(all_job_outputs_path, 'r') as fd:
    job_outputs = json.load(fd)
    if main_job_id in job_outputs:
        print(job_output_to_str(job_outputs[main_job_id], title="Final output: ").lstrip())

print('Local job workspaces can be found in: ' + job_homedirs)
