/*
 * Copyright (c) 2021 Fastly, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>
#include "khash.h"
#include "quicly.h"
#include "quicly/sendstate.h"
#include "quicly/recvstate.h"
#include "quicly_mock.h"

KHASH_MAP_INIT_INT64(quicly_stream_t, quicly_stream_t *)

mquicly_context_t mquicly_context;

struct st_quicly_conn_t {
    struct _st_quicly_conn_public_t super;
    khash_t(quicly_stream_t) * streams;
    struct {
        quicly_address_t local, remote;
    } address;
    struct {
        quicly_error_t err;
        uint64_t frame_type;
        char *reason_phrase;
        int is_remote;
    } connection_close;
};

struct st_quicly_send_context_t {
};

static quicly_conn_t *create_connection(quicly_context_t *ctx, int is_client, struct sockaddr *remote_addr,
                                        struct sockaddr *local_addr)
{
    quicly_conn_t *conn = calloc(1, sizeof(*conn));
    assert(conn != NULL);
    conn->super.ctx = ctx;
    if (is_client) {
        conn->super.local.bidi.next_stream_id = 0;
        conn->super.local.uni.next_stream_id = 2;
        conn->super.remote.bidi.next_stream_id = 1;
        conn->super.remote.uni.next_stream_id = 3;
    } else {
        conn->super.local.bidi.next_stream_id = 1;
        conn->super.local.uni.next_stream_id = 3;
        conn->super.remote.bidi.next_stream_id = 0;
        conn->super.remote.uni.next_stream_id = 2;
    }
    conn->streams = kh_init(quicly_stream_t);

    conn->address.local.sa = *local_addr;
    conn->address.remote.sa = *remote_addr;

    memset(&conn->connection_close, 0, sizeof(conn->connection_close));

    return conn;
}

quicly_error_t quicly_accept(quicly_conn_t **conn, quicly_context_t *ctx, struct sockaddr *dest_addr, struct sockaddr *src_addr,
                             quicly_decoded_packet_t *packet, quicly_address_token_plaintext_t *address_token,
                             const quicly_cid_plaintext_t *new_cid, ptls_handshake_properties_t *handshake_properties,
                             void *appdata)
{
    *conn = create_connection(ctx, 0, src_addr, dest_addr);
    (*conn)->super.state = QUICLY_STATE_CONNECTED;
    return 0;
}

int quicly_stream_sync_sendbuf(quicly_stream_t *stream, int activate)
{
    int ret;

    if (activate) {
        if ((ret = quicly_sendstate_activate(&stream->sendstate)) != 0)
            return ret;
    }

    quicly_stream_scheduler_t *scheduler = stream->conn->super.ctx->stream_scheduler;
    scheduler->update_state(scheduler, stream);
    return 0;
}

void quicly_stream_sync_recvbuf(quicly_stream_t *stream, size_t shift_amount)
{
    stream->recvstate.data_off += shift_amount;
}

int quicly_stream_can_send(quicly_stream_t *stream, int at_stream_level)
{
    /* return if there is nothing to be sent */
    if (stream->sendstate.pending.num_ranges == 0)
        return 0;
    return 1;
}

ptls_t *quicly_get_tls(quicly_conn_t *conn)
{
    /* TODO: is this okay */
    return NULL;
}

int quicly_is_blocked(quicly_conn_t *conn)
{
    return 0;
}

struct sockaddr *quicly_get_sockname(quicly_conn_t *conn)
{
    return &conn->address.local.sa;
}

struct sockaddr *quicly_get_peername(quicly_conn_t *conn)
{
    return &conn->address.remote.sa;
}

void quicly_request_stop(quicly_stream_t *stream, quicly_error_t err)
{
    stream->_send_aux.stop_sending.sender_state = QUICLY_SENDER_STATE_SEND;
}

void quicly_reset_stream(quicly_stream_t *stream, quicly_error_t err)
{
    /* dispose sendbuf state */
    quicly_sendstate_reset(&stream->sendstate);

    stream->_send_aux.reset_stream.sender_state = QUICLY_SENDER_STATE_SEND;

    /* inline expansion of resched_stream_data() */
    /* TODO: consider streams_blocked? */
    quicly_stream_scheduler_t *scheduler = stream->conn->super.ctx->stream_scheduler;
    scheduler->update_state(scheduler, stream);
}

socklen_t quicly_get_socklen(struct sockaddr *sa)
{
    switch (sa->sa_family) {
    case AF_INET:
        return sizeof(struct sockaddr_in);
    case AF_INET6:
        return sizeof(struct sockaddr_in6);
    default:
        assert(!"unexpected socket type");
        return 0;
    }
}

size_t quicly_decode_packet(quicly_context_t *ctx, quicly_decoded_packet_t *packet, const uint8_t *datagram, size_t datagram_size,
                            size_t *off)
{
    assert(0 && "unimplemented");
    return 0;
}

quicly_error_t quicly_send_stream(quicly_stream_t *stream, quicly_send_context_t *s)
{
    /* quicly_send -> scheduler->do_send -> quicly_send_stream -> on_send_emit */
    uint8_t buff[1024];
    uint64_t off = stream->sendstate.pending.ranges[0].start, end_off;
    size_t capacity = sizeof(buff);
    int wrote_all = 0, is_fin;
    quicly_error_t ret;

    if (!quicly_sendstate_is_open(&stream->sendstate) && off == stream->sendstate.final_size) {
        /* special case for emitting FIN only */
        end_off = off;
        wrote_all = 1;
        is_fin = 1;
        goto UpdateState;
    }
    { /* cap the capacity to the current range */
        uint64_t range_capacity = stream->sendstate.pending.ranges[0].end - off;
        if (!quicly_sendstate_is_open(&stream->sendstate) && off + range_capacity > stream->sendstate.final_size) {
            assert(range_capacity > 1); /* see the special case above */
            range_capacity -= 1;
        }
        if (capacity > range_capacity)
            capacity = range_capacity;
    }
    size_t len = capacity;
    size_t emit_off = (size_t)(off - stream->sendstate.acked.ranges[0].end);
    stream->callbacks->on_send_emit(stream, emit_off, buff, &len, &wrote_all);

    end_off = off + len;

    /* determine if the frame incorporates FIN */
    if (!quicly_sendstate_is_open(&stream->sendstate) && end_off == stream->sendstate.final_size) {
        is_fin = 1;
    } else {
        is_fin = 0;
    }

UpdateState:
    /* notify the fuzzing driver of stream send event */
    if (mquicly_context.on_stream_send != NULL) {
        mquicly_context.on_stream_send->cb(mquicly_context.on_stream_send, stream->conn, stream, buff, off, len, is_fin);
    }
    if (stream->sendstate.size_inflight < end_off) {
        stream->sendstate.size_inflight = end_off;
    }
    if ((ret = quicly_ranges_subtract(&stream->sendstate.pending, off, end_off + is_fin)) != 0)
        return ret;
    if (wrote_all) {
        if ((ret = quicly_ranges_subtract(&stream->sendstate.pending, stream->sendstate.size_inflight, UINT64_MAX)) != 0)
            return ret;
    }

    return 0;
}

quicly_error_t quicly_get_close_reason(quicly_conn_t *conn, uint64_t *frame_type, const char **reason_phrase, int *is_remote)
{
    assert(conn->super.state >= QUICLY_STATE_CLOSING);
    if (frame_type != NULL)
        *frame_type = conn->connection_close.frame_type;
    if (reason_phrase != NULL)
        *reason_phrase = conn->connection_close.reason_phrase;
    if (is_remote != NULL)
        *is_remote = conn->connection_close.is_remote;
    return conn->connection_close.err;
}

static quicly_stream_t *open_stream(quicly_conn_t *conn, quicly_stream_id_t stream_id)
{
    quicly_stream_t *stream;

    if ((stream = calloc(1, sizeof(*stream))) == NULL)
        return NULL;
    stream->conn = conn;
    stream->stream_id = stream_id;
    stream->callbacks = NULL;
    stream->data = NULL;

    int r;
    khiter_t iter = kh_put(quicly_stream_t, conn->streams, stream_id, &r);
    assert(iter != kh_end(conn->streams));
    kh_val(conn->streams, iter) = stream;

    int is_client = quicly_is_client(stream->conn);

    if (quicly_stream_has_send_side(is_client, stream->stream_id)) {
        quicly_sendstate_init(&stream->sendstate);
    } else {
        quicly_sendstate_init_closed(&stream->sendstate);
    }
    if (quicly_stream_has_receive_side(is_client, stream->stream_id)) {
        quicly_recvstate_init(&stream->recvstate);
    } else {
        quicly_recvstate_init_closed(&stream->recvstate);
    }

    return stream;
}

static struct st_quicly_conn_streamgroup_state_t *get_streamgroup_state(quicly_conn_t *conn, quicly_stream_id_t stream_id)
{
    if (quicly_is_client(conn) == quicly_stream_is_client_initiated(stream_id)) {
        return quicly_stream_is_unidirectional(stream_id) ? &conn->super.local.uni : &conn->super.local.bidi;
    } else {
        return quicly_stream_is_unidirectional(stream_id) ? &conn->super.remote.uni : &conn->super.remote.bidi;
    }
}

int mquicly_open_stream(quicly_conn_t *conn, quicly_stream_t **stream, int is_remote_initiated, int unidirectional)
{
    struct st_quicly_conn_streamgroup_state_t *group;

    if (is_remote_initiated) {
        group = unidirectional ? &conn->super.remote.uni : &conn->super.remote.bidi;
    } else {
        group = unidirectional ? &conn->super.local.uni : &conn->super.local.bidi;
    }

    *stream = open_stream(conn, group->next_stream_id);
    group->next_stream_id += 4;
    group->num_streams++;

    conn->super.ctx->stream_open->cb(conn->super.ctx->stream_open, *stream);

    return 0;
}

static void destroy_stream(quicly_stream_t *stream, int err)
{
    quicly_conn_t *conn = stream->conn;

    if (stream->callbacks != NULL)
        stream->callbacks->on_destroy(stream, err);

    khiter_t iter = kh_get(quicly_stream_t, conn->streams, stream->stream_id);
    assert(iter != kh_end(conn->streams));
    kh_del(quicly_stream_t, conn->streams, iter);

    struct st_quicly_conn_streamgroup_state_t *group = get_streamgroup_state(conn, stream->stream_id);
    --group->num_streams;

    quicly_sendstate_dispose(&stream->sendstate);
    quicly_recvstate_dispose(&stream->recvstate);

    free(stream);
}

static void destroy_all_streams(quicly_conn_t *conn, int err)
{
    quicly_stream_t *stream;
    kh_foreach_value(conn->streams, stream, { destroy_stream(stream, err); });
    assert(quicly_num_streams(conn) == 0);
}

void mquicly_closed(quicly_conn_t *conn, quicly_error_t err, uint64_t frame_type, ptls_iovec_t reason_phrase, int is_remote)
{
    /* TODO: invoke conn->super.ctx->closed->cb() but h2o does not use it so far */
    assert(conn->super.ctx->closed == NULL);

    conn->connection_close.err = err;
    conn->connection_close.frame_type = frame_type;
    conn->connection_close.reason_phrase = malloc(reason_phrase.len + 1);
    memcpy(conn->connection_close.reason_phrase, reason_phrase.base, reason_phrase.len);
    conn->connection_close.reason_phrase[reason_phrase.len] = '\0';
    conn->connection_close.is_remote = is_remote;

    conn->super.state = is_remote ? QUICLY_STATE_DRAINING : QUICLY_STATE_CLOSING;
    destroy_all_streams(conn, err);
}

quicly_error_t quicly_open_stream(quicly_conn_t *conn, quicly_stream_t **stream, int unidirectional)
{
    return mquicly_open_stream(conn, stream, 0, unidirectional);
}

quicly_error_t quicly_get_or_open_stream(quicly_conn_t *conn, uint64_t stream_id, quicly_stream_t **stream)
{
    /* conn->super.ctx->stream_open->cb */
    assert(0 && "unimplemented");
    return 0;
}

int quicly_is_destination(quicly_conn_t *conn, struct sockaddr *dest_addr, struct sockaddr *src_addr,
                          quicly_decoded_packet_t *decoded)
{
    assert(0 && "unimplemented");
    return 0;
}

uint32_t quicly_num_streams_by_group(quicly_conn_t *conn, int uni, int locally_initiated)
{
    int server_initiated = quicly_is_client(conn) != locally_initiated;
    struct st_quicly_conn_streamgroup_state_t *state = get_streamgroup_state(conn, uni * 2 + server_initiated);
    return state->num_streams;
}

quicly_error_t quicly_get_delivery_rate(quicly_conn_t *conn, quicly_rate_t *delivery_rate)
{
    /* Do nothing */
    *delivery_rate = (quicly_rate_t){};
    return 0;
}

quicly_error_t quicly_get_stats(quicly_conn_t *conn, quicly_stats_t *stats)
{
    /* Do nothing */
    memset(stats, 0, sizeof(*stats));
    return 0;
}

void quicly_stream_noop_on_destroy(quicly_stream_t *stream, quicly_error_t err)
{
}

void quicly_stream_noop_on_send_shift(quicly_stream_t *stream, size_t delta)
{
}

void quicly_stream_noop_on_send_emit(quicly_stream_t *stream, size_t off, void *dst, size_t *len, int *wrote_all)
{
}

void quicly_stream_noop_on_send_stop(quicly_stream_t *stream, quicly_error_t err)
{
}

void quicly_stream_noop_on_receive(quicly_stream_t *stream, size_t off, const void *src, size_t len)
{
}

void quicly_stream_noop_on_receive_reset(quicly_stream_t *stream, quicly_error_t err)
{
}

const quicly_stream_callbacks_t quicly_stream_noop_callbacks = {
    quicly_stream_noop_on_destroy,   quicly_stream_noop_on_send_shift, quicly_stream_noop_on_send_emit,
    quicly_stream_noop_on_send_stop, quicly_stream_noop_on_receive,    quicly_stream_noop_on_receive_reset};

size_t quicly_send_version_negotiation(quicly_context_t *ctx, ptls_iovec_t dest_cid, ptls_iovec_t src_cid, const uint32_t *versions,
                                       void *payload)
{
    assert(0 && "unimplemented");
    return 0;
}

size_t quicly_send_stateless_reset(quicly_context_t *ctx, const void *src_cid, void *payload)
{
    assert(0 && "unimplemented");
    return 0;
}

int quicly_can_send_data(quicly_conn_t *conn, quicly_send_context_t *s)
{
    return 1;
}

quicly_error_t quicly_connect(quicly_conn_t **conn, quicly_context_t *ctx, const char *server_name, struct sockaddr *dest_addr,
                              struct sockaddr *src_addr, const quicly_cid_plaintext_t *new_cid, ptls_iovec_t address_token,
                              ptls_handshake_properties_t *handshake_properties,
                              const quicly_transport_parameters_t *resumed_transport_params, void *appdata)
{
    assert(0 && "unimplemented");
    return 0;
}

int quicly_connection_is_ready(quicly_conn_t *conn)
{
    assert(0 && "unimplemented");
    return 0;
}

void quicly_amend_ptls_context(ptls_context_t *ptls)
{
    assert(0 && "unimplemented");
}

quicly_error_t quicly_receive(quicly_conn_t *conn, struct sockaddr *dest_addr, struct sockaddr *src_addr,
                              quicly_decoded_packet_t *packet)
{
    assert(0 && "unimplemented");
    return 0;
}

quicly_error_t quicly_close(quicly_conn_t *conn, quicly_error_t err, const char *reason_phrase)
{
    if (conn->super.state >= QUICLY_STATE_CLOSING)
        return 0;

    conn->super.state = QUICLY_STATE_CLOSING;
    return 0;
}

int64_t quicly_get_first_timeout(quicly_conn_t *conn)
{
    /* TODO: simulate delay */
    return conn->super.ctx->now->cb(conn->super.ctx->now) + 1;
}

void quicly_free(quicly_conn_t *conn)
{
    destroy_all_streams(conn, 0);
    kh_destroy(quicly_stream_t, conn->streams);
    free(conn->connection_close.reason_phrase);
    free(conn);
}

quicly_error_t quicly_send(quicly_conn_t *conn, quicly_address_t *dest, quicly_address_t *src, struct iovec *datagrams,
                           size_t *num_datagrams, void *buf, size_t bufsize)
{
    quicly_send_context_t s = {};
    quicly_error_t ret;
    if (conn->super.state >= QUICLY_STATE_CLOSING) {
        ret = QUICLY_ERROR_FREE_CONNECTION;
        goto Exit;
    }
    ret = conn->super.ctx->stream_scheduler->do_send(conn->super.ctx->stream_scheduler, conn, &s);

Exit:
    *num_datagrams = 0;
    return ret;
}

uint8_t quicly_send_get_ecn_bits(quicly_conn_t *conn)
{
    return 0;
}

int64_t quicly_foreach_stream(quicly_conn_t *conn, void *thunk, int64_t (*cb)(void *thunk, quicly_stream_t *stream))
{
    assert(0 && "unimplemented");
    return 0;
}

quicly_stream_t *quicly_get_stream(quicly_conn_t *conn, quicly_stream_id_t stream_id)
{
    assert(0 && "unimplemented");
    return NULL;
}

void quicly_send_datagram_frames(quicly_conn_t *conn, ptls_iovec_t *datagrams, size_t num_datagrams)
{
    assert(0 && "unimplemented");
}

void quicly_get_max_data(quicly_conn_t *conn, uint64_t *send_permitted, uint64_t *sent, uint64_t *consumed)
{
    if (send_permitted != NULL)
        *send_permitted = 0xdeadbeef;
    if (sent != NULL)
        *sent = 0xdeadbeef;
    if (consumed != NULL)
        *consumed = 0xdeadbeef;
}

quicly_error_t quicly_send_resumption_token(quicly_conn_t *conn)
{
    return 0;
}

const uint32_t quicly_supported_versions[] = {QUICLY_PROTOCOL_VERSION_1, QUICLY_PROTOCOL_VERSION_DRAFT29,
                                              QUICLY_PROTOCOL_VERSION_DRAFT27, 0};
