#pragma once

#ifdef __cplusplus
extern "C"
{
#endif

#include "tuple.h"
#include "stellar/session.h"

struct session_manager_options
{
    // max session number
    uint64_t max_tcp_session_num;
    uint64_t max_udp_session_num;

    // session overload
    uint8_t tcp_overload_evict_old_sess; // 1: evict old session, 0: bypass new session
    uint8_t udp_overload_evict_old_sess; // 1: evict old session, 0: bypass new session

    // TCP timeout
    uint64_t tcp_init_timeout;           // range: [1, 60000] (ms)
    uint64_t tcp_handshake_timeout;      // range: [1, 60000] (ms)
    uint64_t tcp_data_timeout;           // range: [1, 15999999000] (ms)
    uint64_t tcp_half_closed_timeout;    // range: [1, 604800000] (ms)
    uint64_t tcp_time_wait_timeout;      // range: [1, 600000] (ms)
    uint64_t tcp_discard_timeout;        // range: [1, 15999999000] (ms)
    uint64_t tcp_unverified_rst_timeout; // range: [1, 600000] (ms)
    // UDP timeout
    uint64_t udp_data_timeout;    // range: [1, 15999999000] (ms)
    uint64_t udp_discard_timeout; // range: [1, 15999999000] (ms)

    // duplicate packet filter
    uint8_t duplicated_packet_filter_enable;
    uint32_t duplicated_packet_filter_capacity; // range: [1, 4294967295]
    uint32_t duplicated_packet_filter_timeout;  // range: [1, 60000] (ms)
    double duplicated_packet_filter_error_rate; // range: [0.0, 1.0]

    // evicted session filter
    uint8_t evicted_session_filter_enable;
    uint32_t evicted_session_filter_capacity; // range: [1, 4294967295]
    uint32_t evicted_session_filter_timeout;  // range: [1, 60000] (ms)
    double evicted_session_filter_error_rate; // range: [0.0, 1.0]

    // TCP reassembly
    uint8_t tcp_reassembly_enable;
    uint32_t tcp_reassembly_max_timeout;  // range: [1, 60000] (ms)
    uint32_t tcp_reassembly_max_segments; // range: [2, 512]
};

struct __attribute__((aligned(64))) session_manager_stat
{
    // TCP session
    uint64_t history_tcp_sessions;
    uint64_t tcp_sess_used;
    uint64_t tcp_sess_opening;
    uint64_t tcp_sess_active;
    uint64_t tcp_sess_closing;
    uint64_t tcp_sess_discard;
    uint64_t tcp_sess_closed;

    // UDP session
    uint64_t history_udp_sessions;
    uint64_t udp_sess_used;
    uint64_t udp_sess_opening;
    uint64_t udp_sess_active;
    uint64_t udp_sess_closing;
    uint64_t udp_sess_discard;
    uint64_t udp_sess_closed;

    // Evicted session
    uint64_t tcp_sess_evicted; // sum
    uint64_t udp_sess_evicted; // sum

    // Packet
    uint64_t udp_pkts_bypass_table_full;        // sum
    uint64_t tcp_pkts_bypass_table_full;        // sum
    uint64_t tcp_pkts_bypass_session_not_found; // sum
    uint64_t tcp_pkts_bypass_duplicated;        // sum
    uint64_t udp_pkts_bypass_duplicated;        // sum
    uint64_t udp_pkts_bypass_session_evicted;   // sum

    // TCP segments
    uint64_t tcp_segs_input;            // sum
    uint64_t tcp_segs_consumed;         // sum
    uint64_t tcp_segs_timeout;          // sum
    uint64_t tcp_segs_retransmited;     // sum
    uint64_t tcp_segs_overlapped;       // sum
    uint64_t tcp_segs_omitted_too_many; // sum
    uint64_t tcp_segs_inorder;          // sum
    uint64_t tcp_segs_reordered;        // sum
    uint64_t tcp_segs_buffered;         // sum
    uint64_t tcp_segs_freed;            // sum
};

enum session_scan_flags
{
    SESSION_SCAN_TYPE = 1 << 0,
    SESSION_SCAN_STATE = 1 << 1,

    SESSION_SCAN_SIP = 1 << 2,
    SESSION_SCAN_DIP = 1 << 3,

    SESSION_SCAN_SPORT = 1 << 4,
    SESSION_SCAN_DPORT = 1 << 5,

    SESSION_SCAN_CREATE_TIME = 1 << 6,
    SESSION_SCAN_LAST_PKT_TIME = 1 << 7,
};

struct session_scan_opts
{
    // required
    uint32_t flags;
    uint32_t cursor;
    uint32_t count;

    // optional
    enum session_type type;
    enum session_state state;

    uint32_t addr_family;         // AF_INET or AF_INET6
    union ip_address src_addr[2]; // network byte order
    union ip_address dst_addr[2]; // network byte order
    uint16_t src_port;            // network byte order
    uint16_t dst_port;            // network byte order

    uint64_t create_time_ms[2];
    uint64_t last_pkt_time_ms[2];
};

struct session_manager;
struct session_manager *session_manager_new(struct session_manager_options *opts, uint64_t now_ms);
void session_manager_free(struct session_manager *mgr);

typedef uint64_t (*session_id_generate_fn)(uint64_t now_ms);
void session_manager_set_session_id_generator(struct session_manager *mgr, session_id_generate_fn generator);
void session_manager_record_duplicated_packet(struct session_manager *mgr, const struct packet *pkt);

struct session *session_manager_new_session(struct session_manager *mgr, const struct packet *pkt, uint64_t now_ms);
void session_manager_free_session(struct session_manager *mgr, struct session *sess);

struct session *session_manager_lookup_session_by_packet(struct session_manager *mgr, const struct packet *pkt);
struct session *session_manager_lookup_session_by_id(struct session_manager *mgr, uint64_t sess_id);
int session_manager_update_session(struct session_manager *mgr, struct session *sess, const struct packet *pkt, uint64_t now_ms);

// return session need free by session_manager_free_session()
struct session *session_manager_get_expired_session(struct session_manager *mgr, uint64_t now_ms);
struct session *session_manager_get_evicted_session(struct session_manager *mgr);

// return  0: have already timeout session
// return >0: next expire interval
uint64_t session_manager_get_expire_interval(struct session_manager *mgr);

struct session_manager_stat *session_manager_stat(struct session_manager *mgr);
uint64_t session_manager_scan(const struct session_manager *mgr, const struct session_scan_opts *opts, uint64_t mached_sess_ids[], uint64_t array_size);

#ifdef __cplusplus
}
#endif
