# 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.


module Qpid::Proton

  # The base for both Sender and Receiver, providing common functionality
  # between both ends.
  #
  # A Link has a single parent Qpid::Proton::Session instance.
  #
  class Link < Endpoint

    # @private
    PROTON_METHOD_PREFIX = "pn_link"
    # @private
    include Util::Wrapper

    # The sender will send all deliveries initially unsettled.
    SND_UNSETTLED = Cproton::PN_SND_UNSETTLED
    # The sender will send all deliveries settled to the receiver.
    SND_SETTLED = Cproton::PN_SND_SETTLED
    # The sender may send a mixture of settled and unsettled deliveries.
    SND_MIXED = Cproton::PN_SND_MIXED

    # The receiver will settle deliveries regardless of what the sender does.
    RCV_FIRST = Cproton::PN_RCV_FIRST
    # The receiver will only settle deliveries after the sender settles.
    RCV_SECOND = Cproton::PN_RCV_SECOND

    # @!attribute [r] state
    #
    # Returns the endpoint state flags.
    #
    proton_caller :state

    # @deprecated  Use {Sender#open} or {Receiver#open}
    proton_caller :open

    # Close the local end of the link. The remote end may or may not be closed.
    # @param error [Condition] Optional error condition to send with the close.
    def close(error=nil)
      Condition.assign(_local_condition, error)
      Cproton.pn_link_close(@impl)
    end

    # @!method detach
    #
    # Detaches the link.
    proton_caller :detach

    # Advance the current delivery to the next 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 operatoin is used to finish accessing message
    # data from the current incoming delivery and move on to the next incoming
    # delivery (if any).
    #
    # @return [Boolean] True if the current delivery was changed.
    #
    # @see #current
    #
    proton_caller :advance

    proton_caller :unsettled

    # @!attribute [r] credit
    #
    # Returns 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, 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 eve if
    # credit reaches zero. However those deliveries will end up being buffer by
    # the link until enough credit is obtained from the receiver to send them
    # over the wire. In this case the balance reported will go negative.
    #
    # @return [Integer] The credit balance.
    #
    # @see #flow
    #
    proton_caller :credit

    # @!attribute [r] remote_credit
    #
    # Returns the remote view of the credit.
    #
    # The remote view of the credit for a link differs from the local view of
    # credit for a link by the number of queued deliveries. In other words,
    # remote credit is defined as credit - queued.
    #
    # @see #queued
    # @see #credit
    #
    # @return [Integer] The remove view of the credit.
    #
    proton_caller :remote_credit

    # @!attribute [r] available
    #
    # Returns 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.
    #
    # @return [Integer] The available deliveries hint.
    #
    # @see Sender#offered
    #
    proton_caller :available

    # @!attribute [r] queued
    #
    # Returns 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, or they simply may
    # not have yet had a chance to be written to the wire.
    #
    # @return [Integer] The number of queued deliveries.
    #
    # @see #credit
    #
    proton_caller :queued

    # @!attribute [r] name
    #
    # Returns the name of the link.
    #
    # @return [String] The name.
    #
    proton_caller :name

    # @!attribute [r] sender?
    #
    # Returns if the link is a sender.
    #
    # @return [Boolean] True if the link is a sender.
    #
    proton_is  :sender

    # @!attribute [r] receiver?
    #
    # Returns if the link is a receiver.
    #
    # @return [Boolean] True if the link is a receiver.
    #
    proton_is  :receiver

    # @private
    proton_get :attachments

    # Drains excess credit.
    #
    # 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 Sender 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 Receiver, this operation will return and reset the
    # number of credits the sender has released back to it.
    #
    # @return [Integer] The number of credits drained.
    #
    proton_caller :drained

    # @private
    def self.wrap(impl)
      return unless impl
      return fetch_instance(impl, :pn_link_attachments) ||
        (Cproton.pn_link_is_sender(impl) ? Sender : Receiver).new(impl)
    end

    # @private
    def initialize(impl)
      @impl = impl
      self.class.store_instance(self, :pn_link_attachments)
    end

    # Returns additional error information.
    #
    # Whenever a link operation fails (i.e., returns an error code) additional
    # error details can be obtained from this method. Ther error object that is
    # returned may also be used to clear the error condition.
    #
    # @return [Error] The error.
    #
    def error
      Cproton.pn_link_error(@impl)
    end

    # @deprecated use {Session#each_link, Connection#each_link}
    def next(state_mask)
      deprecated __method__, "Session#each_link, Connection#each_link"
      return Link.wrap(Cproton.pn_link_next(@impl, state_mask))
    end

    # Returns the locally defined source terminus.
    #
    # @return [Terminus] The terminus
    def source
      Terminus.new(Cproton.pn_link_source(@impl))
    end

    # Returns the locally defined target terminus.
    #
    # @return [Terminus] The terminus.
    #
    def target
      Terminus.new(Cproton.pn_link_target(@impl))
    end

    # Returns a representation of the remotely defined source terminus.
    #
    # @return [Terminus] The terminus.
    #
    def remote_source
      Terminus.new(Cproton.pn_link_remote_source(@impl))
    end

    # Returns a representation of the remotely defined target terminus.
    #
    # @return [Terminus] The terminus.
    #
    def remote_target
      Terminus.new(Cproton.pn_link_remote_target(@impl))
    end

    # Returns the parent session.
    #
    # @return [Session] The session.
    #
    def session
      Session.wrap(Cproton.pn_link_session(@impl))
    end

    # Returns the parent connection.
    #
    # @return [Connection] The connection.
    #
    def connection
      self.session.connection
    end


    # @deprecated use {Sender#send}
    def delivery(tag)
      deprecated __method__, "Sender#send"
      Delivery.new(Cproton.pn_delivery(@impl, tag))
    end

    # Returns the current delivery.
    #
    # Each link maintains a sequence of deliveries in the order they were
    # created, along with a reference to the *current* delivery. All send and
    # receive operations on a link take place on the *current* delivery. If a
    # link has no current delivery, the current delivery is automatically
    # pointed to the *next* delivery created on the link.
    #
    # Once initialized, the current delivery remains the same until it is
    # changed by advancing, or until it is settled.
    #
    # @see #next
    # @see Delivery#settle
    #
    # @return [Delivery] The current delivery.
    #
    def current
      Delivery.wrap(Cproton.pn_link_current(@impl))
    end

    # Sets the local sender settle mode.
    #
    # @param mode [Integer] The settle mode.
    #
    # @see #SND_UNSETTLED
    # @see #SND_SETTLED
    # @see #SND_MIXED
    #
    def snd_settle_mode=(mode)
      Cproton.pn_link_set_snd_settle_mode(@impl, mode)
    end

    # Returns the local sender settle mode.
    #
    # @return [Integer] The local sender settle mode.
    #
    # @see #snd_settle_mode
    #
    def snd_settle_mode
      Cproton.pn_link_snd_settle_mode(@impl)
    end

    # Sets the local receiver settle mode.
    #
    # @param mode [Integer] The settle mode.
    #
    # @see #RCV_FIRST
    # @see #RCV_SECOND
    #
    def rcv_settle_mode=(mode)
      Cproton.pn_link_set_rcv_settle_mode(@impl, mode)
    end

    # Returns the local receiver settle mode.
    #
    # @return [Integer] The local receiver settle mode.
    #
    def rcv_settle_mode
      Cproton.pn_link_rcv_settle_mode(@impl)
    end

    # @private
    def _local_condition
      Cproton.pn_link_condition(@impl)
    end

    # @private
    def _remote_condition
      Cproton.pn_link_remote_condition(@impl)
    end

    def ==(other)
      other.respond_to?(:impl) &&
      (Cproton.pni_address_of(other.impl) == Cproton.pni_address_of(@impl))
    end

  end

end
