#ifndef PROTON_LINK_H
#define PROTON_LINK_H 1

/*
 *
 * 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 <proton/import_export.h>
#include <proton/type_compat.h>
#include <proton/condition.h>
#include <proton/terminus.h>
#include <proton/types.h>
#include <proton/object.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @file
 *
 * @copybrief link
 *
 * @addtogroup link
 * @{
 */

/**
 * Construct a new sender on a session.
 *
 * Each sending link between two AMQP containers must be uniquely
 * named. Note that this uniqueness cannot be enforced at the API
 * level, so some consideration should be taken in choosing link
 * names.
 *
 * @param[in] session the session object
 * @param[in] name the name of the link
 * @return a newly constructed sender link or NULL on error
 */
PN_EXTERN pn_link_t *pn_sender(pn_session_t *session, const char *name);

/**
 * Construct a new receiver on a session.
 *
 * Each receiving link between two AMQP containers must be uniquely
 * named. Note that this uniqueness cannot be enforced at the API
 * level, so some consideration should be taken in choosing link
 * names.
 *
 * @param[in] session the session object
 * @param[in] name the name of the link
 * @return a newly constructed receiver link or NULL on error
 */
PN_EXTERN pn_link_t *pn_receiver(pn_session_t *session, const char *name);

/**
 * Free a link object.
 *
 * When a link object is freed, all ::pn_delivery_t objects associated
 * with the session are also freed. Freeing a link will settle any
 * unsettled deliveries on the link.
 *
 * @param[in] link a link object to free (or NULL)
 */
PN_EXTERN void pn_link_free(pn_link_t *link);

/**
 * Get the application context that is associated with a link object.
 *
 * The application context for a link may be set using
 * ::pn_link_set_context.
 *
 * @param[in] link the link whose context is to be returned.
 * @return the application context for the link object
 */
PN_EXTERN void *pn_link_get_context(pn_link_t *link);

/**
 * Set a new application context for a link object.
 *
 * The application context for a link object may be retrieved using
 * ::pn_link_get_context.
 *
 * @param[in] link the link object
 * @param[in] context the application context
 */
PN_EXTERN void pn_link_set_context(pn_link_t *link, void *context);

/**
 * Get the attachments that are associated with a link object.
 *
 * @param[in] link the link whose attachments are to be returned.
 * @return the attachments for the link object
 */
PN_EXTERN pn_record_t *pn_link_attachments(pn_link_t *link);

/**
 * Get the name of a link.
 *
 * @param[in] link a link object
 * @return the name of the link
 */
PN_EXTERN const char *pn_link_name(pn_link_t *link);

/**
 * Test if a link is a sender.
 *
 * @param[in] link a link object
 * @return true if and only if the link is a sender
 */
PN_EXTERN bool pn_link_is_sender(pn_link_t *link);

/**
 * Test if a link is a receiver.
 *
 * @param[in] link a link object
 * @return true if and only if the link is a receiver
 */
PN_EXTERN bool pn_link_is_receiver(pn_link_t *link);

/**
 * Get the endpoint state flags for a link.
 *
 * @param[in] link the link
 * @return the link's state flags
 */
PN_EXTERN pn_state_t pn_link_state(pn_link_t *link);

/**
 * **Deprecated** - Use ::pn_link_condition().
 *
 * Get additional error information associated with the link.
 *
 * Whenever a link operation fails (i.e. returns an error code),
 * additional error details can be obtained using this function. The
 * error object that is returned may also be used to clear the error
 * condition.
 *
 * The pointer returned by this operation is valid until the
 * link object is freed.
 *
 * @param[in] link the link object
 * @return the link's error object
 */
/* PN_DEPRECATED("Use pn_link_condition") */
PN_EXTERN pn_error_t *pn_link_error(pn_link_t *link);

/**
 * Get the local condition associated with a link endpoint.
 *
 * The ::pn_condition_t object retrieved may be modified prior to
 * closing a link in order to indicate a particular condition
 * exists when the link closes. This is normally used to
 * communicate error conditions to the remote peer, however it may
 * also be used in non error cases. See ::pn_condition_t for more
 * details.
 *
 * The pointer returned by this operation is valid until the link
 * object is freed.
 *
 * @param[in] link the link object
 * @return the link's local condition object
 */
PN_EXTERN pn_condition_t *pn_link_condition(pn_link_t *link);

/**
 * Get the remote condition associated with a link endpoint.
 *
 * The ::pn_condition_t object retrieved may be examined in order to
 * determine whether the remote peer was indicating some sort of
 * exceptional condition when the remote link endpoint was
 * closed. The ::pn_condition_t object returned may not be modified.
 *
 * The pointer returned by this operation is valid until the
 * link object is freed.
 *
 * @param[in] link the link object
 * @return the link's remote condition object
 */
PN_EXTERN pn_condition_t *pn_link_remote_condition(pn_link_t *link);

/**
 * Get the parent session for a link object.
 *
 * This operation retrieves the parent ::pn_session_t object that
 * contains the given ::pn_link_t object.
 *
 * @param[in] link the link object
 * @return the parent session object
 */
PN_EXTERN pn_session_t *pn_link_session(pn_link_t *link);

/**
 * Retrieve the first link that matches the given state mask.
 *
 * Examines the state of each link owned by the connection and returns
 * the first link that matches the given state mask. If state contains
 * both local and remote flags, then an exact match against those
 * flags is performed. If state contains only local or only remote
 * flags, then a match occurs if any of the local or remote flags are
 * set respectively. state==0 matches all links.
 *
 * @param[in] connection to be searched for matching Links
 * @param[in] state mask to match
 * @return the first link owned by the connection that matches the
 * mask, else NULL if no links match
 */
PN_EXTERN pn_link_t *pn_link_head(pn_connection_t *connection, pn_state_t state);

/**
 * Retrieve the next link that matches the given state mask.
 *
 * When used with pn_link_head, the application can access all links
 * on the connection that match the given state. See pn_link_head for
 * description of match behavior.
 *
 * @param[in] link the previous link obtained from pn_link_head or
 *                 pn_link_next
 * @param[in] state mask to match
 * @return the next session owned by the connection that matches the
 * mask, else NULL if no sessions match
 */
PN_EXTERN pn_link_t *pn_link_next(pn_link_t *link, pn_state_t state);

/**
 * Open a link.
 *
 * Once this operation has completed, the PN_LOCAL_ACTIVE state flag
 * will be set.
 *
 * @param[in] link a link object
 */
PN_EXTERN void pn_link_open(pn_link_t *link);

/**
 * Close a link.
 *
 * Once this operation has completed, the PN_LOCAL_CLOSED state flag
 * will be set. This may be called without calling
 * ::pn_link_open, in this case it is equivalent to calling
 * ::pn_link_open followed by ::pn_link_close.
 *
 * @param[in] link a link object
 */
PN_EXTERN void pn_link_close(pn_link_t *link);

/**
 * Detach a link.
 *
 * @param[in] link a link object
 */
PN_EXTERN void pn_link_detach(pn_link_t *link);

/**
 * Access the locally defined source definition for a link.
 *
 * The pointer returned by this operation is valid until the link
 * object is freed.
 *
 * @param[in] link a link object
 * @return a pointer to a source terminus
 */
PN_EXTERN pn_terminus_t *pn_link_source(pn_link_t *link);

/**
 * Access the locally defined target definition for a link.
 *
 * The pointer returned by this operation is valid until the link
 * object is freed.
 *
 * @param[in] link a link object
 * @return a pointer to a target terminus
 */
PN_EXTERN pn_terminus_t *pn_link_target(pn_link_t *link);

/**
 * Access the remotely defined source definition for a link.
 *
 * The pointer returned by this operation is valid until the link
 * object is freed. The remotely defined terminus will be empty until
 * the link is remotely opened as indicated by the PN_REMOTE_ACTIVE
 * flag.
 *
 * @param[in] link a link object
 * @return a pointer to the remotely defined source terminus
 */
PN_EXTERN pn_terminus_t *pn_link_remote_source(pn_link_t *link);

/**
 * Access the remotely defined target definition for a link.
 *
 * The pointer returned by this operation is valid until the link
 * object is freed. The remotely defined terminus will be empty until
 * the link is remotely opened as indicated by the PN_REMOTE_ACTIVE
 * flag.
 *
 * @param[in] link a link object
 * @return a pointer to the remotely defined target terminus
 */
PN_EXTERN pn_terminus_t *pn_link_remote_target(pn_link_t *link);

/**
 * Get the current delivery for a link.
 *
 * Each link maintains a sequence of deliveries in the order they were
 * created, along with a pointer to the *current* delivery. All
 * send/recv operations on a link take place on the *current*
 * delivery. If a link has no current delivery, the current delivery
 * is automatically initialized to the next delivery created on the
 * link. Once initialized, the current delivery remains the same until
 * it is changed through use of ::pn_link_advance or until it is
 * settled via ::pn_delivery_settle.
 *
 * @param[in] link a link object
 * @return the current delivery for the link, or NULL if there is none
 */
PN_EXTERN pn_delivery_t *pn_link_current(pn_link_t *link);

/**
 * Advance the current delivery of a link to the next delivery on the
 * link.
 *
 * For sending links this operation is used to finish sending message
 * data for the current outgoing delivery and move on to the next
 * outgoing delivery (if any).
 *
 * For receiving links, this operation is used to finish accessing
 * message data from the current incoming delivery and move on to the
 * next incoming delivery (if any).
 *
 * Each link maintains a sequence of deliveries in the order they were
 * created, along with a pointer to the *current* delivery. The
 * pn_link_advance operation will modify the *current* delivery on the
 * link to point to the next delivery in the sequence. If there is no
 * next delivery in the sequence, the current delivery will be set to
 * NULL. This operation will return true if invoking it caused the
 * value of the current delivery to change, even if it was set to
 * NULL.
 *
 * @param[in] link a link object
 * @return true if the current delivery was changed
 */
PN_EXTERN bool pn_link_advance(pn_link_t *link);

/**
 * Get the credit balance for a link.
 *
 * Links use a credit based flow control scheme. Every receiver
 * maintains a credit balance that corresponds to the number of
 * deliveries that the receiver can accept at any given moment. As
 * more capacity becomes available at the receiver (see
 * ::pn_link_flow), it adds credit to this balance and communicates
 * the new balance to the sender. Whenever a delivery is
 * sent/received, the credit balance maintained by the link is
 * decremented by one. Once the credit balance at the sender reaches
 * zero, the sender must pause sending until more credit is obtained
 * from the receiver.
 *
 * Note that a sending link may still be used to send deliveries even
 * if pn_link_credit reaches zero, however those deliveries will end
 * up being buffered by the link until enough credit is obtained from
 * the receiver to send them over the wire. In this case the balance
 * reported by ::pn_link_credit will go negative.
 *
 * @param[in] link a link object
 * @return the credit balance for the link
 */
PN_EXTERN int pn_link_credit(pn_link_t *link);

/**
 * Get the number of queued deliveries for a link.
 *
 * Links may queue deliveries for a number of reasons, for example
 * there may be insufficient credit to send them to the receiver (see
 * ::pn_link_credit), or they simply may not have yet had a chance to
 * be written to the wire. This operation will return the number of
 * queued deliveries on a link.
 *
 * @param[in] link a link object
 * @return the number of queued deliveries for the link
 */
PN_EXTERN int pn_link_queued(pn_link_t *link);

/**
 * Get the remote view of the credit for a link.
 *
 * The remote view of the credit for a link differs from local view of
 * credit for a link by the number of queued deliveries. In other
 * words ::pn_link_remote_credit is defined to be ::pn_link_credit -
 * ::pn_link_queued.
 *
 * @param[in] link a link object
 * @return the remote view of the credit for a link
 */
PN_EXTERN int pn_link_remote_credit(pn_link_t *link);

/**
 * Get the drain flag for a link.
 *
 * If a link is in drain mode, then the sending endpoint of a link
 * must immediately use up all available credit on the link. If this
 * is not possible, the excess credit must be returned by invoking
 * ::pn_link_drained. Only the receiving endpoint can set the drain
 * mode. See ::pn_link_set_drain for details.
 *
 * @param[in] link a link object
 * @return true if and only if the link is in drain mode
 */
PN_EXTERN bool pn_link_get_drain(pn_link_t *link);

/**
 * Drain excess credit for a link.
 *
 * When a link is in drain mode, the sender must use all excess credit
 * immediately, and release any excess credit back to the receiver if
 * there are no deliveries available to send.
 *
 * When invoked on a sending link that is in drain mode, this
 * operation will release all excess credit back to the receiver and
 * return the number of credits released back to the sender. If the
 * link is not in drain mode, this operation is a noop.
 *
 * When invoked on a receiving link, this operation will return and
 * reset the number of credits the sender has released back to the
 * receiver.
 *
 * @param[in] link a link object
 * @return the number of credits drained
 */
PN_EXTERN int pn_link_drained(pn_link_t *link);

/**
 * Get the available deliveries hint for a link.
 *
 * The available count for a link provides a hint as to the number of
 * deliveries that might be able to be sent if sufficient credit were
 * issued by the receiving link endpoint. See ::pn_link_offered for
 * more details.
 *
 * @param[in] link a link object
 * @return the available deliveries hint
 */
PN_EXTERN int pn_link_available(pn_link_t *link);

/**
 * Describes the permitted/expected settlement behaviours of a sending
 * link.
 *
 * The sender settle mode describes the permitted and expected
 * behaviour of a sending link with respect to settling of deliveries.
 * See ::pn_delivery_settle for more details.
 */
typedef enum {
  PN_SND_UNSETTLED = 0, /**< The sender will send all deliveries
                           initially unsettled. */
  PN_SND_SETTLED = 1, /**< The sender will send all deliveries settled
                         to the receiver. */
  PN_SND_MIXED = 2 /**< The sender may send a mixture of settled and
                      unsettled deliveries. */
} pn_snd_settle_mode_t;

/**
 * Describes the permitted/expected settlement behaviours of a
 * receiving link.
 *
 * The receiver settle mode describes the permitted and expected
 * behaviour of a receiving link with respect to settling of
 * deliveries. See ::pn_delivery_settle for more details.
 */
typedef enum {
  PN_RCV_FIRST = 0,  /**< The receiver will settle deliveries
                        regardless of what the sender does. */
  PN_RCV_SECOND = 1  /**< The receiver will only settle deliveries
                        after the sender settles. */
} pn_rcv_settle_mode_t;

/**
 * Get the local sender settle mode for a link.
 *
 * @param[in] link a link object
 * @return the local sender settle mode
 */
PN_EXTERN pn_snd_settle_mode_t pn_link_snd_settle_mode(pn_link_t *link);

/**
 * Get the local receiver settle mode for a link.
 *
 * @param[in] link a link object
 * @return the local receiver settle mode
 */
PN_EXTERN pn_rcv_settle_mode_t pn_link_rcv_settle_mode(pn_link_t *link);

/**
 * Set the local sender settle mode for a link.
 *
 * @param[in] link a link object
 * @param[in] mode the sender settle mode
 */
PN_EXTERN void pn_link_set_snd_settle_mode(pn_link_t *link, pn_snd_settle_mode_t mode);

/**
 * Set the local receiver settle mode for a link.
 *
 * @param[in] link a link object
 * @param[in] mode the receiver settle mode
 */
PN_EXTERN void pn_link_set_rcv_settle_mode(pn_link_t *link, pn_rcv_settle_mode_t mode);

/**
 * Get the remote sender settle mode for a link.
 *
 * @param[in] link a link object
 * @return the remote sender settle mode
 */
PN_EXTERN pn_snd_settle_mode_t pn_link_remote_snd_settle_mode(pn_link_t *link);

/**
 * Get the remote receiver settle mode for a link.
 *
 * @param[in] link a link object
 * @return the remote receiver settle mode
 */
PN_EXTERN pn_rcv_settle_mode_t pn_link_remote_rcv_settle_mode(pn_link_t *link);

/**
 * Get the number of unsettled deliveries for a link.
 *
 * @param[in] link a link object
 * @return the number of unsettled deliveries
 */
PN_EXTERN int pn_link_unsettled(pn_link_t *link);

/**
 * Get the first unsettled delivery for a link.
 *
 " @param[in] link a link object
 * @return a pointer to the first unsettled delivery on the link
 */
PN_EXTERN pn_delivery_t *pn_unsettled_head(pn_link_t *link);

/**
 * Get the next unsettled delivery on a link.
 *
 * @param[in] delivery a delivery object
 * @return the next unsettled delivery on the link
 */
PN_EXTERN pn_delivery_t *pn_unsettled_next(pn_delivery_t *delivery);

/**
 * Signal the availability of deliveries for a link.
 *
 * @param[in] sender a sender link object
 * @param[in] credit the number of deliveries potentially available
 * for transfer
 */
PN_EXTERN void pn_link_offered(pn_link_t *sender, int credit);

/**
 * Send message data for the current delivery on a link.
 *
 * @param[in] sender a sender link object
 * @param[in] bytes the start of the message data
 * @param[in] n the number of bytes of message data
 * @return the number of bytes sent, or an error code
 */
PN_EXTERN ssize_t pn_link_send(pn_link_t *sender, const char *bytes, size_t n);

/**
 * Grant credit for incoming deliveries on a receiver.
 *
 * @param[in] receiver a receiving link object
 * @param[in] credit the amount to increment the link credit
 */
PN_EXTERN void pn_link_flow(pn_link_t *receiver, int credit);

/**
 * Grant credit for incoming deliveries on a receiver, and set drain
 * mode to true.
 *
 * Use ::pn_link_set_drain to set the drain mode explicitly.
 *
 * @param[in] receiver a receiving link object
 * @param[in] credit the amount to increment the link credit
 */
PN_EXTERN void pn_link_drain(pn_link_t *receiver, int credit);

/**
 * Set the drain mode on a link.
 *
 * @param[in] receiver a receiving link object
 * @param[in] drain the drain mode
 */
PN_EXTERN void pn_link_set_drain(pn_link_t *receiver, bool drain);

/**
 * Receive message data for the current delivery on a link.
 *
 * Use ::pn_delivery_pending on the current delivery to figure out how
 * much buffer space is needed.
 *
 * Note that the link API can be used to stream large messages across
 * the network, so just because there is no data to read does not
 * imply the message is complete. To ensure the entirety of the
 * message data has been read, either invoke ::pn_link_recv until
 * PN_EOS is returned, or verify that
 *
 *     (!pn_delivery_partial(d) && !pn_delivery_aborted(d) && pn_delivery_pending(d)==0)
 *
 * @param[in] receiver a receiving link object
 * @param[in] bytes a pointer to an empty buffer
 * @param[in] n the buffer capacity
 * @return The number of bytes received, or an error code:
 *   - ::PN_EOS: The message has been completely received
 *   - 0: No data available now.
 *     If pn_delivery_partial() there will be further ::PN_DELIVERY events with more data.
 *   - ::PN_STATE_ERR: The link has no current delivery
 *   - ::PN_ABORTED: See pn_delivery_aborted()
 */
PN_EXTERN ssize_t pn_link_recv(pn_link_t *receiver, char *bytes, size_t n);

/**
 * Check if a link is currently draining.
 *
 * A link is defined to be draining when drain mode is set to true,
 * and the sender still has excess credit.
 *
 * @param[in] receiver a receiving link object
 * @return true if the link is currently draining, false otherwise
 */
PN_EXTERN bool pn_link_draining(pn_link_t *receiver);

/**
 * **Unsettled API** - Get the maximum message size for a link.
 *
 * A zero maximum message size means the size is unlimited.
 *
 * @param[in] link a link object
 * @return the maximum message size for a link.
 */
PN_EXTERN uint64_t pn_link_max_message_size(pn_link_t *link);

/**
 * **Unsettled API** - Set the maximum message size for a link.
 *
 * A zero maximum message size means the size is unlimited.
 *
 * @param[in] link a link object
 * @param[in] size the maximum message size for the link
 */
PN_EXTERN void pn_link_set_max_message_size(pn_link_t *link, uint64_t size);

/**
 * **Unsettled API** - Get the remote view of the maximum message size for a link.
 *
 * A zero maximum message size means the size is unlimited.
 *
 * @param[in] link a link object
 * @return the remote view of the maximum message size for a link
 */
PN_EXTERN uint64_t pn_link_remote_max_message_size(pn_link_t *link);

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* link.h */

