#include "test.h"
#include "packet.h"

extern size_t variable_length_int_encode(uint16_t value, Buffer *buffer);
extern size_t variable_length_int_size(uint16_t value);
extern size_t utf8_string_encode(char *string, Buffer *buffer);
extern Buffer *make_buffer_for_header(size_t sz, MQTTControlPacketType type);

// Variable length int length calculation
TestResult test_vl_int_0(void) {
    TESTASSERT(variable_length_int_size(0) == 1, "Values < 128 should fit in one byte");
    TEST_OK();
}

TestResult test_vl_int_127(void) {
    TESTASSERT(variable_length_int_size(127) == 1, "Values < 128 should fit in one byte");
    TEST_OK();
}

TestResult test_vl_int_128(void) {
    TESTASSERT(variable_length_int_size(128) == 2, "Values < 16384 should fit in two bytes");
    TEST_OK();
}

TestResult test_vl_int_16383(void) {
    TESTASSERT(variable_length_int_size(16383) == 2, "Values < 16384 should fit in two bytes");
    TEST_OK();
}

TestResult test_vl_int_16384(void) {
    TESTASSERT(variable_length_int_size(16384) == 3, "Values < 2097151 should fit in three bytes");
    TEST_OK();
}

// Variable length int data check

TestResult test_vl_int_data_0(void) {
    char data[] = { 0 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(0, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_vl_int_data_127(void) {
    char data[] = { 127 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(127, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_vl_int_data_128(void) {
    char data[] = { 0x80, 0x01 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(128, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_vl_int_data_16383(void) {
    char data[] = { 0xff, 0x7f };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(16383, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_vl_int_data_16384(void) {
    char data[] = { 0x80, 0x80, 0x01 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(16384, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_vl_int_data_32767(void) {
    char data[] = { 0xff, 0xff, 0x01 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    variable_length_int_encode(32767, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

// UTF-8 String encoding

TestResult test_utf8_string_null(void) {
    char data[] = { 0x00, 0x00 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    (void)utf8_string_encode(NULL, buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_utf8_string_empty(void) {
    char data[] = { 0x00, 0x00 };
    Buffer *buffer = buffer_allocate(sizeof(data));
    (void)utf8_string_encode("", buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_utf8_string_hello(void) {
    char data[] = { 0x00, 0x05, 'h', 'e', 'l', 'l', 'o' };
    Buffer *buffer = buffer_allocate(sizeof(data));
    (void)utf8_string_encode("hello", buffer);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

// make header
TestResult test_make_header(void) {
    char data[] = { 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 };
    Buffer *buffer = make_buffer_for_header(5, PacketTypeConnect);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        buffer
    );
}

TestResult test_encode_connect_simple(void) {
    char data[] = {
        0x10, 0x10, // header
        0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x02, 0x00, 0x0a, // var header
        0x00, 0x04, 't', 'e', 's', 't' // client id
    };
    ConnectPayload *payload = calloc(1, sizeof(ConnectPayload));

    payload->client_id = "test";
    payload->protocol_level = 4;
    payload->keepalive_interval = 10;
    payload->clean_session = 1;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_connect_will(void) {
    char data[] = {
        0x10, 0x2d, // header
        0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0x2e, 0x00, 0x0a, // var header
        0x00, 0x04, 't', 'e', 's', 't', // client id
        0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l',
        0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd',

    };
    ConnectPayload *payload = calloc(1, sizeof(ConnectPayload));

    payload->client_id = "test";
    payload->protocol_level = 4;
    payload->keepalive_interval = 10;
    payload->clean_session = 1;

    payload->will_topic = "test/lastwill";
    payload->will_message = "disconnected";
    payload->will_qos = MQTT_QOS_1;
    payload->retain_will = true;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_connect_auth(void) {
    char data[] = {
        0x10, 0x39, // header
        0x00, 0x04, 'M', 'Q', 'T', 'T', 0x04, 0xee, 0x00, 0x0a, // var header
        0x00, 0x04, 't', 'e', 's', 't', // client id
        0x00, 0x0d, 't', 'e', 's', 't', '/', 'l', 'a', 's', 't', 'w', 'i', 'l', 'l',
        0x00, 0x0c, 'd', 'i', 's', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'e', 'd',
        0x00, 0x04, 'a', 'n', 'o', 'n', // username
        0x00, 0x04, 't', 'e', 's', 't' // password
    };
    ConnectPayload *payload = calloc(1, sizeof(ConnectPayload));

    payload->client_id = "test";
    payload->protocol_level = 4;
    payload->keepalive_interval = 10;
    payload->clean_session = 1;

    payload->will_topic = "test/lastwill";
    payload->will_message = "disconnected";
    payload->will_qos = MQTT_QOS_1;
    payload->retain_will = true;

    payload->username = "anon";
    payload->password = "test";

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnect, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_connack(void) {
    char data[] = {
        0x20, 0x02, // header
        0x01, // session present
        0x00 // accepted
    };
    ConnAckPayload *payload = calloc(1, sizeof(ConnAckPayload));

    payload->session_present = true;
    payload->status = ConnAckStatusAccepted;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeConnAck, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_publish_no_msg(void) {
    char data[] = {
        0x33, 0x0e, // header, qos1, retain
        0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c',
        0x00, 0x0a // packet id
    };
    PublishPayload *payload = calloc(1, sizeof(PublishPayload));

    payload->qos = MQTT_QOS_1;
    payload->retain = true;
    payload->topic = "test/topic";
    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_publish_with_msg(void) {
    char data[] = {
        0x3b, 0x15, // header, qos1, retain
        0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c',
        0x00, 0x0a, // packet id
        'p', 'a', 'y', 'l', 'o', 'a', 'd'
    };
    PublishPayload *payload = calloc(1, sizeof(PublishPayload));

    payload->qos = MQTT_QOS_1;
    payload->retain = true;
    payload->duplicate = true;
    payload->topic = "test/topic";
    payload->packet_id = 10;
    payload->message = "payload";

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePublish, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_puback(void) {
    char data[] = {
        0x40, 0x02, // header
        0x00, 0x0a  // packet id
    };
    PubAckPayload *payload = calloc(1, sizeof(PubAckPayload));

    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubAck, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_pubrec(void) {
    char data[] = {
        0x50, 0x02, // header
        0x00, 0x0a  // packet id
    };
    PubRecPayload *payload = calloc(1, sizeof(PubRecPayload));

    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRec, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_pubrel(void) {
    char data[] = {
        0x62, 0x02, // header
        0x00, 0x0a  // packet id
    };
    PubRelPayload *payload = calloc(1, sizeof(PubRelPayload));

    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubRel, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_pubcomp(void) {
    char data[] = {
        0x70, 0x02, // header
        0x00, 0x0a  // packet id
    };
    PubCompPayload *payload = calloc(1, sizeof(PubCompPayload));

    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePubComp, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_subscribe(void) {
    char data[] = {
        0x82, 0x0f, // header
        0x00, 0x0a,  // packet id
        0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c',
        0x01 // qos
    };
    SubscribePayload *payload = calloc(1, sizeof(SubscribePayload));

    payload->packet_id = 10;
    payload->topic = "test/topic";
    payload->qos = MQTT_QOS_1;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeSubscribe, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_suback(void) {
    char data[] = {
        0x90, 0x03, // header
        0x00, 0x0a,  // packet id,
        0x02 // status
    };
    SubAckPayload *payload = calloc(1, sizeof(SubAckPayload));

    payload->packet_id = 10;
    payload->status = SubAckStatusQoS2;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeSubAck, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_unsubscribe(void) {
    char data[] = {
        0xa2, 0x0e, // header
        0x00, 0x0a, // packet id
        0x00, 0x0a, 't', 'e', 's', 't', '/', 't', 'o', 'p', 'i', 'c',
    };
    UnsubscribePayload *payload = calloc(1, sizeof(UnsubscribePayload));

    payload->packet_id = 10;
    payload->topic = "test/topic";

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeUnsubscribe, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_unsuback(void) {
    char data[] = {
        0xb0, 0x02, // header
        0x00, 0x0a  // packet id,
    };
    UnsubAckPayload *payload = calloc(1, sizeof(UnsubAckPayload));

    payload->packet_id = 10;

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeUnsubAck, payload });
    free(payload);

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_pingreq(void) {
    char data[] = {
        0xc0, 0x00 // header
    };

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePingReq, NULL });

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_pingresp(void) {
    char data[] = {
        0xd0, 0x00 // header
    };

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypePingResp, NULL });

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TestResult test_encode_disconnect(void) {
    char data[] = {
        0xe0, 0x00 // header
    };

    Buffer *encoded = mqtt_packet_encode(&(MQTTPacket){ PacketTypeDisconnect, NULL });

    return TESTMEMCMP(
        buffer_from_data_copy(data, sizeof(data)),
        encoded
    );
}

TESTS(
    TEST("Variable length int size for 0", test_vl_int_0),
    TEST("Variable length int size for 127", test_vl_int_127),
    TEST("Variable length int size for 128", test_vl_int_128),
    TEST("Variable length int size for 16383", test_vl_int_16383),
    TEST("Variable length int size for 16384", test_vl_int_16384),
    TEST("Variable length int data for 0", test_vl_int_data_0),
    TEST("Variable length int data for 127", test_vl_int_data_127),
    TEST("Variable length int data for 128", test_vl_int_data_128),
    TEST("Variable length int data for 16383", test_vl_int_data_16383),
    TEST("Variable length int data for 16384", test_vl_int_data_16384),
    TEST("Variable length int data for 32767", test_vl_int_data_32767),
    TEST("UTF-8 string encode NULL", test_utf8_string_null),
    TEST("UTF-8 string encode empty string", test_utf8_string_empty),
    TEST("UTF-8 string encode \"hello\"", test_utf8_string_hello),
    TEST("Make header", test_make_header),
    TEST("Encode Connect simple", test_encode_connect_simple),
    TEST("Encode Connect with will", test_encode_connect_will),
    TEST("Encode Connect with auth", test_encode_connect_auth),
    TEST("Encode ConnAck", test_encode_connack),
    TEST("Encode Publish with no message", test_encode_publish_no_msg),
    TEST("Encode Publish with message", test_encode_publish_with_msg),
    TEST("Encode PubAck", test_encode_puback),
    TEST("Encode PubRec", test_encode_pubrec),
    TEST("Encode PubRel", test_encode_pubrel),
    TEST("Encode PubComp", test_encode_pubcomp),
    TEST("Encode Subscribe", test_encode_subscribe),
    TEST("Encode SubAck", test_encode_suback),
    TEST("Encode Unsubscribe", test_encode_unsubscribe),
    TEST("Encode UnsubAck", test_encode_unsuback),
    TEST("Encode PingReq", test_encode_pingreq),
    TEST("Encode PingResp", test_encode_pingresp),
    TEST("Encode Disconnect", test_encode_disconnect)
);