/* GNU gettext - internationalization aids
   Copyright (C) 1995-2024 Free Software Foundation, Inc.

   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 3 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, see <https://www.gnu.org/licenses/>.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

/* Specification.  */
#include "write-catalog.h"

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif

#include <textstyle.h>

#include "fwriteerror.h"
#include "xvasprintf.h"
#include "xstrerror.h"
#include "xerror-handler.h"
#include "gettext.h"

/* Our regular abbreviation.  */
#define _(str) gettext (str)

/* When compiled in src, enable color support.
   When compiled in libgettextpo, don't enable color support.  */
#ifdef GETTEXTDATADIR

# define ENABLE_COLOR 1

# include "relocatable.h"
# include "po-charset.h"
# include "msgl-iconv.h"

# define GETTEXTSTYLESDIR  GETTEXTDATADIR "/styles"

#endif


/* =========== Some parameters for use by 'msgdomain_list_print'. ========== */


/* This variable controls the page width when printing messages.
   Defaults to PAGE_WIDTH if not set.  Zero (0) given to message_page_-
   width_set will result in no wrapping being performed.  */
static size_t page_width = PAGE_WIDTH;

void
message_page_width_set (size_t n)
{
  if (n == 0)
    {
      page_width = INT_MAX;
      return;
    }

  if (n < 20)
    n = 20;

  page_width = n;
}


/* ======================== msgdomain_list_print() ======================== */


void
msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
                      catalog_output_format_ty output_syntax,
                      xerror_handler_ty xeh,
                      bool force, bool debug)
{
  bool to_stdout;

  /* We will not write anything if, for every domain, we have no message
     or only the header entry.  */
  if (!force)
    {
      bool found_nonempty = false;
      size_t k;

      for (k = 0; k < mdlp->nitems; k++)
        {
          message_list_ty *mlp = mdlp->item[k]->messages;

          if (!(mlp->nitems == 0
                || (mlp->nitems == 1 && is_header (mlp->item[0]))))
            {
              found_nonempty = true;
              break;
            }
        }

      if (!found_nonempty)
        return;
    }

  /* Check whether the output format can accommodate all messages.  */
  if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
    {
      if (output_syntax->alternative_is_po)
        xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                     _("Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
      else
        xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                     _("Cannot output multiple translation domains into a single file with the specified output format."));
    }
  else
    {
      if (!output_syntax->supports_contexts)
        {
          const lex_pos_ty *has_context;
          size_t k;

          has_context = NULL;
          for (k = 0; k < mdlp->nitems; k++)
            {
              message_list_ty *mlp = mdlp->item[k]->messages;
              size_t j;

              for (j = 0; j < mlp->nitems; j++)
                {
                  message_ty *mp = mlp->item[j];

                  if (mp->msgctxt != NULL)
                    {
                      has_context = &mp->pos;
                      break;
                    }
                }
            }

          if (has_context != NULL)
            xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL,
                         has_context->file_name, has_context->line_number,
                         (size_t)(-1), false,
                         _("message catalog has context dependent translations, but the output format does not support them."));
        }

      if (!output_syntax->supports_plurals)
        {
          const lex_pos_ty *has_plural;
          size_t k;

          has_plural = NULL;
          for (k = 0; k < mdlp->nitems; k++)
            {
              message_list_ty *mlp = mdlp->item[k]->messages;
              size_t j;

              for (j = 0; j < mlp->nitems; j++)
                {
                  message_ty *mp = mlp->item[j];

                  if (mp->msgid_plural != NULL)
                    {
                      has_plural = &mp->pos;
                      break;
                    }
                }
            }

          if (has_plural != NULL)
            {
              if (output_syntax->alternative_is_java_class)
                xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL,
                             has_plural->file_name, has_plural->line_number,
                             (size_t)(-1), false,
                             _("message catalog has plural form translations, but the output format does not support them. Try generating a Java class using \"msgfmt --java\", instead of a properties file."));
              else
                xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL,
                             has_plural->file_name, has_plural->line_number,
                             (size_t)(-1), false,
                             _("message catalog has plural form translations, but the output format does not support them."));
            }
        }
    }

  to_stdout = (filename == NULL || strcmp (filename, "-") == 0
               || strcmp (filename, "/dev/stdout") == 0);

#if ENABLE_COLOR
  if (output_syntax->supports_color
      && (color_mode == color_yes
          || (color_mode == color_tty && to_stdout
              && isatty (STDOUT_FILENO)
              && getenv ("NO_COLOR") == NULL)))
    {
      int fd;
      ostream_t stream;

      /* Open the output file.  */
      if (!to_stdout)
        {
          fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC,
                     /* 0666 in portable POSIX notation: */
                     S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
          if (fd < 0)
            {
              int err = errno;
              xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                           xstrerror (xasprintf (_("cannot create output file \"%s\""),
                                                 filename),
                                      err));
            }
        }
      else
        {
          fd = STDOUT_FILENO;
          filename = _("standard output");
        }

      style_file_prepare ("PO_STYLE",
                          "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
                          "po-default.css");
      stream =
        styled_ostream_create (fd, filename, TTYCTL_AUTO, style_file_name);
      output_syntax->print (mdlp, stream, page_width, xeh, debug);
      ostream_free (stream);

      /* Make sure nothing went wrong.  */
      if (close (fd) < 0)
        {
          int err = errno;
          xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                       xstrerror (xasprintf (_("error while writing \"%s\" file"),
                                             filename),
                                  err));
        }
    }
  else
#endif
    {
      FILE *fp;
      file_ostream_t stream;

      /* Open the output file.  */
      if (!to_stdout)
        {
          fp = fopen (filename, "wb");
          if (fp == NULL)
            {
              int err = errno;
              xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                           xstrerror (xasprintf (_("cannot create output file \"%s\""),
                                                 filename),
                                      err));
            }
        }
      else
        {
          fp = stdout;
          filename = _("standard output");
        }

      stream = file_ostream_create (fp);

#if ENABLE_COLOR
      if (output_syntax->supports_color && color_mode == color_html)
        {
          html_styled_ostream_t html_stream;

          /* Convert mdlp to UTF-8 encoding.  */
          if (mdlp->encoding != po_charset_utf8)
            {
              mdlp = msgdomain_list_copy (mdlp, 0);
              mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL,
                                           xeh);
            }

          style_file_prepare ("PO_STYLE",
                              "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
                              "po-default.css");
          html_stream = html_styled_ostream_create (stream, style_file_name);
          output_syntax->print (mdlp, html_stream, page_width, xeh, debug);
          ostream_free (html_stream);
        }
      else
        {
          noop_styled_ostream_t styled_stream;

          styled_stream = noop_styled_ostream_create (stream, false);
          output_syntax->print (mdlp, styled_stream, page_width, xeh, debug);
          ostream_free (styled_stream);
        }
#else
      output_syntax->print (mdlp, stream, page_width, xeh, debug);
      /* Don't call ostream_free if file_ostream_create is a dummy.  */
      if (stream != fp)
#endif
        ostream_free (stream);

      /* Make sure nothing went wrong.  */
      if (fwriteerror (fp))
        {
          int err = errno;
          xeh->xerror (CAT_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
                       xstrerror (xasprintf (_("error while writing \"%s\" file"),
                                             filename),
                                  err));
        }
    }
}


/* =============================== Sorting. ================================ */


static int
cmp_by_msgid (const void *va, const void *vb)
{
  const message_ty *a = *(const message_ty **) va;
  const message_ty *b = *(const message_ty **) vb;

  /* Because msgids normally contain only ASCII characters or are UTF-8
     encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
     strcoll() in a C.UTF-8 locale is the same as strcmp().  */
  int cmp = strcmp (a->msgid, b->msgid);
  if (cmp != 0)
    return cmp;

  /* If the msgids are equal, disambiguate by comparing the contexts.  */
  if (a->msgctxt == b->msgctxt)
    return 0;
  if (a->msgctxt == NULL)
    return -1;
  if (b->msgctxt == NULL)
    return 1;
  return strcmp (a->msgctxt, b->msgctxt);
}


void
msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
{
  size_t k;

  for (k = 0; k < mdlp->nitems; k++)
    {
      message_list_ty *mlp = mdlp->item[k]->messages;

      if (mlp->nitems > 0)
        qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
    }
}


/* Sort the file positions of every message.  */

static int
cmp_filepos (const void *va, const void *vb)
{
  const lex_pos_ty *a = (const lex_pos_ty *) va;
  const lex_pos_ty *b = (const lex_pos_ty *) vb;
  int cmp;

  cmp = strcmp (a->file_name, b->file_name);
  if (cmp == 0)
    cmp = (int) a->line_number - (int) b->line_number;

  return cmp;
}

static void
msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
{
  size_t j, k;

  for (k = 0; k < mdlp->nitems; k++)
    {
      message_list_ty *mlp = mdlp->item[k]->messages;

      for (j = 0; j < mlp->nitems; j++)
        {
          message_ty *mp = mlp->item[j];

          if (mp->filepos_count > 0)
            qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
                   cmp_filepos);
        }
    }
}


/* Sort the messages according to the file position.  */

static int
cmp_by_filepos (const void *va, const void *vb)
{
  const message_ty *a = *(const message_ty **) va;
  const message_ty *b = *(const message_ty **) vb;
  int cmp;

  /* No filepos is smaller than any other filepos.  */
  cmp = (a->filepos_count != 0) - (b->filepos_count != 0);
  if (cmp != 0)
    return cmp;

  if (a->filepos_count != 0)
    {
      /* Compare on the file names...  */
      cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
      if (cmp != 0)
        return cmp;

      /* If they are equal, compare on the line numbers...  */
      cmp = a->filepos[0].line_number - b->filepos[0].line_number;
      if (cmp != 0)
        return cmp;
    }

  /* If they are equal, compare on the msgid strings.  */
  /* Because msgids normally contain only ASCII characters or are UTF-8
     encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
     strcoll() in a C.UTF-8 locale is the same as strcmp().  */
  cmp = strcmp (a->msgid, b->msgid);
  if (cmp != 0)
    return cmp;

  /* If the msgids are equal, disambiguate by comparing the contexts.  */
  if (a->msgctxt == b->msgctxt)
    return 0;
  if (a->msgctxt == NULL)
    return -1;
  if (b->msgctxt == NULL)
    return 1;
  return strcmp (a->msgctxt, b->msgctxt);
}


void
msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
{
  size_t k;

  /* It makes sense to compare filepos[0] of different messages only after
     the filepos[] array of each message has been sorted.  Sort it now.  */
  msgdomain_list_sort_filepos (mdlp);

  for (k = 0; k < mdlp->nitems; k++)
    {
      message_list_ty *mlp = mdlp->item[k]->messages;

      if (mlp->nitems > 0)
        qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
    }
}
