#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2018 Lenovo
#
# 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 optparse
import os
import signal
import sys
import time

try:
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
    pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
    sys.path.append(path)

import confluent.client as client
import confluent.screensqueeze as sq

exitcode = 0


def get_update_progress(session, url):
    for res in session.read(url):
        status = res['phase']
        percent = res['progress']
        detail = res['detail']
        if status == 'error':
            text = 'error!'
        else:
            text = '{0}: {1:3.0f}%'.format(status, percent)
    return text, status, detail


def printerror(res, node=None):
    global exitcode
    if 'errorcode' in res:
        exitcode = res['errorcode']
    if 'error' in res:
        if node:
            sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
        else:
            sys.stderr.write('{0}\n'.format(res['error']))
        if 'errorcode' not in res:
            exitcode = 1
    for node in res.get('databynode', ()):
        printerror(res['databynode'][node], node)


def attach_media(noderange, media):
    global exitcode
    session = client.Command()
    if ':' not in media:
        sys.stderr.write('Full URL required for attach\n')
        sys.exit(1)
    resource = '/noderange/{0}/media/attach'.format(noderange)
    for res in session.update(resource, {'url': media}):
        printerror(res)
    list_media(noderange, media)


def list_media(noderange, media):
    session = client.Command()
    resource = '/noderange/{0}/media/current'.format(noderange)
    for res in session.read(resource):
        printerror(res)
        for node in res.get('databynode', []):
            url = res['databynode'][node].get('url', None)
            name =  res['databynode'][node].get('name', None)
            if (url and not url.startswith('file:') and
                    not res['databynode'][node].get('secure', False)):
                name += ' (insecure)'
            if not name:
                continue
            print('{0}: {1}'.format(node, url + '/' + name if url else name))


def detach_media(noderange, media):
    global exitcode
    session = client.Command()
    resource = '/noderange/{0}/media/detach'.format(noderange)
    for res in session.update(resource, {'detachall': 1}):
        printerror(res)


def upload_media(noderange, media):
    global exitcode

    session = client.Command()
    output = sq.ScreenPrinter(noderange, session)
    filename = os.path.abspath(media)
    resource = '/noderange/{0}/media/uploads/'.format(noderange)
    filename = os.path.abspath(filename)
    upargs = {'filename': filename}
    noderrs = {}
    if session.unixdomain:
        filesbynode = {}
        for exp in session.create('/noderange/{0}/attributes/expression'.format(noderange),
                        {'expression': filename}):
            if 'error' in exp:
                sys.stderr.write(exp['error'] + '\n')
                exitcode |= exp.get('errorcode', 1)
            ex = exp.get('databynode', ())
            for node in ex:
                filesbynode[node] = ex[node]['value']
                if not isinstance(filesbynode[node], bytes) and not isinstance(filesbynode[node], str):
                    filesbynode[node] = filesbynode[node].encode('utf-8')
        for node in filesbynode:
            endfilename = filesbynode[node]
            if not os.path.exists(endfilename):
                sys.stderr.write('Unable to locate requested file {0}\n'.format(
                    endfilename))
                sys.exit(404)
            of = open(endfilename, 'rb')
            try:
                session.add_file(endfilename, of.fileno(), 'rb')
            except Exception:
                pass
    nodeurls = {}
    for res in session.create(resource, upargs):
        if 'created' not in res:
            for nodename in res.get('databynode', ()):
                output.set_output(nodename, 'error!')
                noderrs[nodename] = res['databynode'][nodename].get(
                    'error', 'Unknown Error')
            continue
        watchurl = res['created']
        currnode = watchurl.split('/')[1]
        nodeurls[currnode] = '/' + watchurl
    while nodeurls:
        for node in list(nodeurls):
            progress, status, err = get_update_progress(
                session, nodeurls[node])
            if status == 'error':
                exitcode = 1
                noderrs[node] = err
            if status in ('error', 'complete', 'pending'):
                list(session.delete(nodeurls[node]))
                del nodeurls[node]
            output.set_output(node, progress)
        time.sleep(2)
    allerrnodes = ','.join(noderrs)
    if noderrs:
        sys.stderr.write(
            'Nodes had errors receiving media ({0})!\n'.format(allerrnodes))
    for node in noderrs:
        sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
    list_media(noderange, media)

funmap = {
    'upload': upload_media,
    'attach': attach_media,
    'detachall': detach_media,
    'list': list_media,
}


class OptParser(optparse.OptionParser):

    def format_epilog(self, formatter):
        return self.expand_prog_name(self.epilog)

def main():
    argparser = OptParser(
        usage="Usage: %prog <noderange> [list|upload|attach|detachall] "
              "<filename>|all|<url>",
        epilog='\nupload will take the specified file and upload it to the '
               'BMC.\n\n'
               'attach will instruct the BMC to connect a remote media to the '
               'specified url.\n\ndetachall will remove *ALL* uploaded and '
               'attached urls from the BMC\n\nlist shows currently mounted '
               'media.\n\nSee `man %prog` for more info.\n')
    (options, args) = argparser.parse_args()
    media = None
    try:
        noderange = args[0]
        operation = args[1]
        arglength = 2
        if operation in ('attach', 'upload'):
            media = args[2]
            arglength = 3
        if len(args) > arglength:
            argparser.print_help()
            sys.exit(1)
    except IndexError:
        argparser.print_help()
        sys.exit(1)
    client.check_globbing(noderange)
    try:
        handler = funmap[operation]
    except KeyError:
        argparser.print_help()
        sys.exit(1)
    handler(noderange, media)

if __name__ == '__main__':
    main()
    sys.exit(exitcode)
