/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  pcomp
 *  
 *  A simple compressor, based on Six-2-Four.
 *
 *  Six-2-Four - a 4Kb intro packer.
 *
 *  Copyright (c) Kim Holviala a.k.a Kimmy/PULP Productions 1997
 *
 *  Linux version of 624 - Sed october 1999
 *
 *  pcomp compressor by Paul Hayter 2001
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/
/*
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define byte unsigned char
#define word unsigned short
#define dword unsigned long

#define OK 0
#define ERROR 1

#define MAXSIZE 4194304

#define TRUE 1
#define FALSE 0


byte _buffer[MAXSIZE], lzbuffer[MAXSIZE];
byte * buffer=0;
long length=0, lzlength=0;
word  hufflimit1=0, hufflimit2=0, lenlimit=0, decodelength=0;
int maxhuffman=0, dummy=0;
int bitpos=0;

char *rotorchar = "-/|\\";

char *info = "pcomp v1.0 - a simple file compressor.\n"
	     "based on '624' by\n"
	     "Copyright (c) Kim Holviala a.k.a Kimmy/PULP Productions 1997.\n"
	     "Linux version - Sed (sed@free.fr) october 1999\n"
	     "                Licenced under the terms of the GNU General Public Licence.\n";

char *help = "    Usage: pcomp infile\n\n";



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Write one bit to the lzbuffer
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

void writebit(word bit) {
    byte mask = 1;


    mask <<= bitpos;

    if (bit == 0) lzbuffer[lzlength] &= 255-mask;
    else lzbuffer[lzlength] |= mask;

    if (++bitpos > 7) {

        bitpos = 0;
        lzlength++;

	/* it's possible to have a bigger file than the original, in that case,
	 * we must stop
	 */
	if (lzlength >= MAXSIZE) {
	    fprintf(stderr, "\nSorry, but the compressed file would be too big.\n");
	    exit(1);
	}
    }
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Write n bits to the lzbuffer
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

void writedata(word b, word mask) {

    do {
        writebit(b & mask);
        mask >>= 1;

    } while (mask > 0);
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Write one "huffman"-coded number to the lzbuffer
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

void writehuffman(word number) {

    word mask;


    number--;

    if (number < (hufflimit1 * 2)) {

        writebit(0);
        mask = hufflimit1;
    }
    else {
        number -= (hufflimit1 * 2);

        writebit(1);
        mask = hufflimit2;
    }


    do {
        writebit(number & mask);
        mask >>= 1;

    } while (mask > 0);
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Return log2 (if that's what it is?)
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

word log2(word l) {

    switch (l) {
        case 1: return 0;
        case 2: return 1;
        case 4: return 2;
        case 8: return 3;
        case 16: return 4;
        case 32: return 5;
        case 64: return 6;
        case 128: return 7;
        case 256: return 8;
        case 512: return 9;
        case 1024: return 10;
        case 2048: return 11;
        case 4096: return 12;
    }

    return 0;
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Compress the file
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

void squeeze(void) {

    long count1=0, count2=0;
    long bestlen=0, bestpos=0, len=0;
    byte b=0;


    lzlength = 0;
    bitpos=0;

    for (count1 = 0; count1 < length; count1++) {

        bestlen = 0;
        b = buffer[count1];

        for (count2 = (count1 - 1); count2 > (count1 - maxhuffman); count2--) {

            if (count2 < 0) break;

            if (buffer[count2] == b) {

                for (len = 1; len < maxhuffman; len ++) {

                    if ((count1 + len) > length) break;
                    if (buffer[count1 + len] != buffer[count2 + len]) break;
                }

                if (len > bestlen) {

                    bestlen = len;
                    bestpos = count1 - count2;
                }
            }

        }

        if (bestlen == 1 && bestpos < 17) {

            writebit(1);
            writebit(0);
            writedata(bestpos - 1, 8);

            continue;
        }

        if (bestlen > 1) {

            if (bestlen < lenlimit) {

                for (count2 = 0; count2 < bestlen; count2++) writebit(1);
                writebit(0);
            }
            else {
                for (count2 = 0; count2 < lenlimit; count2++) writebit(1);
                writehuffman(bestlen - 1);
            }

            count1 += bestlen - 1;
            writehuffman(bestpos);

            continue;
        }

        writebit(0);
        writedata(b, 128);
    }

    lzlength++;
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Find the best compression values
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

void compress(void) {

    word besthl1=0, besthl2=0, bestll=0, bestlzlen;
    byte rotor = 0;


    lenlimit = 9;
    bestlzlen = 60000;

    for (hufflimit1 = 4; hufflimit1 <= 64; hufflimit1 *= 2)
    for (hufflimit2 = hufflimit1 * 4; hufflimit2 <= 2048; hufflimit2 *= 2) {

        printf("Compressing ... hufflimit1=%d, hufflimit2=%d\n", hufflimit1, hufflimit2);
	fflush(stdout);

        maxhuffman = (hufflimit2 + hufflimit1) * 2;

        squeeze();

	printf("   lzlength = %d\n",lzlength);

        if (lzlength < bestlzlen) {

            besthl1 = hufflimit1;
            besthl2 = hufflimit2;
            bestlzlen = lzlength;
        }
    }

    hufflimit1 = besthl1;
    hufflimit2 = besthl2;
    maxhuffman = (hufflimit2 + hufflimit1) * 2;
    bestlzlen = 60000;

    for (lenlimit = 5; lenlimit <= 15; lenlimit++) {

        printf("\rCompressing ... (%c) ", rotorchar[rotor]);
	fflush(stdout);
	rotor++;
	rotor&=3;

        squeeze();

        if (lzlength < bestlzlen) {

            bestll = lenlimit;
            bestlzlen = lzlength;
        }
    }

    lenlimit = bestll;

    squeeze();
}



/*ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ
 *
 *  Main
 *
 *ħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħħ*/

int main(int argc, char *argv[]) {
    FILE *out;
    FILE *fp;
    char destfile[128], *srcfile, slow;
    char *suffix=".lz";
    long ratio;
    unsigned long starting_offset, new_offset, memory;

    buffer=_buffer;

    puts(info);

    if (argc<2) {
	puts(help);
	fprintf(stderr, "Bad number of arguments.\n");
	return ERROR;
    }

   srcfile = argv[1];
   strcpy(destfile,srcfile);
   strcat(destfile,suffix);
   printf("Loading '%s' ... ", srcfile);

   if ((fp = fopen(srcfile, "rb")) == NULL) {

       printf("File not found!\n");
       return ERROR;
   }

   length =  fread(buffer, 1, MAXSIZE, fp);
   if (length == 0) {

       printf("Error reading from file!\n");
       fclose(fp);

       return ERROR;
   }

   fclose(fp);

   printf("Done!\n");

   hufflimit1 = 16;
   hufflimit2 = 512;
   lenlimit = 9;
   maxhuffman = (hufflimit2 + hufflimit1) * 2;
   squeeze();

//   compress();

   printf("\rCompressing ... Done!\nSaving to '%s' ... ", destfile);

   if ((fp = fopen(destfile, "wb")) == NULL) {
       printf("Cannot create file '%s'\n", destfile);
       return ERROR;
   }
   fwrite( &length,1, 4, fp);
   fwrite(lzbuffer, 1, lzlength, fp);
   fclose(fp);

   ratio = ((long) length - (long) lzlength) * 100 / ((long) length);
   printf("Done!\n\nInput/Output ratio: %i/%i bytes (saved %ld%%)\n",
          length, lzlength, ratio);

  printf("info: llstuff (%d), hl1stuff (%d), hl2stuff (%d), hf2stuff (%d)\n",
     lenlimit-1, log2(hufflimit1)+1, log2(hufflimit2)+1, (hufflimit1 * 2)+1);

}
