/*****************************************************************************
 * dispatcher.hpp : matroska demuxer
 *****************************************************************************
 * Copyright (C) 2016 VLC authors, VideoLAN, Videolabs SAS
 * $Id: 37a94479aec1fc8efe271acb9eec1e263c3d595b $
 *
 * Authors: Filip Roseen <filip@videolabs.io>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
#ifndef VLC_MKV_DISPATCHER_HPP_
#define VLC_MKV_DISPATCHER_HPP_

// ----------------------------------------------------------------------------
// This header contains helpers to simulate lambdas in C++03.
//
// It will be used to create "dispatchers" which can be thought of like a
// `switch` but only more dynamic.
// ----------------------------------------------------------------------------

namespace {

  template<class Impl, class Processor>
  class Dispatcher {
    protected:
      Dispatcher() : _default_handler (NULL) { }
    public:
      template<class It>
      void iterate (It beg, It end, void* const& payload) const {
        for (; beg != end; ++beg)
          static_cast<Impl const*> (this)->Impl::send (*beg, payload);
      }

      void set_default_handler (Processor const& callback) {
        _default_handler = callback;
      }

      void on_create () {
        /* empty default implementation */
      }

      Processor _default_handler;
  };

  template<int>
  struct DispatcherTag;

  template<class T, T*, class DispatcherType>
  class DispatchContainer {
    public:    static DispatcherType dispatcher;
    protected: static vlc_mutex_t   _dispatcher_lock;
  };

  template<class T, T* P, class DT>
  DT DispatchContainer<T, P, DT>::dispatcher;

  template<class T, T* P, class DT>
  vlc_mutex_t DispatchContainer<T, P, DT>::_dispatcher_lock = VLC_STATIC_MUTEX;
}

// ----------------------------------------------------------------------------
//   * `GroupName_##_tag` is used so that we can refer to a static dispatcher
//      of the correct type without instantiating DispatchContainer with a
//      locally declared type (since it is illegal in C++03).
//
//      We are however allowed to pass the variable of a variable declared
//      `extern`, and this will effectively be our handle to the "foreign"
//      static dispatcher.
//
//      We make the variable have type `DispatcherTag<__LINE__>` so that you
//      can use MKV_SWITCH_CREATE with the same name in different parts of the
//      translation-unit (without collision).
//
//   *  `GroupName_ ## _base` is used to declare a bunch of helpers; names that
//       must be available in our fake "lambdas" (C++03 is a pain).
// ----------------------------------------------------------------------------

#define MKV_SWITCH_CREATE(DispatchType_, GroupName_, PayloadType_) \
  typedef DispatcherTag<__LINE__> GroupName_ ## _tag_t; \
  extern GroupName_##_tag_t GroupName_ ## _tag; \
  struct GroupName_; \
  struct GroupName_##_base : DispatchContainer<GroupName_##_tag_t, &GroupName_##_tag, DispatchType_> { \
      typedef      PayloadType_ payload_t;                         \
      typedef     DispatchType_ dispatch_t;                        \
      typedef struct GroupName_ handler_t;                         \
      static void* Payload (payload_t& data) {                     \
          return static_cast<void*> (&data);                       \
      }                                                            \
  };                                                               \
  struct GroupName_ : GroupName_ ## _base

// ----------------------------------------------------------------------------
//   * `Dispatcher` is a static function used to access the dispatcher in a
//      thread-safe manner. We only want _one_ thread to actually construct
//      and initialize it, hence the lock.
// ----------------------------------------------------------------------------

#define MKV_SWITCH_INIT()                     \
  static dispatch_t& Dispatcher () {          \
      static handler_t * p_handler = NULL;    \
      vlc_mutex_lock( &_dispatcher_lock );    \
      if (unlikely( p_handler == NULL) ) {    \
          static handler_t handler;           \
          p_handler = &handler;               \
          p_handler->dispatcher.on_create (); \
      }                                       \
      vlc_mutex_unlock( &_dispatcher_lock );  \
      return p_handler->dispatcher;           \
  } struct PleaseAddSemicolon {}

// ----------------------------------------------------------------------------
//   * The following is to be used inside `struct GroupName_`, effectivelly
//     declaring a local struct and a data-member, `ClassName_ ## _processor`,
//     of that type.
//
//     When the data-member is constructed it will run `InitializationExpr_`,
//     meaning that it can access the static dispatcher and register itself (or
//     whatever is desired).
//
//   * Since we need to do type-erasure, once again, because of the fact that
//     C++03 does not support locally declared types as template-arguments, we
//     declare a static function `ClassName_ ## _callback` that will cast our
//     payload from `void*` to the appropriate type.
//
//   * The body of `ClassName_ ## _handler` will be written by the user of the
//     MACRO, and the implementation will effectively be invoked whenever the
//     dispatcher decides to (through `ClassName_ ## _callback`).
//
//   * Since the type of the `VariableName_` argument to `ClassName_ ## _handler`
//     might not necessarily be the same as the type passed from the dispatcher
//     (because of the type-erasure), the macro provides a way to change the
//     type with a `UnwrapExpr_` (see the function call within `ClassName_ ##
//     _callback`).
// ----------------------------------------------------------------------------

#define MKV_SWITCH_CASE_DEFINITION(ClassName_, RealType_, Type_, VariableName_, PayloadName_, InitializationExpr_, UnwrapExpr_) \
  struct ClassName_##_processor {                                             \
    ClassName_##_processor () { InitializationExpr_; }                        \
  } ClassName_##__wrapper;                                                    \
  static inline void ClassName_##_callback (Type_ data, void* PayloadName_) { \
    ClassName_##_handler (UnwrapExpr_, *static_cast<payload_t*>(PayloadName_));      \
  }                                                                             \
  static inline void ClassName_##_handler (RealType_& VariableName_, payload_t& PayloadName_)

#endif
