#include "compressed.hpp"

#include <memory>

#include <limits.h>
#include <stddef.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <malloc.h>

#define COMPRESS_ASSERT(val) if (!(val)) return LZO_E_ERROR
#define M2_MAX_OFFSET   0x0800

#define NEED_OP(x)  if ((unsigned)(op_end - op) < (unsigned)(x)) return LZO_E_OUTPUT_OVERRUN;
#define TEST_LB()   if (m_pos < out || m_pos >= op) return LZO_E_LOOKBEHIND_OVERRUN;

#define COPY4(dst,src)    * (unsigned *)(dst) = * (const unsigned *)(src)

#define LZO_E_OK                    0
#define LZO_E_ERROR                 (-1)
#define LZO_E_OUTPUT_OVERRUN        (-5)
#define LZO_E_LOOKBEHIND_OVERRUN    (-6)
#define LZO_E_OUTPUT_UNDERRUN    (-4)

namespace ace {
    namespace p3d {
        /*lzo1x_decompress_safe   ( const lzo_bytep src, lzo_uint  src_len,
                                lzo_bytep dst, lzo_uintp dst_len,
                                lzo_voidp wrkmem  ) */
        int _compressed_base::_decompress_safe(std::istream & in, uint32_t expected_size) {
            // We read 512 bytes at a time, until we have hit the end of the compressed stream
            uint8_t     *buffer;
            uint32_t    input_size = 0;
            int32_t     result;
            std::streampos   save_pos;

            save_pos = in.tellg();
            buffer = new uint8_t[expected_size + 1024];

            in.read((char *)buffer, expected_size + 1024);
            input_size = in.gcount();
            if (in.eof()) {
                in.clear();
            }
            _data = std::unique_ptr<uint8_t[]>(new uint8_t[expected_size + (expected_size % 8)]);

            result = _mikero_lzo1x_decompress_safe(buffer, _data.get(), expected_size);
            if (result < 0) {
                LOG(ERROR) << "Decompression failed";
                assert(false);
            }
            in.seekg(save_pos);
            in.seekg(result, in.cur);

            delete[] buffer;

            return result;
        }
        int _compressed_base::_mikero_lzo1x_decompress_safe(const uint8_t* in, uint8_t* out, uint32_t OutLen) {
            register uint8_t* op;
            register const uint8_t* ip;
            register size_t t;
            register const uint8_t* m_pos;

            uint8_t* const op_end = out + OutLen;

            OutLen = 0;
            op = out;
            ip = in;

            if (*ip > 17)
            {
                t = *ip++ - 17;
                if (t < 4) goto match_next;
                COMPRESS_ASSERT(t > 0);// return LZO_E_ERROR;
                NEED_OP(t);
                do *op++ = *ip++; while (--t > 0);
                goto first_literal_run;
            }

            while (1)
            {
                t = *ip++;
                if (t >= 16)           goto match;
                if (t == 0)
                {
                    while (*ip == 0)
                    {
                        t += 255;
                        ip++;
                    }
                    t += 15 + *ip++;
                }
                COMPRESS_ASSERT(t > 0); NEED_OP(t + 3);

                COPY4(op, ip);
                op += 4; ip += 4;
                if (--t > 0)
                {
                    if (t >= 4)
                    {
                        do {
                            COPY4(op, ip);
                            op += 4; ip += 4; t -= 4;
                        } while (t >= 4);
                        if (t > 0) do *op++ = *ip++; while (--t > 0);
                    }
                    else
                        do *op++ = *ip++; while (--t > 0);
                }

            first_literal_run:

                t = *ip++;
                if (t >= 16)  goto match;

                m_pos = op - (1 + M2_MAX_OFFSET);
                m_pos -= t >> 2;
                m_pos -= *ip++ << 2;

                TEST_LB();
                NEED_OP(3);
                *op++ = *m_pos++; *op++ = *m_pos++; *op++ = *m_pos;

                goto match_done;

                do {
                match:
                    if (t >= 64)
                    {

                        m_pos = op - 1;
                        m_pos -= (t >> 2) & 7;
                        m_pos -= *ip++ << 3;
                        t = (t >> 5) - 1;
                        TEST_LB();     COMPRESS_ASSERT(t > 0); NEED_OP(t + 3 - 1);
                        goto copy_match;
                    }
                    else if (t >= 32)
                    {
                        t &= 31;
                        if (t == 0)
                        {
                            while (*ip == 0)
                            {
                                t += 255;
                                ip++;
                            }
                            t += 31 + *ip++;
                        }

                        m_pos = op - 1;
                        m_pos -= (ip[0] >> 2) + (ip[1] << 6);

                        ip += 2;
                    }
                    else if (t >= 16)
                    {

                        m_pos = op;
                        m_pos -= (t & 8) << 11;

                        t &= 7;
                        if (t == 0)
                        {
                            while (*ip == 0)
                            {
                                t += 255;
                                ip++;
                            }
                            t += 7 + *ip++;
                        }

                        m_pos -= (ip[0] >> 2) + (ip[1] << 6);

                        ip += 2;
                        ////// done
                        if (m_pos == op)
                        {
                            COMPRESS_ASSERT(t == 1);
                            if (m_pos != op_end)
                                return LZO_E_LOOKBEHIND_OVERRUN;
                            return ip - in;
                        }
                        m_pos -= 0x4000;
                    }
                    else
                    {
                        m_pos = op - 1;
                        m_pos -= t >> 2;
                        m_pos -= *ip++ << 2;

                        TEST_LB();
                        NEED_OP(2);
                        *op++ = *m_pos++; *op++ = *m_pos;
                        goto match_done;
                    }

                    TEST_LB();
                    COMPRESS_ASSERT(t > 0);
                    NEED_OP(t + 3 - 1);

                    if (t >= 2 * 4 - (3 - 1) && (op - m_pos) >= 4)
                    {
                        COPY4(op, m_pos);
                        op += 4; m_pos += 4; t -= 4 - (3 - 1);
                        do {
                            COPY4(op, m_pos);
                            op += 4; m_pos += 4; t -= 4;
                        } while (t >= 4);
                        if (t > 0) do *op++ = *m_pos++; while (--t > 0);
                    }
                    else
                    {
                    copy_match:
                        *op++ = *m_pos++; *op++ = *m_pos++;

                        do *op++ = *m_pos++; while (--t > 0);

                    }
                match_done:

                    t = ip[-2] & 3;
                    if (t == 0)   break;
                match_next:
                    COMPRESS_ASSERT(t > 0); COMPRESS_ASSERT(t < 4); NEED_OP(t);
                    *op++ = *ip++;
                    if (t > 1) { *op++ = *ip++; if (t > 2) { *op++ = *ip++; } }

                    t = *ip++;
                } while (1);
            }
            //    return LZO_E_EOF_NOT_FOUND;/never gets here


        }
    }
}