#!/usr/bin/env python

#
# 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.
#

import sys, json
import  qpid_dispatch_site
from qpid_dispatch.management import Node, Url
from collections import Mapping, Sequence
from optparse import OptionGroup
from qpid_dispatch_internal.tools.command import OptionParser, Option, UsageError, connection_options, check_args, main

def attr_split(attrstr):
    """Split an attribute string of the form name=value or name to indicate None"""
    nv = attrstr.split("=", 1)
    if len(nv) == 1: return [nv[0], None]
    else: return nv

class QdManage():

    def __init__(self):

        self.operations = ['query', 'create', 'read', 'update', 'delete',
                           'get-types', 'get-operations', 'get-attributes', 'get-annotations',
                           'get-mgmt-nodes']

        usage = "%prog <operation> [options...] [arguments...]"
        description = "Operations: "+", ".join(self.operations)

        op = OptionParser(usage=usage, option_class=Option, description=description)
        op.add_option('--type', help='Type of entity to operate on.')
        op.add_option('--name', help='Name of entity to operate on.')
        op.add_option('--identity', help='Identity of entity to operate on.', metavar="ID")
        op.add_option("--indent", type="int", default=2,
                     help="Pretty-printing indent. -1 means don't pretty-print (default %default)")
        op.add_option('--stdin', action='store_true',
                      help='Read attributes as JSON map or list of maps from stdin.')


        op.add_option_group(connection_options(op))

        self.op = op

    def run(self, argv):
        self.opts, self.args = self.op.parse_args(argv[1:])
        if len(self.args) == 0: raise UsageError("No operation specified")
        operation = self.args.pop(0)
        if operation not in self.operations: raise UsageError("Unknown operation: %s" % operation)
        self.node = Node(Url(self.opts.bus), self.opts.router)
        method = getattr(self, operation.replace('-','_'))
        method()

    def main(self, argv):
        return main(self.run, argv, self.op)

    def print_json(self, data):
        """Print data as JSON"""
        print json.dumps(data, indent=self.opts.indent)

    def call_node(self, method, *argnames, **kwargs):
        """Call method on node, use opts named in argnames"""
        names = set(argnames)
        for k in self.opts.__dict__:
            if k in names and hasattr(self.opts, k):
                kwargs[k] = getattr(self.opts, k)
        return getattr(self.node, method)(**kwargs)

    def call_bulk(self, func):
        """Call function for attributes from stdin or --attributes option"""
        if self.opts.stdin:
            data = json.load(sys.stdin)
            if isinstance(data, Mapping):
                self.print_json(func(data).attributes)
            elif isinstance(data, Sequence):
                self.print_json([func(attrs).attributes for attrs in data])
            else: raise ValueError("stdin is not a JSON map or list")
        else:
            self.print_json(func(self.opts.attributes).attributes)

    def query(self):
        """query [ATTR...]          Print attributes of entities."""
        if self.args:  self.opts.attribute_names = self.args
        result = self.call_node('query', 'type', 'attribute_names')
        self.print_json(result.get_dicts(clean=True))

    def create(self):
        """create [ATTR=VALUE...]   Create a new entity."""
        if self.args:
            self.opts.attributes = dict(attr_split(arg) for arg in self.args)
        self.call_bulk(lambda attrs: self.call_node('create', 'type', 'name', attributes=attrs))

    def read(self):
        """read                     Print attributes of selected entity."""
        check_args(self.args, 0)
        self.print_json(self.call_node('read', 'type', 'name', 'identity').attributes)

    def update(self):
        """update [ATTR=VALUE...]   Update an entity."""
        if self.args:
            self.opts.attributes = dict(attr_split(arg) for arg in self.args)
        self.call_bulk(
            lambda attrs: self.call_node('update', 'type', 'name', 'identity', attributes=attrs))

    def delete(self):
        """delete                   Delete an entity"""
        check_args(self.args, 0)
        self.call_node('delete', 'type', 'name', 'identity')

    def get_types(self):
        """get-types [TYPE]         List entity types with their base types."""
        check_args(self.args, 1)
        if self.args: self.opts.type = self.args[0]
        self.print_json(self.call_node('get_types', 'type'))

    def get_annotations(self):
        """get-annotations [TYPE]   List entity types with the annotations they implement."""
        check_args(self.args, 1)
        if self.args: self.opts.type = self.args[0]
        self.print_json(self.call_node('get_annotations', 'type'))

    def get_attributes(self):
        """get-attributes [TYPE]    List entity types with their attributes."""
        check_args(self.args, 1)
        if self.args: self.opts.type = self.args[0]
        self.print_json(self.call_node('get_attributes', 'type'))

    def get_operations(self):
        """get-operations [TYPE]    List entity types with their operations."""
        check_args(self.args, 1)
        if self.args: self.opts.type = self.args[0]
        self.print_json(self.call_node('get_operations', 'type'))

    def get_mgmt_nodes(self):
        """get-mgmt-nodes           List of other known management nodes"""
        check_args(self.args, 0)
        self.print_json(self.call_node('get_mgmt_nodes'))

if __name__ == "__main__":
    sys.exit(QdManage().main(sys.argv))
