#include "unpack.h"
#include "hevent.h"
#include "herr.h"
#include "hlog.h"
#include "hmath.h"

int hio_unpack(hio_t* io, void* buf, int readbytes) {
    unpack_setting_t* setting = io->unpack_setting;
    switch(setting->mode) {
    case UNPACK_BY_FIXED_LENGTH:
        return hio_unpack_by_fixed_length(io, buf, readbytes);
    case UNPACK_BY_DELIMITER:
        return hio_unpack_by_delimiter(io, buf, readbytes);
    case UNPACK_BY_LENGTH_FIELD:
        return hio_unpack_by_length_field(io, buf, readbytes);
    default:
        hio_read_cb(io, buf, readbytes);
        return readbytes;
    }
}

int hio_unpack_by_fixed_length(hio_t* io, void* buf, int readbytes) {
    const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head;
    const unsigned char* ep = (const unsigned char*)buf + readbytes;
    unpack_setting_t* setting = io->unpack_setting;

    int fixed_length = setting->fixed_length;
    assert(io->readbuf.len >= fixed_length);

    const unsigned char* p = sp;
    int remain = ep - p;
    int handled = 0;
    while (remain >= fixed_length) {
        hio_read_cb(io, (void*)p, fixed_length);
        handled += fixed_length;
        p += fixed_length;
        remain -= fixed_length;
    }

    io->readbuf.head = 0;
    io->readbuf.tail = remain;
    if (remain) {
        // [p, p+remain] => [base, base+remain]
        if (p != (unsigned char*)io->readbuf.base) {
            memmove(io->readbuf.base, p, remain);
        }
    }

    return handled;
}

int hio_unpack_by_delimiter(hio_t* io, void* buf, int readbytes) {
    const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head;
    const unsigned char* ep = (const unsigned char*)buf + readbytes;
    unpack_setting_t* setting = io->unpack_setting;

    unsigned char* delimiter = setting->delimiter;
    int delimiter_bytes = setting->delimiter_bytes;

    const unsigned char* p = (const unsigned char*)buf - delimiter_bytes + 1;
    if (p < sp) p = sp;
    int remain = ep - p;
    int handled = 0;
    int i = 0;
    while (remain >= delimiter_bytes) {
        for (i = 0; i < delimiter_bytes; ++i) {
            if (p[i] != delimiter[i]) {
                goto not_match;
            }
        }
match:
        p += delimiter_bytes;
        remain -= delimiter_bytes;
        hio_read_cb(io, (void*)sp, p - sp);
        handled += p - sp;
        sp = p;
        continue;
not_match:
        ++p;
        --remain;
    }

    remain = ep - sp;
    io->readbuf.head = 0;
    io->readbuf.tail = remain;
    if (remain) {
        // [sp, sp+remain] => [base, base+remain]
        if (sp != (unsigned char*)io->readbuf.base) {
            memmove(io->readbuf.base, sp, remain);
        }
        if (io->readbuf.tail == io->readbuf.len) {
            if (io->readbuf.len >= setting->package_max_length) {
                hloge("recv package over %d bytes!", (int)setting->package_max_length);
                io->error = ERR_OVER_LIMIT;
                hio_close(io);
                return -1;
            }
            int newsize = MIN(io->readbuf.len * 2, setting->package_max_length);
            hio_alloc_readbuf(io, newsize);
        }
    }

    return handled;
}

int hio_unpack_by_length_field(hio_t* io, void* buf, int readbytes) {
    const unsigned char* sp = (const unsigned char*)io->readbuf.base + io->readbuf.head;
    const unsigned char* ep = (const unsigned char*)buf + readbytes;
    unpack_setting_t* setting = io->unpack_setting;

    const unsigned char* p = sp;
    int remain = ep - p;
    int handled = 0;
    unsigned int head_len = setting->body_offset;
    unsigned int body_len = 0;
    unsigned int package_len = head_len;
    const unsigned char* lp = NULL;
    while (remain >= setting->body_offset) {
        body_len = 0;
        lp = p + setting->length_field_offset;
        if (setting->length_field_coding == BIG_ENDIAN) {
            for (int i = 0; i < setting->length_field_bytes; ++i) {
                body_len = (body_len << 8) | (unsigned int)*lp++;
            }
        }
        else if (setting->length_field_coding == LITTLE_ENDIAN) {
            for (int i = 0; i < setting->length_field_bytes; ++i) {
                body_len |= ((unsigned int)*lp++) << (i * 8);
            }
        }
        else if (setting->length_field_coding == ENCODE_BY_VARINT) {
            int varint_bytes = ep - lp;
            body_len = varint_decode(lp, &varint_bytes);
            if (varint_bytes == 0) break;
            if (varint_bytes == -1) {
                hloge("varint is too big!");
                io->error = ERR_OVER_LIMIT;
                hio_close(io);
                return -1;
            }
            head_len = setting->body_offset + varint_bytes - setting->length_field_bytes;
        }
        package_len = head_len + body_len + setting->length_adjustment;
        if (remain >= package_len) {
            hio_read_cb(io, (void*)p, package_len);
            handled += package_len;
            p += package_len;
            remain -= package_len;
        } else {
            break;
        }
    }

    io->readbuf.head = 0;
    io->readbuf.tail = remain;
    if (remain) {
        // [p, p+remain] => [base, base+remain]
        if (p != (unsigned char*)io->readbuf.base) {
            memmove(io->readbuf.base, p, remain);
        }
        if (package_len > io->readbuf.len) {
            if (package_len > setting->package_max_length) {
                hloge("package length over %d bytes!", (int)setting->package_max_length);
                io->error = ERR_OVER_LIMIT;
                hio_close(io);
                return -1;
            }
            int newsize = LIMIT(package_len, io->readbuf.len * 2, setting->package_max_length);
            hio_alloc_readbuf(io, newsize);
        }
    }

    return handled;
}
