/*
 * Postscript buffering functions.
 *
 * This file is part of abcm2ps.
 *
 * Copyright (C) 1998-2009 Jean-François Moine
 * Adapted from abc2ps, Copyright (C) 1996,1997 Michael Methfessel
 *
 * 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.
 */

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

#include "abcparse.h"
#include "abc2ps.h" 

#define BUFFLN	80		/* max number of lines in output buffer */

static int ln_num;		/* number of lines in buffer */
static float ln_pos[BUFFLN];	/* vertical positions of buffered lines */
static int ln_buf[BUFFLN];	/* buffer location of buffered lines */
static float ln_lmarg[BUFFLN];	/* left margin of buffered lines */
static float ln_scale[BUFFLN];	/* scale of buffered lines */
static signed char ln_font[BUFFLN];	/* font of buffered lines */
static char buf[BUFFSZ];	/* output buffer.. should hold one tune */
static float cur_lmarg = 0;	/* current left margin */
static float cur_scale = 1.0;	/* current scale */
static float posy;		/* vertical position on page */
static float bposy;		/* current position in buffered data */
static int nepsf;		/* counter for epsf output files */
static int nbpages;		/* number of pages in the output file */
static char outfnam[STRL1];	/* internal file name for open/close */
static struct FORMAT *p_fmt;	/* current format while treating a new page */

char *mbf;			/* where to PUTx() */
int nbuf;			/* number of bytes buffered */
int use_buffer;			/* 1 if lines are being accumulated */

/* -- convert a date -- */
static void cnv_date(time_t *ltime)
{
	char buf[TEX_BUF_SZ];

	tex_str(cfmt.dateformat);
	strcpy(buf, tex_buf);
	strftime(tex_buf, TEX_BUF_SZ, buf, localtime(ltime));
}

/* -- initialize the postscript file -- */
static void init_ps(char *str, int is_epsf)
{
	time_t ltime;
	int i;
	char version[32];

	if (is_epsf) {
/*fixme: no landscape for EPS?*/
		fprintf(fout, "%%!PS-Adobe-3.0 EPSF-3.0\n"
			"%%%%BoundingBox: 0 0 %.0f %.0f\n",
			p_fmt->pagewidth - p_fmt->leftmargin
				- p_fmt->rightmargin + 20,
			-bposy);
		cur_lmarg = p_fmt->leftmargin - 10;
	} else {
		fprintf(fout, "%%!PS-Adobe-2.0\n");
		fprintf(fout, "%%%%BoundingBox: 0 0 %.0f %.0f\n",
			p_fmt->pagewidth,
			p_fmt->pageheight);
	}
	fprintf(fout, "%%%%Title: %s\n", str);
	time(&ltime);
#ifndef WIN32
	strftime(tex_buf, TEX_BUF_SZ, "%b %e, %Y %H:%M", localtime(&ltime));
#else
	strftime(tex_buf, TEX_BUF_SZ, "%b %#d, %Y %H:%M", localtime(&ltime));
#endif
	fprintf(fout, "%%%%Creator: abcm2ps-" VERSION "\n"
		"%%%%CreationDate: %s\n", tex_buf);
	if (!is_epsf)
		fprintf(fout, "%%%%Pages: (atend)\n");
	fprintf(fout, "%%%%LanguageLevel: 2\n"
		"%%%%EndComments\n"
		"%%CommandLine:");
	for (i = 1; i < s_argc; i++) {
		fprintf(fout,
			strchr(s_argv[i], ' ') != 0 ? " \'%s\'" : " %s",
			s_argv[i]);
	}
	fprintf(fout, "\n\n");
	if (is_epsf)
		fprintf(fout,
			"gsave /origstate save def mark\n"
			"100 dict begin\n");
	strcpy(version, "/creator [(abcm2ps) " VERSION "] def");
	for (i = 0; i < strlen(version); i++) {
		if (version[i] == '.')
			version[i] = ' ';
	}
	fprintf(fout, "%%%%BeginSetup\n"
		"/!{bind def}bind def\n"
		"/bdef{bind def}!\n"		/* for compatibility */
		"/T/translate load def\n"
		"/M/moveto load def\n"
		"/RM/rmoveto load def\n"
		"/RL/rlineto load def\n"
		"/RC/rcurveto load def\n"
		"/SLW/setlinewidth load def\n"
		"/defl 0 def\n"	/* decoration flags - see deco.c for values */
		"/dlw{0.7 SLW}!\n"

	/* (simple!) level2 emulation */
		"/languagelevel where{pop}{/languagelevel 1 def}ifelse\n"
		"languagelevel 1 eq{\n"
		" /rectstroke{\n"
		"	4 2 roll M 1 index 0 RL 0 exch RL neg 0 RL closepath\n"
		"	stroke}!\n"
		" /rectfill{\n"
		"	4 2 roll M 1 index 0 RL 0 exch RL neg 0 RL closepath\n"
		"	fill}!\n"
		" /selectfont{exch findfont exch dup\n"
		"	type/arraytype eq{makefont}{scalefont}ifelse setfont}!\n"
		" /product where{pop}{/product()def}ifelse\n"
		" }if\n"
		"%s\n", version);
	define_symbols();
	define_encodings();
	user_ps_write();
	define_fonts();
	fprintf(fout, "%%%%EndSetup\n");
	file_initialized = 1;
}

/* -- close_output_file -- */
void close_output_file(void)
{
	long m;

	if (fout == 0)
		return;
	if (multicol_start != 0) {	/* if no '%%multocol end' or filtering */
		multicol_start = 0;
		write_buffer();
	}
	if (tunenum == 0)
		error(0, 0, "No tunes written to output file");
	close_page();
	fprintf(fout, "%%%%Trailer\n"
		"%%%%Pages: %d\n"
		"%%EOF\n", nbpages);
	if (fout != stdout) {
		m = ftell(fout);
		fclose(fout);
		fprintf(stderr,
			"Output written on %s (%d page%s, %d title%s, %ld bytes)\n",
			outfnam,
			nbpages, nbpages == 1 ? "" : "s",
			tunenum, tunenum == 1 ? "" : "s",
			m);
	}
	fout = 0;
	file_initialized = 0;
	nbpages = tunenum = 0;
	defl = 0;
}

/* -- close the PS page -- */
void close_page(void)
{
	if (!in_page)
		return;
	in_page = 0;
	fprintf(fout, "%%%%PageTrailer\n"
		"grestore\n"
		"showpage\n");
	cur_lmarg = 0;
	cur_scale = 1.0;
	outft = -1;
}

/* -- output a header/footer element -- */
static void format_hf(char *p)
{
	char *q;
	time_t ltime;

	for (;;) {
		if (*p == '\0')
			break;
		if ((q = strchr(p, '$')) != 0)
			*q = '\0';
		fprintf(fout, "%s", p);
		if (q == 0)
			break;
		p = q + 1;
		switch (*p) {
		case 'd':
			ltime = mtime;
			goto dput;
		case 'D':
			time(&ltime);
		dput:	cnv_date(&ltime);
			fprintf(fout, "%s", tex_buf);
			break;
		case 'F':		/* ABC file name */
#if DIRSEP!='\\'
			fprintf(fout, "%s", in_fname);
#else
			{
				int i;
				char *r;

				q = in_fname;
				r = tex_buf;
				i = TEX_BUF_SZ;
				for (;;) {
					if (--i <= 0 || *q == '\0')
						break;
					if ((*r++ = *q++) == '\\') {
						i--;
						*r++ = '\\';
					}
				}
				*r = '\0';
			}
			fprintf(fout, "%s", tex_buf);
#endif
			break;
		case 'I':		/* information field */
			p++;
			if (*p < 'A' || *p > 'Z' || info[*p - 'A'] == 0)
				break;
			fprintf(fout, "%s", &info[*p - 'A']->as.text[2]);
			break;
		case 'P':		/* page number */
			if (p[1] == '0') {
				p++;
				if (pagenum & 1)
					break;
			} else if (p[1] == '1') {
				p++;
				if ((pagenum & 1) == 0)
					break;
			}
			fprintf(fout, "%d", pagenum);
			break;
		case 'T':		/* tune title */
			q = &info['T' - 'A']->as.text[2];
			while (isspace((unsigned char) *q))
				q++;
			tex_str(q);
			fprintf(fout, "%s", tex_buf);
			break;
		case 'V':
			fprintf(fout,"abcm2ps-"  VERSION);
			break;
		default:
			continue;
		}
		p++;
	}
}

/* -- output the header or footer -- */
static float headfooter(int header,
			float pwidth,
			float pheight)
{
	char str[512];
	char *p, *q, *r;
	float size, y, wsize;
	struct FONTSPEC *f, f_sav;
	int defft_sav;

	if (header) {
		p = cfmt.header;
		f = &cfmt.font_tb[HEADERFONT];
		size = f->size;
		y = 2;
	} else {
		p = cfmt.footer;
		f = &cfmt.font_tb[FOOTERFONT];
		size = f->size;
		y = - (pheight - cfmt.topmargin - cfmt.botmargin)
			- size + 2;
	}
	if (*p == '-') {
		if (pagenum == 1)
			return 0;
		p++;
	}
	defft_sav = get_str_font();
	memcpy(&f_sav, &cfmt.font_tb[0], sizeof f_sav);
	wsize = 0;
	str_font(f - cfmt.font_tb);
	fprintf(fout, "%.1f F%d ", size, f->fnum);
	outft = f - cfmt.font_tb;

	/* may have 2 lines */
	if ((r = strstr(p, "\\n")) != 0) {
		if (!header)
			y += size;
		wsize += size;
		*r = '\0';
	}
	for (;;) {
		tex_str(p);
		strcpy(str, tex_buf);

		/* left side */
		p = str;
		if ((q = strchr(p, '\t')) != 0) {
			if (q != p) {
				*q = '\0';
				fprintf(fout, "%.1f %.1f M(",
					p_fmt->leftmargin, y);
				format_hf(p);
				fprintf(fout, ")show\n");
			}
			p = q + 1;
		}
		if ((q = strchr(p, '\t')) != 0)
			*q = '\0';

		/* center */
		if (q != p) {
			fprintf(fout, "%.1f %.1f M(",
				pwidth * 0.5, y);
			format_hf(p);
			fprintf(fout, ")showc\n");
		}

		/* right side */
		if (q != 0) {
			p = q + 1;
			if (*p != '\0') {
				fprintf(fout, "%.1f %.1f M(",
					pwidth - p_fmt->rightmargin, y);
				format_hf(p);
				fprintf(fout, ")showr\n");
			}
		}
		if (r == 0)
			break;
		*r = '\\';
		p = r + 2;
		r = 0;
		y -= size;
	}
	memcpy(&cfmt.font_tb[0], &f_sav, sizeof cfmt.font_tb[0]);
	str_font(defft_sav);
	return wsize;
}

/* -- initialize postscript page -- */
static void init_page(void)
{
	float pheight, pwidth;

	if (in_page)
		return;

	p_fmt = info['X' - 'A'] == 0 ? &cfmt : &dfmt;	/* global format */

	if (!file_initialized)
		init_ps(in_fname, 0);
	in_page = 1;
	nbpages++;
	outft = -1;

	fprintf(fout, "%%%%Page: %d %d\n",
		nbpages, nbpages);
	if (cfmt.landscape) {
		pheight = p_fmt->pagewidth;
		pwidth = cfmt.pageheight;
		fprintf(fout, "%%%%PageOrientation: Landscape\n"
			"gsave 90 rotate 0 %.1f T\n",
			-cfmt.topmargin);
	} else {
		pheight = cfmt.pageheight;
		pwidth = p_fmt->pagewidth;
		fprintf(fout, "gsave 0 %.1f T\n",
			pheight - cfmt.topmargin);
	}
	fprintf(fout,
		"%% --- width %.1f\n",		/* for index */
		((cfmt.landscape ? cfmt.pageheight : cfmt.pagewidth)
		 - cfmt.leftmargin - cfmt.rightmargin) / cfmt.scale);

	posy = pheight - cfmt.topmargin - cfmt.botmargin;

	/* output the header and footer */
	if (cfmt.header == 0) {
		char *p = 0;

		switch (pagenumbers) {
		case 1: p = "$P\t"; break;
		case 2: p = "\t\t$P"; break;
		case 3: p = "$P0\t\t$P1"; break;
		case 4: p = "$P1\t\t$P0"; break;
		}
		if (p != 0)
			cfmt.header = strdup(p);
	}
	if (cfmt.header != 0) {
		float dy;

		dy = headfooter(1, pwidth, pheight);
		if (dy != 0) {
			fprintf(fout, "0 %.1f T\n", -dy);
			posy -= dy;
		}
	}
	if (cfmt.footer != 0)
		posy -= headfooter(0, pwidth, pheight);
	pagenum++;
	outft = -1;
}

/* -- open the output file -- */
void open_output_file(void)
{
	int i;
	char fnm[STRL1];

	strcpy(fnm, outfn);
	i = strlen(fnm) - 1;
	if (i < 0)
		strcpy(fnm, OUTPUTFILE);
	else if (i == 0 && fnm[0] == '-')
		;
	else {
		if (fnm[i] == '=') {
			char *p;

			if ((p = strrchr(in_fname, DIRSEP)) == 0)
				p = in_fname;
			else	p++;
/*fixme: should check if there is a DIRSEP at the end of fnm*/
			strcpy(&fnm[i], p);
			strext(fnm, "ps");
		} else if (fnm[i] == DIRSEP)
			strcpy(&fnm[i + 1], OUTPUTFILE);
#if 0
/*fixme: fnm may be a directory*/
		else	...
#endif
	}
	if (strcmp(fnm, outfnam) == 0)
		return;

	close_output_file();
	strcpy(outfnam, fnm);
	if (i != 0 || fnm[0] != '-') {
		if ((fout = fopen(fnm, "w")) == 0) {
			fprintf(stderr, "Cannot create output file %s\n", fnm);
			exit(2);
		}
	} else	fout = stdout;
}

/* -- cut off extension on a file identifier -- */
static void cutext(char *fid)
{
	char *p;

	if ((p = strrchr(fid, DIRSEP)) == 0)
		p = fid;
	if ((p = strrchr(p, '.')) != 0)
		*p = '\0';
}

/* -- epsf_title -- */
static void epsf_title(char *p)
{
	char c;

	while ((c = *p) != '\0') {
		if (c == ' ')
			*p = '_';
		else if (c == DIRSEP || (unsigned) c >= 127)
			*p = '.';
		p++;
	}
}

/* -- output the EPS file -- */
void write_eps(void)
{
	int i;
	long m;
	char *p, fnm[STRL1], finf[STRL1];

	p_fmt = info['X' - 'A'] == 0 ? &cfmt : &dfmt;	/* global format */
	close_output_file();
	strcpy(fnm, outfn);
	if (fnm[0] == '\0')
		strcpy(fnm, OUTPUTFILE);
	cutext(fnm);
	i = strlen(fnm) - 1;
	if (fnm[i] == '=') {
		p = &info['T' - 'A']->as.text[2];
		while (isspace((unsigned char) *p))
			p++;
		strncpy(&fnm[i], p, sizeof fnm - i - 4);
		fnm[sizeof fnm - 5] = '\0';
		epsf_title(&fnm[i]);
	} else	sprintf(&fnm[i + 1], "%03d", ++nepsf);
	strcat(fnm, ".eps");
	if ((fout = fopen(fnm, "w")) == 0) {
		fprintf(stderr, "Cannot open output file %s\n", fnm);
		exit(2);
	}
	sprintf(finf, "%.72s (%.4s)", in_fname, &info['X' - 'A']->as.text[2]);
	init_ps(finf, 1);
	fprintf(fout, "0 %.1f T\n", -bposy);
	write_buffer();
	fprintf(fout, "showpage\nend\n"
		"cleartomark origstate restore grestore\n");
	m = ftell(fout);
	fclose(fout);
	fout = 0;
	fprintf(stderr, "Output written on %s (%ld bytes)\n",
		fnm, m);
	cur_lmarg = 0;
	cur_scale = 1.0;
}

/* -- start a new page -- */
static void newpage(void)
{
	close_page();
	init_page();
}

/*  subroutines to handle output buffer  */

/* -- update the output buffer pointer -- */
/* called from the PUTx() macros */
void a2b(void)
{
	int l;

	if (!in_page && !epsf)
		init_page();

	l = strlen(mbf);
	nbuf += l;
	if (nbuf >= BUFFSZ - 500) {	/* must have place for 1 more line */
		error(1, 0, "a2b: buffer full, BUFFSZ=%d", BUFFSZ);
		exit(3);
	}
	mbf += l;
}

/* -- translate down by 'h' absolute points in output buffer -- */
void abskip(float h)
{
	PUT1("0 %.2f T\n", -h / cfmt.scale);
	bposy -= h;
}

/* -- translate down by 'h' scaled points in output buffer -- */
void bskip(float h)
{
	bposy -= h * cfmt.scale;
	PUT1("0 %.2f T\n", -h);
}

/* -- clear_buffer -- */
void clear_buffer(void)
{
	nbuf = 0;
	bposy = 0;
	ln_num = 0;
	mbf = buf;
}

/* -- write buffer contents, break at full pages -- */
void write_buffer(void)
{
	int i, l, b2, np;
	float p1, dp;
	int outft_sav;

	if (nbuf == 0 || multicol_start != 0)
		return;
	outft_sav = outft;
	i = 0;
	p1 = 0;
	for (l = 0; l < ln_num; l++) {
		b2 = ln_buf[l];
		dp = ln_pos[l] - p1;
		np = posy + dp < 0 && !epsf;
		if (np) {
			newpage();
			if (ln_font[l] >= 0) {
				struct FONTSPEC *f;

				f = &cfmt.font_tb[ln_font[l]];
				fprintf(fout, "%.1f F%d\n",
					f->size, f->fnum);
			}
		}
		if (ln_scale[l] != cur_scale) {
			fprintf(fout, "%.2f dup scale\n",
				ln_scale[l] / cur_scale);
			cur_scale = ln_scale[l];
		}
		if (ln_lmarg[l] != cur_lmarg) {
			fprintf(fout, "%.1f 0 T\n",
				(ln_lmarg[l] - cur_lmarg) / cur_scale);
			cur_lmarg = ln_lmarg[l];
		}
		if (np) {
			fprintf(fout, "0 %.1f T\n", -cfmt.topspace);
			posy -= cfmt.topspace * cfmt.scale;
		}
		if (buf[i] != '\001')
			fwrite(&buf[i], 1, b2 - i, fout);
		else {				/* %%EPS - see parse.c */
			FILE *f;
			char line[BSIZE], *p, *q;

			i++;
			p = strchr(&buf[i], '\n');
			fwrite(&buf[i], 1, p - &buf[i] + 1, fout);
			i = p - buf + 1;
			p = strchr(&buf[i], '%');
			*p++ = '\0';
			q = strchr(p, '\n');
			*q = '\0';
			if ((f = fopen(p, "r")) == 0) {
				error(1, 0, "Cannot open EPS file '%s'", p);
			} else {
				fprintf(fout,
					"save	%% EPS file '%s'\n"
					"/showpage{}def/setpagedevice{pop}def\n"
					"%s T\n", 
					p, &buf[i]);
				while (fgets(line, sizeof line, f))	/* copy the file */
					fwrite(line, 1, strlen(line), fout);
				fclose(f);
				strcpy(line, "restore	% end EPS\n");
				fwrite(line, 1, strlen(line), fout);
			}
		}
		i = b2;
		posy += dp;
		p1 = ln_pos[l];
	}
	fwrite(&buf[i], 1, nbuf - i, fout);
	clear_buffer();
	outft = outft_sav;
}

/* -- handle completed block in buffer -- */
/* if the added stuff does not fit on current page, write it out
   after page break and change buffer handling mode to pass though */
void buffer_eob(void)
{
	if (bposy == 0 && nbuf == 0)
		return;
	if (ln_num > 0 && ln_buf[ln_num - 1] == nbuf)
		return;
	if (ln_num >= BUFFLN) {
		int mc_sav;

		error(1, 0, "max number of buffer lines exceeded"
			" -- check BUFFLN");
		mc_sav = multicol_start;
		multicol_start = 0;
		write_buffer();
		multicol_start = mc_sav;
		use_buffer = 0;
	}
	ln_buf[ln_num] = nbuf;
	ln_pos[ln_num] = bposy;
	ln_lmarg[ln_num] = cfmt.leftmargin;
	ln_scale[ln_num] = cfmt.scale;
	ln_font[ln_num] = outft;
	ln_num++;
	if (!use_buffer) {
		write_buffer();
		return;
	}
	if (posy + bposy < 0 && !epsf && multicol_start == 0) {
		if (tunenum > 1)
			newpage();
		write_buffer();
		use_buffer = 0;
	}
}

/* -- dump buffer if not enough place for a music line -- */
void check_buffer(void)
{
	if (nbuf > BUFFSZ - 5000) {	/* assume music line < 5000 bytes */
		error(0, 0,
		      "Possibly bad page breaks, BUFFSZ exceeded");
		write_buffer();
		use_buffer = 0;
	}
}

/* -- return the current vertical offset in the page -- */
float get_bposy(void)
{
	return posy + bposy;
}
