#!/usr/bin/env python
#
# Copyright (C) 2013 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.

import sys, os
import json
import argparse
import re
from dxpy.templating.utils import (print_intro, get_name, get_version, get_metadata, Completer, get_ordinal_str,
                                   prompt_for_var, prompt_for_yn, use_completer, get_language, language_options,
                                   get_pattern, get_parallelized_io, fill_in_name_and_ver, clean,
                                   create_files_from_templates)
from dxpy.utils.printing import *
from collections import OrderedDict

IO_NAME_PATTERN = re.compile('^[a-zA-Z_][0-9a-zA-Z_]*$')

API_VERSION = '1.0.0'

manifest = []

parser = argparse.ArgumentParser(description="Create a source code directory for a DNAnexus app.  You will be prompted for various metadata for the app as well as for its input and output specifications.")
parser.add_argument('--json-file', help='Use the metadata and IO spec found in the given file')
parser.add_argument('--language', help='Programming language of your app')
args = parser.parse_args()

dx_home_dir = os.environ.get('DNANEXUS_HOME', None)
if not isinstance(dx_home_dir, basestring):
    sys.stderr.write(fill('''Error: Environment variable DNANEXUS_HOME is not set; cannot find code templates''') + '\n')
    exit(1)

if args.json_file is not None and not os.path.exists(args.json_file):
    parser.error('File not found: ' + args.json_file)

print_intro(API_VERSION)

if args.json_file is not None:
    with open(args.json_file, 'r') as json_file:
        app_json = json.loads(json_file.read())
        # Re-confirm the name
        name = get_name(default=app_json.get('name'))
        app_json['name'] = name
        version = get_version(default=app_json.get('version'))
        app_json['version'] = version
    description = '<!-- Insert a description of your app here -->'
    try:
        os.mkdir(app_json['name'])
    except:
        sys.stderr.write(fill('''Unable to create a directory for %s, please check that it is a valid app name and the working directory exists and is writable.''' % app_json['name']) + '\n')
        sys.exit(1)
else:
    ##################
    # BASIC METADATA #
    ##################

    name = get_name()

    try:
        os.mkdir(name)
    except:
        sys.stderr.write(fill('''Unable to create a directory for %s, please check that it is a valid app name and the working directory exists and is writable.''' % name) + '\n')
        sys.exit(1)

    title, summary, description = get_metadata(API_VERSION)

    version = get_version()

    app_json = OrderedDict()
    app_json["name"] = name

    if title != '':
        app_json["title"] = title
    if summary != '':
        app_json['summary'] = summary

    app_json["dxapi"] = API_VERSION
    app_json["version"] = version

    ############
    # IO SPECS #
    ############

    class_completer = Completer(['int', 'float', 'string', 'boolean', 'hash',
                                 'array:int', 'array:float', 'array:string', 'array:boolean',
                                 'record', 'file', 'gtable', 'applet',
                                 'array:record', 'array:file', 'array:gtable', 'array:applet'])
    bool_completer = Completer(['true', 'false'])

    print ''
    print BOLD() + 'Input Specification' + ENDC()
    print ''

    input_spec = True
    input_names = []
    printed_classes = False

    if input_spec:
        app_json['inputSpec'] = []
        print fill('You will now be prompted for each input parameter to your app.  Each parameter should have a unique name that uses only the underscore "_" and alphanumeric characters, and does not start with a number.')

        while True:
            print ''
            ordinal = get_ordinal_str(len(app_json['inputSpec']) + 1)
            input_name = prompt_for_var(ordinal + ' input name (<ENTER> to finish)', allow_empty=True)
            if input_name == '':
                break
            if input_name in input_names:
                print fill('Error: Cannot use the same input parameter name twice.  Please choose again.')
                continue
            if not IO_NAME_PATTERN.match(input_name):
                print fill('Error: Parameter names may use only underscore "_", ASCII letters, and digits; and may not start with a digit.  Please choose again.')
                continue
            input_names.append(input_name)

            input_label = prompt_for_var('Label (optional human-readable name)', '')

            use_completer(class_completer)
            if not printed_classes:
                print 'Your input parameter must be of one of the following classes:'
                print '''applet         array:float    array:string   gtable         string
array:applet   array:gtable   boolean        hash           
array:boolean  array:int      file           int            
array:file     array:record   float          record
'''
                printed_classes = True

            while True:
                input_class = prompt_for_var('Choose a class (<TAB> twice for choices)')
                if input_class in class_completer.choices:
                    break
                else:
                    print fill('Not a recognized class; please choose again.')

            use_completer()

            optional = prompt_for_yn('This is an optional parameter')

            default_val = None
            if optional and input_class in ['int', 'float', 'string', 'boolean']:
                default_val = prompt_for_yn('A default value should be provided')
                if default_val:
                    while True:
                        if input_class == 'boolean':
                            use_completer(bool_completer)
                            default_val = prompt_for_var('  Default value', choices=['true', 'false'])
                            use_completer()
                        elif input_class == 'string':
                            default_val = prompt_for_var('  Default value', allow_empty=True)
                        else:
                            default_val = prompt_for_var('  Default value')

                        try:
                            if input_class == 'boolean':
                                default_val = (default_val == 'true')
                            elif input_class == 'int':
                                default_val = int(default_val)
                            elif input_class == 'float':
                                default_val = float(default_val)
                            break
                        except:
                            print 'Not a valid default value for the given class ' + input_class
                else:
                    default_val = None

            # Fill in the input parameter's JSON
            parameter_json = OrderedDict()

            parameter_json["name"] = input_name
            if input_label != '':
                parameter_json['label'] = input_label
            parameter_json["class"] = input_class
            parameter_json["optional"] = optional
            if default_val is not None:
                parameter_json['default'] = default_val

            app_json['inputSpec'].append(parameter_json)

    print ''
    print BOLD() + 'Output Specification' + ENDC()
    print ''

    output_spec = True
    output_names = []
    if output_spec:
        app_json['outputSpec'] = []
        print fill('You will now be prompted for each output parameter of your app.  Each parameter should have a unique name that uses only the underscore "_" and alphanumeric characters, and does not start with a number.')

        while True:
            print ''
            ordinal = get_ordinal_str(len(app_json['outputSpec']) + 1)
            output_name = prompt_for_var(ordinal + ' output name (<ENTER> to finish)', allow_empty=True)
            if output_name == '':
                break
            if output_name in output_names:
                print fill('Error: Cannot use the same output parameter name twice.  Please choose again.')
                continue
            if not IO_NAME_PATTERN.match(output_name):
                print fill('Error: Parameter names may use only underscore "_", ASCII letters, and digits; and may not start with a digit.  Please choose again.')
                continue
            output_names.append(output_name)

            output_label = prompt_for_var('Label (optional human-readable name)', '')

            use_completer(class_completer)
            if not printed_classes:
                print 'Your output parameter must be of one of the following classes:'
                print '''applet         array:float    array:string   gtable         string
array:applet   array:gtable   boolean        hash           
array:boolean  array:int      file           int            
array:file     array:record   float          record
'''
                printed_classes = True
            while True:
                output_class = prompt_for_var('Choose a class (<TAB> twice for choices)')
                if output_class in class_completer.choices:
                    break
                else:
                    print fill('Not a recognized class; please choose again.')

            use_completer()

            # Fill in the output parameter's JSON
            parameter_json = OrderedDict()
            parameter_json["name"] = output_name
            if output_label != '':
                parameter_json['label'] = output_label
            parameter_json["class"] = output_class

            app_json['outputSpec'].append(parameter_json)

file_input_names = []
file_array_input_names = []
file_output_names = []
gtable_input_names = []
gtable_output_names = []
if 'inputSpec' in app_json:
    file_input_names = [param['name'] for param in app_json['inputSpec'] if param['class'] == 'file']
    file_array_input_names = [param['name'] for param in app_json['inputSpec'] if param['class'] == 'array:file']
    gtable_input_names = [param['name'] for param in app_json['inputSpec'] if param['class'] == 'gtable']
if 'outputSpec' in app_json:
    file_output_names = [param['name'] for param in app_json['outputSpec'] if param['class'] == 'file']
    gtable_output_names = [param['name'] for param in app_json['outputSpec'] if param['class'] == 'gtable']

########################
# LANGUAGE AND PATTERN #
########################

print ''
print BOLD() + 'Template Options' + ENDC()

# Prompt for programming language if not specified

language = args.language if args.language is not None else get_language()

interpreter = language_options[language].get_interpreter()
app_json["runSpec"] = OrderedDict({"interpreter": interpreter})

# Prompt for execution pattern

template_dir = os.path.join(dx_home_dir, 'doc', 'example-templates', language_options[language].get_path())
pattern = get_pattern(template_dir)
template_dir = os.path.join(template_dir, pattern)

pattern_suffix = ''
parallelized_input_field = ''
parallelized_output_field = ''
if pattern == 'parallelized' and len([filename for filename in os.listdir(os.path.join(template_dir, 'src')) if filename.startswith('code-')]) > 0:
    parallelized_input_field, parallelized_output_field = get_parallelized_io(file_input_names,
                                                                              gtable_input_names,
                                                                              gtable_output_names)
    pattern_suffix = ''
    if parallelized_input_field != '':
        pattern_suffix += '-input-gtable'
    if parallelized_output_field != '':
        pattern_suffix += '-output-gtable'

with open(os.path.join(template_dir, 'dxapp.json'), 'r') as template_app_json_file:
    file_text = fill_in_name_and_ver(template_app_json_file.read(), name, version)
    template_app_json = json.loads(file_text)
    for key in template_app_json['runSpec']:
        app_json['runSpec'][key] = template_app_json['runSpec'][key]

#################
# WRITING FILES #
#################

print ''
print BOLD() + '*** Generating ' + DNANEXUS_LOGO() + BOLD() + ' App Template... ***' + ENDC()

with open(os.path.join(name, 'dxapp.json'), 'w') as prog_file:
    prog_file.write(clean(json.dumps(app_json, indent=2)) + '\n')
manifest.append(os.path.join(name, 'dxapp.json'))

print ''
print fill('''Your app specification has been written to the
dxapp.json file. You can specify more app options by editing this file
directly (see https://wiki.dnanexus.com/Developer-Portal for complete
documentation).''' + ('''  Note that without an input and output specification,
your app can only be built as an APPLET on the system.  To publish it to
the DNAnexus community, you must first specify your inputs and outputs.
''' if not ('inputSpec' in app_json and 'outputSpec' in app_json) else ""))
print ''

for subdir in 'src', 'test', 'resources':
    try:
        os.mkdir(os.path.join(name, subdir))
        manifest.append(os.path.join(name, subdir, ''))
    except:
        sys.stderr.write("Unable to create subdirectory %s/%s" % (name, subdir))
        sys.exit(1)

entry_points = ['main']

if pattern == 'parallelized':
    entry_points = ['main', 'process', 'postprocess']
elif pattern == 'scatter-process-gather':
    entry_points = ['main', 'scatter', 'map', 'process', 'postprocess']

manifest += create_files_from_templates(template_dir, app_json, language,
                                        file_input_names, file_array_input_names, file_output_names,
                                        pattern, pattern_suffix,
                                        parallelized_input_field, parallelized_output_field,
                                        entry_points=entry_points, description=description)

print "Created files:"
for filename in sorted(manifest):
    print "\t", filename
print "\n" + fill('''App directory created!  See
https://wiki.dnanexus.com/Developer-Portal for tutorials on how to modify these files,
or run "dx-build-app(let) %s" while logged in with dx.''' % name) + "\n"
print fill('''Running the DNAnexus build utility will create an
executable on the DNAnexus platform.  Any files found in the ''' +
BOLD() + 'resources' + ENDC() + ''' directory will be uploaded so that
they will be present in the root directory when the executable is
run.''')
