// # NDNLPv2 API: NACK in client Face


// ## extending ndn::Face class

namespace ndn {

typedef function<void(const Interest&, const Data&)> DataCallback;
typedef function<void(const Interest&, const lp::Nack&)> NackCallback;
typedef function<void(const Interest&)> TimeoutCallback;
// note: OnData and OnTimeout typedefs were mistakes, because type names should be a noun.
//       Also, OnData shouldn't need a mutable reference for the Data.
//       Therefore, this API is using a new set of callbacks.

partial class Face
{
public: // consumer
  /** \brief sends an Interest
   *  \param interest the Interest; a copy will be made, so that the caller
   *                  is not required to maintain the argument unchanged
   *  \param afterSatisfied a function to be invoked if a Data is returned
   *  \param afterNacked a function to be invoked if a Network NACK is returned
   *  \param afterTimeout a function to be invoked if neither Data nor Network NACK
   *                      is returned within InterestLifetime
   */
  const PendingInterestId*
  expressInterest(const Interest& interest,
                  const DataCallback& afterSatisfied,
                  const NackCallback& afterNacked,
                  const TimeoutCallback& afterTimeout);
  // note: I wanted to make the last two parameters optional, but it would cause compilation error
  //       because std::function<> template has aggresive constructor which takes everything,
  //       so that the compiler thinks an expressInterest call with two or three arguments
  //       is ambiguous with the deprecated form below.
  //       Declaring XCallback as plain function signatures instead of std::function helps,
  //       but that would reject std::bind arguments.
  //       Thus, this has to require four arguments for now.
  //       After the other form is removed, the last two arguments could default to nullptr.

  /** \deprecated use expressInterest(Interest, DataCallback, NackCallback, TimeoutCallback)
   */
  const PendingInterestId*
  expressInterest(const Interest& interest,
                  const OnData& onData,
                  const OnTimeout& onTimeout = nullptr);

public: // producer
  /** \brief sends a Network NACK
   *  \param nack the Nack; a copy will be made, so that the caller
   *              is not required to maintain the argument unchanged
   */
  void
  put(const lp::Nack& nack);
};

} // namespace ndn


// ## implementation detail: PendingInterest struct

/** \brief represents a pending Interest
 *  \note This type is implementation detail of Face class.
 *
 *  The Face maintains a collection of PendingInterests.
 *  A PendingInterest is added when an Interest is expressed,
 *  and is removed when it's satisified by a Data packet,
 *  rejected by a Nack, or timed out.
 */
partial class PendingInterest
{
public:
  PendingInterest(shared_ptr<const Interest> interest,
                  const DataCallback& dataCallback,
                  const NackCallback& nackCallback,
                  const TimeoutCallback& timeoutCallback,
                  Scheduler& scheduler);

  /** \return the Interest
   */
  shared_ptr<const Interest>
  getInterest() const;

  /** \brief invokes the DataCallback
   *  \note If the DataCallback is an empty function, this method does nothing.
   */
  void
  invokeDataCallback(const Data& data);

  /** \brief invokes the NackCallback
   *  \note If the NackCallback is an empty function, this method does nothing.
   */
  void
  invokeNackCallback(const lp::Nack& nack);

private:
  shared_ptr<const Interest> m_interest;
  DataCallback m_dataCallback;
  NackCallback m_nackCallback;
  TimeoutCallback m_timeoutCallback;
};
// unchanged: setDeleter, invokeTimeoutCallback, m_timeoutEvent, m_deleter

// ## implementation detail: FaceImpl

partial class FaceImpl
{
public:
  void
  nackPendingInterests(const Nack& nack);
  // note: The procedure is similar to satisfyPendingInterests.

  void
  asyncExpressInterest(shared_ptr<const Interest> interest,
                       const DataCallback& afterSatisfied,
                       const NackCallback& afterNacked,
                       const TimeoutCallback& afterTimeout);

  void
  asyncPutNack(shared_ptr<const Nack> nack);
  // note: It's intentional to use shared_ptr<const Nack> instead of const shared_ptr<const Nack>& because that's semantically wrong.
};

// asyncExpressInterest, asyncPutData, asyncPutNack should construct lp::Packet from every network layer packet,
// and set LocalControlHeader fields into LpPacket.

// ## implementation detail: Face

// expressInterest is updated to call asyncExpressInterest with afterNacked argument.

/** \brief extract local fields from NDNLPv2 packet and tag onto a network layer packet
 */
template<typename NETPKT>
static void
extractLpLocalFields(NETPKT& netPacket, const lp::Packet& lpPacket);
// note: This extracts IncomingFaceId field and puts it into LocalControlHeader.
//       Eventually LocalControlHeader will be deprecated and replaced with a Tag.
// This template requires Nack class to be extended with 'nfd::LocalControlHeader& getLocalControlHeader()' API,
// but the storage of this field can be redirected to the Interest contained within.

void
Face::onReceiveElement(const Block& blockFromDaemon)
{
  lp::Packet lpPacket(blockFromDaemon); // bare Interest/Data is a valid lp::Packet, no need to distinguish

  Block netPacket = /* extract Fragment as a Block */;
  switch (netPacket.type()) {
  case tlv::Interest:
    shared_ptr<Interest> interest = /* parse netPacket as Interest */;
    if (lpPacket.has<lp::NackField>()) {
      auto nack = make_shared<lp::Nack>(std::move(*interest));
      interest.reset();
      extractLpLocalFields(*nack, lpPacket);
      m_impl->nackPendingInterests(*nack);
    }
    else {
      extractLpLocalFields(*interest, lpPacket);
      /* incoming Interest processing */
    }
    break;
  case tlv::Data:
    shared_ptr<Data> data = /* parse netPacket as Data */;
    extractLpLocalFields(*data, lpPacket);
    /* incoming Data processing */
    break;
  }
}