/* Copyright (c) 1993, Joseph Arceneaux.

   This file 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 1, or (at your option)
   any later version.

   This software 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.

   If you do not have a copy of the GNU General Public License, you
   may obtain a copy by writing to the Free Software Foundation,
   675 Mass Ave, Cambridge, MA 02139, USA.  */



#include "sys.h"
#include "indent.h"

/* Check the limits of the comment buffer, and expand as neccessary. */

#define CHECK_COM_SIZE \
	if (e_com >= l_com) \
          { \
	    register nsize = l_com-s_com+400; \
	    combuf = (char *) xrealloc (combuf, nsize); \
	    e_com = combuf + (e_com-s_com) + 1; \
	    l_com = combuf + nsize - 5; \
	    s_com = combuf + 1; \
	  }

/* The number of comments handled so far. */
int out_coms;

/* Output a comment.  `buf_ptr' is pointing to the character after
   the beginning comment delimiter when this is called.  This handles
   both C and C++ comments.

   As far as indent is concerned, there are basically two types
   of comments -- those on lines by themselves and those which are
   on lines with other code.  Variables (and the options specifying them)
   affecting the printing of comments are:

   `format_comments'                ("fca"):  Ignore newlines in the
       comment and perform filling up to `max_col'.  Double newlines
       indicate paragraph breaks.

   `format_col1_comments'           ("fc1"):  Format comments which
       begin in column 1.

   `unindent_displace'              ("d"):  The hanging indentation for
       comments which do not appear to the right of code.

   `comment_delimiter_on_blankline' ("cdb"):  If set, place the comment
       delimiters on lines by themselves.  This only affects comments
       which are not to the right of code.

   `com_ind'                        ("c"):  The column in which to begin
       comments that are to the right of code.

   `decl_com_ind'                   ("cd"):  The column in which to begin
       comments that are to the right of declarations.

   `else_endif_col'                 ("cp"):  The column in which to begin
       comments to the right of preprocessor directives.

   `star_comment_cont'              ("sc"):  Place a star ('*') to the
       left of the comment body.

   `max_col'                        ("l"):  The length of a line.  Formatted
       comments which extend past this column will be continued on the
       following line.

   `block_comment_max_col'          ("lc"): Unused.

   `blanklines_before_blockcomments'("nbbb"): Unused.

   */

void
print_comment ()
{
  register int column, format;
  enum codes comment_type;

  int start_column, save_length, found_column;
  int first_comment_line, right_margin;
  int boxed_comment, stars, blankline_delims, paragraph_break,
      merge_blank_comment_lines;
  char *line_break_ptr = 0;
  char *save_ptr = 0;
  char *text_on_line = 0;
  char *start_delim, *end_delim;

  char *line_preamble;
  int line_preamble_length, visible_preamble;

  /* Increment the parser stack, as we will store some things
     there for dump_line to use. */
  inc_pstack ();

  /* Have to do it this way because this piece of shit program doesn't
     always place the last token code on the stack. */
  if (*(token + 1) == '/')
    comment_type = cplus_comment;
  else
    comment_type = comment;

  /* First, decide what kind of comment this is: C++, C, or boxed C.
     Even if this appears to be a normal C comment, we may change our
     minds if we find a star in the right column of the second line,
     in which case that's a boxed comment too. */
  if (comment_type == cplus_comment)
    {
      start_delim = "//";
      line_preamble = "// ";
      line_preamble_length = 3;
      visible_preamble = 1;
      boxed_comment = 0;
      stars = 0;
      blankline_delims = 0;
    }
  else if (*buf_ptr == '*' || *buf_ptr == '-'
	   || *buf_ptr == '=' || *buf_ptr == '_')
    /* Boxed comment */
    {
      start_delim = "/*";
      end_delim = "*/";
      line_preamble = " ";
      line_preamble_length = 1;
      visible_preamble = 0;
      boxed_comment = 1;
      stars = 0;
      blankline_delims = 0;
    }
  else
    {
      start_delim = "/*";
      end_delim = "*/";
      line_preamble = 0;
      line_preamble_length = 0;
      visible_preamble = 0;
      boxed_comment = 0;
      stars = star_comment_cont;
      blankline_delims = comment_delimiter_on_blankline;
    }

  paragraph_break = 0;
  merge_blank_comment_lines = 0;
  first_comment_line = com_lines;
  right_margin = max_col;

  /* Now, compute the correct indentation for this comment
     and whether or not it should be formatted. */
  found_column = current_column () - 2;
  if (boxed_comment)
    {
      start_column = found_column;
      format = 0;
      blankline_delims = 0;
    }
  else
    {
      /* First handle comments which begin the line. */
      if ((s_lab == e_lab) && (s_code == e_code))
	{
	  /* This is a top-level comment, not within some code. */
	  if (parser_state_tos->ind_level <= 0)
	    {
	      if (parser_state_tos->col_1)
		{
		  format = format_col1_comments;
		  start_column = 1;
		}
	      else
		{
		  format = format_comments;
		  start_column = found_column;
		}
	    }
	  /* Here for comments starting a line, in the middle of code. */
	  else
	    {
	      if (parser_state_tos->col_1)
		{
		  format = format_col1_comments;
		  start_column = 1;
		}
	      else
		{
		  format = format_comments;
		  start_column = (parser_state_tos->ind_level
				  - unindent_displace + 1);
		  if (start_column < 0)
		    start_column = 1;
		}
	    }
	}
      else
	/* This comment follows code of some sort. */
	{
	  int target;

	  /* First, compute where the comment SHOULD go. */
	  if (parser_state_tos->decl_on_line)
	    target = decl_com_ind;
	  else if (else_or_endif)
	    target = else_endif_col;
	  else
	    target = com_ind;

	  /* Now determine if the code on the line is short enough
	     to allow the comment to begin where it should. */
	  if (s_code != e_code)
	    start_column = count_columns (compute_code_target (), s_code);
	  else
	    /* s_lab != e_lab : there is a label here. */
	    start_column = count_columns (compute_label_target (), s_lab);

	  if (start_column < target)
	    start_column = target;
	  else
	    {
	      /* If the too-long code is a pre-processor command,
		 start the comment 1 space afterwards, otherwise
		 start at the next tab mark. */
	      if (else_or_endif)
		{
		  start_column++;
		  else_or_endif = false;
		}
	      else
		start_column += (tabsize - (start_column % tabsize) + 1);
	    }

	  format = format_comments;
	}
    }

  if (! line_preamble)
    {
      line_preamble_length = 3;
      if (stars)
	{
	  line_preamble = " * ";
	  visible_preamble = 1;
	}
      else
	{
	  line_preamble = "   ";
	  visible_preamble = 0;
	}
    }

  /* These are the parser stack variables used to communicate
     formatting information to dump_line (). */
  parser_state_tos->com_col = start_column;
  parser_state_tos->box_com = boxed_comment;
  if (boxed_comment)
    {
      stars = 0;
      blankline_delims = 0;
    }

  /* Output the beginning comment delimiter.  They are both two
     characters long. */
  memcpy (e_com, start_delim, 2);
  e_com += 2;
  column = start_column + 2;

  /* If the user specified -cdb, put the delimiter on one line. */
  if (blankline_delims)
    {
      char *p = buf_ptr;

      *e_com = '\0';
      dump_line ();

      /* Check if the delimiter was already on a line by itself,
	 and skip whitespace if formating. */
      while ((*p == ' ' || *p == TAB) && p < buf_end)
	p++;
      if (*p == EOL)
	buf_ptr = p + 1;
      else if (format)
	buf_ptr = p;
      if (buf_ptr >= buf_end)
	fill_buffer ();

      column = start_column;
      goto begin_line;
    }
  else if (format)
    {
      *e_com++ = ' ';
      column = start_column + 3;
      while (*buf_ptr == ' ' || *buf_ptr == TAB)
	buf_ptr++;
      if (buf_ptr >= buf_end)
	fill_buffer ();
    }

  /* Iterate through the lines of the comment */
  while (1)
    {
      /* Iterate through the characters on one line */
      while (1)
	{
	  CHECK_COM_SIZE;

	  switch (*buf_ptr)
	    {
	    case ' ':
	    case TAB:
	      /* If formatting, and previous break marker is
	         nonexistant, or before text on line, reset
		 it to here. */
	      if (format && line_break_ptr < text_on_line)
		line_break_ptr = e_com;

	      if (*buf_ptr == ' ')
		{
		  *e_com++ = ' ';
		  column++;
		}
	      else
		{
		  /* Convert the tab to the appropriate number of spaces,
		     based on the column we found the comment in, not
		     the one we're printing in. */
		  int tab_width
		    = (tabsize - ((column + found_column - start_column - 1)
				  % tabsize));
		  column += tab_width;
		  while (tab_width--)
		    *e_com++ = ' ';
		}
	      break;

	    case EOL:
	      /* We may be at the end of a C++ comment */
	      if (comment_type == cplus_comment)
		{
		  dump_line ();
		  buf_ptr++;
		  if (buf_ptr >= buf_end)
		    fill_buffer ();

		  parser_state_tos->tos--;
		  parser_state_tos->com_col = start_column;
		  return;
		}

	      if (format)
		{
		  /* Newline and null are the two characters which
		     end an input line, so check here if we need to
		     get the next line. */
		  buf_ptr++;
		  if (buf_ptr >= buf_end)
		    fill_buffer ();

		  /* If this is "\n\n", it's a paragraph break. */
		  if (*buf_ptr == EOL || ! text_on_line)
		    {
		      paragraph_break = 1;
		      goto end_line;
		    }

		  /* This is a single newline.  Transform it, and any
		     following whitespace into a single blank. */
		  line_break_ptr = e_com;
		  *e_com++ = ' ';
		  column++;
		  while (*buf_ptr == TAB || *buf_ptr == ' ')
		    buf_ptr++;
		  if (buf_ptr >= buf_end)
		    fill_buffer ();
		  continue;
		}
	      else
		/* We are printing this line "as is", so output it
		   and continue on to the next line. */
		goto end_line;
	      break;

	    case '*':
	      /* Check if we've reached the end of the comment. */
	      if (comment_type == comment)
		{
		  if (*(buf_ptr + 1) == '/')
		    {
		      /* If it's not a boxed comment, put some whitespace
		         before the ending delimiter.  Otherwise, simply
			 insert the delimiter. */
		      if (! boxed_comment)
			{
			  if (text_on_line)
			    {
			      if (blankline_delims)
				{
				  *e_com = '\0';
				  dump_line ();
				  *e_com++ = ' ';
				}
			      else
				/* Here I also tried some code to ensure
				   that the ending delimiter didn't exceed
				   max_col of formatted comments.  But it's
				   not worth the hassle. */
				if (*(e_com - 1) != ' ')
				  *e_com++ = ' ';
			    }
			  else
			    e_com = s_com + 1;
			}

		      *e_com++ = '*';
		      *e_com++ = '/';
		      *e_com = '\0';

		      /* Skip any whitespace following the comment.  If
			 there is only whitespace after it, print the line. */
		      buf_ptr += 2;
		      while (*buf_ptr == ' ' || *buf_ptr == TAB)
			buf_ptr++;
		      if (buf_ptr >= buf_end)
			fill_buffer ();

		      parser_state_tos->tos--;
		      parser_state_tos->com_col = start_column;
		      return;
		    }

		  /* If this star is on the second line of the
		     comment in the same column as the star of the
		     beginning delimiter, then consider it
		     a boxed comment. */
		  if (first_comment_line == com_lines - 1
		      && e_com == s_com + line_preamble_length
		      && current_column () == start_column + 1)
		    {
		      line_preamble = " ";
		      line_preamble_length = 1;
		      boxed_comment = 1;
		      format = 0;
		      blankline_delims = 0;
		      *s_com = ' ';
		      *(s_com + 1) = '*';
		      e_com = s_com + 2;
		      column++;
		      break;
		    }
		}
	      /* If it was not the end of the comment, drop through
	         and insert the star on the line. */

	    default:
	      /* Some textual character. */
	      text_on_line = e_com;
	      *e_com++ = *buf_ptr;
	      column++;
	      break;
	    }


	  /* If we are formatting, check that we haven't exceeded the
	     line length.  If we haven't set line_break_ptr, keep going. */
	  if (format && column > right_margin && line_break_ptr)
	    {
	      if (line_break_ptr < e_com - 1)
		{
		  *line_break_ptr = '\0';
		  save_ptr = line_break_ptr + 1;
		  save_length = e_com - save_ptr;
		  e_com = line_break_ptr;

		  /* If we had to go past `right_margin' to print stuff out,
		     extend `right_margin' out to this point. */
		  if ((column - save_length) > right_margin)
		    right_margin = column - save_length;
		}
	      else
		*e_com = '\0';
	      goto end_line;
	    }

	  buf_ptr++;
	}


    end_line:
      /* Compress pure whitespace lines into newlines. */
      if (! text_on_line
	  && ! visible_preamble
	  && ! (first_comment_line == com_lines))
	e_com = s_com;
      *e_com = '\0';
      dump_line ();

      /* If formatting (paragraph_break is only used for formatted
	 comments) and user wants blank lines merged, kill all white
	 space after the "\n\n" indicating a paragraph break. */
      if (paragraph_break)
	{
	  if (merge_blank_comment_lines)
	    while (*buf_ptr == EOL || *buf_ptr == ' ' || *buf_ptr == TAB)
	      if (++buf_ptr >= buf_end)
		fill_buffer ();
	  paragraph_break = 0;
	}
      else
	{
	  /* If it was a paragraph break (`if' clause), we scanned ahead
	     one character.  So, here in the `else' clause, advance buf_ptr. */
	  buf_ptr++;
	  if (buf_ptr >= buf_end)
	    fill_buffer ();
	}

    begin_line:
      /* Indent the line properly.  If it's a boxed comment, align with
	 the '*' in the beginning slash-star and start inserting there.
	 Otherwise, insert blanks for alignment, or a star if the
	 user specified -sc. */
      if (line_preamble)
	{
	  memcpy (e_com, line_preamble, line_preamble_length);
	  e_com += line_preamble_length;
	  column = start_column + line_preamble_length;
	}
      else
	column = start_column;
      line_break_ptr = 0;

      /* If we have broken the line before the end for formatting,
         copy the text after the break onto the beginning of this
	 new comment line. */
      if (save_ptr)
	{
	  while ((*save_ptr == ' ' || *save_ptr == TAB) && save_length)
	    {
	      save_ptr++;
	      save_length--;
	    }
	  memcpy (e_com, save_ptr, save_length);
	  text_on_line = e_com;
	  e_com += save_length;
	  /* We only break if formatting, in which cases there
	     are no tabs, only spaces.*/
	  column += save_length;
	  save_ptr = 0;
	}
      else
	{
	  while (*buf_ptr == ' ' || *buf_ptr == TAB)
	    buf_ptr++;
	  if (buf_ptr >= buf_end)
	    fill_buffer ();
	  text_on_line = 0;
	}
    }
}
