#include <cstddef>
#include <cstdint>
#include <fuzzing/datasource/datasource.hpp>
#include <fuzzing/memory.hpp>
#include <theora/theoradec.h>

#define THEORA_NUM_HEADER_PACKETS 3

class TheoraDecoder {
    private:
        fuzzing::datasource::Datasource& ds;

        th_setup_info* tsi = nullptr;
        th_dec_ctx* ctx = nullptr;
        th_info ti;
        th_comment tc;

        bool initialize(void);
        void processComments(void) const;
        bool decodePacket(void);
        void writeImage(const th_ycbcr_buffer& image) const;
    public:
        TheoraDecoder(fuzzing::datasource::Datasource& ds);
        ~TheoraDecoder(void);
        void Run(void);
};

TheoraDecoder::TheoraDecoder(fuzzing::datasource::Datasource& ds) :
    ds(ds)
{ }

TheoraDecoder::~TheoraDecoder(void) {
    /* noret */ th_info_clear(&ti);
    /* noret */ th_comment_clear(&tc);

    if ( ctx != nullptr ) {
        th_decode_free(ctx);
    }

    if ( tsi != nullptr ) {
        th_setup_free(tsi);
    }
}

bool TheoraDecoder::initialize(void) {
    /* noret */ th_info_init(&ti);
    /* noret */ th_comment_init(&tc);

    for (int i = 0; i < THEORA_NUM_HEADER_PACKETS; i++) {
        ogg_packet op = { 0 };

        std::vector<uint8_t> packet;
        /* Fill packet */
        {
            try {
                packet = ds.GetData(0);
            } catch ( ... ) {
                return false;
            }

            op.packet = packet.data();
            op.bytes = packet.size();
            op.b_o_s = 1;
        }

        if (th_decode_headerin(&ti, &tc, &tsi, &op) < 0) {
            return false;
        }
    }

    /* noret */ processComments();

    /* Limit picture resolution to prevent OOMs */
    if ( ti.frame_width > 1024 || ti.frame_height > 1024 ) {
        return false;
    }

    ctx = th_decode_alloc(&ti, tsi);
    if ( ctx == nullptr ) {
        return false;
    }

    /* noret */ th_setup_free(tsi);
    tsi = nullptr;

    return true;
}

void TheoraDecoder::processComments(void) const {
    for (int i = 0; i < tc.comments; i++) {
        if (tc.user_comments[i]) {
            const int len = tc.comment_lengths[i];
            fuzzing::memory::memory_test(tc.user_comments[i], len);
        }
    }
}

bool TheoraDecoder::decodePacket(void) {
    int err;
    ogg_packet op = { 0 };

    std::vector<uint8_t> packet;

    /* Fill packet */
    {
        memset(&op, 0, sizeof(op));
        packet = ds.GetData(0);
        op.packet = packet.data();
        op.bytes = packet.size();
        op.granulepos = -1;
        /* TODO op.packetno */
    }

    /* Decode */
    {
        ogg_int64_t granulepos;
        err = th_decode_packetin(ctx, &op, &granulepos);
        if (err < 0) {
            return false;
        }

        /* Verify that granulepos has been set */
        fuzzing::memory::memory_test(granulepos);
    }

    /* Write image data */
    if (err != TH_DUPFRAME) {
        th_ycbcr_buffer ycbcrbuf;
        err = th_decode_ycbcr_out(ctx, ycbcrbuf);
        if (err != 0) {
            return false;
        }

        /* noret */ writeImage(ycbcrbuf);
    }

    return true;
}

void TheoraDecoder::writeImage(const th_ycbcr_buffer& ycbcrbuf) const {
    /* Modelled after examples/player_example.c */

    const th_pixel_fmt px_fmt = ti.pixel_fmt;
    const int y_offset = (ti.pic_x&~1) + ycbcrbuf[0].stride * (ti.pic_y&~1);
    const int w = (ti.pic_x+ti.frame_width+1&~1)-(ti.pic_x&~1);
    const int h = (ti.pic_y+ti.frame_height+1&~1)-(ti.pic_y&~1);


    if ( px_fmt == TH_PF_422) {
        const int uv_offset = (ti.pic_x/2) + (ycbcrbuf[1].stride) * (ti.pic_y);

        for(int i = 0; i < h; i++) {
            {
                const uint8_t* in_y = ycbcrbuf[0].data+y_offset+ycbcrbuf[0].stride*i;
                fuzzing::memory::memory_test(in_y, w);
            }

            {
                const uint8_t* in_u = ycbcrbuf[1].data+uv_offset+ycbcrbuf[1].stride*i;
                const uint8_t* in_v = ycbcrbuf[2].data+uv_offset+ycbcrbuf[2].stride*i;
                fuzzing::memory::memory_test(in_u, w >> 1);
                fuzzing::memory::memory_test(in_v, w >> 1);
            }
        }
    } else {
        const int uv_offset = (ti.pic_x/2) + (ycbcrbuf[1].stride) * (ti.pic_y/2);

        for(int i = 0; i < h; i++) {
            fuzzing::memory::memory_test(ycbcrbuf[0].data+y_offset+ycbcrbuf[0].stride*i, w);
        }

        for(int i = 0; i < h/2; i++){
            fuzzing::memory::memory_test(ycbcrbuf[2].data+uv_offset+ycbcrbuf[2].stride*i, w/2);
            fuzzing::memory::memory_test(ycbcrbuf[1].data+uv_offset+ycbcrbuf[1].stride*i, w/2);
        }
    }
}

void TheoraDecoder::Run(void) {
    if ( initialize() == false ) {
        return;
    }

    try {
        size_t numDecoded = 0;
        while ( ++numDecoded < 10 && ds.Get<bool>() == true ) {
            if ( decodePacket() == false ) {
                break;
            }
        }
    } catch ( ... ) { }
}

extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
    return 0;
}

extern "C" int main() {
    unsigned char data[] = {
  	0x2f, 0x00, 0x00, 0x00, 0x80, 0x74, 0x68, 0x65, 0x6f, 0x72, 0x61, 0x00,
  0x77, 0x10, 0x00, 0x3f, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0xfd, 0x00, 0x65, 0xb4, 0xb6, 0x29, 0xb6, 0xb6, 0xb6, 0x00, 0x00,
  0x00, 0x80, 0x01, 0x00, 0x01, 0xeb, 0x57, 0x57, 0x00, 0xf8, 0x07, 0x00,
  0x61, 0x57, 0x1c, 0x4a, 0x00, 0x00, 0x00, 0x81, 0x74, 0x68, 0x65, 0x6f,
  0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x65, 0x6f, 0x6f, 0x62, 0x61, 0x00, 0x77, 0x00, 0x06, 0x3f, 0x60, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6,
  0xff, 0xb6, 0xf7, 0x7f, 0x00, 0xb6, 0xb6, 0xb6, 0x2c, 0xc1, 0x02, 0x00,
  0x40, 0x60, 0x00, 0x00, 0xf8, 0x00, 0x30, 0xb5, 0xe3, 0xe8, 0x00, 0x00,
  0x00, 0x82, 0x74, 0x68, 0x65, 0x6f, 0x72, 0x61, 0x60, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xff, 0xb6,
  0xf7, 0x7f, 0x00, 0xb6, 0xb6, 0xb6, 0x2c, 0xc1, 0x45, 0x00, 0x40, 0x60,
  0x00, 0x00, 0xf8, 0x00, 0x30, 0xb5, 0xe3, 0xe8, 0x00, 0x00, 0x00, 0x82,
  0x74, 0x68, 0x65, 0x6f, 0x72, 0x61, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x00,
  0x00, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x65,
  0x6f, 0x6f, 0x72, 0x61, 0x00, 0x06, 0x60, 0x77, 0x00, 0x3f, 0x00, 0x00,
  0x00, 0x00, 0xb6, 0xb6, 0xb6, 0xb6, 0x01, 0x00, 0x00, 0xb6, 0x25, 0x58,
  0x57, 0x57, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xee, 0xee, 0xee,
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xee, 0xee, 0xee, 0xee,
  0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
  0xee, 0xee, 0xee, 0xee, 0x49, 0x9d, 0x49, 0x25, 0xff, 0xff, 0xff, 0xff,
  0xff, 0xff, 0x7d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xbf, 0xbf,
  0xbf, 0xbf, 0xbf, 0xbf, 0x96, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
  0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
  0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xbf, 0xbf, 0xbf, 0xbf,
  0xbf, 0x96, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
  0xbf, 0xbf, 0x32, 0x40, 0x40, 0x40, 0xbf, 0x40, 0x40, 0x40, 0xbf, 0xbf,
  0xbf, 0xbf, 0xbf, 0xbf, 0x26, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0x03, 0x00,
  0x00, 0x00, 0x68, 0x65, 0x6f, 0x95, 0x9e, 0xff, 0x88, 0xff, 0x00, 0x40,
  0x00, 0x00, 0x00, 0xf9, 0xff, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00,
  0x00, 0x80, 0x74, 0x68
    };
    unsigned int size = 400;

    fuzzing::datasource::Datasource ds(data, size);
    TheoraDecoder decoder(ds);

    decoder.Run();

    return 0;
}
