//-----------------------------------------------------------------------------
// 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.4426530040802915866
#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	corner
//
// purpose	Generate routed border corners and output them as GIF image
//		files.
//
// 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	corner filename.ext [ ... filename.ext ]
//
// CGI		http://www.domain.tld/cgi-bin/corner/filename.ext
//		http://www.domain.tld/corner.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)))

//-----------------------------------------------------------------------------
// function	convert_rgb
//
// purpose	Convert a string of cetal (hexadecimal) characters which
//		represent an RGB color triplet into three double values.
//
// arguments	1 (const char *) the string to convert
//		2 (char * *) where to store end pointer
//		3 (double *) where to store red value
//		4 (double *) where to store green value
//		5 (double *) where to store blue value
//
// returns	(int) == 0: OK
//		(int)  < 0: error
//-----------------------------------------------------------------------------
static
int
convert_rgb (
    const char *	arg_str
    ,
    char * *		arg_end
    ,
    double *		arg_red
    ,
    double *		arg_green
    ,
    double *		arg_blue
)
{
    double		fscale	;

    const char *	ptr	;

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

    size_t		len	;

    int			num	;
    int			i	;


    //---------------------------------------------------------------
    // Pre-determine the length so we know how many digits per color.
    // Go ahead and store the end pointer once the end is found.
    //---------------------------------------------------------------
    ptr = arg_str;
    while ( ( '0' <= * ptr && * ptr <= '9' ) ||
	    ( 'A' <= * ptr && * ptr <= 'F' ) ||
	    ( 'a' <= * ptr && * ptr <= 'f' ) ) ++ ptr;
    if ( arg_end ) * (const char * *) arg_end = ptr;
    len = ptr - arg_str;
    num = len / 3;
    if ( num > 7 ) return -1;
    ptr = arg_str;

    //--------------------------------------------------------------
    // Calculate the scaling factor to get into the 0.0 - 1.0 range.
    //--------------------------------------------------------------
    scale = 0;
    for ( i = num; i; -- i ) {
	scale <<= 4;
	scale += 15;
    }
    fscale = (double) scale;

    //--------------------------------
    // Scan and convert the red value.
    //--------------------------------
    red = 0;
    for ( i = num; i; -- i ) {
	red <<= 4;
	red += ( (* ptr ++) | 4400 ) % 55;
    }
    if ( arg_red ) * arg_red = pow( ( (double) red ) / fscale, GAMMA );

    //----------------------------------
    // Scan and convert the green value.
    //----------------------------------
    green = 0;
    for ( i = num; i; -- i ) {
	green <<= 4;
	green += ( (* ptr ++) | 4400 ) % 55;
    }
    if ( arg_green ) * arg_green = pow( ( (double) green ) / fscale, GAMMA );

    //---------------------------------
    // Scan and convert the blue value.
    //---------------------------------
    blue = 0;
    for ( i = num; i; -- i ) {
	blue <<= 4;
	blue += ( (* ptr ++) | 4400 ) % 55;
    }
    if ( arg_blue ) * arg_blue = pow( ( (double) blue ) / fscale, GAMMA );

    return 0;
}


//-----------------------------------------------------------------------------
// function	circle_part
//
// purpose	Calculate the average intensity value for a rectangular area
//		where a specified circle contributes 1.0 and the area outside
//		the circle contributes 0.0.
//
// arguments	1 (double) rectangular area left bound
//		2 (double) rectangular area right bound
//		3 (double) rectangular area top bound
//		4 (double) rectangular area bottom bound
//		5 (double) circle radius (circle origin is 0,0)
//		6 (int) limit on recursive descent
//
// returns	(double) average intensity from 0.0 to 1.0
//-----------------------------------------------------------------------------
static
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		;


    //------------------------------------------------------------
    // If the region is entirely in one quadrant, try these tests.
    //------------------------------------------------------------
    if ( ( arg_x_left * arg_x_right > 0.0 ) && ( arg_y_top * arg_y_bottom > 0.0 ) ) {

	//-----------------------------------------------
	// Pre-calculate squares for circle radius tests.
	//-----------------------------------------------
	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 the region is outside the circle-square, 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 the region is entirely inside the circle, 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 the region is entirely outside the circle, return 0.0.
	// Be sure the region does not straddle the circle.
	//----------------------------------------------------------
	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 split recursion limit exceeded, just assume 0.5.
    //----------------------------------------------------
    if ( arg_limit < 1 ) return 0.5;

    //---------------------------------------------------------------
    // Split the region into quadrants and calculate each separately.
    //---------------------------------------------------------------
    -- arg_limit;
    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 value / 4.0;
}


//-----------------------------------------------------------------------------
// function	pixel_value
//
// purpose	Correct the gamma of a pixel value in the range 0.0 to 1.0
//		and convert to an integer value in the range 0 to 255.
//
// argument	1 (double) linear pixel value in range 0.0 to 1.0
//
// returns	(int) gamma corrected quantized integer pixel value
//-----------------------------------------------------------------------------
static inline
int
pixel_value (
    double	arg_pixel
    )
{
    int		pixel	;

    pixel = (int) ( (65536.0*255.0) * pow( arg_pixel, 1.0/GAMMA ) );
    pixel += 128;
    pixel >>= 16;
    return pixel;
}


//-----------------------------------------------------------------------------
// function	make_image
//-----------------------------------------------------------------------------
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				;

    char		file_ext	[4]		;


    //-- Find the last '.' in the name.
    ptr = arg_name;
    last_dot = NULL;
    while ( * ptr ) {
	if ( * ptr == '.' ) last_dot = ptr;
	++ ptr;
    }
    if ( last_dot && last_dot[0] && last_dot[1] && last_dot[2] ) {
	file_ext[0] = last_dot[1] | 0x20;
	file_ext[1] = last_dot[2] | 0x20;
	file_ext[2] = last_dot[3] | 0x20;
    } else {
	file_ext[0] = 'g';
	file_ext[1] = 'i';
	file_ext[2] = 'f';
    }

    //-- 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 = arg_name[i] | 0x20;
	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; }
	else {
	    return 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 = pixel_value( pix_r );
	    ig = pixel_value( pix_g );
	    ib = pixel_value( pix_b );
	    * image_ptr = ( ir << 16 ) | ( ig << 8 ) | ib;
	    ++ image_ptr;
	    pix_xl = pix_xr;
	}
	pix_yt = pix_yb;
    }

    //------------------------------------------------------------
    // If the request is for PNG or PPM/PNM then handle that here.
    //------------------------------------------------------------
    if ( file_ext[0] == 'p' ) {

	//-----------------------------------------------------
	// If the request is for PNG then generate that format.
	//-----------------------------------------------------
	if ( file_ext[2] == 'g' ) {
	    return 1;
	}

	//--------------------------------------------------------------
	// If the request if for PPM/PNM then generate that format.
	//--------------------------------------------------------------
	if ( file_ext[2] == '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;
    }

    //------------------------------------------------------
    // If the request is for JPEG then generate that format.
    //------------------------------------------------------
    if ( file_ext[0] == 'j' ) {
	return 1;
    }

    //-----------------------------------------------------
    // If the request is for GIF then generate that format.
    //-----------------------------------------------------
    if ( file_ext[0] == 'g' ) {

	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;
    }

    return 1;
}

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

    char *	path_info	;
    char *	http_referer	;
    char *	referer_host	;
    char *	referer_sld	;
    char *	server_name	;
    char *	server_sld	;

    char	copy_referer	[65536];


    //---------------------------------------------------------------
    // Check to see if this process is a CGI request in a web server.
    //---------------------------------------------------------------
    if ( ( path_info = getenv( "PATH_INFO" ) ) ) {

	//-----------------------------------------------------------------
	// Make sure the second level domain matches in the referer header.
	//-----------------------------------------------------------------
	if ( ( http_referer = getenv( "HTTP_REFERER" ) ) ) {
	    strncpy( copy_referer, http_referer, sizeof copy_referer );
	    copy_referer[ ( sizeof copy_referer ) - 1 ] = 0;
	    str_split_url( copy_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 was 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;
	    }
	}

	//----------------------------------------------------------------
	// Make the image based on the description in the final component
	// of the PATH_INFO CGI environment variable, outputing to stdout.
	//----------------------------------------------------------------
	make_image( str_tail( path_info, '/', 1 ), stdout );
	return 0;
    }

    //-------------------------------------------------------------
    // Scan arguments to generate and write an image file for each.
    //-------------------------------------------------------------
    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;
}

