#include "png_text.h"

/*
typedef struct{
   //Width:              4 bytes
	u_int32_t width;
   //Height:             4 bytes
	u_int32_t height;
   //Bit depth:          1 byte
	char bit_depth;
   //Color type:         1 byte
	char color_type;
   //Compression method: 1 byte
	char compression_method;
   //Filter method:      1 byte
	char filter_method;
   //Interlace method:   1 byte
	char interlace_method;

}ihdrs;
*/
   /* Table of CRCs of all 8-bit messages. */
   unsigned long crc_table[256];
   
   /* Flag: has the table been computed? Initially false. */
   int crc_table_computed = 0;
   
   /* Make the table for a fast CRC. */
   void make_crc_table(void)
   {
     unsigned long c;
     int n, k;
   
     for (n = 0; n < 256; n++) {
       c = (unsigned long) n;
       for (k = 0; k < 8; k++) {
         if (c & 1)
           c = 0xedb88320L ^ (c >> 1);
         else
           c = c >> 1;
       }
       crc_table[n] = c;
     }
     crc_table_computed = 1;
   }
   
   /* Update a running CRC with the bytes buf[0..len-1]--the CRC
      should be initialized to all 1's, and the transmitted value
      is the 1's complement of the final running CRC (see the
      crc() routine below)). */
   
   unsigned long update_crc(unsigned long crc, char *buf,
                            int len)
   {
     unsigned long c = crc;
     int n;
   
     if (!crc_table_computed)
       make_crc_table();
     for (n = 0; n < len; n++) {
       c = crc_table[(c ^ (unsigned char)buf[n]) & 0xff] ^ (c >> 8);
     }
     return c;
   }
   
   /* Return the CRC of the bytes buf[0..len-1]. */
   unsigned long get_crc(char *buf, int len)
   {
     return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
   }




int Valid_Png(char *filename){
	char signature[] = {137, 80, 78, 71, 13, 10, 26, 10};
	//FILE* fp;
	int input;
	struct stat buffer;
	int status;
	size_t file_bytes;
	size_t bytes_zdatas = 0;
	char *mem_file;
	u_int32_t count1 = 0;
	u_int32_t count2 = 0;
	//int  u_int32_t
	//char length, type, crc;
	//A 4-byte unsigned integer
	u_int32_t length;
	//A 4-byte chunk type code.
	u_int32_t type;
	//A 4-byte CRC
	u_int32_t crc;
	union{ char a[4]; u_int32_t b;}x;


	//unsigned long length, type, crc;

	char *data;
	char *crcdata;
	u_int32_t total_chunks = 0;
	struct ihdrs ihdr;

	//if( ( fp = fopen( filename, "r" ) ) == NULL ) {
	//	fprintf( stderr, "\tError: You do not appear to have write permissions to %s !\n", filename );
	//	return(1);
	//}
	//fclose( fp );

	input = open(filename, O_RDONLY);
	if(input == -1){
		fprintf( stderr, "Error opening %s\n", filename );
		return(1);
	}
	status = fstat(input, &buffer);
	file_bytes=buffer.st_size;

	mem_file=(char *)mmap(0, file_bytes, PROT_READ, MAP_SHARED, input, 0);
	if (mem_file == MAP_FAILED) {
		close(input);
		fprintf(stderr, "Error mmapping the file %s\n", filename );
		return (1);
	}
	//%uz = c99 %lu = c90   z== length modifier
	printf("opened %.02lfKB file: %s\n", (double)file_bytes/1024, filename);
	for(count1=0; count1<8; count1++){
		if(signature[count1] != mem_file[count1]){
			printf("%d: %d not %d\n", (int)count1, (int)signature[count1], (int)mem_file[count1]);
			return(1);
		}
	}
	printf("8 byte signature matched !\n");

	//4 chunk components: length, type, data, crc
	//first chunk is IHDR and ending with an IEND chunk
	// ancillary, private, reserved, safe-to-copy   bit 5's

	   /*
	   grab IHDR
	   Width:              4 bytes
	   Height:             4 bytes
	   Bit depth:          1 byte
	   Color type:         1 byte
	   Compression method: 1 byte
	   Filter method:      1 byte
	   Interlace method:   1 byte
	   */
	   /*
	   Color    Allowed    Interpretation
	   Type    Bit Depths
	   0       1,2,4,8,16  Each pixel is a grayscale sample.
	   2       8,16        Each pixel is an R,G,B triple.
	   3       1,2,4,8     Each pixel is a palette index;
                       a PLTE chunk must appear.
	   4       8,16        Each pixel is a grayscale sample,
                       followed by an alpha sample.
	   6       8,16        Each pixel is an R,G,B triple,
                       followed by an alpha sample.
	   */
	   /*PNG filter method 0 defines five basic filter types:
	   Type    Name
	   0       None
	   1       Sub
	   2       Up
	   3       Average
	   4       Paeth
	   */

	//crc check of image
	while(count1<file_bytes){
		x.a[0] = mem_file[count1+3];
		x.a[1] = mem_file[count1+2];
		x.a[2] = mem_file[count1+1];
		x.a[3] = mem_file[count1];
		length = x.b;
		count1+=4;

		//printf("%u: %u\n", count1, length);

		crcdata=&mem_file[count1];

		x.a[0] = mem_file[count1+3];
		x.a[1] = mem_file[count1+2];
		x.a[2] = mem_file[count1+1];
		x.a[3] = mem_file[count1];
		type = x.b;
		count1+=4;

		if(x.a[3] == 'I' && x.a[2] == 'D' && x.a[1] == 'A' && x.a[0] == 'T'){
			bytes_zdatas+=length;
			//printf("%u\n", length);
		}
		data=&mem_file[count1];
		count1+=length;
		//32-bit crc of length bit data 
		x.a[0] = mem_file[count1+3];
		x.a[1] = mem_file[count1+2];
		x.a[2] = mem_file[count1+1];
		x.a[3] = mem_file[count1];
		crc = x.b;
		count1+=4;
		total_chunks++;

		if( crc != get_crc(crcdata, (int)length+4) ){

		//if( Verify_Crc(crcdata, crc, length+4) ){
			//calculated on the preceding bytes in the chunk, including the chunk type code and chunk data fields, but not including the length field.
			printf("crc match failed at byte %d\n", (int)count1);
			return(1);
		}
	}
	printf("%u chunks verified\n", total_chunks);
	printf("CRC checks matched !\n");

	printf("%zubytes needed for datastream concatonation\n", bytes_zdatas);

	//verify that chunk ordering/presence is correct
		//variables should contain IEND chunk at this point
		x.b = type;
		if(x.a[3] != 'I' || x.a[2] != 'E' || x.a[1] != 'N' || x.a[0] != 'D'){
			printf("IEND chunk missing or ordered wrong!\n");
			return(1);
		}
		printf("IEND chunk found\n");

		//check IHDR and set corresponding variables
		count1=8;
        x.a[0] = mem_file[count1+3];
        x.a[1] = mem_file[count1+2];
        x.a[2] = mem_file[count1+1];
        x.a[3] = mem_file[count1];
        length = x.b;
        count1+=4;

        //crcdata=&mem_file[count1];
        x.a[0] = mem_file[count1+3];
        x.a[1] = mem_file[count1+2];
        x.a[2] = mem_file[count1+1];
        x.a[3] = mem_file[count1];
        type = x.b;
		if(x.a[3] != 'I' || x.a[2] != 'H' || x.a[1] != 'D' || x.a[0] != 'R'){
			printf("IHDR chunk missing or ordered wrong!\n");
			return(1);
		}
		printf("IHDR chunk found\n");
        count1+=4;

        data=&mem_file[count1];
		x.a[0] = mem_file[count1+3];
		x.a[1] = mem_file[count1+2];
		x.a[2] = mem_file[count1+1];
		x.a[3] = mem_file[count1];
		ihdr.width = x.b;
		count1+=4;
        x.a[0] = mem_file[count1+3];
        x.a[1] = mem_file[count1+2];
        x.a[2] = mem_file[count1+1];
        x.a[3] = mem_file[count1];
        ihdr.height = x.b;
		count1+=4;
		ihdr.bit_depth          = mem_file[count1++];
		ihdr.color_type         = mem_file[count1++];
		ihdr.compression_method = mem_file[count1++];
		ihdr.filter_method      = mem_file[count1++];
		ihdr.interlace_method   = mem_file[count1++];

		printf("image codes:\n");
		printf("width:\t%d\n", ihdr.width);
		printf("height:\t%d\n", ihdr.height);
		printf("bit_depth:\t%d\n", ihdr.bit_depth);
		printf("color_type:\t%d\n", ihdr.color_type);
		printf("compression_method:\t%d\n", ihdr.compression_method);
		printf("filter_method:\t%d\n", ihdr.filter_method);
		printf("interlace_method:\t%d\n", ihdr.interlace_method);

		if(ihdr.width > 2147483647){
			printf("Invalid image width detected: excedes 2147483647 (png specification)!\n");
			return(1);
		}
		if(ihdr.height > 2147483647){
			printf("Invalid image height detected: excedes 2147483647 (png specification)!\n");
			return(1);
		}
		if(ihdr.width == 0){
			printf("Invalid image width detected: 0 value\n");
			return(1);
		}
		if(ihdr.height == 0){
			printf("Invalid image height detected: 0 value\n");
			return(1);
		}
		//valid bit depths
		//1, 2, 4, 8, and 16
		if(ihdr.bit_depth != 1){
			if(ihdr.bit_depth != 2){
				if(ihdr.bit_depth != 4){
					if(ihdr.bit_depth != 8){
						if(ihdr.bit_depth != 16){
							printf("Invalid bit depth!\n");
							return(1);
						}
					}
				}
			}
		}

		//valid color types
		//0, 2, 3, 4, and 6
		if(ihdr.color_type != 0){
			if(ihdr.color_type != 2){
				if(ihdr.color_type != 3){
					if(ihdr.color_type != 4){
						if(ihdr.color_type != 6){
							printf("Invalid color type!\n");
						}
					}
				}
			}
		}

		//valid bit depth / color type combinations
		if(ihdr.color_type == 0){
			if(ihdr.bit_depth != 1){
				if(ihdr.bit_depth != 2){
					if(ihdr.bit_depth != 4){
						if(ihdr.bit_depth != 8){
							if(ihdr.bit_depth != 16){
								printf("Unsupported bitdepth for colortype 0!\n");
								return(1);
							}
						}
					}
				}
			}
		}
		//PLTE chunk must appear for color type 3, and can appear for color types 2 and 6; 
		//it must not appear for color types 0 and 4. If this chunk does appear, it must precede the first IDAT chunk. 
		//There must not be more than one PLTE chunk. 

		//image meets png specification at this point

		//I'm not supporting any PLTE chunks (palette index images) at this point.
		//specifically exiting on color type 3 (which requires one), and ignoring PLTE chunks in types 2 and 6 (or others)
		if(ihdr.color_type == 3){
			printf("This program does not support images requiring use of a palette index!\n");
			return(1);
		}
		//I'm not supporting interlacing either
		if(ihdr.interlace_method != 0){
			printf("This program does not support interlaced images!\n");
			return(1);
		}


/*
	int ret;
	int zlib_status;
	z_stream strm;
     strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = bytes_zdatas;
    strm.next_in = Z_NULL;
    ret = inflateInit(&strm);
    if (ret != Z_OK)
        return ret;
*/

	char *ds_out;
	ds_out = (char *)malloc( ceil(bytes_zdatas*1.5)*sizeof(char));
	char *ds_in;
	ds_in = (char *)malloc(bytes_zdatas*sizeof(char));
	size_t in_total = 0;
	size_t out_total = 0;

	//start analying chunks and assembling zlib datastream
	count1 = 8;
	count2 = 0;
	while(count1<file_bytes){
        x.a[0] = mem_file[count1+3];
        x.a[1] = mem_file[count1+2];
        x.a[2] = mem_file[count1+1];
        x.a[3] = mem_file[count1];
        length = x.b;
        count1+=4;

        x.a[0] = mem_file[count1+3];
        x.a[1] = mem_file[count1+2];
        x.a[2] = mem_file[count1+1];
        x.a[3] = mem_file[count1];
        type = x.b;
		count1+=4;
		if(x.a[3] == 'I' && x.a[2] == 'D' && x.a[1] == 'A' && x.a[0] == 'T'){
			//data=&mem_file[count1];
			(void)memcpy( (void *)&ds_in[in_total], (const void *)&mem_file[count1], length );
			in_total+=length;
		}
		count1+=length;
		count1+=4;

		printf("%u: %c%c%c%c\n", count2, x.a[3], x.a[2], x.a[1], x.a[0]);
		//printf("%u: %u\n", count2, count1);
		count2++;
		//if(
	}
	//while(x.a[3] != 'I' && x.a[2] != 'E' && x.a[1] != 'N' && x.a[0] != 'D');

	//while(count1<file_bytes){


	FILE *test_output;

	test_output = fopen("output1.zlib", "w");

	if( test_output == NULL ){
		printf("error opening file\n");
	}else{
		//fprintf(test_output, "%s", ds_in);
		out_total = fwrite ( (const void *)ds_in, in_total, 1, test_output );

	}
	fclose(test_output);


	//deflate zlib datastream
	//use zlib.h functions
	    /* decompress until deflate stream ends or end of file */


/*
    do {

        strm.avail_in = fread(in, 1, CHUNK, source);
        if (ferror(source)) {
            (void)inflateEnd(&strm);
            return Z_ERRNO;
        }
        if (strm.avail_in == 0)
            break;
        strm.next_in = in;

        do {
            strm.avail_out = CHUNK;
            strm.next_out = out;
            ret = inflate(&strm, Z_NO_FLUSH);
            assert(ret != Z_STREAM_ERROR);
            switch (ret) {
            case Z_NEED_DICT:
                ret = Z_DATA_ERROR;
            case Z_DATA_ERROR:
            case Z_MEM_ERROR:
                (void)inflateEnd(&strm);
                return ret;
            }
            have = CHUNK - strm.avail_out;
            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
                (void)inflateEnd(&strm);
                return Z_ERRNO;
            }
        } while (strm.avail_out == 0);
    } while (ret != Z_STREAM_END);
    (void)inflateEnd(&strm);
    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}
*/





	//unfilter



	if (munmap(mem_file, file_bytes) == -1) {
		fprintf(stderr, "Error un-mmapping the file %s\n", filename );
		return(1);
	}
	close(input);
	//free(strm);
	free(ds_out);
	free(ds_in);
	//printf("!\n");
	return(0);
}