/*
 * Copyright © 2014 IIMarckus <imarckus@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint16_t
getint16BE(FILE *f)
{
	uint16_t n;
	n  = fgetc(f) << 8;
	n |= fgetc(f);

	return n;
}

uint32_t
getint32BE(FILE *f)
{
	uint32_t n;
	n  = fgetc(f) << 24;
	n |= fgetc(f) << 16;
	n |= fgetc(f) << 8;
	n |= fgetc(f);

	return n;
}

void
printpng(uint16_t **pixels, int height, int width)
{
	png_structp png_ptr;
	png_infop info_ptr;
	int x, y;
	png_byte **row_pointers;
	int pixel_size;
	int depth;

	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL,
	    NULL);
	if (png_ptr == NULL) {
		errx(1, "Could not create PNG write struct");
	}
    
	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
		png_destroy_write_struct(&png_ptr, NULL);
		errx(1, "Could not create PNG info struct");
	}
    
	if (setjmp (png_jmpbuf (png_ptr))) {
		png_destroy_write_struct(&png_ptr, &info_ptr);
		errx(1, "Internal PNG error");
	}
    
	depth = 8;
	png_set_IHDR(png_ptr, info_ptr, width, height, depth,
	    PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
	     PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    
	pixel_size = 3;
	row_pointers = png_malloc(png_ptr, height * sizeof (png_byte *));
	for (y = 0; y < height; ++y) {
		png_byte *row = png_malloc(png_ptr,
		    sizeof(uint8_t) * width * pixel_size);
		row_pointers[y] = row;
		for (x = 0; x < width; ++x) {
			*row++ = (pixels[x][y] >> 11) << 3;
			*row++ = ((pixels[x][y] >> 6) & 0x1f) << 3;
			*row++ = ((pixels[x][y] >> 1) & 0x1f) << 3;
		}
	}

	png_init_io(png_ptr, stdout);
	png_set_rows(png_ptr, info_ptr, row_pointers);
	png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

	for (y = 0; y < height; y++) {
		png_free(png_ptr, row_pointers[y]);
	}

	png_free(png_ptr, row_pointers);
	png_destroy_write_struct(&png_ptr, &info_ptr);
}

/*
 * read a face from offset in f and store it into pixels.
 */
void
getface(FILE *f, long offset, int height, int width, uint16_t **pixels,
    int inset)
{
	/*
	 * In the original format, on every other line, every other pair of
	 * pixels is swapped. Here we transpose it into top-bottom left-right.
	 */
	uint16_t q[4];
	int x, y;

	fseek(f, offset, SEEK_SET);

	for (y = 0; y < height; ++y) {
		for (x = inset; x < width + inset; ++x) {
			if (y % 2 == 0) {
				pixels[x][y] = fgetc(f) << 8;
				pixels[x][y] |= fgetc(f);
			} else {
				q[x % 4] = fgetc(f) << 8;
				q[x % 4] |= fgetc(f);
				if (x % 4 == 3) {
					pixels[x - 3][y] = q[2];
					pixels[x - 2][y] = q[3];
					pixels[x - 1][y] = q[0];
					pixels[x][y] = q[1];
				}
			}
		}
	}
}

int
main(int argc, char *argv[])
{
	FILE *f;
	int x, y;
	uint16_t **pixels;
	int height, width;
	uint32_t nfaces;
	uint32_t *offsets;
	uint16_t *heights, *widths;
	int inset;

	if (argc != 2) {
		errx(1, "Usage: face2png file");
	}

	f = fopen(argv[1], "rb");
	if (f == NULL) {
		err(1, "Could not open file '%s'", argv[1]);
	}

	nfaces = getint32BE(f);
	offsets = reallocarray(NULL, nfaces, sizeof *offsets);
	heights = reallocarray(NULL, nfaces, sizeof *heights);
	widths = reallocarray(NULL, nfaces, sizeof *widths);
	if (offsets == NULL || heights == NULL || widths == NULL) {
		err(1, "Could not allocate memory");
	}

	for (y = 0; y < nfaces; ++y) {
		offsets[y] = getint32BE(f);
	}
	height = 0;
	width = 0;
	for (y = 0; y < nfaces; ++y) {
		fseek(f, offsets[y] - 8, SEEK_SET);

		/*
		 * XXX
		 * check whether height follows width or width follows height
		 */
		heights[y] = getint16BE(f);
		widths[y] = getint16BE(f);

		getint32BE(f);

		/* height equals the largest height */
		height = heights[y] > height ? heights[y] : height;
		/* width equals the sum of the widths */
		width += widths[y];
	}

	pixels = reallocarray(NULL, width, sizeof *pixels);
	if (pixels == NULL) {
		err(1, "Could not allocate memory");
	}
	for (x = 0; x < width; ++x) {
		pixels[x] = reallocarray(NULL, height, sizeof
		    *pixels[x]);
		if (pixels[x] == NULL) {
			err(1, "Could not allocate memory");
		}
	}

	inset = 0;
	for (y = 0; y < nfaces; ++y) {
		getface(f, offsets[y], heights[y], widths[y], pixels, inset);
		inset += widths[y];
	}

	printpng(pixels, height, width);

	fclose(f);

	return 0;
}
