/* gridunpack.c: extract a file from a gridpix */

/*
 * usage: gridunpack gpxfile\&tilenum\&smooth
 * (file number 1 is first file)
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <jpeglib.h>
#include <math.h>

#include "gridpix.h"
#include "utils.h"

/* #define DEBUG 1 */

int main(int argc, char **argv, char **env)
#ifndef SEPARATE
{
    char *cp, *tp ;
    char *na[4] ;
    int gettile(int, char **, char **) ;

    printf("Content-type: image/jpeg\n\n") ;

    na[0] = argv[0] ;

    cp = malloc(strlen(argv[1]) * 2 + 100) ;
    strcpy(cp, GRIDROOT) ;
#ifdef SUBDIR
    strncat(cp, argv[1], 12) ;
    strcat(cp, "/images/") ;
#endif
    tp = index(argv[1], '\\') ;
    strncat(cp, argv[1], tp-argv[1]) ;
    strcat(cp, ".gpx") ;

    na[1] = cp ;

    cp = index(argv[1], '\\') ;
    na[2] = cp + 2 ;
    cp = index(na[2], '\\') ;
    *cp = '\0' ;
    
    na[3] = cp+2 ;

    return gettile(4, na, env) ;
}

int gettile(int argc, char **argv, char **env)
#endif
{
    char *progname ;
    char *filename ;
    unsigned long filenum ;
    char *buf ;
    FILE *fp ;
    m_int_t offset, size, s ;
    off_t offset1, size1 ;		/* type 1 */
    f_int_t offset2, size2 ;		/* type 2 */
    struct gridpix gp ;
    int gpxtype ;
    int minx, miny, maxx, maxy ;
    struct jpeg_compress_struct cinfo ;
    struct jpeg_decompress_struct dinfo ;
    struct jpeg_error_mgr cerr, derr ;
    JSAMPROW *scanline ;
    int i, j, k, l ;
    char *debug = getenv("DEBUG") ;
    int ratio ;
    int smooth ;

    ratio = 1 ;
    minx = miny = maxx = maxy = -1 ;		/* sentinel */

    progname = argv[0] ;

    if (argc != 4 && argc != 8) {
	fprintf(stderr, "usage: %s file num [minx miny maxx maxy]\n", progname) ;
	exit(1) ;
    }

    filename = argv[1] ;
    filenum = atoi(argv[2]) ;
    smooth = atoi(argv[3]) ;

    if (argc ==  8) {
	minx = atoi(argv[4]) ;
	miny = atoi(argv[5]) ;
	maxx = atoi(argv[6]) ;
	maxy = atoi(argv[7]) ;
	if (minx < 0) {
	    fprintf(stderr, "minx is less than 0 (%d)\n", minx) ;
	    exit(1) ;
	}
	if (miny < 0) {
	    fprintf(stderr, "miny is less than 0 (%d)\n", miny) ;
	    exit(1) ;
	}
    }

    if ((fp = fopen(filename, "r")) == NULL) {
	fprintf(stderr, "\"%s\"", filename) ;
	perror("") ;
	exit(1) ;
    }

    if ((gpxtype = readheader(&gp, fp)) == 0) {
	exit(1) ;
    }
    
    if (filenum <= 0) {
	fprintf(stderr, "error: invalid index number %lu\n", filenum) ;
	exit(1) ;
    }

    if (filenum > gp.header.numfiles) {
	int width, height, tilewidth, tileheight, numfiles ;
	int xnum, ynum, oxnum, oynum, row, col ;
	int xsize, ysize ;

	tilewidth = gp.header.tilewidth ;
	tileheight = gp.header.tileheight ;
	oxnum =  (gp.header.width+tilewidth-1)/tilewidth ;
	oynum = (gp.header.height+tileheight-1)/tileheight ;
	numfiles = gp.header.numfiles ;
	for (i = gp.header.layers+1 ; ; i++) {
	    ratio *= gp.header.ratio ;
	    width = gp.header.width * ratio ;
	    height = gp.header.height * ratio ;
	    xnum = (width+tilewidth-1) / tilewidth ;
	    ynum = (height+tileheight-1) / tileheight ;
	    if (numfiles + xnum*ynum>= filenum)
		break ;
	    numfiles += xnum*ynum ;
	}
	row = (filenum - numfiles - 1) / xnum ;
	col = (filenum - numfiles - 1) % xnum ;
	xsize = tilewidth ;
	ysize = tileheight ;
	if (row + 1 == ynum) {
	    ysize = (height%tileheight) / ratio ;
	    if (ysize == 0)
		ysize = tileheight/ratio ;
	}
	if (col + 1 == xnum) {
	    xsize = (width%tilewidth) / ratio ;
	    if (xsize == 0)
		xsize = tilewidth/ratio ;
	}

	filenum = gp.header.numfiles - oxnum*oynum +
	    (row/ratio)*oxnum + (col/ratio) + 1 ;
	minx = tilewidth * (col%ratio) / ratio ;
	if (col + 1 == xnum)
	    maxx = minx + xsize - 1 ;
	else
	    maxx = tilewidth * (col%ratio+1) / ratio - 1 ;
	miny = tileheight * (row%ratio) / ratio ;
	if (row + 1 == ynum)
	    maxy = miny + ysize - 1 ;
	else
	    maxy = tileheight * (row%ratio+1) / ratio - 1 ;
	if (debug)
	    fprintf(stderr, "filenum %ld, %d, coord (%d, %d), (%d %d %d %d) %d %d %d %d\n",
		    filenum, ratio, row, col, minx, miny, maxx, maxy, oxnum, oynum, xsize, ysize) ;
    }
    if (debug)
	exit(1) ;

    if (gpxtype == 1) {
	if (fseek(fp, gp.header.headersize + (filenum-1) * sizeof(offset1),
		  SEEK_SET) < 0) {
	    fprintf(stderr, "error: can't seek %qd bytes\n",
		    gp.header.headersize + (filenum-1) * sizeof(offset1)) ;
	    exit(1) ;
	}

	if ((s = fread(&offset1, sizeof(offset1), 1, fp)) < 1) {
	    fprintf(stderr, "error: can't read the offset\n") ;
	    exit(1) ;
	}

	if ((s = fread(&size1, sizeof(size1), 1, fp)) < 1) {
	    fprintf(stderr, "error: can't read the offset\n") ;
	    exit(1) ;
	}

	size = size1 ;
	offset = offset1 ;
    }
    else {	/* type 2 */
	if (fseek(fp, gp.header.headersize + (filenum-1) * sizeof(offset2),
		  SEEK_SET) < 0) {
	    fprintf(stderr, "error: can't seek %qd bytes\n",
		    gp.header.headersize + (filenum-1) * sizeof(offset2)) ;
	    exit(1) ;
	}

	if ((s = fread(&offset2, sizeof(offset2), 1, fp)) < 1) {
	    fprintf(stderr, "error: can't read the offset\n") ;
	    exit(1) ;
	}

	if ((s = fread(&size2, sizeof(size2), 1, fp)) < 1) {
	    fprintf(stderr, "error: can't read the offset\n") ;
	    exit(1) ;
	}

	fromfint(&size, size2) ;
	fromfint(&offset, offset2) ;
    }

    size -= offset ;

#ifdef DEBUG
    fprintf(stderr, "number of files: %lu, file number: %lu\n",
	    gp.header.numfiles, filenum) ;
    fprintf(stderr, "offset = %qd, size = %qd\n", offset, size) ;
#endif

    if (fseek(fp, offset, SEEK_SET) < 0) {
	fprintf(stderr, "error: can't seek %qd bytes\n", offset) ;
	exit(1) ;
    }

    buf = malloc(size) ;
    if (buf == 0) {
	fprintf(stderr, "panic: cannot allocate %qd bytes", size) ;
	exit(1) ;
    }

    if (minx == -1) {		/* no subtiling */
	if ((s = fread(buf, 1, size, fp)) < size) {
	    perror("read") ;
	    exit(1) ;
	}
	fwrite(buf, 1, size, stdout) ;
    }
    else {
	dinfo.err = jpeg_std_error(&derr) ;
	jpeg_create_decompress(&dinfo) ;
	cinfo.err = jpeg_std_error(&cerr) ;
	jpeg_create_compress(&cinfo) ;

	jpeg_stdio_src(&dinfo, fp) ;
	jpeg_stdio_dest(&cinfo, stdout) ;
	jpeg_read_header(&dinfo, TRUE) ;
	jpeg_start_decompress(&dinfo) ;
	if (maxx > dinfo.output_width) {
	    printf("error: requested width (%d) is greater than image's (%d)\n",
		   maxx, dinfo.output_width) ;
	    exit(1) ;
	}
	if (maxy > dinfo.output_height) {
	    printf("error: requested height (%d) is greater than image's (%d)\n",
		   maxy, dinfo.output_height) ;
	    exit(1) ;
	}
	if (smooth) {
	    cinfo.image_width = (maxx - minx + 1) * ratio ;
	    cinfo.image_height = (maxy - miny + 1) * ratio ;
	}
	else {
	    cinfo.image_width = (maxx - minx + 1) ;
	    cinfo.image_height = (maxy - miny + 1) ;
	}
	cinfo.input_components = dinfo.out_color_components ;
	cinfo.in_color_space = JCS_RGB ;
	jpeg_set_defaults(&cinfo) ;
	if (smooth)
	    cinfo.smoothing_factor =
		(50 + 10 * ratio < 100) ? 50 + 10 * ratio : 100 ;
	jpeg_start_compress(&cinfo, TRUE) ;
	scanline = xmalloc(dinfo.output_height * sizeof(scanline[0])) ;
	if (smooth)
	    scanline[0] =
		xmalloc(3 * dinfo.output_width * dinfo.output_height * ratio) ;
	else
	    scanline[0] = xmalloc(3 * dinfo.output_width * dinfo.output_height) ;

	for (i = 0 ; i < dinfo.output_height ; i++) {
	    jpeg_read_scanlines(&dinfo, scanline+i, 1) ;
	    if (smooth) {
		if (ratio > 1)
		    for (j = dinfo.output_width-1 ; j >= 0 ; j--)
			for (k = ratio-1 ; k >= 0 ; k--)
			    for (l = 0 ; l < 3 ; l++)
				scanline[i][(j*ratio+k)*3+l] = scanline[i][j*3+l] ;
		if (i < dinfo.output_height-1)
		    scanline[i+1] = scanline[i] + 3 * dinfo.output_width * ratio ;
	    }
	    else {
		if (i < dinfo.output_height-1)
		    scanline[i+1] = scanline[i] + 3 * dinfo.output_width ;
	    }
	}
	for (i = miny ; i <= maxy ; i++) {
	    if (smooth) {
		scanline[i] += 3 * minx * ratio ;
		for (k = 0 ; k < ratio ; k++)
		    jpeg_write_scanlines(&cinfo, scanline+i, 1) ;
	    }
	    else {
		scanline[i] += 3 * minx ;
		jpeg_write_scanlines(&cinfo, scanline+i, 1) ;
	    }
	}
	jpeg_finish_decompress(&dinfo) ;
	jpeg_finish_compress(&cinfo) ;

	jpeg_destroy_decompress(&dinfo) ;
	jpeg_destroy_compress(&cinfo) ;
    }
	
    fclose(fp) ;

    exit(0) ;
}
