#!/usr/bin/python3
#
# Copyright (c) 2017 SUSE LLC, Robert Schweikert <rjschwei@suse.com>
#
# This file is part of gcemetadata.
#
# gcemetadata is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.
#
"""
Query and display GCE metadata related to the instance

If no options are specified, all will be displayed.
"""


import getopt
import sys

import gcemetadata.gcemetadata as gcemetadata
import gcemetadata.gcemetautils as gcemetautils

from gcemetadata.gcemetaExceptions import GCEMetadataException

general_options = ['api', 'help', 'identity-format=', 'listapis', 'output=',
                   'version', 'xml']
opts_with_args = ['identity']
opts_with_id_targets = ['disks', 'licenses', 'network-interfaces']


def print_option(level, indent, options, previous_opt=None):
    prefix = '--'
    for opt in options.keys():
        if type(options[opt]) == dict:
            if opt in opts_with_id_targets:
                # Figure out the ID
                option_ids = list(options[opt].keys())
                option_ids.sort()
                cnt = 0
                for option_id in option_ids:
                    if not cnt:
                        print(indent * level + prefix + opt)
                        cnt += 1
                    if opt == 'disks':
                        info = indent * (level + 1)
                        info += prefix + 'diskid ' + option_id
                        print(info)
                    elif opt == 'network-interfaces':
                        info = indent * (level + 1)
                        info += prefix + 'netid ' + option_id
                        print(info)
                    elif opt == 'licenses':
                        info = (
                            indent * (level + 1) +
                            prefix + 'licenseid ' + option_id
                        )
                        print(info)
                    print_option(
                        level + 2, indent, options[opt][option_id], opt
                    )
            else:
                sub_level_indent = 1
                if previous_opt == 'network-interfaces':
                    info = indent * level
                    info += prefix + 'subnetid ' + opt
                    print(info)
                    sub_level_indent += 1
                print_option(level + sub_level_indent, indent, options[opt])
        if opt in opts_with_args:
            opt += ' VALUE'
        if not opt.isdigit():
            print(indent * level + prefix + opt)


def usage(options, categories, e=None):
    if e:
        print("Error:", e, file=sys.stderr)

    print("Syntax: %s [--options]" % sys.argv[0])
    print(__doc__.strip())

    print("Options:")
    indent = '    '
    for cat in categories:
        level = 1
        print(indent + '--query ' + cat[:-1])
        level += 1
        print_option(level, indent, options[cat])

    print(indent + 'General options:')
    for item in general_options:
        if item[-1] == '=':
            item = item[:-1]
        print(indent * 2 + '--' + item)
    if e:
        sys.exit(1)


def main():
    api = None
    api_arg_pos = None
    if '-a' in sys.argv:
        api_arg_pos = sys.argv.index('-a')
    elif '--api' in sys.argv:
        api_arg_pos = sys.argv.index('--api')
    if api_arg_pos:
        api = sys.argv[api_arg_pos + 1].strip()
        del sys.argv[api_arg_pos + 1]
        del sys.argv[api_arg_pos]

    meta = None
    if api:
        try:
            meta = gcemetadata.GCEMetadata(api)
        except GCEMetadataException as e:
            print(e, file=sys.stderr)
            sys.exit(1)
    else:
        try:
            meta = gcemetadata.GCEMetadata()
        except GCEMetadataException as e:
            print(e, file=sys.stderr)
            sys.exit(1)

    try:
        getopt_metaopts = meta.get_flattened_opts()
        getopt_metaopts += general_options
        getopt_metaopts.append('diskid=')
        getopt_metaopts.append('licenseid=')
        getopt_metaopts.append('netid=')
        getopt_metaopts.append('query=')
        getopt_metaopts.append('subnetid=')
        command_line_opts = []
        for opt in getopt_metaopts:
            if opt in opts_with_args:
                opt += '='
            command_line_opts.append(opt)
        # Special case for identity handling
        # It is possible to have an instance that does not support access to
        # the guest-attributes information from the metadata server. If this
        # is the case the logically linked options of "identity-format" and
        # "identity" cannot be handled properly as the "identity" option gets
        # removed from the processed command line options. This leads to
        # "identity-format" having the wrong value and triggering an
        # incorrect message (Issue #2).
        # This is the only place in the code where we know about
        # the connection between the two options.
        # Raising an error at a lower level would break the best effort
        # nature of the command. One can still retrieve metadata
        # on an instance that does not afford access to the guest-attributes
        if (
                '--identity-format' in sys.argv[1:] and
                'identity=' not in command_line_opts
        ):
            print(
                'Unable to access instance identity information',
                file=sys.stderr
            )
            sys.exit(1)
        opts, args = getopt.gnu_getopt(
            sys.argv[1:],
            'hlo:vq:x',
            command_line_opts
        )
    except getopt.GetoptError as e:
        usage(meta.get_api_map(), meta.get_option_categories(), e)

    if len(opts) == 0:
        gcemetautils.display_all(meta)
        return
    elif len(opts) == 1:
        option = opts[0]
        if option[0] in ['-o', '--output']:
            gcemetautils.display_all(meta, outfile=option[1])
            return
        elif option[0] in ['-x', '--xml']:
            gcemetautils.display_all(meta, gen_xml=True)
            return
    elif len(opts) == 2:
        found_xml = None
        for opt, val in opts:
            if opt in ['-o', '--output']:
                outfile = val
            elif opt in ['-x', '--xml']:
                found_xml = True
        if found_xml and outfile:
            gcemetautils.display_all(meta, outfile=outfile,  gen_xml=True)
            return

    found_query = None
    gen_xml = False
    metaopts = []
    out_file = None
    for opt, val in opts:
        if opt in ('-h', '--help'):
            usage(meta.get_api_map(), meta.get_option_categories())
            sys.exit(0)
        elif opt in ('-l', '--listapis'):
            apis = meta.get_available_api_versions()
            if apis:
                print('Available APIs:')
                for api in apis:
                    print('    %s' % api)
            else:
                print('No APIs available')
            sys.exit(0)
        elif opt == '--diskid':
            try:
                meta.set_disk_device(val)
            except GCEMetadataException as e:
                usage(meta.get_api_map(), meta.get_option_categories(), e)
            continue
        elif opt == '--identity':
            if not val:
                print('--identity argument must have a value', file=sys.stderr)
                sys.exit(1)
            meta.set_identity_arg(val)
        elif opt == '--identity-format':
            supported_vals = ['full', 'standard']
            if val not in supported_vals:
                msg = '--identity-format possible values: "%s"'
                print(msg % supported_vals, file=sys.stderr)
                sys.exit(1)
            meta.set_identity_format(val)
            continue
        elif opt == '--licenseid':
            try:
                meta.set_license_id(val)
            except GCEMetadataException as e:
                usage(meta.get_api_map(), meta.get_option_categories(), e)
            continue
        elif opt == '--netid':
            try:
                meta.set_net_device(val)
            except GCEMetadataException as e:
                usage(meta.get_api_map(), meta.get_option_categories(), e)
            continue
        elif opt in ('-o', '--output'):
            out_file = val
            continue
        elif opt in ('-q', '--query'):
            found_query = 1
            try:
                meta.set_data_category(val)
            except GCEMetadataException as e:
                usage(meta.get_api_map(), meta.get_option_categories(), e)
            continue
        elif opt == '--subnetid':
            try:
                meta.set_subnet(val)
            except GCEMetadataException as e:
                usage(meta.get_api_map(), meta.get_option_categories(), e)
            continue
        elif opt in ('-v', '--version'):
            try:
                print(meta.get_version())
            except:
                print('Cannot find version data file, please report a bug')
                sys.exit(1)
            sys.exit(0)
        elif opt in ('-x', '--xml'):
            gen_xml = True
            continue

        metaopts.append(opt.replace('--', '').replace('=', ''))

    if not found_query:
        print('The use of --query is required.')
        sys.exit(1)

    if out_file:
        try:
            if gen_xml:
                gcemetautils.write_xml_file(out_file, meta, metaopts)
            else:
                gcemetautils.write_file(out_file, meta, metaopts, True)
        except IOError as e:
            print(e, file=sys.stderr)
            sys.exit(1)
    else:
        if gen_xml:
            gcemetautils.display_xml(meta, metaopts)
        else:
            gcemetautils.display(meta, metaopts)

if __name__ == "__main__":
    main()
