//-----------------------------------------------------------------------------
// Copyright © 2003 - Philip Howard - All rights reserved
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//-----------------------------------------------------------------------------
// package	libh/angif
// homepage	http://libh.slashusr.org/
//-----------------------------------------------------------------------------
// author	Philip Howard
// email	libh at ipal dot org
// homepage	http://phil.ipal.org/
//-----------------------------------------------------------------------------
// This file is best viewed using a fixed spaced font such as Courier
// and in a display at least 120 columns wide.
//-----------------------------------------------------------------------------

#define GAMMA 2.44265
#define MAX_CIRCLES 64

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include <libh/angif.h>
#include <libh/string.h>


//-----------------------------------------------------------------------------
// program	rtc
//
// purpose	Generate circles and portions of circles and output them
//		as GIF image files.  These are usable for corners in HTML
//		tables to make them rounted in appearance.
//
// usage	This program may be used either the command line to make an
//		image file which will be written to the filename used to
//		specify the image, or as a CGI program to dynamically make
//		the image to be written to stdout for a web browser.
//
// command	rtc filename.ext [ ... filename.ext ]
//
// CGI		http://www.domain.tld/cgi-bin/rtc/filename.ext
//		http://www.domain.tld/rtc.cgi/filename.ext
//
// filename	The parameters for defining the image are encoded in the
//		file name.  The first part of the name is one of "ul","ur",
//		ll","lr", to identify which quadrant is produced.  Each
//		circle part consists of a color followed by a radius.
//		The background color and image size are the last numbers
//		followed by the extension.  Colors are coded in RGB with
//		3,6,9,12,15,18, or 21 cetal digits.  Radii and size are
//		coded in decimal digits.
//
// example	ur-fff-20-000-40-fff-40.gif
//-----------------------------------------------------------------------------

#define RESCALE(a,b,x,y,z) (((((b)-(a))*((z)-(x)))+((a)*((y)-(x))))/((y)-(x)))

int
convert_rgb (
    char *	arg_str
,
    char * *	arg_end
,
    double *	arg_red
,
    double *	arg_green
,
    double *	arg_blue
)
{
    double	fscale	;

    char *	ptr	;

    long	red	;
    long	green	;
    long	blue	;
    long	scale	;

    size_t	len	;

    int		num	;
    int		i	;


    //-- Scan for length.
    ptr = arg_str;
    while ( ( '0' <= * ptr && * ptr <= '9' ) ||
	    ( 'A' <= * ptr && * ptr <= 'F' ) ||
	    ( 'a' <= * ptr && * ptr <= 'f' ) ) ++ ptr;
    if ( arg_end ) * arg_end = ptr;
    len = ptr - arg_str;
    num = len / 3;
    if ( num > 7 ) return 1;
    ptr = arg_str;

    //-- Calculate scaling value.
    scale = 0;
    for ( i = num; i; -- i ) {
	scale <<= 4;
	scale += 15;
    }
    fscale = (double) scale;

    //-- Scan red.
    red = 0;
    for ( i = num; i; -- i ) {
	red <<= 4;
	red += ( (* ptr) - 9 ) % 39;
	++ ptr;
    }
    if ( arg_red ) * arg_red = pow( ( (double) red ) / fscale, GAMMA );

    //-- Scan green.
    green = 0;
    for ( i = num; i; -- i ) {
	green <<= 4;
	green += ( (* ptr) - 9 ) % 39;
	++ ptr;
    }
    if ( arg_green ) * arg_green = pow( ( (double) green ) / fscale, GAMMA );

    //-- Scan blue.
    blue = 0;
    for ( i = num; i; -- i ) {
	blue <<= 4;
	blue += ( (* ptr) - 9 ) % 39;
	++ ptr;
    }
    if ( arg_blue ) * arg_blue = pow( ( (double) blue ) / fscale, GAMMA );

    return 0;
}


double
circle_part (
    double	arg_x_left
,
    double	arg_x_right
,
    double	arg_y_top
,
    double	arg_y_bottom
,
    double	arg_radius
,
    int		arg_limit
)
{
    double	sq_radius	;
    double	sq_x_left	;
    double	sq_x_right	;
    double	sq_y_top	;
    double	sq_y_bottom	;
    double	center_x	;
    double	center_y	;
    double	value		;


    //-- Calculate squares for comparison.
    sq_radius = arg_radius * arg_radius;
    sq_x_left = arg_x_left * arg_x_left;
    sq_x_right = arg_x_right * arg_x_right;
    sq_y_top = arg_y_top * arg_y_top;
    sq_y_bottom = arg_y_bottom * arg_y_bottom;

    //-- If cell is outside the circle-square, then return 0.0.
    if ( ( sq_y_top > sq_radius && sq_y_bottom > sq_radius ) ||
	 ( sq_x_left > sq_radius && sq_x_right > sq_radius ) ) {
	return 0.0;
    }

    //-- If all corners are inside the circle, then return 1.0.
    if  ( ( sq_y_top + sq_x_left ) < sq_radius &&
	  ( sq_y_top + sq_x_right ) < sq_radius &&
	  ( sq_y_bottom + sq_x_left ) < sq_radius &&
	  ( sq_y_bottom + sq_x_right ) < sq_radius ) {
	return 1.0;
    }

    //-- If all corners are outside the circle, and
    //-- do not cross either axis, then return 0.0.
    if ( ( sq_y_top + sq_x_left ) > sq_radius &&
	 ( sq_y_top + sq_x_right ) > sq_radius &&
	 ( sq_y_bottom + sq_x_left ) > sq_radius &&
	 ( sq_y_bottom + sq_x_right ) > sq_radius &&
	 ( arg_y_top * arg_y_bottom ) > 0.0 &&
	 ( arg_x_left * arg_x_right ) > 0.0 ) {
	return 0.0;
    }

    //-- If recursion limit exceeded, just assume 0.5.
    if ( arg_limit < 1 ) return 0.5;
    -- arg_limit;

    //-- Split cell into quadrants and recurse.
    center_x = ( arg_x_left + arg_x_right ) / 2.0;
    center_y = ( arg_y_top + arg_y_bottom ) / 2.0;
    value  = circle_part( arg_x_left, center_x, arg_y_top, center_y, arg_radius, arg_limit );
    value += circle_part( center_x, arg_x_right, arg_y_top, center_y, arg_radius, arg_limit );
    value += circle_part( arg_x_left, center_x, center_y, arg_y_bottom, arg_radius, arg_limit );
    value += circle_part( center_x, arg_x_right, center_y, arg_y_bottom, arg_radius, arg_limit );

    //-- Return average of quadrants.
    return value / 4.0;
}


int
make_image (
    char *	arg_name
,
    FILE *	arg_file
)
{
    angif_header	gif_header	;
    angif_stream	gif_stream	;
    angif_image		gif_image	;

    double	circle_red	[MAX_CIRCLES]	;
    double	circle_green	[MAX_CIRCLES]	;
    double	circle_blue	[MAX_CIRCLES]	;
    double	circle_radius	[MAX_CIRCLES]	;

    double	fsize		;
    double	box_xl		;
    double	box_xr		;
    double	box_yt		;
    double	box_yb		;
    double	pix_xl		;
    double	pix_xr		;
    double	pix_yt		;
    double	pix_yb		;
    double	y		;
    double	x		;

    long *	image_mem	;
    long *	image_ptr	;

    char *	last_dot	;
    char *	ptr		;

    int		num_circles	;
    int		isize		;
    int		ok		;
    int		i		;


    //-- Find the last '.' in the name.
    ptr = arg_name;
    last_dot = NULL;
    while ( * ptr ) {
	if ( * ptr == '.' ) last_dot = ptr;
	++ ptr;
    }

    //-- Skip over first 2 letters.
    ptr = arg_name + 2;

    //-- Collect up to MAX_CIRCLES circle colors and sizes.
    //-- The last is the background color and image size.
    i = 0;
    fsize = 0.0;
    while ( i < MAX_CIRCLES ) {

	//-- Scan for and convert color.
	if ( ptr >= last_dot || ! * ptr ) break;
	while ( ptr < last_dot && * ptr && ! isalnum( * ptr ) ) ++ ptr;
	if ( ptr >= last_dot || ! * ptr ) break;
	ok = convert_rgb( ptr, & ptr, circle_red+i, circle_green+i, circle_blue+i );
	if ( ok != 0 ) return ok;

	//-- Scan for and convert size.
	if ( ptr < last_dot && * ptr ) {
	    while ( ptr < last_dot && * ptr && ! isalnum( * ptr ) ) ++ ptr;
	    if ( ptr >= last_dot || ! * ptr ) break;
	    fsize += strtod( ptr, & ptr );
	}
	circle_radius[i] = fsize;
	++ i;
    }
    num_circles = i - 1;
    if ( num_circles < 1 ) return 1;

    //-- Image size is the last radius.
    fsize = ceil( circle_radius[ num_circles ] );
    isize = (int) fsize;
    if ( isize < 1 || isize > 2048 ) return 1;

    //-- Determine the requested corner.
    box_xl = box_xr = box_yt = box_yb = 0.0;
    ok = 0;
    for ( i = 0; i < 2; ++ i ) {
	int ch;
	ch = tolower( arg_name[ i ] );
	if	( ch == 't' ) { box_yt = fsize;  box_yb = 0.0;    ok = 1; }
	else if	( ch == 'b' ) { box_yt = 0.0;    box_yb = -fsize; ok = 1; }
	else if	( ch == 'l' ) { box_xl = -fsize; box_xr = 0.0;    ok = 1; }
	else if	( ch == 'r' ) { box_xl = 0.0;    box_xr = fsize;  ok = 1; }
    }
    if ( ! ok ) return 1;

    //-- Grab space to build the image.
    image_mem = malloc( sizeof (long) * isize * isize );
    if ( ! image_mem ) return 0;
    image_ptr = image_mem;

    //-- Step through all the pixels.
    pix_yt = box_yt;
    for ( y = 0.0; y < fsize; ) {
	y += 1.0;
	pix_yb = RESCALE( box_yt, box_yb, 0.0, fsize, y );
	pix_xl = box_xl;
	for ( x = 0.0; x < fsize; ) {
	    double pix_r, pix_g, pix_b, mult, part, prev;
	    long ir,ig,ib;

	    x += 1.0;
	    pix_xr = RESCALE( box_xl, box_xr, 0.0, fsize, x );

	    //-- Accumulate the colors for this pixel.
	    pix_r = pix_g = pix_b = 0.0;
	    prev = 0.0;
	    for ( i = 0; i < num_circles && prev < 1.0; ++ i ) {
		part = circle_part( pix_xl, pix_xr, pix_yt, pix_yb, circle_radius[i], 6 );
		if ( part > 0.0 ) {
		    mult = part - prev;
		    pix_r += mult * circle_red[i];
		    pix_g += mult * circle_green[i];
		    pix_b += mult * circle_blue[i];
		    prev = part;
		}
	    }
	    if ( prev < 1.0 ) {
		mult = 1.0 - prev;
		pix_r += mult * circle_red[num_circles];
		pix_g += mult * circle_green[num_circles];
		pix_b += mult * circle_blue[num_circles];
	    }
	    ir = (int) ( 256.0 * pow( pix_r, 1.0 / GAMMA ) );
	    if ( ir > 255 ) ir = 255;
	    if ( ir < 0 ) ir = 0;
	    ig = (int) ( 256.0 * pow( pix_g, 1.0 / GAMMA ) );
	    if ( ig > 255 ) ig = 255;
	    if ( ig < 0 ) ig = 0;
	    ib = (int) ( 256.0 * pow( pix_b, 1.0 / GAMMA ) );
	    if ( ib > 255 ) ib = 255;
	    if ( ib < 0 ) ib = 0;
	    * image_ptr = ( ir << 16 ) | ( ig << 8 ) | ib;
	    ++ image_ptr;
	    pix_xl = pix_xr;
	}
	pix_yt = pix_yb;
    }

    if ( ( last_dot[1] == 'p' || last_dot[1] == 'P' ) &&
	 ( last_dot[2] == 'n' || last_dot[2] == 'N' ) &&
	 ( last_dot[3] == 'g' || last_dot[3] == 'G' ) ) {

	return 1;
    }

    else
    if ( ( last_dot[1] == 'g' || last_dot[1] == 'G' ) &&
	 ( last_dot[2] == 'i' || last_dot[2] == 'I' ) &&
	 ( last_dot[3] == 'f' || last_dot[3] == 'F' ) ) {

	if ( getenv( "REQUEST_METHOD" ) ) {
	    fprintf( arg_file, "Content-type: image/gif\r\n" );
	    fprintf( arg_file, "\r\n" );
	}

	//-- Setup a stream for the GIF data.
	angif_init_stream( & gif_stream );
	angif_set_file( & gif_stream, arg_file );

	//-- Initialize the GIF header.
	angif_init_header( & gif_header );
	gif_header.width	= isize;
	gif_header.height	= isize;
	gif_header.color_res	= 7;

	//-- Initialize the GIF image block.
	angif_init_image( & gif_image );
	gif_image.data_rgb	= image_mem;
	gif_image.image_width	= isize;
	gif_image.image_height	= isize;
	gif_image.array_width	= isize;
	gif_image.array_height	= isize;

	//-- Output the GIF stream.
	angif_put_header( & gif_stream, & gif_header );
	angif_put_rgb( & gif_stream, & gif_image );
	angif_put_trailer( & gif_stream );

	return 0;

    }

    else
    if ( ( last_dot[1] == 'p' || last_dot[1] == 'P' ) &&
	 ( last_dot[2] == 'p' || last_dot[2] == 'n' || last_dot[2] == 'P' || last_dot[2] == 'N' ) &&
	 ( last_dot[3] == 'm' || last_dot[3] == 'M' ) ) {
	int ix,iy;

	if ( getenv( "REQUEST_METHOD" ) ) {
	    fprintf( arg_file, "Content-type: image/ppm\r\n" );
	    fprintf( arg_file, "\r\n" );
	}
	fprintf( arg_file, "P6\n%d %d 255\n", isize, isize );

	image_ptr = image_mem;
	for ( ix = 0; ix < isize; ++ ix ) {
	    for ( iy = 0; iy < isize; ++ iy ) {
		long pix;
		pix = * image_ptr ++;
		putc( pix >> 16, arg_file );
		putc( pix >>  8, arg_file );
		putc( pix      , arg_file );
	    }
	}

	return 0;

    } else {

	return 1;

    }
}

int
main (
    int		argc
,
    char * *	argv
)
{
    FILE *	fp		;

    char *	path_info	;


    //-- Check to see if this is run for an HTTP request.
    path_info = getenv( "PATH_INFO" );
    if ( path_info ) {

#ifdef CHECK_REFERER
	char *	http_referer	;

	//-- Make sure the referer is OK.
	//-- The second level domains must match.
	if ( ( http_referer = getenv( "HTTP_REFERER" ) ) ) {
	    if ( ( http_referer = strdup( http_referer ) ) ) {
		char *	referer_host	;
		char *	referer_sld	;
		char *	server_name	;
		char *	server_sld	;
		str_split_url( http_referer, NULL, NULL, NULL, & referer_host, NULL, NULL, NULL );
		referer_sld = str_tail( referer_host, '.', 2 );
		server_name = getenv( "SERVER_NAME" );
		server_sld = str_tail( server_name, '.', 2 );
		if ( str_cmp_lim_lower( referer_sld, server_sld, ~0 ) != 0 ) {
		    printf( "Status: 409 Conflicting Referer\r\n"
			    "Connection: close\r\n"
			    "Content-Type: text/html; charset=iso-8859-1\r\n"
			    "\r\n"
			    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n"
			    "<HTML><HEAD>\r\n"
			    "<TITLE>409 Conflicting Referer</TITLE>\r\n"
			    "</HEAD><BODY>\r\n"
			    "<H1>Conflicting Referer</H1>\r\n"
			    "A conflicting referer encountered while accessing %s on this server.<P>\r\n"
			    "<HR>\r\n"
			    "<ADDRESS>%s Server at %s Port %s</ADDRESS>\r\n"
			    "</BODY></HTML>\r\n",
			    getenv( "REQUEST_URI" ),
			    getenv( "SERVER_SOFTWARE" ),
			    server_name,
			    getenv( "SERVER_PORT" ) );
		    fflush( stdout );
		    return 0;
		}
		free( http_referer );
	    }
	}
#endif

	//-- Get the final component.
	path_info = str_tail( path_info, '/', 1 );

	//-- Make an image for an HTTP request.
	make_image( path_info, stdout );

	return 0;
    }

    //-- Make an image for each name given.
    while ( -- argc && * ++ argv ) {
	if ( ( fp = fopen( * argv, "wb" ) ) ) {
	    if ( make_image( * argv, fp ) == 0 ) {
		fprintf(stderr,"wrote image \"%s\"\n",*argv);
	    } else {
		fprintf(stderr,"error writing \"%s\"\n",*argv);
	    }
	    fclose( fp );
	} else {
	    fprintf(stderr,"unable to open \"%s\"\n",*argv);
	}
    }

    return 0;
}

