/*
 * Copyright Notice
 * ----------------
 * Copyright © 1998, 1999 by: Glenn Randers-Pehrson
 * This specification is a modification of the PNG 1.0 specification. It is being
 * provided by the copyright holder under the provisions of the 1996 MIT copyright and license:
 * Copyright © 1996 by: Massachusetts Institute of Technology (MIT)
 * This W3C specification is being provided by the copyright holders under
 * the following license. By obtaining, using and/or copying this specification, you
 * agree that you have read, understood, and will comply with the following terms
 * and conditions:
 * Permission to use, copy, and distribute this specification for any purpose
 * and without fee or royalty is hereby granted, provided that the full text of
 * this NOTICE appears on ALL copies of the specification or portions thereof,
 * including modifications, that you make.
 * THIS SPECIFICATION IS PROVIDED ”AS IS,” AND COPYRIGHT HOLD-ERS
 * MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
 * IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, COPYRIGHT
 * HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY
 * OR FITNESS FOR ANY PARTICULAR PURPOSE OR
 * THAT THE USE OF THE SPECIFICATION WILL NOT INFRINGE ANY
 * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER
 * RIGHTS. COPYRIGHT HOLDERS WILL BEAR NO LIABILITY FOR ANY
 * USE OF THIS SPECIFICATION.
 * The name and trademarks of copyright holders may NOT be used in
 * advertising or publicity pertaining to the specification without specific, written
 * prior permission. Title to copyright in this specification and any associated
 * documentation will at all times remain with copyright holders.
 */

#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "png.h"

FILE *get_file(char *path) {
	FILE *fp = fopen(path, "rb");
	if (fp == NULL) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	return fp;
}

char *load_file(char *path) {
	FILE *fp = get_file(path);
	fseek(fp, 0, SEEK_END);
	long size = ftell(fp);
	rewind(fp);
	char *buffer = malloc(size);
	if (buffer == NULL) {
		perror("malloc");
		exit(EXIT_FAILURE);
	}
	fread(buffer, 1, size, fp);
	fclose(fp);
	return buffer;
}

int is_png(char *buffer) {
	return memcmp(buffer, PNG_SIGNATURE, PNG_SIGNATURE_SIZE) == 0;
}

png_chunk *get_png_chunk(char *buffer, unsigned int offset) {
	png_chunk *chunk = malloc(sizeof(png_chunk));
	if (chunk == NULL) {
		perror("malloc");
		exit(EXIT_FAILURE);
	}
	memcpy(&chunk->length, buffer + offset + 0, 4);
	// Convert length to host byte order because PNG lengths is in network byte order/Big Endian
	chunk->length = ntohl(chunk->length);
	memcpy(&chunk->type, buffer + offset + 4, 4);
	chunk->data = malloc(chunk->length);
	if (chunk->data == NULL) {
		perror("malloc");
		exit(EXIT_FAILURE);
	}
	memcpy(chunk->data, buffer + offset + 8, chunk->length);
	memcpy(&chunk->crc, buffer + offset + 8 + chunk->length, 4);
	return chunk;
}

png_chunk **get_png_chunks(char *buffer) {
	png_chunk **chunks = malloc(sizeof(png_chunk *));
	if (chunks == NULL) {
		perror("malloc");
		exit(EXIT_FAILURE);
	}
	// Running offset of the buffer
	unsigned int offset = PNG_SIGNATURE_SIZE;
	// Index of the current chunk
	int i = 0;
	while (true) {
		chunks[i] = get_png_chunk(buffer, offset);
		// 12 = 4 (length) + 4 (type) + 4 (crc)
		offset += 12 + chunks[i]->length;
		if (memcmp(chunks[i]->type, "IEND", 4) == 0) {
			break;
		}
		i++;
		// Add one because realloc is 1-indexed when multiplying by sizeof
		png_chunk **err_check = realloc(chunks, sizeof(png_chunk *) * (i + 1));
		if (err_check == NULL) {
			destroy_chunks(chunks);
			perror("realloc");
			exit(EXIT_FAILURE);
		}
		chunks = err_check;
	}
	return chunks;
}

int get_number_of_chunks(png_chunk **chunks) {
	int i = 0;
	while (memcmp(chunks[i]->type, "IEND", 4) != 0) {
		i++;
	}
	// Add one for the IEND chunk
	return ++i;
}

void xor_data(png_chunk *chunk) {
	for (int i = 0; i < chunk->length; i++) {
		chunk->data[i] ^= KEY;
	}
}

void destroy_chunks(png_chunk **chunks) {
	unsigned int size = get_number_of_chunks(chunks);
	for (int i = 0; i < size; i++) {
		free(chunks[i]->data);
		free(chunks[i]);
	}
	free(chunks);
}

void write_png_chunks(char *path, png_chunk **chunks) {
	FILE *fp = fopen(path, "wb");
	if (fp == NULL) {
		perror("fopen");
		exit(EXIT_FAILURE);
	}
	fwrite(PNG_SIGNATURE, PNG_SIGNATURE_SIZE, 1, fp);
	unsigned int size = get_number_of_chunks(chunks);
	for (int i = 0; i < size; i++) {
		// Convert length to network byte order for writing
		chunks[i]->length = htonl(chunks[i]->length);
		fwrite(&chunks[i]->length, 4, 1, fp);
		fwrite(chunks[i]->type, 4, 1, fp);
		// Convert back so we can accurately use it to write the length of the data
		chunks[i]->length = ntohl(chunks[i]->length);
		fwrite(chunks[i]->data, chunks[i]->length, 1, fp);
		fwrite(&chunks[i]->crc, 4, 1, fp);
	}
	fclose(fp);
}
