/* Bernhard Reiter 	Fri Oct 10 20:07:12 MET DST 1997
 *  (It`s a Weekendhack)
 * $Id: piechart.c,v 1.7 1998/01/31 16:13:02 breiter Exp $
 *
 * Copyright (C) 1997,1998 by Bernhard Reiter 
 * 
 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *	
 *	Creates piechart, must be linked with a libplot library
 *	reads ascii input file from stdin.
 *
 *	format: one slice per line. first string is the label
 *		followed by a value.
 *		Empty lines and lines starting with "#" are ignored
 *
 *	TODO: many improvements possible.
 *		- make stuff more dynamic (max text and linelength)
 *		- better handling of progname
 *		- better scanning of input lines
 *		- move much stuff into command line options
 *		e.g.	
 *			+ fonts
 *			+ rotation of the pie
 *		- use getopt_long
 *		- make better assumptions on how to place the labels
 *		- add printing percentage numbers and values;as options
 *		- special handling of very small slices
 *		- make multi word label possible
 *		- mutli-line title?
 *		- does every system have strdup()?
 *		- 
 *		...
 */

#include <stdio.h>
#include <plot.h>

#define VERSION "0.6a (RCS-$Revision: 1.7 $)"
void print_version(void)
{
             printf("piechart version " VERSION "\n"); 
             printf("Copyright (C) 1998 by Bernhard Reiter. \n"
             	    "The GNU GENERAL PUBLIC LICENSE applies. "
             	    	"Absolutly No Warranty!\n");
#ifdef DEBUG
             printf("compiled with option: DEBUG\n"); 
#endif
}

/*
 * $Log: piechart.c,v $
 * Revision 1.7  1998/01/31  16:13:02  breiter
 * changed fill() -> filltype() as fill() is only temporarily supported
 *
 * Revision 1.6  1998/01/31  16:05:40  breiter
 * using a path to draw one slice now. This is far simpler.
 * No need for LINEWIDTH_FILL anymore.
 *
 * Revision 1.5  1998/01/30  16:06:37  breiter
 * adapted for use with libplot from plotutils-2.0
 * added +T display-type commandline option therefore.
 *
 * Revision 1.4  1997/10/11  17:19:14  breiter
 * cosmetic changes. version information enhanced.
 *
 * Revision 1.3  1997/10/11  16:31:56  breiter
 * version information enhanced.
 *
 * Revision 1.2  1997/10/11  16:09:15  breiter
 * bug fixes. small improvements. userspace is always square now.
 *
 * Revision 1.1  1997/10/11  15:07:27  breiter
 * Initial revision
 *
 */

     #include <stdlib.h>
     #include <math.h>
     #include <string.h> 	/* for strdup() */
     
/* this program used getopt and relys on that it is included in the 
 * stdlib. I wanted to use getopt_long from Gnu, but it is not included
 * in the clib i have here. So it is still left TODO.
 */

/*******************************************************************************
 * Configurations -- change, what you like
 * 	If time permits some stuff could be influenced by command line options
 ******************************************************************************/

/* Colors the slices are getting filled with.
 * the color names are feed into the libplot functions.
 * The plotutils distribution contains a file doc/colors.txt which lists the
 * recogized names.
 *
 * if the nullpointer is reached the color pointer is resetted starting
 * with the first color again.
 */
char *colortable[] = {
"red","blue","green","yellow", "brown",
 "coral",  "magenta","cyan", "seagreen3",
NULL
};

/*	Color in which the separating lines and the circle are drawn
 */
#define LINECOLOR "black"

/* LINEWIDTH_LINES is for the separating lines and the circle
 * -1 means default 
 */
#define LINEWIDTH_LINES -1

/* Some hardcoded limits on the size of the labels (^= TEXTSIZE) and number
 * of slices (^=MAXSLICES).
 * TEXTSIZE_S has to be the same as TEXTSIZE and is used to construct a sscanf
 *	format string.
 *(You see, how lasy i was. I was not using some object orientated language
 *	like objective-c and left all the neat dynamic string handling for
 *	the interested hacker and or some version in the future.)
 */
#define TEXTSIZE 100
#define TEXTSIZE_S "100"
#define MAXSLICES 50

/* if an input line starts with this character, it is ignored.		*/
#define COMMENTCHAR '#'

/*******************************************************************************
 * Beware: This following code is for hackers only.
 * 	( Yeah, it is not THAT bad, you can risk a look, if you know some C ..)
 ******************************************************************************/

/* Program structure outline:
 *	- get all options 
 *	- read all input data (only from stdin so far)
 *	- print
 *		+ init stuff
 *		+ print title
 *		+ print color part for slices
 *		+ print separating lines and circle
 *		+ print labels
 *		+ clean up stuff
 */


/* A nice structure, we will fill some of it, when we read the input.
 */
struct slice {
	char *text;		/* label for the slice			*/
	double value;		/* value for the slice			*/
};


/* Attention: Main Progam to be started.... :)				*/

int main(int argc, char **argv)
{
char * progname=argv[0];	/* a hack, for printing errors out	*/
char * title=NULL;		/* Title of the chart			*/
int return_value;		/* return value for libplot calls.	*/
char *display_type = "meta";	/* default libplot output format 	*/
int handle;			/* handle for open plotter		*/

char line [BUFSIZ];		/* input line buffer			*/
struct slice *slices[MAXSLICES];/* the array of slices			*/
int n_slices=0;			/* number of slices in slices[]	;)	*/
int t;				/* loop var(s) 				*/
double sum;			/* sum of all slice values		*/

/* well, we do not have the gnu getopt long here. :-( 
 * so i use getopt for now
 */
          int c;		
          extern char *optarg;
          extern int optind;
          int errflg = 0;
          int show_usage=0;
          int show_version=0;
	  int specified_display_type=0;
          while ((c = getopt(argc, argv, "Vt:T:h")) != EOF)
               switch (c) {
               case 't':
                    if (title)
                         errflg++;
                    else
                         title=strdup(optarg);
                    break;
               case 'T':
                    if (specified_display_type)
                         errflg++;
                    else {
		    	specified_display_type++;
	  	        display_type = strdup(optarg);
		    }
                    break;
               case 'V':
                    if (show_version)
                         errflg++;
                    else
                         show_version++;
                    break;
               case 'h':
                    if (show_usage)
                         errflg++;
                    else
                         show_usage++;
                     break;
               case '?':
                    errflg++;
               }
          if (errflg) {
               (void)fprintf(stderr, "parameters were bad!\n");
               show_usage++;
          }
          if(show_version)
          {
             print_version(); 
             exit(1);
          }
          if(show_usage)
          {
             print_version();
             printf("usage: %s [options]\n",progname); 
             printf("\t the stdin is read once.\n");
             printf("\t options are:\n"\
             	    "\t\t-h\t\tprint this help and exit\n"\
             	    "\t\t-t Title\tset \"Title\" as piechart title\n"\
             	    "\t\t-T Display-Type\tone of "\
		    	"X, ps, fig, hpgl, tek, or meta\n"\
             	    "\t\t-V\t\tprint version and exit\n"\
             	    );
             
             exit(1);
          }

/* Everything is fine with the options now ... */

/* So, let us read the standardinput */
while( !(feof(stdin) || ferror(stdin)) )
{
char *c; 			/* string return from fgets		*/
struct slice * aslice;		/* freshly filled slice-structure	*/
int r;				/* number of items scanned by sscanf()	*/

	c=fgets(line,BUFSIZ,stdin);
	if(!c) continue;	/* encountered error of eof		*/
	if(line[strlen(line)-1]!='\n')
	{
		fprintf(stderr,"line was too long!\n");
		exit(2);
	}
				/* strip newline */
	line[strlen(line)-1]='\0';
				/* strip carridge return, if there is one*/
	if(line[strlen(line)-1]=='\r') 
		line[strlen(line)-1]='\0';
		
				/* Skip empty lines or lines beginning  
				 * with COMMENTCHAR			*/
	if(!(line[0]==COMMENTCHAR || !(line) || strlen(line)==0))
	{
#ifdef DEBUG
		fprintf(stderr,"Scanning line: %s\n",line);
#endif
		aslice=malloc(sizeof(struct slice));
		if(!aslice)
			perror(progname),exit(10);
			
		aslice->text=malloc(TEXTSIZE);
		if(!aslice->text)
			perror(progname),exit(10);
			
				/* scanning with sscanf() always means
				 * trouble...i know...			*/
		r=sscanf(line,"%" TEXTSIZE_S "s %lf \n", \
			aslice->text,&aslice->value);
		if(r!=2)
			fprintf(stderr,"line couln`t be scanned\n"),exit(8);
		
		if(n_slices>=MAXSLICES)
			fprintf(stderr,"too many slices\n"),exit(8);
			
		slices[n_slices++]=aslice;
	}
}

if(ferror(stdin))
{
	perror(progname);
	exit(5);
}

#ifdef DEBUG
fprintf(stderr,"Read %d slices!\n",n_slices);
#endif 

/* Let us count the values */
sum=0.;
for(t=0;t<n_slices;t++)
	sum+=slices[t]->value;
	

/* initialising one plot session	*/
				/* specify type of plotter		*/
handle=newpl (display_type, NULL, stdout, stderr);
selectpl (handle);             	/* select the plotter 			*/
return_value= openpl();
if(return_value)
{
	fprintf(stderr,"openpl returned %d!\n",return_value);
}

				/* creating your user coordinates	*/
if(title)
	return_value= fspace(-1.4,-1.4,1.4,1.4);
else
	return_value= fspace(-1.2,-1.2,1.2,1.2);
if(return_value)
{	fprintf(stderr,"fspace returned %d!\n",return_value);	}


/* we should be ready to plot, now! */



				/* i like to think in degrees. 		*/
#define X(radius,angle) (cos(angle)*(radius))
#define Y(radius,angle) (sin(angle)*(radius))

#define RAD(angle) (((angle)/180.)*M_PI)

#define XY(radius,angle) (X((radius),RAD(angle))),(Y((radius),RAD(angle)))

/* plot title if there is one */
if(title&&*title)
{
	fmove(0,1.2);
	alabel('c','b',title);
}

pencolorname(LINECOLOR);

/* and now for the slices		*/
{
    double distance,angle=0;
    char **color=colortable;
    double r=1;

    savestate();
    joinmod("round");

				/* drawing the slices			*/
    
    filltype(1);
    flinewidth(LINEWIDTH_LINES);
    pencolorname(LINECOLOR);
    for(t=0;t<n_slices;t++)
				/* draw one path for every slice 	*/
    {
    	distance=(slices[t]->value/sum)*360.;
    	fillcolorname(*color);
				
	fmove(0,0);		/* start at center..			*/
	fcont(XY(r,angle));	
    	if(distance>179)
    	{			/* we need to draw a semicircle first 	*/
				/* we have to be sure to draw 
				   counterclockwise (180 wouldn`t work 
				   in all cases)			*/
	    farc(0,0,XY(r,angle),XY(r,angle+179)); 
	    angle+=179;	
	    distance-=179;
    	}
	farc(0,0,XY(r,angle),XY(r,angle+distance));
	fcont(0,0);		/* return to center			*/
	endpath();		/* not really necessary, but intuitive	*/
	
	angle+=distance;	/* log fraction of circle already drawn	*/
				 
	color++; 		/* next color for next slice 		*/
	if(!*color) color=colortable;/* start over if all colors used 	*/
    }

    				/* the closing circle at the end 	*/
    filltype(0);
    fcircle(0.,0.,r);	
    				/* one point in the middle		*/
    colorname(LINECOLOR);
    filltype(1);
    fpoint(0,0);	
   					
    restorestate();
}

/* and now for the text		*/
{
    double distance,angle=0,place;
    double r=1;
    char h,v;
    savestate();

    for(t=0;t<n_slices;t++) 
    {
    	distance=(slices[t]->value/sum)*360.;
    				/* let us calculate the position ...	*/
	place=angle+0.5*distance;
				/* and the alignment			*/
	if(place<180)
		v='b';
	else
		v='t';
	if(place<90 || place>270)
		h='l';
	else
		h='r';
				/* plot now!				*/
	fmove(XY(r,place));
	alabel(h,v,slices[t]->text);
	
	angle+=distance;
    }
   					
    restorestate();
}





				/* end a plot sesssion			*/
return_value= closepl();
if(return_value)
{
	fprintf(stderr,"closepl returned %d!\n",return_value);
}

selectpl (0);		 	/* select default plotter		*/
deletepl (handle);	 	/* clean up by deleting plotter we used	*/
			
return 0;			/* that`s it.				*/
}
