// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//! A collection of a all the interactions a `Connection` is interested in

use s2n_quic_core::time::Timestamp;

/// A collection of a all the interactions a `Connection` is interested in
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct ConnectionInterests {
    /// Is `true` if the `Connection` has entered it's final state and
    /// can therefore be removed from the `Connection` map.
    pub finalization: bool,
    /// Is `true` if the `Connection` has entered the closing state and
    /// shared state should be freed
    pub closing: bool,
    /// Is `true` if a `Connection` completed the handshake and should be transferred
    /// to the application via an accept call.
    pub accept: bool,
    /// Is `true` if a `Connection` wants to send data
    pub transmission: bool,
    /// Is `true` if a `Connection` needs a new connection id
    pub new_connection_id: bool,
    /// Is `true` if a `Connection` should attempt to receive or send ACKs
    pub ack: bool,
    /// Is `Some(Timestamp)` if the connection needs to be woken up at the specified time
    pub timeout: Option<Timestamp>,
}

impl ConnectionInterests {
    /// Merges 2 `ConnectionInterests` collections.
    ///
    /// For most interests, if at least one `ConnectionInterests` instance is
    /// interested in a certain interaction, the interest will be set on the
    /// returned `ConnectionInterests` instance.
    ///
    ///
    /// Thereby the operation performs a field-wise logical `OR`
    ///
    /// The `finalization` interest is the exception. A `Connection` can only
    /// be finalized if all parts are interested in finalization.
    pub fn merge(self, other: ConnectionInterests) -> ConnectionInterests {
        ConnectionInterests {
            finalization: self.finalization && other.finalization,
            closing: self.closing && other.closing,
            accept: self.accept || other.accept,
            transmission: self.transmission || other.transmission,
            new_connection_id: self.new_connection_id || other.new_connection_id,
            ack: self.ack || other.ack,
            timeout: match (self.timeout, other.timeout) {
                (Some(a), Some(b)) => Some(a.min(b)),
                (Some(a), None) => Some(a),
                (None, Some(b)) => Some(b),
                (None, None) => None,
            },
        }
    }
}

// Overload the `+` and `+=` operator for `ConnectionInterests` to support merging
// multiple interest sets.

impl core::ops::Add for ConnectionInterests {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        self.merge(rhs)
    }
}

impl core::ops::AddAssign for ConnectionInterests {
    fn add_assign(&mut self, rhs: Self) {
        *self = self.merge(rhs);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::time::Duration;

    #[test]
    fn test_merge_connection_interests() {
        let a = ConnectionInterests {
            transmission: false,
            accept: true,
            finalization: true,
            closing: true,
            new_connection_id: false,
            ack: false,
            timeout: None,
        };

        let b_time = unsafe { Timestamp::from_duration(Duration::from_secs(123)) };
        let b = ConnectionInterests {
            transmission: true,
            accept: false,
            finalization: false,
            closing: false,
            new_connection_id: true,
            ack: true,
            timeout: Some(b_time),
        };

        let c_time = unsafe { Timestamp::from_duration(Duration::from_secs(456)) };
        let c = ConnectionInterests {
            transmission: false,
            accept: false,
            finalization: true,
            closing: true,
            new_connection_id: false,
            ack: false,
            timeout: Some(c_time),
        };

        assert_eq!(
            ConnectionInterests {
                transmission: true,
                accept: true,
                finalization: false,
                closing: false,
                new_connection_id: true,
                ack: true,
                timeout: Some(b_time),
            },
            a + b
        );

        assert_eq!(
            ConnectionInterests {
                transmission: false,
                accept: true,
                finalization: true,
                closing: true,
                new_connection_id: false,
                ack: false,
                timeout: Some(c_time),
            },
            a + c
        );

        assert_eq!(
            ConnectionInterests {
                transmission: true,
                accept: false,
                finalization: false,
                closing: false,
                new_connection_id: true,
                ack: true,
                timeout: Some(b_time),
            },
            b + c
        );
    }
}