/*
  This file is part of TALER
  Copyright (C) 2020 Taler Systems SA

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

  TALER 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
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file taler-merchant-httpd_statics.c
 * @brief logic to load and return static resource files by client language preference
 * @author Christian Grothoff
 */
#include "platform.h"
#include <gnunet/gnunet_util_lib.h>
#include "taler_merchant_util.h"
#include <taler/taler_util.h>
#include <taler/taler_mhd_lib.h>
#include <taler/taler_templating_lib.h>
#include "taler-merchant-httpd_statics.h"
#include <gnunet/gnunet_mhd_compat.h>


/**
 * Entry in a key-value array we use to cache templates.
 */
struct TVE
{
  /**
   * A name, used as the key. NULL for the last entry.
   */
  char *name;

  /**
   * Language the template is in.
   */
  char *lang;

  /**
   * Pre-built reply.
   */
  struct MHD_Response *reply;

};


/**
 * Array of templates loaded into RAM.
 */
static struct TVE *loaded;

/**
 * Length of the #loaded array.
 */
static unsigned int loaded_length;


/**
 * Load Mustach template into memory.  Note that we intentionally cache
 * failures, that is if we ever failed to load a template, we will never try
 * again.
 *
 * @param connection the connection we act upon
 * @param name name of the template file to load
 *        (MUST be a 'static' string in memory!)
 * @return NULL on error, otherwise the template
 */
static const struct TVE *
lookup_file (struct MHD_Connection *connection,
             const char *name)
{
  double best_q = 0.0;
  struct TVE *best = NULL;
  const char *lang;

  lang = MHD_lookup_connection_value (connection,
                                      MHD_HEADER_KIND,
                                      MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
  if (NULL == lang)
    lang = "en";
  /* find best match by language */
  for (unsigned int i = 0; i<loaded_length; i++)
  {
    double q;

    if (0 != strcmp (loaded[i].name,
                     name))
      continue; /* does not match by name */
    if (NULL == loaded[i].lang) /* no language == always best match */
      return &loaded[i];
    q = TALER_pattern_matches (lang,
                               loaded[i].lang);
    if (q < best_q)
      continue;
    best_q = q;
    best = &loaded[i];
  }
  if (NULL == best)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "No static file found for `%s'\n",
                name);
    return NULL;
  }
  return best;
}


MHD_RESULT
TMH_return_static (const struct TMH_RequestHandler *rh,
                   struct MHD_Connection *connection,
                   struct TMH_HandlerContext *hc)
{
  const struct TVE *tmpl;

  tmpl = lookup_file (connection,
                      hc->infix);
  if (NULL == tmpl)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to load static file `%s'\n",
                hc->infix);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
                                       hc->infix);
  }

  return MHD_queue_response (connection,
                             MHD_HTTP_OK,
                             tmpl->reply);
}


/**
 * Function called with a static file's filename.
 *
 * @param cls closure
 * @param filename complete filename (absolute path)
 * @return #GNUNET_OK to continue to iterate,
 *  #GNUNET_NO to stop iteration with no error,
 *  #GNUNET_SYSERR to abort iteration with error!
 */
static enum GNUNET_GenericReturnValue
load_static_file (void *cls,
                  const char *filename)
{
  char *lang;
  char *end;
  int fd;
  struct stat sb;
  const char *name;
  struct MHD_Response *reply;

  if ('.' == filename[0])
    return GNUNET_OK;
  name = strrchr (filename,
                  '/');
  if (NULL == name)
    name = filename;
  else
    name++;
  lang = strchr (name,
                 '.');
  if (NULL == lang)
    return GNUNET_OK; /* name must include _some_ extension */
  lang++;
  end = strchr (lang,
                '.');
  if (NULL == end)
  {
    /* language was not present, we ONLY have the extension */
    end = lang - 1;
    lang = NULL;
  }
  /* finally open template */
  fd = open (filename,
             O_RDONLY);
  if (-1 == fd)
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "open",
                              filename);
    return GNUNET_SYSERR;
  }
  if (0 !=
      fstat (fd,
             &sb))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "open",
                              filename);
    GNUNET_break (0 == close (fd));
    return GNUNET_OK;
  }

  reply = MHD_create_response_from_fd (sb.st_size,
                                       fd);
  if (NULL == reply)
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "open",
                              filename);
    GNUNET_break (0 == close (fd));
    return GNUNET_OK;
  }

  {
    static struct MimeMap
    {
      const char *ext;
      const char *mime;
    } mm[] = {
      { .ext = ".css", .mime = "text/css" },
      { .ext = ".js", .mime = "text/javascript" },
      { .ext = ".html", .mime = "text/html" },
      { .ext = ".htm", .mime = "text/html" },
      { .ext = ".txt", .mime = "text/plain" },
      { .ext = ".pdf", .mime = "application/pdf" },
      { .ext = ".jpg", .mime = "image/jpeg" },
      { .ext = ".jpeg", .mime = "image/jpeg" },
      { .ext = ".png", .mime = "image/png" },
      { .ext = ".apng", .mime = "image/apng" },
      { .ext = ".gif", .mime = "image/gif" },
      { .ext = ".svg", .mime = "image/svg+xml" },
      { .ext = ".tiff", .mime = "image/tiff" },
      { .ext = ".ico", .mime = "image/x-icon" },
      { .ext = ".bmp", .mime = "image/bmp" },
      { .ext = ".epub", .mime = "application/epub+zip" },
      { .ext = ".xml", .mime = "text/xml" },
      { .ext = NULL, .mime = NULL }
    };
    const char *mime;

    mime = NULL;
    for (unsigned int i = 0; NULL != mm[i].ext; i++)
      if (0 == strcasecmp (mm[i].ext,
                           end))
      {
        mime = mm[i].mime;
        break;
      }
    if (NULL != mime)
      GNUNET_break (MHD_NO !=
                    MHD_add_response_header (reply,
                                             MHD_HTTP_HEADER_CONTENT_TYPE,
                                             mime));
  }

  GNUNET_array_grow (loaded,
                     loaded_length,
                     loaded_length + 1);
  if (NULL != lang)
  {
    GNUNET_asprintf (&loaded[loaded_length - 1].name,
                     "%.*s%s",
                     (int) (lang - name) - 1,
                     name,
                     end);
    loaded[loaded_length - 1].lang = GNUNET_strndup (lang,
                                                     end - lang);
  }
  else
  {
    loaded[loaded_length - 1].name = GNUNET_strdup (name);
  }
  loaded[loaded_length - 1].reply = reply;
  return GNUNET_OK;
}


/**
 * Preload static files.
 */
enum GNUNET_GenericReturnValue
TMH_statics_init ()
{
  char *dn;
  int ret;

  {
    char *path;

    path = GNUNET_OS_installation_get_path (TALER_MERCHANT_project_data (),
                                            GNUNET_OS_IPK_DATADIR);
    if (NULL == path)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    GNUNET_asprintf (&dn,
                     "%smerchant/static/",
                     path);
    GNUNET_free (path);
  }
  ret = GNUNET_DISK_directory_scan (dn,
                                    &load_static_file,
                                    NULL);
  if (-1 == ret)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Could not load static resources from `%s': %s\n",
                dn,
                strerror (errno));
    GNUNET_free (dn);
    return GNUNET_SYSERR;
  }
  GNUNET_free (dn);
  return GNUNET_OK;
}


/**
 * Nicely shut down.
 */
void __attribute__ ((destructor))
get_statics_fini (void);

/* Declaration avoids compiler warning */
void __attribute__ ((destructor))
get_statics_fini ()
{
  for (unsigned int i = 0; i<loaded_length; i++)
  {
    GNUNET_free (loaded[i].name);
    GNUNET_free (loaded[i].lang);
    MHD_destroy_response (loaded[i].reply);
  }
  GNUNET_array_grow (loaded,
                     loaded_length,
                     0);
}
