/*
 * aPLib compression library  -  the smaller the better :)
 *
 * C depacker
 *
 * Copyright (c) 1998-2009 by Joergen Ibsen / Jibz
 * All Rights Reserved
 *
 * http://www.ibsensoftware.com/
 */

#include "depack.h"

/* internal data structure */
typedef struct {
    const unsigned char *source;
    unsigned char *destination;
    unsigned int tag;
    unsigned int bitcount;
} APDEPACKDATA;

static int aP_getbit(APDEPACKDATA *ud)
{
    unsigned int bit;

    /* check if tag is empty */
    if (!ud->bitcount--)
    {
        /* load next tag */
        ud->tag = *ud->source++;
        ud->bitcount = 7;
    }

    /* shift bit out of tag */
    bit = (ud->tag >> 7) & 0x01;
    ud->tag <<= 1;

    return bit;
}

static unsigned int aP_getgamma(APDEPACKDATA *ud)
{
    unsigned int result = 1;

    /* input gamma2-encoded bits */
    do {
        result = (result << 1) + aP_getbit(ud);
    } while (aP_getbit(ud));

    return (result);
}

unsigned int _stdcall aP_depack(const void *source, void *destination)
{
    APDEPACKDATA ud;
    unsigned int offs, len, R0, LWM;
    int done;
    int i;

    ud.source = (const unsigned char *) source;
    ud.destination = (unsigned char *) destination;
    ud.bitcount = 0;

    LWM = 0;
    done = 0;

    /* first byte verbatim */
    *ud.destination++ = *ud.source++;

    /* main decompression loop */
    while (!done)
    {
        if (aP_getbit(&ud))
        {
            if (aP_getbit(&ud))
            {
                if (aP_getbit(&ud))
                {
                    offs = 0;

                    for (i = 4; i; i--) offs = (offs << 1) + aP_getbit(&ud);

                    if (offs)
                    {
                        *ud.destination = *(ud.destination - offs);
                        ud.destination++;
                    } else {
                        *ud.destination++ = 0x00;
                    }

                    LWM = 0;

                } else {

                    offs = *ud.source++;

                    len = 2 + (offs & 0x0001);

                    offs >>= 1;

                    if (offs)
                    {
                        for (; len; len--)
                        {
                            *ud.destination = *(ud.destination - offs);
                            ud.destination++;
                        }
                    } else done = 1;

                    R0 = offs;
                    LWM = 1;
                }

            } else {

                offs = aP_getgamma(&ud);

                if ((LWM == 0) && (offs == 2))
                {
                    offs = R0;

                    len = aP_getgamma(&ud);

                    for (; len; len--)
                    {
                        *ud.destination = *(ud.destination - offs);
                        ud.destination++;
                    }

                } else {

                    if (LWM == 0) offs -= 3; else offs -= 2;

                    offs <<= 8;
                    offs += *ud.source++;

                    len = aP_getgamma(&ud);

                    if (offs >= 32000) len++;
                    if (offs >= 1280) len++;
                    if (offs < 128) len += 2;

                    for (; len; len--)
                    {
                        *ud.destination = *(ud.destination - offs);
                        ud.destination++;
                    }

                    R0 = offs;
                }

                LWM = 1;
            }

        } else {

            *ud.destination++ = *ud.source++;
            LWM = 0;
        }
    }

    return (unsigned int)(ud.destination - (unsigned char *)destination);
}
