/* Copyleft Andreas Nordal
 * All rights reversed.
 *
 * Although the right to copy is on your side, the copyleft
 * is on mine. If you leave a contribution in here, take
 * your share of copyleft and put your name under mine. As
 * long as you know what copyleft means and you maintain
 * the copyleft, you may do whatever you want with this
 * license and source code. May the source be with us.
 *                      Be free.
 */

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

const char *usage="antibom - Removes byte-order mark (BOM) from files\n"
	"Options:\n"
	"-h	--help	Show this\n"
	"--		End of options - treat next arguments as filenames\n"
	"	+N	Insert N bytes at the start of file, making it N bytes larger\n"
	"	-N	Remove N bytes from the start of file, making it N bytes smaller\n"
	"-s		Show all supported BOMs raw with corresponding charset\n"
	"-S		Show all supported BOMs hexadecimally with corresponding charset\n"
	"-B CHARSET	Prepend BOM corresponding to CHARSET.\n"
	"-R		Remove BOMs (default)\n\n"
	"Antibom reads its parameter list sequentially, meaning that an option must take place before any file that is to be affected by it. An option will revoke any previous options that conflicts with it.";

void unknown(const char *problem, const char *expect){
	fprintf(stderr, "%s: Unknown %s.\n", problem, expect);
}

struct bom_t{
	const char *magic;
	const unsigned char len;
	const char *charset;
};

struct bom_t bom[]={
	{"\xEF\xBB\xBF",	3, "utf-8"},
	{"\0\0\xFE\xFF",	4, "utf-32-b"},
	{"\xFF\xFE\0\0",	4, "utf-32-l"},
	{"\xFE\xFF",		2, "utf-16-b"},
	{"\xFF\xFE",		2, "utf-16-l"},
	{"\x0E\xFE\xFF",	3, "scsu"},
	{"\xDD\x73\x66\x73",	4, "utf-ebcdic"},
	{"\xFB\xEE\x28",	3, "bocu-1"},
	{"",			0, ""}
};

void hexprint(){
	char y=0;
	while(bom[y].len){
		char x=0;
		printf("0x");
		while(x < bom[y].len){
			printf("%02hhX", bom[y].magic[x]);
			x++;
		}
		for(x*=2; x<10; x++) printf(" ");
		puts(bom[y].charset);
		y++;
	}
}

void rawprint(){
	char y=0;
	while(bom[y].len){
		char x=0;
		while(x < bom[y].len){
			printf("%c", bom[y].magic[x]);
			x++;
		}
		for(; x<5; x++) printf(" ");
		puts(bom[y].charset);
		y++;
	}
}

long fileoffset(FILE *fp){
	int head;
	char y=0;
	while(bom[y].len){
		char x=0;
		fseek(fp, 0, 0);
		while( (head=fgetc(fp)) != EOF ){
			if((char)head != bom[y].magic[x]) break;
			x++;
			if(x == bom[y].len){
				return -x;
			}
		}
		y++;
	}
	return 0;
}

void set_off(FILE *fp, long offset){
	char buffer[8];
	size_t readcount;
	if(offset < 0){
		fseek(fp, 0, SEEK_SET);
		do{
			fseek(fp, -offset, SEEK_CUR);
			readcount = fread(buffer, 1, 8, fp);
			fseek(fp, offset-readcount, SEEK_CUR);
			fwrite(buffer, 1, readcount, fp);
		}while(readcount);
		printf("size=%ld\n", ftell(fp));
		ftruncate(fileno(fp), ftell(fp));
	}else{
		fseek(fp, 0, SEEK_END);
		fseek(fp, ftell(fp) & (~7), SEEK_SET);
		do{
			readcount = fread(buffer, 1, 8, fp);
			fseek(fp, offset-readcount, SEEK_CUR);
			fwrite(buffer, 1, readcount, fp);
		}while(fseek(fp, -readcount-offset-8, SEEK_CUR) == 0);
	}
}

int main(int argc, char *argv[]){
	int argn=1;
	char enableopts='-';
	long offset=0;
	unsigned char set=8;
	while(argn < argc){
		if(enableopts && (argv[argn][0]=='-' || argv[argn][0]=='+')
		   && argv[argn][1]>='1' && argv[argn][1]<='9'){
			sscanf(argv[argn], "%ld", &offset);
		}else if(argv[argn][0] == enableopts){
			unsigned short x=1;
			while(argv[argn][x] != '\0'){
				switch(argv[argn][x]){
					case 'h':
						puts(usage);
						break;
					case '-':
						if(argv[argn][2] == 'h'){
							puts(usage);
						}
						else enableopts=0;
						argv[argn][x+1]='\0';
						break;
					case 's':
						rawprint();
						break;
					case 'S':
						hexprint();
						break;
					case 'B':
						argn++;
						set=0;
						while(bom[set].len){
							if(strcmp(argv[argn], bom[set].charset)==0){
								offset = bom[set].len;
								goto springbreak;
							}
							set++;
						}
						unknown(argv[argn], "charset");
						break;
					case 'R':
						offset=0;
						break;
					default:
						unknown(argv[argn], "option");
				}
				x++;
			}
		}else{
			FILE *fp=fopen(argv[argn], "r+b");
			if(fp == NULL){
				perror(argv[argn]);
			}else{
				if(!offset) offset = fileoffset(fp);
				if(offset){
					printf("%s ", argv[argn]);
					if(offset < 0) printf("<< %d\n", -offset);
					else           printf(">> %d\n", offset);
					set_off(fp, offset);
					if(bom[set].len){
						fseek(fp, 0, SEEK_SET);
						fwrite(bom[set].magic, 1, bom[set].len, fp);
					}
				}
				
				fclose(fp);
			}
		}
		springbreak:
		argn++;
	}
	return 0;
}

