#!/usr/bin/env python
"""Executable Python script for testing the action proxy.
/*
 * 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.
 */

  This script is useful for testing the action proxy (or its derivatives)
  by simulating invoker interactions. Use it in combination with
  docker run <image> which starts up the action proxy.
  Example:
     docker run -i -t -p 8080:8080 dockerskeleton # locally built images may be referenced without a tag
     ./invoke.py init <action source file>
     ./invoke.py run '{"some":"json object as a string"}'

  For additional help, try ./invoke.py -h
"""

import os
import re
import sys
import json
import base64
import requests
import codecs
import argparse
try:
    import argcomplete
except ImportError:
    argcomplete = False

def main():
    try:
        args = parseArgs()
        exitCode = {
            'init' : init,
            'run'   : run
        }[args.cmd](args)
    except Exception as e:
        print(e)
        exitCode = 1
    sys.exit(exitCode)

def dockerHost():
    dockerHost = 'localhost'
    if 'DOCKER_HOST' in os.environ:
        try:
            dockerHost = re.compile('tcp://(.*):[\\d]+').findall(os.environ['DOCKER_HOST'])[0]
        except Exception:
            print('cannot determine docker host from %s' % os.environ['DOCKER_HOST'])
            sys.exit(-1)
    return dockerHost

def containerRoute(args, path):
    return 'http://%s:%s/%s' % (args.host, args.port, path)

class objectify(object):
    def __init__(self, d):
        self.__dict__ = d

def parseArgs():
    parser = argparse.ArgumentParser(description='initialize and run an OpenWhisk action container')
    parser.add_argument('-v', '--verbose', help='verbose output', action='store_true')
    parser.add_argument('--host', help='action container host', default=dockerHost())
    parser.add_argument('-p', '--port', help='action container port number', default=8080, type=int)

    subparsers = parser.add_subparsers(title='available commands', dest='cmd')

    initmenu = subparsers.add_parser('init', help='initialize container with src or zip/tgz file')
    initmenu.add_argument('-b', '--binary', help='treat artifact as binary', action='store_true')
    initmenu.add_argument('-r', '--run', nargs='?', default=None, help='run after init')
    initmenu.add_argument('main', nargs='?', default='main', help='name of the "main" entry method for the action')
    initmenu.add_argument('artifact', help='a source file or zip/tgz archive')
    initmenu.add_argument('env', nargs='?', help='the environment variables to export to the action, either a reference to a file or an inline JSON object', default=None)

    runmenu = subparsers.add_parser('run', help='send arguments to container to run action')
    runmenu.add_argument('payload', nargs='?', help='the arguments to send to the action, either a reference to a file or an inline JSON object', default=None)

    if argcomplete:
        argcomplete.autocomplete(parser)
    return parser.parse_args()

def init(args):
    main = args.main
    artifact = args.artifact
    
    if artifact and (args.binary or artifact.endswith('.zip') or artifact.endswith('tgz') or artifact.endswith('jar')):
        with open(artifact, 'rb') as fp:
            contents = fp.read()
        contents = str(base64.b64encode(contents), 'utf-8')
        binary = True
    elif artifact != '':
        with(codecs.open(artifact, 'r', 'utf-8')) as fp:
            contents = fp.read()
        binary = False
    else:
        contents = None
        binary = False

    r = requests.post(
        containerRoute(args, 'init'),
        json = {
            "value": {
                "code": contents,
                "binary": binary,
                "main": main,
                "env": processPayload(args.env)
            }
        })

    print(r.text)

    if r.status_code == 200 and args.run != None:
        runArgs = objectify({})
        runArgs.__dict__ = args.__dict__.copy()
        runArgs.payload = args.run
        run(runArgs)

def run(args):
    value = processPayload(args.payload)
    if args.verbose:
        print('Sending value: %s...' % json.dumps(value)[0:40])
    r = requests.post(containerRoute(args, 'run'), json = {"value": value})
    print(str(r.content, 'utf-8'))

def processPayload(payload):
    if payload and os.path.exists(payload):
        with open(payload) as fp:
            return json.load(fp)
    try:
        d = json.loads(payload if payload else '{}')
        if isinstance(d, dict):
            return d
        else:
            raise
    except:
        print('payload must be a JSON object.')
        sys.exit(-1)

if __name__ == '__main__':
    main()
