/*
 * Copyright (c) 2002, The Tendra Project <http://www.ten15.org/>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *    		 Crown Copyright (c) 1997
 *
 *    This TenDRA(r) Computer Program is subject to Copyright
 *    owned by the United Kingdom Secretary of State for Defence
 *    acting through the Defence Evaluation and Research Agency
 *    (DERA).  It is made available to Recipients with a
 *    royalty-free licence for its use, reproduction, transfer
 *    to other parties and amendment for any purpose not excluding
 *    product development provided that any such use et cetera
 *    shall be deemed to be acceptance of the following conditions:-
 *
 *        (1) Its Recipients shall ensure that this Notice is
 *        reproduced upon any copies or amended versions of it;
 *
 *        (2) Any amended version of it shall be clearly marked to
 *        show both the nature of and the organisation responsible
 *        for the relevant amendment or amendments;
 *
 *        (3) Its onward transfer from a recipient to another
 *        party shall be deemed to be that party's acceptance of
 *        these conditions;
 *
 *        (4) DERA gives no warranty or assurance as to its
 *        quality or suitability for any purpose and DERA accepts
 *        no liability whatsoever in relation to any use to which
 *        it may be put.
 *
 * $TenDRA: tendra/src/tools/disp/pretty.c,v 1.7 2005/07/24 09:40:26 stefanf Exp $
 */


#include "config.h"
#include "types.h"
#include "ascii.h"
#include "file.h"
#include "tree.h"


/*
 *    FORWARD DECLARATIONS
 */

static void display(int) ;
static void expression(word *, int, int, int, int, int) ;


/*
 *    FLAGS SET BY EXTERNAL OPTIONS
 *
 *    Various flags may be given on the command line with affect the form
 *    of the output.  These are documented elsewhere.
 */

BoolT helpflag = TRUE;
BoolT progress = FALSE;
BoolT quickflag = FALSE;


/*
 *    VARIABLES USED IN PRINTING ROUTINE
 *
 *    The variable maxcol is used to specify the maximum number of columns
 *    into which the output should be fitted.  maxtab gives the current
 *    indentation.  dot_spacing is used to indicate the distance between
 *    the vertical alignment lines when helpflag is true.
 */

int maxcol = 80;
static int maxtab;
static int dot_spacing;


/*
 *    CHOICE TABLE
 *
 *    This table is used to decide the maximum suitable indentation.  For
 *    example the entry { a, b, c } means, try printing with an indentation
 *    of a.  If this fits into the required number of columns, try with
 *    the (larger) indentation c.  Otherwise try with the (smaller)
 *    indentation b.
 */

#define M 100

static int dec [7][3] = {
    { 4, 2, 8 },
    { 2, 1, 3 },
    { 8, 6, M },
    { 1, 0, 1 },
    { 3, 2, 3 },
    { 6, 4, 6 },
    { M, 8, M }
};


/*
 *    PRINT THE TDF TREE
 *
 *    This routine has overall control of the printing of the tree.  It
 *    determines the most suitable indentation by means of trial runs,
 *    and then prints the tree with that indentation.
 */

void
pretty_tree()
{
    int i, j;
    int maximum0;

    initialize_tree ();

    /* Do some trial runs to try to fit into right number of columns... */
    printflag = 0;

    /* With a maximum indentation of 0... */
    maxtab = 0;
    display (0);
    maximum0 = maximum;

    /* Try some larger/smaller values */
    if (!quickflag && maximum < maxcol) {
		maxtab = 4;
		for (i = 0 ; i < 3 ; i++) {
			display (1);
			j = -1;
			while (dec[++j][0] != maxtab);
			maxtab = dec[j][ maximum < maxcol ? 2 : 1 ];
		}
    }

    /* Work out dot spacing */
    dot_spacing = 8;
    if (maxtab != 100) dot_spacing = 2 * (maxtab + 1);
    if (maxtab == 0) {
		if (maximum0 > maxcol) maxcol = maximum0;
		dot_spacing = 4;
    }
    init_spaces (dot_spacing);

    /* Actually do the printing */
    printflag = 1;
    display (0);
    if (progress) {
		IGNORE fprintf (stderr, "Printed in %d columns.\n", maximum);
    }
    return;
}


/*
 *    TRY TO PRETTY-PRINT THE TDF TREE
 *
 *    This routine actually outputs the tree.  If test is true, then this
 *    is only a trial run, and may be aborted if the output exceeds the
 *    required number of columns.
 */

static void
display(int test)
{
    word *ptr;
    maximum = 0;
    for (ptr = word1.bro ; ptr ; ptr = ptr->bro) {
		column = 0;
		expression (ptr, 0, 0, 0, 1, 0);
		if (test && (maximum > maxcol)) return;
    }
    return;
}


/*
 *    OUTPUT A CHARACTER
 *
 *    This routine outputs a single character into the output file.
 */

#define put_out(c)						\
    {								\
	if (c) {						\
	    if (printflag) IGNORE fputc ((c), pp_file) ;	\
	    column++ ;						\
	    lastc = (c) ;					\
	}							\
    }


/*
 *    OUTPUT A NEWLINE
 *
 *    This routine outputs a newline character into the output file.
 *    It also causes column to be compared against maximum.
 */

#define new_line()						\
    {								\
	if (column > maximum) maximum = column ;		\
	if (printflag) IGNORE fputc (NEWLINE, pp_file) ;	\
	column = 0 ;						\
	lastc = NEWLINE ;					\
    }


/*
 *    OUTPUT A TDF EXPRESSION
 *
 *    This routine is called recursively to print the tree.  ptr gives
 *    the current position within the tree.  col gives the column
 *    where printing should start.  flag is true to indicate that a
 *    newline should be output at the end.  pending gives the number
 *    of trailing close brackets which are pending.  first gives a
 *    character which needs to be output before the current
 *    expression, and last gives one which should be output after it.
 */

static void
expression(word *ptr, int col, int first,
		   int last, int flag, int pending)
{
    word *p;
    char bar = '|';
    char open = '(';
    char close = ')';
    char comma = ',';
    int more, pends;
    char sort, opener, sep;
    int len, visible, horiz;
    int mflag, new_col, temp_col, temp_max;

    /* print initial spaces */
    if (column == 0) spaces (col);

    /* output first character */
    put_out (first);

    /* copy out initial text */
    col = column;
    len = (int) ptr->length;
    sort = ptr->type;
    if (printflag) IGNORE fputs (ptr->text, pp_file);
    column += len;

    /* if we have parameters, we need to decode them */
    if (sort != SIMPLE) {

		/* are the brackets visible or not? */
		visible = ((sort == HORIZ_NONE || sort == VERT_NONE) ? 0 : 1);
		if (!visible) {
			open = close = bar = 0;
			comma = ' ';
		}

		/* are we printing horizontally or vertically? */
		horiz = (sort == HORIZ_BRACKETS || sort == HORIZ_NONE);

		/* does the maximum tab come into effect? */
		mflag = (!horiz && (len > maxtab));
		new_col = col + (mflag ? maxtab : len) + 1;

		if (sort == VERT_BRACKETS) {
			/* try to print things with only one parameter horizontally */
			p = ptr->son;
			if (p == null) {
				horiz = 1;
			} else {
				if (p->son == null && p->bro == null) horiz = 1;
			}
		}

		/* have a test run, if printing horizontally */
		if (horiz && printflag) {
			horiz = 1;
			/* save old values */
			temp_col = column;
			temp_max = maximum;
			printflag = 0;
			maximum = column;
			/* open bracket */
			put_out (open);
			sep = comma;
			pends = 0;
			/* print parameters */
			for (p = ptr->son ; p ; p = p->bro) {
				if (p->bro == null) {
					/* for last, take trailing brackets into account */
					sep = close;
					pends = pending + visible;
				}
				/* print this parameter */
				expression (p, column, 0, sep, 0, pends);
			}
			/* close bracket */
			put_out (last);
			/* if this doesn't fit in, try vertically */
			if (maximum >= maxcol || column + pending >= maxcol) {
				horiz = 0;
				mflag = (len > maxtab);
				new_col = col + (mflag ? maxtab : len) + 1;
			}
			/* restore old values */
			column = temp_col;
			maximum = temp_max;
			printflag = 1;
		}

		if (horiz && printflag) {
			/* do horizontal printing of parameters */
			/* open bracket */
			put_out (open);
			sep = comma;
			pends = 0;
			/* print parameters */
			for (p = ptr->son ; p ; p = p->bro) {
				/* for last, take trailing brackets into account */
				if (p->bro == null) {
					sep = close;
					pends = pending + visible;
				}
				/* print this parameter */
				expression (p, column, 0, sep, 0, pends);
			}
		} else {
			/* do vertical printing of parameters */
			put_out (open);
			opener = bar;
			if (mflag) {
				new_line ();
				spaces (new_col - 1);
			}
			sep = comma;
			more = 1;
			pends = 0;
			new_col = column;
			if (!mflag) {
				new_col--;
				opener = 0;
			}
			/* print parameters */
			for (p = ptr->son ; p ; p = p->bro) {
				if (p->bro == null) {
					/* last requires special treatment */
					sep = 0;
					more = 0;
					pends = pending + visible;
				}
				/* print this parameter */
				expression (p, new_col, opener, sep, more, pends);
				opener = bar;
			}
			if (visible) put_out (close);
		}
    }

    /* output last character */
    if (last != ',' || (lastc != '.' && lastc != ':')) put_out (last);

    /* and a newline if required */
    if (flag) new_line ();
    return;
}
