// Copyright 2004-present Facebook. All Rights Reserved.

#ifndef FBXPLATMODULE
#define FBXPLATMODULE

#include <folly/dynamic.h>

#include <functional>

#include <map>
#include <tuple>
#include <vector>

using namespace std::placeholders;

namespace facebook { namespace xplat { namespace module {

/**
 * Base class for Catalyst native modules whose implementations are
 * written in C++.  Native methods are represented by instances of the
 * Method struct.  Generally, a derived class will manage an instance
 * which represents the data for the module, and non-Catalyst-specific
 * methods can be wrapped in lambdas which convert between
 * folly::dynamic and native C++ objects.  The Callback arguments will
 * pass through to js functions passed to the analogous javascript
 * methods.  At most two callbacks will be converted.  Results should
 * be passed to the first callback, and errors to the second callback.
 * Exceptions thrown by a method will be converted to platform
 * exceptions, and handled however they are handled on that platform.
 * (TODO mhorowitz #7128529: this exception behavior is not yet
 * implemented.)
 *
 * There are two sets of constructors here.  The first set initializes
 * a Method using a name and anything convertible to a std::function.
 * This is most useful for registering a lambda as a RN method.  There
 * are overloads to support functions which take no arguments,
 * arguments only, and zero, one, or two callbacks.
 *
 * The second set of methods is similar, but instead of taking a
 * function, takes the method name, an object, and a pointer to a
 * method on that object.
 */

class CxxModule {
public:
  typedef std::function<void(std::vector<folly::dynamic>)> Callback;

  struct Method {
    std::string name;
    size_t callbacks;
    std::function<void(folly::dynamic, Callback, Callback)> func;

    // std::function/lambda ctors

    Method(std::string aname,
           std::function<void()>&& afunc)
      : name(std::move(aname))
      , callbacks(0)
      , func(std::bind(std::move(afunc))) {}

    Method(std::string aname,
           std::function<void(folly::dynamic)>&& afunc)
      : name(std::move(aname))
      , callbacks(0)
      , func(std::bind(std::move(afunc), _1)) {}

    Method(std::string aname,
           std::function<void(folly::dynamic, Callback)>&& afunc)
      : name(std::move(aname))
      , callbacks(1)
      , func(std::bind(std::move(afunc), _1, _2)) {}

    Method(std::string aname,
           std::function<void(folly::dynamic, Callback, Callback)>&& afunc)
      : name(std::move(aname))
      , callbacks(2)
      , func(std::move(afunc)) {}

    // method pointer ctors

    template <typename T>
    Method(std::string aname, T* t, void (T::*method)())
      : name(std::move(aname))
      , callbacks(0)
      , func(std::bind(method, t)) {}

    template <typename T>
    Method(std::string aname, T* t, void (T::*method)(folly::dynamic))
      : name(std::move(aname))
      , callbacks(0)
      , func(std::bind(method, t, _1)) {}

    template <typename T>
    Method(std::string aname, T* t, void (T::*method)(folly::dynamic, Callback))
      : name(std::move(aname))
      , callbacks(1)
      , func(std::bind(method, t, _1, _2)) {}

    template <typename T>
    Method(std::string aname, T* t, void (T::*method)(folly::dynamic, Callback, Callback))
      : name(std::move(aname))
      , callbacks(2)
      , func(std::bind(method, t, _1, _2, _3)) {}
  };

  /**
   * This may block, if necessary to complete cleanup before the
   * object is destroyed.
   */
  virtual ~CxxModule() {}

  /**
   * @return the name of this module. This will be the name used to {@code require()} this module
   * from javascript.
   */
  virtual std::string getName() = 0;

  /**
   * Each entry in the map will be exported as a property to JS.  The
   * key is the property name, and the value can be anything.
   */
  virtual auto getConstants() -> std::map<std::string, folly::dynamic> = 0;

  /**
   * @return a list of methods this module exports to JS.
   */
  virtual auto getMethods() -> std::vector<Method> = 0;
};

}}}

#endif
