//=============================================================================
//
// project name:    TIGCC Tools Suite
// file name:       ebook.c
// initial date:    13/03/2002
// author:          thomas.nussbaumer@gmx.net
//
// description:
//
// Experimental Port of TTPack tool (exepack compression tool) for the TIGCC
//
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// STATUS: compiles, but is NOT working on a TI calc (see below)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// to compile it for the TI: tigcc -Os -Wall -W -DVERSION_TI ttpack.c
//
// IMPORTANT NOTES:
//
// Actually the port will NOT run on a TI calc. There are exactly 2 reasons
// why:
//
// (1) I haven't solved the "required memory" problem yet! This version still
//     requires 128kB (in one buffer!!) + 15 * input_size
//     This is absolutely not possible. The reported warning "integer overflow
//     in expression" is a warning about the 128kB buffer's calloc() call.
//     But also many of the other calloc's are invalid if the input size is to
//     large.
//
// (2) Due to the above fact I haven't adapted the main() function to treat
//     work correctly on a TI. Using stdio functions is quite stupid and space
//     consuming, but until problem (1) is not solved it makes no sense to
//     invest some time in the rest.
//
// Nevertheless I will add this file to the TIGCC Tools Suite, so it won't get
// lost. I have invested quite much time to remove all unnecessary stuff from
// it and to change carefully int's to short's.
//
// To see if the code is not broken, it will compile also on the PC. You can
// use batchfile check.bat to check the functionality of this code. It
// compresses two files and decompresses them again. At the end of the batchfile
// it will run "diff" to see if the decompressed files are equal to the
// original files. If the "diff" reports nothing it's ok.
//
// IMHO this "stripped-down" version its much more "readable" than the
// full-blown ttpack file from directory calctools (just in the case you want
// try to understand how the compression algorithm works).
//
// $Id: ttpack.c,v 1.1 2002/03/13 15:48:19 tnussb Exp $
//
//=============================================================================
#if defined(VERSION_TI)
#define USE_TI89
#define USE_TI92P
#define SAVE_SCREEN
#define OPTIMIZE_ROM_CALLS
#define NO_EXIT_SUPPORT
#include <tigcclib.h>
#define MAIN_RETURN(n)     if (n) ngetchx();if (outBuffer) free(outBuffer);outBuffer=NULL;return
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAIN_RETURN(n)     if (outBuffer) free(outBuffer);outBuffer=NULL;return n
#endif

//-----------------------------------------------------------------------------
// content of packhead.h - inserted here by intention to get independent from
// the include file
//-----------------------------------------------------------------------------
#define MAGIC_CHAR1     'T'
#define MAGIC_CHAR2     'P'

#define MAX_RLE_ENTRIES 31

// size = 16 bytes
typedef struct {
    unsigned char origsize_lo; // original size lowbyte
    unsigned char origsize_hi; // original size highbyte
    unsigned char magic1;      // must be equal to MAGIC_CHAR1
    unsigned char magic2;      // must be equal to MAGIC_CHAR2
    unsigned char compsize_lo; // compressed size lowbyte
    unsigned char compsize_hi; // compressed size lowbyte
    unsigned char esc1;        // escape >> (8-escBits)
    unsigned char notused3;
    unsigned char notused4;
    unsigned char esc2;        // escBits
    unsigned char gamma1;      // maxGamma + 1
    unsigned char gamma2;      // (1<<maxGamma)
    unsigned char extralz;     // extraLZPosBits
    unsigned char notused1;
    unsigned char notused2;
    unsigned char rleentries;  // rleUsed
} PackedHeader;


#define GetUnPackedSize(p)  (unsigned int)((p)->origsize_lo | ((p)->origsize_hi << 8))
#define IsPacked(p)         ((p)->magic1 == MAGIC_CHAR1 && (p)->magic2 == MAGIC_CHAR2)


typedef struct {
    unsigned char value[MAX_RLE_ENTRIES];
} RLEEntries;

//-----------------------------------------------------------------------------
// END of content of packhead.h
//-----------------------------------------------------------------------------



#ifndef min
#define min(a,b) ((a<b)?(a):(b))
#endif

#define LRANGE          64768  // (((2<<7)-3)*256) /* 0..125, 126 -> 1..127 */
#define MAXLZLEN        256    // (2<<7)
#define MAXRLELEN       65024  //(((2<<7)-2)*256) /* 0..126 -> 1..127 */

enum MODE { LITERAL=0, LZ77=1, RLE=2, MMARK=4 };

unsigned short* rle_count      = NULL;  // if RLE starts on indata[i] then rle_count[i] holds length of the run
unsigned short* elr            = NULL;
unsigned short* lz_count       = NULL;  // if LZ77 starts on indata[i] then lz_count[i] holds length of the run
unsigned short* lzpos          = NULL;
unsigned short* length         = NULL;  // TOMTOM: from int
unsigned short  inlen          = 0;     // TOMTOM: from short
unsigned char*  indata         = NULL;
unsigned char*  mode           = NULL;
unsigned char*  newesc         = NULL;
unsigned short* backSkip       = NULL;
short           lzopt          = 0;    // TOMTOM: from int
unsigned char   escBits        = 2;    // TOMTOM: from int
unsigned char   escMask        = 0xc0; // TOMTOM: from int
unsigned char   extraLZPosBits = 0;    // TOMTOM: from int
unsigned char   rleUsed        = 31;   // TOMTOM: from int
unsigned char   rleLen[256]    = {0};


static const unsigned char lenValue[256] = {
    0,1,3,3,5,5,5,5,7,7,7,7,7,7,7,7,
    9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
    11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
    11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
    13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
    13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
    13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
    13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
    14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14
};



static unsigned char rleValues[32]  = {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                                       0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
unsigned char*  outBuffer      = NULL;
unsigned short  outPointer     = 0;
unsigned char   bitMask        = 0x80;   // TOMTOM: from int

#define OUT_SIZE 65520   // TOMTOM: changed from 0xffff


void TTPackInit(void) {
    unsigned short i;

    rleValues[0] = 1;
    for (i=1;i<32;i++) rleValues[i] = 0;

    for (i=0;i<256;i++) {
        rleLen[i]   = 0;
        //lenValue[i] = 0;
    }

    memset(outBuffer,0,OUT_SIZE);

    outPointer     = 0;
    bitMask        = 0x80;


    lzopt          = 0;
    escBits        = 2;
    escMask        = 0xc0;
    extraLZPosBits = 0;
    rleUsed        = 31;
    rle_count      = NULL;
    elr            = NULL;
    lz_count       = NULL;
    lzpos          = NULL;
    length         = NULL;
    inlen          = 0;
    indata         = NULL;
    mode           = NULL;
    newesc         = NULL;
    backSkip       = NULL;
}

//=============================================================================
// save packed file
//=============================================================================
// TOMTOM: routine checked
unsigned short SavePack(char* filename,unsigned short escape) {
    FILE *fp = NULL;
    short  i;

    if ((fp = fopen(filename, "wb"))) {
        PackedHeader   cth;
        RLEEntries     re;
        unsigned short size = outPointer + rleUsed + sizeof(PackedHeader);

        cth.origsize_lo  = inlen & 0xff;
        cth.origsize_hi  = (inlen >> 8);
        cth.magic1       = MAGIC_CHAR1;
        cth.magic2       = MAGIC_CHAR2;
        cth.compsize_lo  = size & 0xff;
        cth.compsize_hi  = size >> 8;
        cth.esc1         = (escape >> (8-escBits));
        cth.notused3     = 0; // just to make sure it has a defined value
        cth.notused4     = 0; // just to make sure it has a defined value
        cth.esc2         = escBits;
        cth.gamma1       = 8;
        cth.gamma2       = 128;
        cth.extralz      = extraLZPosBits;
        cth.notused1     = 0; // just to make sure it has a defined value
        cth.notused2     = 0; // just to make sure it has a defined value
        cth.rleentries   = rleUsed;

        for (i=0; i<rleUsed; i++) re.value[i] = rleValues[i+1];
        fwrite(&cth, 1, sizeof(PackedHeader), fp); // write header
        fwrite(&re,  1, cth.rleentries, fp);       // write rle values
        fwrite(outBuffer, outPointer, 1, fp);      // write compressed data
        fclose(fp);
        return 0;
    }
    return 1;
}

//=============================================================================
// sets/clears next bit in output buffer
//
// NOTE: clearing is not performed in this function, because it function
//       "believes" that the buffer was completely cleared before.
//=============================================================================
void PutBit(short bit) {
    if (bit && outPointer < OUT_SIZE) outBuffer[outPointer] |= bitMask;

    // proceed to next bit
    bitMask >>= 1;
    if (!bitMask) {
        bitMask = 0x80;
        outPointer++;
    }
}

//=============================================================================
//
//=============================================================================
void PutValue(unsigned short value) {
    unsigned short bits = 0, count = 0;

    while (value>1) {
        bits = (bits<<1) | (value & 1); /* is reversed compared to value */
        value >>= 1;
        count++;
        PutBit(1);
    }

    if (count<7) PutBit(0);

    while (count--) {
        PutBit((bits & 1));     /* output is reversed again -> same as value */
        bits >>= 1;
    }
}

//=============================================================================
//
//=============================================================================
void PutNBits(unsigned short byte, unsigned short bits) {
    while (bits--)
        PutBit((byte & (1<<bits)));
}

//=============================================================================
//
//=============================================================================
void OutputNormal(unsigned short *esc, unsigned char data, unsigned short newesc) {
    if ((unsigned short)(data & escMask) == *esc) {
        PutNBits((*esc>>(8-escBits)), escBits); /* escBits>=0 */
        PutBit(0);
        PutBit(1);
        PutBit(0);
        *esc = newesc;
        PutNBits((*esc>>(8-escBits)), escBits); /* escBits>=0 */
        PutNBits(data, 8-escBits);
        return;
    }
    PutNBits(data, 8);
}

//=============================================================================
// Outputs EOF marker
//=============================================================================
void OutputEof(unsigned short *esc) {
    PutNBits((*esc>>(8-escBits)), escBits);  // escBits>=0
    PutValue(3);                             // >2
    PutValue(255);                           // Maximum value
    if (bitMask != 0x80) outPointer++;
}

//=============================================================================
//
//=============================================================================
void PutRleByte(unsigned char data) {
    short index;

    // loop through the most used RLE values table ...
    for (index = 1; index < 32; index++) {
        if (data == rleValues[index]) {
            // if it is one of the most used RLE value write it out and return
            PutValue(index);
            return;
        }
    }

    // not one of the most used RLE values -> different encoding
    PutValue(32 + (data>>3));
    PutNBits(data, 3);
}

//=============================================================================
// initialize rleLen array (unsigned char)
//=============================================================================
void InitRleLen(void) {
    short i;

    memset(rleLen,lenValue[32] + 3,256);
    for (i=1; i<32; i++) rleLen[rleValues[i]] = lenValue[i];
}

//=============================================================================
//
//=============================================================================
unsigned short LenRle(unsigned short len, unsigned data) {
    unsigned short out = 0;

    do {
        if (len == 1) {
            out += escBits + 1;
            len = 0;
        }
        else if (len <= 128) {
            out += escBits + 3 + lenValue[len-1] + rleLen[data];
            len = 0;
        }
        else {
            unsigned short tmp = min(len, MAXRLELEN);
            out += escBits + 18 + lenValue[((tmp-1)>>8)+1] + rleLen[data];
            len -= tmp;
        }
    }
    while (len);

    return out;
}

//=============================================================================
//
//=============================================================================
int OutputRle(unsigned short *esc, unsigned char *data, unsigned short rlelen) {
    unsigned short len = rlelen;

    while (len) {
        if (len >= 2 && len <= 128) {
            PutNBits((*esc>>(8-escBits)), escBits);     /* escBits>=0 */
            PutBit(0);
            PutBit(1);
            PutBit(1);
            PutValue(len-1);
            PutRleByte(*data);
            return 0;
        }
        if (len<3) {
            while (len--) OutputNormal(esc, *data, *esc);
            return 0;
        }

        if (len <= MAXRLELEN) {
            /* Run-length encoding */
            PutNBits((*esc>>(8-escBits)), escBits);     /* escBits>=0 */

            PutBit(0);
            PutBit(1);
            PutBit(1);

            PutValue(128 + (((len-1)&0xff)>>1));
            PutNBits((len-1), 1);
            PutValue(((len-1)>>8) + 1);
            PutRleByte(*data);
            return 0;
        }

        /* Run-length encoding */
        PutNBits((*esc>>(8-escBits)), escBits); /* escBits>=0 */

        PutBit(0);
        PutBit(1);
        PutBit(1);

        PutValue(128 + (((MAXRLELEN-1)&0xff)>>1));
        PutNBits((MAXRLELEN-1) & 0xff, 1);
        PutValue(((MAXRLELEN-1)>>8)+1);
        PutRleByte(*data);
        len -= MAXRLELEN;
        data += MAXRLELEN;
    }
    return 0;
}

//=============================================================================
//
//=============================================================================
// !!! TOMTOM - return value changed to long !!!!
long LenLz(int lzlen, int lzpos) {
    if (lzlen==2) {
        if (lzpos <= 256) return escBits + 2 + 8;
        else              return 100000;
    }

    return escBits + 8 + extraLZPosBits +
           lenValue[((lzpos-1)>>(8+extraLZPosBits))+1] +
           lenValue[lzlen-1];
}

//=============================================================================
//
//=============================================================================
int OutputLz(unsigned short *esc, int lzlen, int lzpos, int curpos) {
    if (lzlen >= 2 && lzlen <= MAXLZLEN) {
        int tmp;

        PutNBits((*esc>>(8-escBits)), escBits); /* escBits>=0 */

        tmp = ((lzpos-1)>>(8+extraLZPosBits))+2;

        if (lzlen==2) {
            PutValue(1);
            PutBit(0);
            if (lzpos > 256) printf("Error at %d: lzpos too long (%d) for lzlen==2\n",curpos, lzpos);
        }
        else {
            PutValue(lzlen-1);
            PutValue(((lzpos-1) >> (8+extraLZPosBits)) +1);
            PutNBits(((lzpos-1) >> 8), extraLZPosBits);
        }
        PutNBits(((lzpos-1) & 0xff) ^ 0xff, 8);

        return 3;
    }
    printf("Error: lzlen too short/long (%d)\n", lzlen);
    return lzlen;
}

/* Non-recursive version */
/* NOTE! IMPORTANT! the "length" array length must be inlen+1 */
//=============================================================================
//
//=============================================================================
unsigned short OptimizeLength(short optimize) {
    short i;

    length[inlen] = 0;          /* one off the end, our 'target' */
    for (i=inlen-1; i>=0; i--) {
        unsigned short r1 = 8 + length[i+1], r2, r3;

        if (!lz_count[i] && !rle_count[i]) {
            length[i] = r1;
            mode[i] = LITERAL;
            continue;
        }

        /* If rle>MAXLZLEN, skip to the start of the rle-MAXLZLEN.. */
        if (rle_count[i] > MAXLZLEN && elr[i] > 1) {
            int z = elr[i];

            i -= elr[i];

            r2 = LenRle(rle_count[i], indata[i]) + length[i+ rle_count[i]];
            if (optimize) {
                unsigned short ii, mini = rle_count[i], minv = r2;
                unsigned short bot = rle_count[i] - 128;

                if (bot < 2) bot = 2;

                for (ii=mini-1; ii>=bot; ii--) {
                    unsigned short v = LenRle(ii, indata[i]) + length[i + ii];
                    if (v < minv) {
                        minv = v;
                        mini = ii;
                    }
                }
                if (minv != r2) {
                    lzopt += r2 - minv;
                    rle_count[i] = mini;
                    r2 = minv;
                }
            }
            length[i] = r2;
            mode[i] = RLE;

            for (; z>=0; z--) {
                length[i+z] = r2;
                mode[i+z] = RLE;
            }
            continue;
        }
        r3 = r2 = r1 + 1000; /* r3 >= r2 > r1 */

        if (rle_count[i]) {
            r2 = LenRle(rle_count[i], indata[i]) + length[i+ rle_count[i]];

            if (optimize) {
                unsigned short ii;
                unsigned short mini = rle_count[i], minv = r2;

                /* Check only the original length and all shorter
                   lengths that are power of two.

                   Does not really miss many 'minimums' this way,
                   at least not globally..

                   Makes the assumption that the Elias Gamma Code is
                   used, i.e. values of the form 2^n are 'optimal' */
                ii = 2;
                while (rle_count[i] > ii) {
                    unsigned short v = LenRle(ii, indata[i]) + length[i + ii];
                    if (v < minv) {
                        minv = v;
                        mini = ii;
                    }
                    ii <<= 1;
                }
                if (minv != r2) {
                    lzopt += r2 - minv;
                    rle_count[i] = mini;
                    r2 = minv;
                }
            }
        }
        if (lz_count[i]) {
            r3 = LenLz(lz_count[i], lzpos[i]) + length[i + lz_count[i]];

            if (optimize && lz_count[i]>2) {
                unsigned short ii, mini = lz_count[i], minv = r3;
                unsigned short topLen = LenLz(lz_count[i], lzpos[i]) - lenValue[lz_count[i]-1];

                /* Check only the original length and all shorter
                   lengths that are power of two.

                   Does not really miss many 'minimums' this way,
                   at least not globally..

                   Makes the assumption that the Elias Gamma Code is
                   used, i.e. values of the form 2^n are 'optimal' */
                ii = 4;
                while (lz_count[i] > ii) {
                    unsigned short v = topLen + lenValue[ii-1] + length[i + ii];
                    if (v < minv) {
                        minv = v;
                        mini = ii;
                    }
                    ii <<= 1;
                }
                /*
                  Note:
                  2-byte optimization checks are no longer done
                  with the rest, because the equation gives too long
                  code lengths for 2-byte matches if extraLzPosBits>0.
                  */
                /* Two-byte rescan/check */
                if (backSkip[i] && backSkip[i] <= 256) {
                    /* There are previous occurrances (near enough) */
                    unsigned short v = LenLz(2, (int)backSkip[i]) + length[i + 2];

                    if (v < minv) {
                        minv = v;
                        mini = 2;
                        lz_count[i] = mini;
                        r3 = minv;
                        lzpos[i] = (int)backSkip[i];
                    }
                }
                if (minv != r3 && minv < r2) {
                    lzopt += r3 - minv;
                    lz_count[i] = mini;
                    r3 = minv;
                }
            }
        }

        if (r2 <= r1) {
            if (r2 <= r3) {
                length[i] = r2;
                mode[i]   = RLE;
            }
            else {
                length[i] = r3;
                mode[i]   = LZ77;
            }
        }
        else {
            if (r3 <= r1) {
                length[i] = r3;
                mode[i]   = LZ77;
            }
            else {
                length[i] = r1;
                mode[i]   = LITERAL;
            }
        }
    }
    return length[0];
}


/*
    The algorithm in the OptimizeEscape() works as follows:
    1) Only unpacked bytes are processed, they are marked
       with MMARK. We proceed from the end to the beginning.
       Variable A (old/new length) is updated.
    2) At each unpacked byte, one and only one possible
       escape matches. A new escape code must be selected
       for this case. The optimal selection is the one which
       provides the shortest number of escapes to the end
       of the file,
        i.e. A[esc] = 1+min(A[0], A[1], .. A[states-1]).
       For other states A[esc] = A[esc];
       If we change escape in this byte, the new escape is
       the one with the smallest value in A.
    3) The starting escape is selected from the possibilities
       and mode 0 is restored to all mode 3 locations.

 */

//=============================================================================
//
//=============================================================================
int OptimizeEscape(unsigned short *startEscape, unsigned short *nonNormal) {
    long  i;
    short j;
    unsigned short states = (1<<escBits);
    long minp = 0, minv = 0, other = 0;
    long a[256]; /* needs int/long */
    long b[256]; /* Remembers the # of escaped for each */
    short  esc8 = 8-escBits;

    for (i=0; i<256; i++) b[i] = a[i] = -1;

    if (states>256) {
        printf("Escape optimize: only 256 states (%d)!\n",states);
        return 0;
    }

    /* Mark those bytes that are actually outputted */
    for (i=0; i<inlen; ) {
        switch (mode[i]) {
            case LZ77:
                other++;
                i += lz_count[i];
                break;

            case RLE:
                other++;
                i += rle_count[i];
                break;

            /*case LITERAL:*/
            default:
                mode[i++] = MMARK; /* mark it used so we can identify it */
                break;
        }
    }

    for (i=inlen-1; i>=0; i--) {
        /* Using a table to skip non-normal bytes does not help.. */
        if (mode[i] == MMARK) {
            int k = (indata[i] >> esc8);

            /* Change the tag values back to normal */
            mode[i] = LITERAL;

            /*
                k are the matching bytes,
                minv is the minimum value,
                minp is the minimum index
             */

            newesc[i] = (minp << esc8);
            a[k] = minv + 1;
            b[k] = b[minp] + 1;
            if (k==minp) {
                /* Minimum changed -> need to find a new minimum */
                /* a[k] may still be the minimum */
                minv++;
                for (k=states-1; k>=0; k--) {
                    if (a[k] < minv) {
                        minv = a[k];
                        minp = k;
                        /*
                            There may be others, but the first one that
                            is smaller than the old minimum is equal to
                            any other new minimum.
                         */
                        break;
                    }
                }
            }
        }
    }

    /* Select the best value for the initial escape */
    if (startEscape) {
        i = inlen;      /* make it big enough */
        for (j=states-1; j>=0; j--) {
            if (a[j] <= i) {
                *startEscape = (j << esc8);
                i = a[j];
            }
        }
    }
    if (nonNormal)
        *nonNormal = other;
    return b[startEscape ? (*startEscape>>esc8) : 0];
}

//=============================================================================
// Initialize the RLE byte code table (rleValues) according to all RLE's found
// so far (most used RLE bytes set first in rleValues table).
//=============================================================================
void InitRleAccordingToHistory(short* rleHist) {
    short p, most_used_value, most_used_count, i;  // TOMTOM: all vars changed to short

    for (i=1; i<32; i++) {
        most_used_value = -1;
        most_used_count = 0;

        // find most_used_count and value
        for (p=0; p<256; p++) {
            if (rleHist[p] > most_used_count) {
                most_used_count = rleHist[p];
                most_used_value = p;
            }
        }

        // there is a most used count ...
        if (most_used_count>0) {
            // set rleValues to this ...
            rleValues[i] = most_used_value;
            rleHist[most_used_value] = 0;
        }
        else {
            // no more different RLE encodings found
            break;
        }
    }
    InitRleLen(); // initialize rleLen array

    rleUsed = i-1;
    //return i;
}


//=============================================================================
// Initialize the RLE byte code table according to RLE's actually used O(n)
// CALLED ONLY ONCE
//=============================================================================
static inline void OptimizeRle(short* rleHist) {
    unsigned short p;

    memset(rleHist,0,sizeof(short)*256);

    for (p=0; p<inlen; ) {
        switch (mode[p]) {
            case LZ77:
                /* skip to next possible RLE encoding */
                p += lz_count[p];
                break;
            case RLE:
                /* RLE encoding found -> increase entry for this byte
                   and step to next possible RLE encoding */
                rleHist[indata[p]]++;
                p += rle_count[p];
                break;
            default:
                /* skip to next byte */
                p++;
                break;
        }
    }

    InitRleAccordingToHistory(rleHist);
}

//=============================================================================
//
//=============================================================================
unsigned short PackLz77(unsigned short* startEscape) {
    unsigned short  loop;
    short           i, j;
    long            p;
    unsigned short  escape;
    unsigned char*  hashValue;
    unsigned char*  a;
    short           k;
    unsigned short* lastPair;
    unsigned short  err_occured = 0;
    short           rescan = 0;
    short           rleHist[256];

    memset(rleHist,0,sizeof(short)*256);

    InitRleLen(); // initialize rleLen array

    // holds compression mode for each input byte (LITERAL,LZ77,RLE ...)
    mode      = (unsigned char  *)malloc(inlen);
    // if RLE starts on indata[i] then rle_count[i] holds length of the run
    rle_count = (unsigned short *)calloc(sizeof(unsigned short), inlen);
    // if LZ77 starts on indata[i] then lz_count[i] holds length of the run
    lz_count  = (unsigned short *)calloc(sizeof(unsigned short), inlen);

    length    = (unsigned short *)calloc(sizeof(unsigned short), inlen + 1);
    elr       = (unsigned short *)calloc(sizeof(unsigned short), inlen);
    lzpos     = (unsigned short *)calloc(sizeof(unsigned short), inlen);
    newesc    = (unsigned char  *)malloc(inlen);
    backSkip  = (unsigned short *)calloc(sizeof(unsigned short), inlen);
    hashValue = (unsigned char  *)malloc(inlen);
    lastPair  = (unsigned short *)calloc(sizeof(unsigned short), 256*256);


    /* error checking */
    if (!length || !mode || !rle_count || !elr || !lz_count || !lzpos || !newesc ||
        !lastPair || !backSkip || !hashValue)
    {
        printf("ERROR: Memory allocation failed!\n");
        err_occured = 1;
        goto errorexit;
    }

    i = 0;
    j = 0;
    a = indata + inlen;
    for (p=inlen-1; p>=0; p--) {
        k = j;
        j = i;
        i = *--a;       /* Only one read per position */
        hashValue[p] = i*3 + j*5 + k*7; /* 7.95 % */
    }

    /* Detect all RLE and LZ77 jump possibilities */
    for (p=0; p<inlen; p++) {
        /* check run-length code - must be done, LZ77 search needs it! */
        if (rle_count[p] <= 0) {
            unsigned char *a = indata + p;
            int val = *a++; /* if this were uchar, it would go to stack..*/
            int top = inlen - p;
            int rlelen = 1;

            /* Loop for the whole RLE */
            while (rlelen<top && *a++ == (unsigned char)val) rlelen++;

            if (rlelen>=2) {
                rleHist[indata[p]]++;

                for (i=rlelen-1; i>=0; i--) {
                    rle_count[p+i] = rlelen-i;
                    elr[p+i] = i;       /* For RLE backward skipping */
                }
            }
        }

        /* check LZ77 code */
        if (p+rle_count[p]+1<inlen) {
            long bot = p - LRANGE, maxval, maxpos, rlep = rle_count[p];
            unsigned char hashCompare = hashValue[p];

            /*
                There's always 1 equal byte, although it may
                not be marked as RLE.
             */
            if (rlep <= 0) rlep = 1;
            if (bot < 0)   bot = 0;
            bot += (rlep-1);

            /*
                First get the shortest possible match (if any).
                If there is no 2-byte match, don't look further,
                because there can't be a longer match.
             */
            i = lastPair[ (indata[p]<<8) | indata[p+1] ] -1;
            if (i>=0 && i>=bot) {
                /* Got a 2-byte match at least */
                maxval = 2;
                maxpos = p-i;

                /*
                    A..AB       rlep # of A's, B is something else..

                    Search for bytes that are in p + (rlep-1), i.e.
                    the last rle byte ('A') and the non-matching one
                    ('B'). When found, check if the rle in the compare
                    position (i) is long enough (i.e. the same number
                    of A's at p and i-rlep+1).

                    There are dramatically less matches for AB than for
                    AA, so we get a huge speedup with this approach.
                    We are still guaranteed to find the most recent
                    longest match there is.
                 */

                i = (int)lastPair[(indata[p+(rlep-1)]<<8) | indata[p+rlep]] -1;
                while (i>=bot) {   /* bot>=rlep-1, i>=bot  ==> i>=rlep-1 */

                    /* Equal number of A's ? */
                    if (!(rlep-1) || rle_count[i-(rlep-1)]==rlep) {   /* 'head' matches */
                        /* rlep==1 ==> (rlep-1)==0 */
                        /* ivanova.run: 443517 rlep==1,
                           709846 rle_count[i+1-rlep]==rlep */

                        /*
                            Check the hash values corresponding to the last
                            two bytes of the currently longest match and
                            the first new matching(?) byte. If the hash
                            values don't match, don't bother to check the
                            data itself.
                         */
                        if (hashValue[i+maxval-rlep-1] == hashCompare)
                        {
                            unsigned char *a = indata + i+2;    /* match  */
                            unsigned char *b = indata + p+rlep-1+2;/* curpos */
                            int topindex = inlen-(p+rlep-1);

                            /* the 2 first bytes ARE the same.. */
                            j = 2;
                            while (j < topindex && *a++==*b++) j++;

                            if (j + rlep-1 > maxval) {
                                int tmplen = j+rlep-1, tmppos = p-i+rlep-1;

                                if (tmplen > MAXLZLEN) tmplen = MAXLZLEN;

                                /* Accept only versions that really are shorter */
                                if (tmplen*8 - LenLz(tmplen, tmppos) >
                                    maxval*8 - LenLz(maxval, maxpos)) {
                                    maxval = tmplen;
                                    maxpos = tmppos;
                                    hashCompare = hashValue[p+maxval-2];
                                }
                                if (maxval == MAXLZLEN) break;
                            }
                        }
                    }
                    if (!backSkip[i]) break; /* No previous occurrances (near enough) */
                    i -= (int)backSkip[i];
                }

                /*
                    If there is 'A' in the previous position also,
                    RLE-like LZ77 is possible, although rarely
                    shorter than real RLE.
                 */
                if (p && rle_count[p-1] > maxval) {
                    maxval = rle_count[p-1] - 1;
                    maxpos = 1;
                }
                /*
                    Last, try to find as long as possible match
                    for the RLE part only.
                 */
                if (maxval < MAXLZLEN && rlep > maxval) {
                    bot = p - LRANGE;
                    if (bot < 0) bot = 0;

                    /* Note: indata[p] == indata[p+1] */
                    i = (int)lastPair[indata[p]*257] -1;
                    while (i>=bot) {
                        if (elr[i] + 2 > maxval) {
                            maxval = min(elr[i] + 2, rlep);
                            maxpos = p - i + (maxval-2);
                            if (maxval == rlep) break; /* Got enough */
                        }
                        i -= elr[i];
                        if (!backSkip[i]) break; /* No previous occurrances (near enough) */
                        i -= (int)backSkip[i];
                    }
                }
                if (p+maxval > inlen) {
                    printf("Error @ %ld, lzlen %ld, pos %ld - exceeds inlen\n",p, maxval, maxpos);
                    maxval = inlen - p;
                    // TOMTOM: new GOTO errorexit
                    err_occured = 2;
                    goto errorexit;
                }
                if (maxpos<=256 || maxval > 2) {
                    if (maxpos < 0) {
                        printf("Error @ %ld, lzlen %ld, pos %ld\n",p, maxval, maxpos);
                        // TOMTOM: new GOTO errorexit
                        err_occured = 3;
                        goto errorexit;
                    }
                    lz_count[p] = (maxval<MAXLZLEN)?maxval:MAXLZLEN;
                    lzpos[p] = maxpos;
                }
            }
        }


        /* Update the two-byte history ('hash table') &
           backSkip ('linked list') */
        if (p+1<inlen) {
            long index = (indata[p]<<8) | indata[p+1];
            long ptr = p - (lastPair[index]-1);

            if (ptr > p || ptr > 0xffff) ptr = 0;

            backSkip[p] = ptr;
            lastPair[index] = p+1;
        }
    }

    /* Initialize the RLE selections */
    InitRleAccordingToHistory(rleHist);

    /* Check the normal bytes / all ratio */
    {
        long mb, mv;

        /*
            Absolute maximum number of escaped bytes with
            the escape optimize is 2^-n, where n is the
            number of escape bits used.

            This worst case happens only on equal-
            distributed normal bytes (01230123..).
            This is why the typical values are so much smaller.
         */

        mb = 0;
        mv = 8*OUT_SIZE;
        for (escBits=1; escBits<9; escBits++) {
            unsigned short escaped, other = 0, c;

            escMask = (0xff00>>escBits) & 0xff;

            /* Find the optimum path for selected escape bits (no optimize) */
            OptimizeLength(0);

            /* Optimize the escape selections for this path & escBits */
            escaped = OptimizeEscape(&escape, &other);

            /* Compare value: bits lost for escaping -- bits lost for prefix */
            c = (escBits+3)*escaped + other*escBits;
            if (c < mv) {
                mb = escBits;
                mv = c;
            } else {
                /* minimum found */
                break;
            }
        }
        if (mb==1) {    /* Minimum was 1, check 0 */
            unsigned short escaped;

            escBits = 0;
            escMask = 0;

            /* Find the optimum path for selected escape bits (no optimize) */
            OptimizeLength(0);
            /* Optimize the escape selections for this path & escBits */
            escaped = OptimizeEscape(&escape, NULL);

            if (3*escaped < mv) mb = 0;
        }

        escBits = mb;
        escMask = (0xff00>>escBits) & 0xff;
    }

    /* Find the optimum path (optimize) */
    OptimizeLength(1);

    {
        long lzstat[5] = {0,0,0,0,0}, i, cur = 0, old = extraLZPosBits;

        for (p=0; p<inlen; ) {
            switch (mode[p]) {
            case LZ77:
                extraLZPosBits = 0;
                lzstat[0] += LenLz(lz_count[p], lzpos[p]);
                extraLZPosBits = 1;
                lzstat[1] += LenLz(lz_count[p], lzpos[p]);
                extraLZPosBits = 2;
                lzstat[2] += LenLz(lz_count[p], lzpos[p]);
                extraLZPosBits = 3;
                lzstat[3] += LenLz(lz_count[p], lzpos[p]);
                extraLZPosBits = 4;
                lzstat[4] += LenLz(lz_count[p], lzpos[p]);
                p += lz_count[p];
                break;
            case RLE:
                p += rle_count[p];
                break;

            default:
                p++;
                break;
            }
        }
        for (i=0; i<5; i++) {
            if (lzstat[i] < lzstat[cur]) cur = i;
        }
        extraLZPosBits = cur;

        /* Find the optimum path (optimize) */
        if (extraLZPosBits != old) OptimizeLength(1);
    }


    /* Optimize the escape selections */
    OptimizeEscape(&escape, NULL);
    if (startEscape) *startEscape = escape;
    OptimizeRle(rleHist); /* Retune the RLE selections */

    for (p=0; p<inlen; ) {
        switch (mode[p]) {
        case LITERAL: /* normal - NOT compressed */
            length[p] = outPointer;
            OutputNormal(&escape, indata[p], newesc[p]);
            p++;
            break;

        case LZ77: /* lz77 - compressed */
            /* Not possible for smaller backSkip table
               (the table is overwritten during previous use) */
            /* Re-search matches to get the closest one */
            if (lzopt && lz_count[p] > 2 && lz_count[p] > rle_count[p]) {
                long bot = p - lzpos[p] + 1, i;
                unsigned short rlep = rle_count[p];

                if (!rlep)   rlep = 1;
                if (bot < 0) bot = 0;
                bot += (rlep-1);

                i = p - backSkip[p];
                while (i>=bot) {
                    /* Equal number of A's ? */
                    if (rlep==1 || rle_count[i-rlep+1]==rlep) { /* 'head' matches */
                        unsigned char *a = indata + i+1;        /* match  */
                        unsigned char *b = indata + p+rlep-1+1; /* curpos */
                        long topindex = inlen-(p+rlep-1);

                        j = 1;
                        while (j < topindex && *a++==*b++)
                            j++;

                        if (j + rlep-1 >= lz_count[p]) {
                            long tmppos = p-i+rlep-1;

                            rescan += LenLz(lz_count[p], lzpos[p]) -
                                      LenLz(lz_count[p], tmppos);
                            lzpos[p] = tmppos;
                            break;
                        }
                    }
                    if (!backSkip[i]) break; /* No previous occurrances (near enough) */
                    i -= backSkip[i];
                }
            }

            for (loop=0; loop<lz_count[p]; loop++) length[p+loop] = outPointer;
            OutputLz(&escape, lz_count[p], lzpos[p], p);
            p += lz_count[p];
            break;

        case RLE: /* rle - compressed */
            for (loop=0; loop<rle_count[p]; loop++) length[p+loop] = outPointer;
            OutputRle(&escape, indata+p, rle_count[p]);
            p += rle_count[p];
            break;

        default: /* Error Flynn :-) */
            printf("Internal error: mode %d\n", mode[p]);
            // TOMTOM: new GOTO errorexit
            err_occured = 4;
            goto errorexit;
            break;
        }
    }
    OutputEof(&escape);

errorexit:
    if (rle_count) free(rle_count);
    if (elr)       free(elr);
    if (lz_count)  free(lz_count);
    if (lzpos)     free(lzpos);
    if (length)    free(length);
    if (mode)      free(mode);
    if (newesc)    free(newesc);
    if (lastPair)  free(lastPair);
    if (backSkip)  free(backSkip);
    if (hashValue) free(hashValue);
    return err_occured;
}


//=============================================================================
// as usual: the main, but a long one ...
//=============================================================================
#if defined(VERSION_TI)
void _main(void) {
#else
int main(int argc, char *argv[]) {
#endif
    int   buflen;
    int   newlen;
    unsigned short startEscape;
    unsigned short n;
    FILE* infp;
    char* fileIn;
    char* fileOut;

    //printf("------ %d\n",(1<<7));

#if defined(VERSION_TI)
    ESI   argptr;

    InitArgPtr(argptr);

    if (GetArgType(argptr) == STR_TAG) {
        fileIn = GetStrnArg(argptr);
        if (GetArgType(argptr) == STR_TAG) {
            fileOut = GetStrnArg(argptr);
        }
        else {
            printf("ERROR: outfile missing!\n");
            ngetchx();
            return;
        }
    }
    else {
        printf("ERROR: infile missing!\n");
        ngetchx();
        return;
    }
#else
    if (argc != 3) {
        printf("Usage: ttpack infile outfile\n");
        return 1;
    }

    fileIn = argv[1];
    fileOut = argv[2];
#endif

    if (!(outBuffer = malloc(OUT_SIZE))) {
        printf("ERROR: malloc(65520) failed\n");
        MAIN_RETURN(1);
    }
    TTPackInit();


    if (!(infp = fopen(fileIn, "rb"))) {
        printf("ERROR: Could not open %s for reading!\n", fileIn);
        MAIN_RETURN(1);
    }

    /* Read in the data */
    inlen  = 0;
    buflen = 0;
    indata = NULL;
    while (1) {
        if (buflen < inlen + LRANGE) {
            unsigned char *tmp = realloc(indata, buflen + LRANGE);
            if (!tmp) {
                free(indata);
                printf("ERROR: realloc failed!\n");
                MAIN_RETURN(1);
            }
            indata = tmp;
            buflen += LRANGE;
        }
        newlen = fread(indata + inlen, 1, LRANGE, infp);
        if (newlen <= 0) break;
        inlen += newlen;
    }

    fclose(infp);

    if (inlen  > (0xffff - 0x259)) {
        printf("ERROR: File is too large to handle (>64936 Bytes)\n");
        if (indata) free(indata);
        MAIN_RETURN(1);
    }

    if (!(n = PackLz77(&startEscape))) {
        if ((n=SavePack(fileOut,startEscape))) {
            printf("ERROR: saving to %s failed\n", fileOut);
        }
    }

    if (indata) free(indata);
    MAIN_RETURN(n);
}

//#############################################################################
//###################### NO MORE FAKES BEYOND THIS LINE #######################
//#############################################################################
//
//=============================================================================
// Revision History
//=============================================================================
//
// $Log: ttpack.c,v $
// Revision 1.1  2002/03/13 15:48:19  tnussb
// initially added version
//
//
