#include <string.h>
#include <fstream>
#include <iostream>
#include <vector>

using namespace std;

void help(char* anam) {
	cout<<"Usage:"<<endl;
	cout<<anam<<" pack filename [filename ...]\t\tpack files"<<endl;
	cout<<anam<<" depack filename [filename ...]\t\tunpack files"<<endl;
}

void puthalf(unsigned char val,bool *fh,unsigned char *bt,FILE *file) {
	if (*fh) {*bt=((*bt)&0x0f)|((val<<4)&0xf0);} else {*bt=((*bt)&0xf0)|(val&15);fputc(*bt,file);}
	*fh=!(*fh);
}

void packfile(const string ifname) {
	FILE* ifile;
	ifile=fopen(ifname.c_str(),"rb");
	if (ifile==NULL) {cout<<"WARNING: file '"<<ifname<<"' doesn't exist"<<endl; return;}
	fseek(ifile,0,SEEK_END);
	const streampos ilen=ftell(ifile);
	if (ilen>0xffff) {cout<<"WARNING: file '"<<ifname<<"' is larger than 64K. I can't pack it"<<endl; fclose(ifile); return;}
	rewind(ifile);
	vector<unsigned char> buffer(ilen);
	fread(&buffer[0],ilen,1,ifile);
	fclose(ifile);
	int i,frq[256],tmp,nonz;
	unsigned char sym[256],stm;
	for(i=0;i<256;i++) {frq[i]=0; sym[i]=(i&0xff);}
	nonz=0; for(i=0;i<ilen;i++) {if (frq[buffer[i]]==0) nonz++; frq[buffer[i]]++;}
	bool changed;
	do {
		changed=false;
		for (i=0;i<255;i++)
			if (frq[i]<frq[i+1]) {
				tmp=frq[i];frq[i]=frq[i+1];frq[i+1]=tmp;
				stm=sym[i];sym[i]=sym[i+1];sym[i+1]=stm;
				changed=true;
			}
	} while (changed);
	FILE *ofile;
	ofile=fopen((ifname+".dpk").c_str(),"wb");
	if (ofile==NULL) {cout<<"WARINING: Can't open file '"<<ifname<<"'.dpk for writing"<<endl; return;}
	fputc('D',ofile);
	fputc('Z',ofile);
	fputc('1',ofile);
	fputc(0,ofile);
	fputc(ilen&0xff,ofile);
	fputc(((ilen&0xff00)>>8),ofile);
	fputc(nonz,ofile);
	for(i=0;i<nonz;i++) fputc(sym[i],ofile);
	bool firsthalf=true;
	unsigned char byte=0;
	int k;
	for(i=0;i<ilen;i++) {
		for(k=0;k<nonz;k++) if(sym[k]==buffer[i]) break;
		while(k>14) {puthalf(15,&firsthalf,&byte,ofile); k-=15;}
		puthalf((unsigned char)(k&15),&firsthalf,&byte,ofile);
	}
	if (!firsthalf) puthalf(15,&firsthalf,&byte,ofile);
	fclose(ofile);
	cout<<"Pack '"<<ifname<<"' into '"<<ifname<<".dpk'"<<endl;
}

void unpackfile(const string iname) {
	FILE* ifile;
	ifile=fopen(iname.c_str(),"r");
	if (ifile==NULL) {cout<<"WARNING: can't open file '"<<iname<<"'"<<endl; return;}
	fseek(ifile,0,SEEK_END);
	const streampos iflen=ftell(ifile);
	rewind(ifile);
	vector<unsigned char> buffer(iflen);
	fread(&buffer[0],iflen,1,ifile);
	fclose(ifile);
	if (buffer[0]!=0x44 || buffer[1]!=0x5a || buffer[3]!=0) {cout<<"WARINING: '"<<iname<<"' is not DizPack file"<<endl; return;}
	if (buffer[2]!=0x31) {cout<<"WARINING: '"<<iname<<"' has unsupported DizPack version"<<endl; return;}
	int pos=buffer[6]+7;				// data begin
	int len=(buffer[5]<<8)+buffer[4];	// unpacked len
	FILE *ofile;
	ofile=fopen((iname+".out").c_str(),"wb");
	if (ofile==NULL) {cout<<"WARINING: can't open '"<<iname<<".out' for writing"<<endl;return;}
	int i;
	bool firsthalf=true;
	unsigned char half=0,byte=0,wbyte=0;
	for (i=0;i<len;i++) {
		byte=0;
		do {
			if (firsthalf) {wbyte=buffer[pos++]; half=((wbyte&0xf0)>>4);} else {half=wbyte&0x0f;}
			firsthalf=!firsthalf;
			byte+=half;
		} while (half==15);
		fputc(buffer[7+byte],ofile);
	}
	fclose(ofile);
	cout<<"Unpack '"<<iname<<"' into '"<<iname<<".out'"<<endl;
}

int main(int ac,char* av[]) {
	int i;
	bool execz=false;
	if (ac<3) help(av[0]);
	else {
		if (strcmp(av[1],"pack")==0) {execz=true; for (i=2;i<ac;i++) packfile(av[i]);}
		if (strcmp(av[1],"depack")==0) {execz=true; for (i=2;i<ac;i++) unpackfile(av[i]);}
		if (!execz) help(av[0]);
	}
	return 0;
}
