/*      Id: tbl_layout.c,v 1.50 2021/08/10 12:55:04 schwarze Exp  */
/*
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <[email protected]>
* Copyright (c) 2012, 2014, 2015, 2017, 2020, 2021
*               Ingo Schwarze <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"

#include <sys/types.h>

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

#include "mandoc_aux.h"
#include "mandoc.h"
#include "tbl.h"
#include "libmandoc.h"
#include "tbl_int.h"

struct  tbl_phrase {
       char             name;
       enum tbl_cellt   key;
};

static  const struct tbl_phrase keys[] = {
       { 'c',           TBL_CELL_CENTRE },
       { 'r',           TBL_CELL_RIGHT },
       { 'l',           TBL_CELL_LEFT },
       { 'n',           TBL_CELL_NUMBER },
       { 's',           TBL_CELL_SPAN },
       { 'a',           TBL_CELL_LONG },
       { '^',           TBL_CELL_DOWN },
       { '-',           TBL_CELL_HORIZ },
       { '_',           TBL_CELL_HORIZ },
       { '=',           TBL_CELL_DHORIZ }
};

#define KEYS_MAX ((int)(sizeof(keys)/sizeof(keys[0])))

static  void             mods(struct tbl_node *, struct tbl_cell *,
                               int, const char *, int *);
static  void             cell(struct tbl_node *, struct tbl_row *,
                               int, const char *, int *);
static  struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *,
                               enum tbl_cellt);


static void
mods(struct tbl_node *tbl, struct tbl_cell *cp,
               int ln, const char *p, int *pos)
{
       char            *endptr;
       unsigned long    spacing;
       size_t           sz;
       int              isz;
       enum mandoc_esc  fontesc;

mod:
       while (p[*pos] == ' ' || p[*pos] == '\t')
               (*pos)++;

       /* Row delimiters and cell specifiers end modifier lists. */

       if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL)
               return;

       /* Throw away parenthesised expression. */

       if ('(' == p[*pos]) {
               (*pos)++;
               while (p[*pos] && ')' != p[*pos])
                       (*pos)++;
               if (')' == p[*pos]) {
                       (*pos)++;
                       goto mod;
               }
               mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, ln, *pos, NULL);
               return;
       }

       /* Parse numerical spacing from modifier string. */

       if (isdigit((unsigned char)p[*pos])) {
               if ((spacing = strtoul(p + *pos, &endptr, 10)) > 9)
                       mandoc_msg(MANDOCERR_TBLLAYOUT_SPC, ln, *pos,
                           "%lu", spacing);
               else
                       cp->spacing = spacing;
               *pos = endptr - p;
               goto mod;
       }

       switch (tolower((unsigned char)p[(*pos)++])) {
       case 'b':
               cp->font = ESCAPE_FONTBOLD;
               goto mod;
       case 'd':
               cp->flags |= TBL_CELL_BALIGN;
               goto mod;
       case 'e':
               cp->flags |= TBL_CELL_EQUAL;
               goto mod;
       case 'f':
               break;
       case 'i':
               cp->font = ESCAPE_FONTITALIC;
               goto mod;
       case 'm':
               mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, ln, *pos, "m");
               goto mod;
       case 'p':
       case 'v':
               if (p[*pos] == '-' || p[*pos] == '+')
                       (*pos)++;
               while (isdigit((unsigned char)p[*pos]))
                       (*pos)++;
               goto mod;
       case 't':
               cp->flags |= TBL_CELL_TALIGN;
               goto mod;
       case 'u':
               cp->flags |= TBL_CELL_UP;
               goto mod;
       case 'w':
               sz = 0;
               if (p[*pos] == '(') {
                       (*pos)++;
                       while (p[*pos + sz] != '\0' && p[*pos + sz] != ')')
                               sz++;
               } else
                       while (isdigit((unsigned char)p[*pos + sz]))
                               sz++;
               if (sz) {
                       free(cp->wstr);
                       cp->wstr = mandoc_strndup(p + *pos, sz);
                       *pos += sz;
                       if (p[*pos] == ')')
                               (*pos)++;
               }
               goto mod;
       case 'x':
               cp->flags |= TBL_CELL_WMAX;
               goto mod;
       case 'z':
               cp->flags |= TBL_CELL_WIGN;
               goto mod;
       case '|':
               if (cp->vert < 2)
                       cp->vert++;
               else
                       mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
                           ln, *pos - 1, NULL);
               goto mod;
       default:
               mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
                   ln, *pos - 1, "%c", p[*pos - 1]);
               goto mod;
       }

       while (p[*pos] == ' ' || p[*pos] == '\t')
               (*pos)++;

       /* Ignore parenthised font names for now. */

       if (p[*pos] == '(')
               goto mod;

       isz = 0;
       if (p[*pos] != '\0')
               isz++;
       if (strchr(" \t.", p[*pos + isz]) == NULL)
               isz++;

       fontesc = mandoc_font(p + *pos, isz);

       switch (fontesc) {
       case ESCAPE_FONTPREV:
       case ESCAPE_ERROR:
               mandoc_msg(MANDOCERR_FT_BAD,
                   ln, *pos, "TS %s", p + *pos - 1);
               break;
       default:
               cp->font = fontesc;
               break;
       }
       *pos += isz;
       goto mod;
}

static void
cell(struct tbl_node *tbl, struct tbl_row *rp,
               int ln, const char *p, int *pos)
{
       int              i;
       enum tbl_cellt   c;

       /* Handle leading vertical lines */

       while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') {
               if (p[*pos] == '|') {
                       if (rp->vert < 2)
                               rp->vert++;
                       else
                               mandoc_msg(MANDOCERR_TBLLAYOUT_VERT,
                                   ln, *pos, NULL);
               }
               (*pos)++;
       }

again:
       while (p[*pos] == ' ' || p[*pos] == '\t')
               (*pos)++;

       if (p[*pos] == '.' || p[*pos] == '\0')
               return;

       /* Parse the column position (`c', `l', `r', ...). */

       for (i = 0; i < KEYS_MAX; i++)
               if (tolower((unsigned char)p[*pos]) == keys[i].name)
                       break;

       if (i == KEYS_MAX) {
               mandoc_msg(MANDOCERR_TBLLAYOUT_CHAR,
                   ln, *pos, "%c", p[*pos]);
               (*pos)++;
               goto again;
       }
       c = keys[i].key;

       /* Special cases of spanners. */

       if (c == TBL_CELL_SPAN) {
               if (rp->last == NULL)
                       mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, ln, *pos, NULL);
               else if (rp->last->pos == TBL_CELL_HORIZ ||
                   rp->last->pos == TBL_CELL_DHORIZ)
                       c = rp->last->pos;
       } else if (c == TBL_CELL_DOWN && rp == tbl->first_row)
               mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, ln, *pos, NULL);

       (*pos)++;

       /* Allocate cell then parse its modifiers. */

       mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos);
}

void
tbl_layout(struct tbl_node *tbl, int ln, const char *p, int pos)
{
       struct tbl_row  *rp;

       rp = NULL;
       for (;;) {
               /* Skip whitespace before and after each cell. */

               while (p[pos] == ' ' || p[pos] == '\t')
                       pos++;

               switch (p[pos]) {
               case ',':  /* Next row on this input line. */
                       pos++;
                       rp = NULL;
                       continue;
               case '\0':  /* Next row on next input line. */
                       return;
               case '.':  /* End of layout. */
                       pos++;
                       tbl->part = TBL_PART_DATA;

                       /*
                        * When the layout is completely empty,
                        * default to one left-justified column.
                        */

                       if (tbl->first_row == NULL) {
                               tbl->first_row = tbl->last_row =
                                   mandoc_calloc(1, sizeof(*rp));
                       }
                       if (tbl->first_row->first == NULL) {
                               mandoc_msg(MANDOCERR_TBLLAYOUT_NONE,
                                   ln, pos, NULL);
                               cell_alloc(tbl, tbl->first_row,
                                   TBL_CELL_LEFT);
                               if (tbl->opts.lvert < tbl->first_row->vert)
                                       tbl->opts.lvert = tbl->first_row->vert;
                               return;
                       }

                       /*
                        * Search for the widest line
                        * along the left and right margins.
                        */

                       for (rp = tbl->first_row; rp; rp = rp->next) {
                               if (tbl->opts.lvert < rp->vert)
                                       tbl->opts.lvert = rp->vert;
                               if (rp->last != NULL &&
                                   rp->last->col + 1 == tbl->opts.cols &&
                                   tbl->opts.rvert < rp->last->vert)
                                       tbl->opts.rvert = rp->last->vert;

                               /* If the last line is empty, drop it. */

                               if (rp->next != NULL &&
                                   rp->next->first == NULL) {
                                       free(rp->next);
                                       rp->next = NULL;
                                       tbl->last_row = rp;
                               }
                       }
                       return;
               default:  /* Cell. */
                       break;
               }

               /*
                * If the last line had at least one cell,
                * start a new one; otherwise, continue it.
                */

               if (rp == NULL) {
                       if (tbl->last_row == NULL ||
                           tbl->last_row->first != NULL) {
                               rp = mandoc_calloc(1, sizeof(*rp));
                               if (tbl->last_row)
                                       tbl->last_row->next = rp;
                               else
                                       tbl->first_row = rp;
                               tbl->last_row = rp;
                       } else
                               rp = tbl->last_row;
               }
               cell(tbl, rp, ln, p, &pos);
       }
}

static struct tbl_cell *
cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos)
{
       struct tbl_cell *p, *pp;

       p = mandoc_calloc(1, sizeof(*p));
       p->spacing = SIZE_MAX;
       p->font = ESCAPE_FONTROMAN;
       p->pos = pos;

       if ((pp = rp->last) != NULL) {
               pp->next = p;
               p->col = pp->col + 1;
       } else
               rp->first = p;
       rp->last = p;

       if (tbl->opts.cols <= p->col)
               tbl->opts.cols = p->col + 1;

       return p;
}