// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_
#define CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_

#include <deque>

#include "base/synchronization/lock.h"
#include "ipc/ipc_channel_proxy.h"

// Base class used to allow synchronous IPC messages to be sent and
// received in an asynchronous manner. To use this class add it as a filter to
// your IPC channel using ChannelProxy::AddFilter(). From then on, before
// sending a synchronous message, call SyncMessageReplyDispatcher::Push() with
// a callback and a key. This class will then handle the message response and
// will call the callback when it is received.
//
// This class is intended to be extended by classes implementing
// HandleMessageType with delegation for the messages they expect to receive in
// cases where you care about the return values of synchronous messages.
//
// Sample usage pattern:
// Define a class which inherits from SyncMessageCallContext which specifies
// the output_type tuple and has a Completed member function.
// class SampleContext
//     : public SyncMessageReplyDispatcher::SyncMessageCallContext {
// public:
//  typedef Tuple1<int> output_type;
//  void Completed(int arg) {}
// };
//
// // Add handling for desired message types.
// class SyncMessageReplyDispatcherImpl : public SyncMessageReplyDispatcher {
//   virtual bool HandleMessageType(const IPC::Message& msg,
//                                  SyncMessageReplyDispatcher* context) {
//    switch (context->message_type()) {
//      case AutomationMsg_CreateExternalTab::ID:
//        InvokeCallback<CreateExternalTabContext>(msg, context);
//        break;
//     [HANDLING FOR OTHER EXPECTED MESSAGE TYPES]
//   }
// }
//
// // Add the filter
// IPC::SyncChannel channel_;
// channel_.AddFilter(new SyncMessageReplyDispatcherImpl());
//
// sync_->Push(msg, new SampleContext, this);
// channel_->ChannelProxy::Send(msg);
//
class SyncMessageReplyDispatcher : public IPC::ChannelProxy::MessageFilter {
 public:
  class SyncMessageCallContext {
   public:
    SyncMessageCallContext()
        : id_(0),
          message_type_(0),
          key_(NULL) {}

    virtual ~SyncMessageCallContext() {}

    uint32 message_type() const {
      return message_type_;
    }

   private:
    int id_;
    uint32 message_type_;
    void* key_;

    friend class SyncMessageReplyDispatcher;
  };

  SyncMessageReplyDispatcher() {}
  void Push(IPC::SyncMessage* msg, SyncMessageCallContext* context,
            void* key);
  void Cancel(void* key);

 protected:
  typedef std::deque<SyncMessageCallContext*> PendingSyncMessageQueue;

  SyncMessageCallContext* GetContext(const IPC::Message& msg);

  virtual bool OnMessageReceived(const IPC::Message& msg);

  // Child classes must implement a handler for the message types they are
  // interested in handling responses for. If you don't care about the replies
  // to any of the sync messages you are handling, then you don't have to
  // implement this.
  virtual bool HandleMessageType(const IPC::Message& msg,
                                 SyncMessageCallContext* context);

  template <typename T>
  void InvokeCallback(const IPC::Message& msg,
                      SyncMessageCallContext* call_context) {
    if (!call_context || !call_context->key_) {
      NOTREACHED() << "Invalid context parameter";
      return;
    }

    T* context = static_cast<T*>(call_context);
    T::output_type tmp;  // Acts as "initializer" for output parameters.
    IPC::ParamDeserializer<T::output_type> deserializer(tmp);
    if (deserializer.MessageReplyDeserializer::SerializeOutputParameters(msg)) {
      DispatchToMethod(context, &T::Completed, deserializer.out_);
      delete context;
    } else {
      // TODO(stoyan): How to handle errors?
    }
  }

  PendingSyncMessageQueue message_queue_;
  base::Lock message_queue_lock_;
};

#endif  // CHROME_FRAME_SYNC_MSG_REPLY_DISPATCHER_H_