/*
 *
 * 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.
 *
 */

#include "unit_test.h"
#include "qpid/Exception.h"
#include "qpid/amqp_0_10/Unit.h"
#include "qpid/amqp_0_10/ControlHolder.h"
#include "qpid/amqp_0_10/CommandHolder.h"
#include "qpid/amqp_0_10/handlers.h"
#include "qpid/amqp_0_10/specification.h"

QPID_AUTO_TEST_SUITE(handler_tests)

using namespace qpid::amqp_0_10;
using namespace std;

string called;                  // Set by called handler function

// Note on handlers:
// 
// Control and Command handlers are separate, both behave the same way,
// so substitute "control or command" for command in the following.
//
// Command handlers derive from CommandHandler and implement functions
// for all the commands they handle. Handling an unimplemented command
// will raise NotImplementedException.
//
// Using virtual inheritance from CommandHandler allows multiple
// handlers to be aggregated into one with multiple inheritance,
// See test code for example. 
//
// E.g. the existing broker model would have two control handlers:
//  - ConnectionHandler: ControlHandler for connection controls.
//  - SessionHandler: ControlHandler for session controls.
// It would have class-command handlers for each AMQP class:  
//    - QueueHandler, MessageHandler etc.. handle each class.
// And an aggregate handler in place of BrokerAdapter
//  - BrokerCommandHandler: public QueueHandler, MessageHandler ...
//  
// In other applications (e.g. cluster) any combination of commands
// can be handled by a given handler. It _might_ simplify the code
// to collaps ConnectionHandler and SessionHandler into a single
// ControlHandler (or it might not.)

struct TestExecutionHandler : public virtual CommandHandler {
    void executionSync() { called = "executionSync"; }
    // ... etc. for all execution commands
};

struct TestMessageHandler : public virtual CommandHandler {
    void messageCancel(const Str8&)  { called="messageCancel"; }
    // ... etc.
};

// Aggregate handler for all recognised commands.
struct TestCommandHandler :
    public TestExecutionHandler,
    public TestMessageHandler
    // ... etc. handlers for all command classes.
{};                            // Nothing to do.


// Sample unit handler, written as a static_visitor. 
// Note it could equally be written with if/else statements
// in handle.
// 
struct TestUnitHandler : public boost::static_visitor<void> {
    TestCommandHandler handler;
    void handle(const Unit& u) { u.applyVisitor(*this); }
    
    void operator()(const Body&)  { called="Body"; }
    void operator()(const Header&) { called="Header"; }
    void operator()(const ControlHolder&) { throw qpid::Exception("I don't do controls."); }
    void operator()(const CommandHolder& c) { c.invoke(handler); }
};

QPID_AUTO_TEST_CASE(testHandlers) {
    TestUnitHandler handler;
    Unit u;

    u = Body();
    handler.handle(u);
    BOOST_CHECK_EQUAL("Body", called);

    u = Header();
    handler.handle(u);
    BOOST_CHECK_EQUAL("Header", called);

    // in_place<Foo>(...) is equivalent to Foo(...) but
    // constructs Foo directly in the holder, avoiding
    // a copy.
    
    u = CommandHolder(in_place<execution::Sync>());
    handler.handle(u);
    BOOST_CHECK_EQUAL("executionSync", called);

    u = ControlHolder(in_place<connection::Start>(Map(), Str16Array(), Str16Array()));
    try {
        handler.handle(u);
    } catch (const qpid::Exception&) {}

    u = CommandHolder(in_place<message::Cancel>(Str8()));
    handler.handle(u);
    BOOST_CHECK_EQUAL("messageCancel", called);
}

QPID_AUTO_TEST_SUITE_END()
