#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "stellar/tuple.h"
#include "stellar/session_mq.h"
#include "packet_inject_main.h"

struct packet_inject_plugin_ctx
{
    struct stellar *st;
    int sess_plug_id;
    int tcp_topic_id;
    int udp_topic_id;
    char name[64];
};

static void *on_sess_new(struct session *sess, void *plugin_ctx)
{
    struct packet_inject_plugin_ctx *ctx = (struct packet_inject_plugin_ctx *)plugin_ctx;
    printf("[%s] pluign handle session new: %s\n", ctx->name, session_get0_readable_addr(sess));
    return NULL;
}

static void on_sess_free(struct session *sess, void *sess_ctx, void *plugin_ctx)
{
    struct packet_inject_plugin_ctx *ctx = (struct packet_inject_plugin_ctx *)plugin_ctx;
    printf("[%s] pluign handle session free: %s\n", ctx->name, session_get0_readable_addr(sess));
}

static void on_sess_msg(struct session *sess, int topic_id, const void *msg, void *sess_ctx, void *plugin_ctx)
{
    struct packet_inject_plugin_ctx *ctx = (struct packet_inject_plugin_ctx *)plugin_ctx;
    printf("[%s] pluign handle session msg: %s (C2S received packets: %lu, S2C received packets: %lu)\n",
           ctx->name, session_get0_readable_addr(sess),
           session_get_stat(sess, FLOW_DIRECTION_C2S, STAT_RAW_PACKETS_RECEIVED),
           session_get_stat(sess, FLOW_DIRECTION_S2C, STAT_RAW_PACKETS_RECEIVED));

    char buffer[1024] = {0};
    // struct packet *pkt = (struct packet *)msg;
    const struct tuple6 *tuple = session_get_tuple6(sess);
    if (rule.ip_type == 4 &&
        memcmp(&tuple->src_addr.v4, &rule.v4, sizeof(struct in_addr)) &&
        memcmp(&tuple->dst_addr.v4, &rule.v4, sizeof(struct in_addr)))
    {
        return;
    }
    if (rule.ip_type == 6 &&
        memcmp(&tuple->src_addr.v6, &rule.v6, sizeof(struct in6_addr)) &&
        memcmp(&tuple->dst_addr.v6, &rule.v6, sizeof(struct in6_addr)))
    {
        return;
    }
    if (rule.port != 0 && tuple->src_port != rule.port && tuple->dst_port != rule.port)
    {
        return;
    }
    if (session_get_stat(sess, FLOW_DIRECTION_C2S, STAT_INJECTED_PACKETS_SUCCESS) > 0 ||
        session_get_stat(sess, FLOW_DIRECTION_S2C, STAT_INJECTED_PACKETS_SUCCESS) > 0)
    {
        return;
    }
    if (rule.direction == AFTER_RECV_C2S_N_PACKET && session_get_stat(sess, FLOW_DIRECTION_C2S, STAT_RAW_PACKETS_RECEIVED) != rule.number)
    {
        return;
    }
    if (rule.direction == AFTER_RECV_S2C_N_PACKET && session_get_stat(sess, FLOW_DIRECTION_S2C, STAT_RAW_PACKETS_RECEIVED) != rule.number)
    {
        return;
    }
    switch (rule.inject_type)
    {
    case INJECT_TYPE_TCP_RST:
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_C2S);
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_S2C);
        session_set_discard(sess);
        break;
    case INJECT_TYPE_TCP_FIN:
        stellar_inject_tcp_fin(ctx->st, sess, FLOW_DIRECTION_C2S);
        stellar_inject_tcp_fin(ctx->st, sess, FLOW_DIRECTION_S2C);
        session_set_discard(sess);
        break;
    case INJECT_TYPE_TCP_PAYLOAD:
        snprintf(buffer, sizeof(buffer), "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s", 5 + 5 + 2, "Hello");
        stellar_inject_tcp_payload(ctx->st, sess, FLOW_DIRECTION_S2C, buffer, strlen(buffer)); // inject payload to client
        stellar_inject_tcp_payload(ctx->st, sess, FLOW_DIRECTION_S2C, "World\r\n", 7);         // inject payload to client
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_S2C);                             // inject RST to client
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_C2S);                             // inject RST to server
        session_set_discard(sess);
        break;
    case INJECT_TYPE_TCP_PAYLOAD_FIN_RST:
        snprintf(buffer, sizeof(buffer), "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s", 5 + 5 + 2, "Hello");
        stellar_inject_tcp_payload(ctx->st, sess, FLOW_DIRECTION_S2C, buffer, strlen(buffer)); // inject payload to client
        stellar_inject_tcp_payload(ctx->st, sess, FLOW_DIRECTION_S2C, "World\r\n", 7);         // inject payload to client
        stellar_inject_tcp_fin(ctx->st, sess, FLOW_DIRECTION_S2C);                             // inject FIN to client
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_S2C);                             // inject RST to client
        stellar_inject_tcp_fin(ctx->st, sess, FLOW_DIRECTION_C2S);                             // inject FIN to server
        stellar_inject_tcp_rst(ctx->st, sess, FLOW_DIRECTION_C2S);                             // inject RST to server
        session_set_discard(sess);
        break;
    case INJECT_TYPE_UDP_PAYLOAD:
        stellar_inject_udp_payload(ctx->st, sess, FLOW_DIRECTION_C2S, "Hello Server", 12);
        stellar_inject_udp_payload(ctx->st, sess, FLOW_DIRECTION_S2C, "Hello Client", 12);
        session_set_discard(sess);
        break;
    case INJECT_TYPE_CTRL_MSG:
        // TOOD
        break;
    default:
        break;
    }
}

extern "C"
{
    void *packet_inject_plugin_init(struct stellar *st)
    {
        struct packet_inject_plugin_ctx *ctx = (struct packet_inject_plugin_ctx *)calloc(1, sizeof(struct packet_inject_plugin_ctx));
        if (ctx == NULL)
        {
            return NULL;
        }
        ctx->st = st;
        ctx->sess_plug_id = stellar_session_plugin_register(st, on_sess_new, on_sess_free, ctx);
        ctx->tcp_topic_id = stellar_session_mq_get_topic_id(st, TOPIC_TCP);
        ctx->udp_topic_id = stellar_session_mq_get_topic_id(st, TOPIC_UDP);
        snprintf(ctx->name, sizeof(ctx->name), "packet_inject");

        stellar_session_mq_subscribe(st, ctx->tcp_topic_id, on_sess_msg, ctx->sess_plug_id);
        stellar_session_mq_subscribe(st, ctx->udp_topic_id, on_sess_msg, ctx->sess_plug_id);

        printf("[%s] plugin init\n", ctx->name);

        return ctx;
    }

    void packet_inject_plugin_exit(void *plugin_ctx)
    {
        struct packet_inject_plugin_ctx *ctx = (struct packet_inject_plugin_ctx *)plugin_ctx;
        if (ctx)
        {
            printf("[%s] plugin exit\n", ctx->name);
            free(ctx);
        }
    }
}
