#!/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 qmf
import sys
import time


class Model:
    # attr_reader :parent_class, :child_class
    def __init__(self):
        self.parent_class = qmf.SchemaObjectClass("org.apache.qpid.qmf", "parent")
        self.parent_class.add_property(qmf.SchemaProperty("name", qmf.TYPE_SSTR, {"index":True}))
        self.parent_class.add_property(qmf.SchemaProperty("state", qmf.TYPE_SSTR))

        self.parent_class.add_property(qmf.SchemaProperty("uint64val", qmf.TYPE_UINT64))
        self.parent_class.add_property(qmf.SchemaProperty("uint32val", qmf.TYPE_UINT32))
        self.parent_class.add_property(qmf.SchemaProperty("uint16val", qmf.TYPE_UINT16))
        self.parent_class.add_property(qmf.SchemaProperty("uint8val", qmf.TYPE_UINT8))

        self.parent_class.add_property(qmf.SchemaProperty("int64val", qmf.TYPE_INT64))
        self.parent_class.add_property(qmf.SchemaProperty("int32val", qmf.TYPE_INT32))
        self.parent_class.add_property(qmf.SchemaProperty("int16val", qmf.TYPE_INT16))
        self.parent_class.add_property(qmf.SchemaProperty("int8val", qmf.TYPE_INT8))

        self.parent_class.add_property(qmf.SchemaProperty("sstrval", qmf.TYPE_SSTR))
        self.parent_class.add_property(qmf.SchemaProperty("lstrval", qmf.TYPE_LSTR))

        self.parent_class.add_property(qmf.SchemaProperty("mapval", qmf.TYPE_MAP))
        self.parent_class.add_property(qmf.SchemaProperty("listval", qmf.TYPE_LIST))


        self.parent_class.add_statistic(qmf.SchemaStatistic("queryCount", qmf.TYPE_UINT32, {"unit":"query", "desc":"Query count"}))

        _method = qmf.SchemaMethod("echo", {"desc":"Check responsiveness of the agent object"})
        _method.add_argument(qmf.SchemaArgument("sequence", qmf.TYPE_UINT32, {"dir":qmf.DIR_IN_OUT}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("set_numerics", {"desc":"Set the numeric values in the object"})
        _method.add_argument(qmf.SchemaArgument("test", qmf.TYPE_SSTR, {"dir":qmf.DIR_IN}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("test_map_list", {"desc":"A method call that accepts map and list arguments."})
        _method.add_argument(qmf.SchemaArgument("inMap", qmf.TYPE_MAP, {"dir":qmf.DIR_IN}))
        _method.add_argument(qmf.SchemaArgument("inList", qmf.TYPE_LIST, {"dir":qmf.DIR_IN}))
        _method.add_argument(qmf.SchemaArgument("outMap", qmf.TYPE_MAP, {"dir":qmf.DIR_OUT}))
        _method.add_argument(qmf.SchemaArgument("outList", qmf.TYPE_LIST, {"dir":qmf.DIR_OUT}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("set_short_string", {"desc":"Set the short string value in the object"})
        _method.add_argument(qmf.SchemaArgument("value", qmf.TYPE_SSTR, {"dir":qmf.DIR_IN_OUT}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("set_long_string", {"desc":"Set the long string value in the object"})
        _method.add_argument(qmf.SchemaArgument("value", qmf.TYPE_LSTR, {"dir":qmf.DIR_IN_OUT}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("create_child", {"desc":"Create a new child object"})
        _method.add_argument(qmf.SchemaArgument("child_name", qmf.TYPE_LSTR, {"dir":qmf.DIR_IN}))
        _method.add_argument(qmf.SchemaArgument("child_ref", qmf.TYPE_REF, {"dir":qmf.DIR_OUT}))
        self.parent_class.add_method(_method)

        _method = qmf.SchemaMethod("probe_userid", {"desc":"Return the user-id for this method call"})
        _method.add_argument(qmf.SchemaArgument("userid", qmf.TYPE_SSTR, {"dir":qmf.DIR_OUT}))
        self.parent_class.add_method(_method)

        self.child_class = qmf.SchemaObjectClass("org.apache.qpid.qmf", "child")
        self.child_class.add_property(qmf.SchemaProperty("name", qmf.TYPE_SSTR, {"index":True}))

        self.event_class = qmf.SchemaEventClass("org.apache.qpid.qmf", "test_event", qmf.SEV_NOTICE)
        self.event_class.add_argument(qmf.SchemaArgument("uint32val", qmf.TYPE_UINT32))
        self.event_class.add_argument(qmf.SchemaArgument("strval", qmf.TYPE_LSTR))
        self.event_class.add_argument(qmf.SchemaArgument("mapval", qmf.TYPE_MAP))
        self.event_class.add_argument(qmf.SchemaArgument("listval", qmf.TYPE_LIST))

    def register(self, agent):
        agent.register_class(self.parent_class)
        agent.register_class(self.child_class)
        agent.register_class(self.event_class)



class App(qmf.AgentHandler):
    '''
    Object that handles events received by the Agent.
    '''
    def get_query(self, context, query, userId):
        '''
        Respond to a Query request from a console.
        '''
        #print "Query: user=%s context=%d class=%s" % (userId, context, query.class_name())
        #if query.object_id():
        #    print query.object_id().object_num_low()
        self._parent.inc_attr("queryCount")
        if query.class_name() == 'parent':
            self._agent.query_response(context, self._parent)
        elif query.object_id() == self._parent_oid:
            self._agent.query_response(context, self._parent)
        self._agent.query_complete(context)
    
    
    def method_call(self, context, name, object_id, args, userId):
        '''
        Invoke a method call requested by the console.
        '''
        #print "Method: name=%s user=%s context=%d object_id=%s args=%s" % (name, userId, context, object_id, args)
        if name == "echo":
            self._agent.method_response(context, 0, "OK", args)

        elif name == "test_map_list":
            # build the output map from the input map, accessing each key,
            # value to ensure they are encoded/decoded
            outMap = {}
            for key,value in args['inMap'].items():
                outMap[key] = value

            # same deal for the output list
            outList = []
            for value in args['inList']:
                outList.append(value)

            args['outMap'] = outMap
            args['outList'] = outList
            self._agent.method_response(context, 0, "OK", args)

        elif name == "set_numerics":
            _retCode = 0
            _retText = "OK"

            if args['test'] == "big":
                #
                # note the alternate forms for setting object attributes:
                #
                self._parent.set_attr("uint64val", 0x9494949449494949)
                self._parent.uint32val = 0xa5a55a5a
                self._parent.set_attr("uint16val", 0xb66b)
                self._parent["uint8val"] = 0xc7

                self._parent.int64val = 1000000000000000000
                self._parent.set_attr("int32val", 1000000000)
                self._parent["int16val"] = 10000
                self._parent.set_attr("int8val",  100)

                event = qmf.QmfEvent(self._model.event_class)
                event.uint32val = self._parent.get_attr("uint32val")
                event.strval = "Unused"
                event.mapval = self._parent.get_attr("mapval")
                event.listval = self._parent["listval"]

                self._agent.raise_event(event)

                ## Test the __getattr__ implementation:
                ## @todo: remove once python_client implements this
                ## form of property access
                assert self._parent["uint8val"] == 0xc7
                assert self._parent.uint64val == 0x9494949449494949

            # note the alternative argument access syntax:
            elif args.test == "small":
                self._parent.set_attr("uint64val", 4)
                self._parent.set_attr("uint32val", 5)
                self._parent.set_attr("uint16val", 6)
                self._parent.set_attr("uint8val",  7)

                self._parent.set_attr("int64val", 8)
                self._parent.set_attr("int32val", 9)
                self._parent.set_attr("int16val", 10)
                self._parent.set_attr("int8val",  11)

                event = qmf.QmfEvent(self._model.event_class)
                event.uint32val = self._parent.uint32val
                event.strval = "Unused"
                self._agent.raise_event(event)

            elif args['test'] == "negative":
                self._parent.set_attr("uint64val", 0)
                self._parent.set_attr("uint32val", 0)
                self._parent.set_attr("uint16val", 0)
                self._parent.set_attr("uint8val",  0)

                self._parent.set_attr("int64val", -10000000000)
                self._parent.set_attr("int32val", -100000)
                self._parent.set_attr("int16val", -1000)
                self._parent.set_attr("int8val",  -100)

                event = qmf.QmfEvent(self._model.event_class)
                event.uint32val = self._parent.uint32val
                event.strval = "Unused"
                self._agent.raise_event(event)

            else:
                _retCode = 1
                _retText = "Invalid argument value for test"

            self._agent.method_response(context, _retCode, _retText, args)

        elif name == "set_short_string":
            self._parent.set_attr('sstrval', args['value'])
            event = qmf.QmfEvent(self._model.event_class)
            event.uint32val = 0
            event.strval = self._parent.sstrval
            self._agent.raise_event(event)

            self._agent.method_response(context, 0, "OK", args)

        elif name == "set_long_string":
            self._parent.set_attr('lstrval', args['value'])
            event = qmf.QmfEvent(self._model.event_class)
            event.uint32val = 0
            event.strval = self._parent.lstrval
            self._agent.raise_event(event)

            self._agent.method_response(context, 0, "OK", args)

        elif name == "create_child":
            #
            # Instantiate an object based on the Child Schema Class
            #
            _oid = self._agent.alloc_object_id(2)
            args['child_ref'] = _oid
            self._child = qmf.AgentObject(self._model.child_class)
            self._child.set_attr("name", args["child_name"])
            self._child.set_object_id(_oid)
            self._agent.method_response(context, 0, "OK", args)

        elif name == "probe_userid":
            args['userid'] = userId
            self._agent.method_response(context, 0, "OK", args)

        else:
            self._agent.method_response(context, 1, "Unimplemented Method: %s" % name, args)


    def main(self):
        '''
        Agent application's main processing loop.
        '''
        # Connect to the broker
        self._settings = qmf.ConnectionSettings()
        self._settings.sendUserId = True
        if len(sys.argv) > 1:
            self._settings.host = str(sys.argv[1])
        if len(sys.argv) > 2:
            self._settings.port = int(sys.argv[2])
        self._connection = qmf.Connection(self._settings)

        # Instantiate an Agent to serve me queries and method calls
        self._agent = qmf.Agent(self, "agent_test_label")

        # Dynamically define the parent and child schemas, then
        # register them with the agent
        self._model = Model()
        self._model.register(self._agent)

        # Tell the agent about our connection to the broker
        self._agent.set_connection(self._connection)

        # Instantiate and populate an instance of the Parent
        # Schema Object
        self._parent = qmf.AgentObject(self._model.parent_class)

        ## @todo how do we force a test failure?
        # verify the properties() and statistics() object methods:
        assert len(self._parent.properties()) == 14
        assert len(self._parent.statistics()) == 1

        self._parent.set_attr("name", "Parent One")
        self._parent.set_attr("state", "OPERATIONAL")

        self._parent.set_attr("uint64val", 0)
        self._parent.set_attr("uint32val", 0)
        self._parent.set_attr("uint16val", 0)
        self._parent.set_attr("uint8val",  0)

        self._parent.set_attr("int64val", 0)
        self._parent.set_attr("int32val", 0)
        self._parent.set_attr("int16val", 0)
        self._parent.set_attr("int8val",  0)

        # a list containing a list that contains a map (so there!)
        self._parent.set_attr("listval", ['a', 1, 'b', -2,
                                          ['c', True, 3.1415,
                                           {"hi": 10, "lo": 5, "neg": -3}]])
        # a default map
        self._parent.set_attr("mapval", {'aLong' : long(9999999999),
                                         'aInt'  : int(54321),
                                         'aSigned' : -666,
                                         'aString' : "A String",
                                         'aFloat' : 3.1415,
                                         'aMap'  : {'first' : 1,
                                                    'second': 2},
                                         'aList' : ['x', -1, 'y', 2]})



        self._parent_oid = self._agent.alloc_object_id(1)
        self._parent.set_object_id(self._parent_oid)

        # Now wait for events arriving on the connection
        # to the broker...
        while True:
            time.sleep(1000)



app = App()
app.main()

