/***********************************************************************
 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
   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 2, 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.
***********************************************************************/

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

#include "fc_prehdrs.h"

#ifdef FREECIV_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <unistd.h>
#include <errno.h>

/* dependencies */
#include "cvercmp.h"

/* utility */
#include "capability.h"
#include "fcintl.h"
#include "log.h"
#include "mem.h"
#include "netfile.h"
#include "registry.h"

/* tools */
#include "mpdb.h"

#include "download.h"

static const char *download_modpack_recursive(const char *URL,
                                              const struct fcmp_params *fcmp,
                                              dl_msg_callback mcb,
                                              dl_pb_callback pbcb,
                                              int recursion);

/**********************************************************************//**
  Message callback called by netfile module when downloading files.
**************************************************************************/
static void nf_cb(const char *msg, void *data)
{
  dl_msg_callback mcb = (dl_msg_callback) data;

  if (mcb != NULL) {
    mcb(msg);
  }
}

/**********************************************************************//**
  Download modpack from a given URL
**************************************************************************/
const char *download_modpack(const char *URL,
                             const struct fcmp_params *fcmp,
                             dl_msg_callback mcb,
                             dl_pb_callback pbcb)
{
  return download_modpack_recursive(URL, fcmp, mcb, pbcb, 0);
}

/**********************************************************************//**
  Download modpack and its recursive dependencies.
**************************************************************************/
static const char *download_modpack_recursive(const char *URL,
                                              const struct fcmp_params *fcmp,
                                              dl_msg_callback mcb,
                                              dl_pb_callback pbcb,
                                              int recursion)
{
  char local_dir[2048];
  char local_name[2048];
  int start_idx;
  int total_files;
  struct section_file *control;
  const char *control_capstr;
  enum modpack_type type;
  const char *typestr;
  const char *mpname;
  const char *mpver;
  const char *src_name;
  bool partial_failure = FALSE;
  int dep;
  const char *dep_name;
  struct section_list *sec;

  if (recursion > 5) {
    return _("Recursive dependencies too deep");
  }

  if (URL == NULL || URL[0] == '\0') {
    return _("No URL given");
  }

  if (strlen(URL) < strlen(MODPACKDL_SUFFIX)
      || strcmp(URL + strlen(URL) - strlen(MODPACKDL_SUFFIX),
                MODPACKDL_SUFFIX)) {
    return _("This does not look like modpack URL");
  }

  for (start_idx = strlen(URL) - strlen(MODPACKDL_SUFFIX);
       start_idx > 0 && URL[start_idx - 1] != '/';
       start_idx--) {
    /* Nothing */
  }

  if (start_idx <= 0) {
    return _("This does not look like modpack URL");
  }

  log_normal(_("Installing modpack %s from %s"), URL + start_idx, URL);

  if (fcmp->inst_prefix == NULL) {
    return _("Cannot install to given directory hierarchy");
  }

  if (mcb != NULL) {
    char buf[2048];

    /* TRANS: %s is a filename with suffix '.mpdl' */
    fc_snprintf(buf, sizeof(buf), _("Downloading \"%s\" control file."), URL + start_idx);
    mcb(buf);
  }

  control = netfile_get_section_file(URL, nf_cb, mcb);

  if (control == NULL) {
    return _("Failed to get and parse modpack control file");
  }

  control_capstr = secfile_lookup_str(control, "info.options");
  if (control_capstr == NULL) {
    secfile_destroy(control);
    return _("Modpack control file has no capability string");
  }

  if (!has_capabilities(MODPACK_CAPSTR, control_capstr)) {
    log_error("Incompatible control file:");
    log_error("  control file options: %s", control_capstr);
    log_error("  supported options:    %s", MODPACK_CAPSTR);

    secfile_destroy(control);

    return _("Modpack control file is incompatible");  
  }

  mpname = secfile_lookup_str(control, "info.name");
  if (mpname == NULL) {
    return _("Modpack name not defined in control file");
  }
  mpver = secfile_lookup_str(control, "info.version");
  if (mpver  == NULL) {
    return _("Modpack version not defined in control file");
  }

  typestr = secfile_lookup_str(control, "info.type");
  type = modpack_type_by_name(typestr, fc_strcasecmp);
  if (!modpack_type_is_valid(type)) {
    return _("Illegal modpack type");
  }

  if (type == MPT_SCENARIO) {
    fc_snprintf(local_dir, sizeof(local_dir),
                "%s" DIR_SEPARATOR "scenarios", fcmp->inst_prefix);
  } else {
    fc_snprintf(local_dir, sizeof(local_dir),
                "%s" DIR_SEPARATOR DATASUBDIR, fcmp->inst_prefix);
  }

  dep = 0;
  do {
    dep_name = secfile_lookup_str_default(control, NULL,
                                          "dependencies.list%d.modpack", dep);
    if (dep_name != NULL) {
      const char *dep_URL;
      const char *inst_ver;
      const char *dep_typestr;
      enum modpack_type dep_type;
      bool needed = TRUE;

      dep_URL = secfile_lookup_str_default(control, NULL,
                                           "dependencies.list%d.URL", dep);

      if (dep_URL == NULL) {
        return _("Dependency has no download URL");
      }

      dep_typestr = secfile_lookup_str(control, "dependencies.list%d.type", dep);
      dep_type = modpack_type_by_name(dep_typestr, fc_strcasecmp);
      if (!modpack_type_is_valid(dep_type)) {
        return _("Illegal dependency modpack type");
      }

      inst_ver = mpdb_installed_version(dep_name, type);

      if (inst_ver != NULL) {
        const char *dep_ver;

        dep_ver = secfile_lookup_str_default(control, NULL,
                                             "dependencies.list%d.version", dep);

        if (dep_ver != NULL && cvercmp_max(dep_ver, inst_ver)) {
          needed = FALSE;
        }
      }

      if (needed) {
        const char *msg;
        char dep_URL_full[2048];

        log_debug("Dependency modpack \"%s\" needed.", dep_name);

        if (mcb != NULL) {
          mcb(_("Download dependency modpack"));
        }

        if (dep_URL[0] == '.') {
          char URLstart[start_idx];

          strncpy(URLstart, URL, start_idx - 1);
          URLstart[start_idx - 1] = '\0';
          fc_snprintf(dep_URL_full, sizeof(dep_URL_full), "%s%s",
                      URLstart, dep_URL + 1);
        } else {
          sz_strlcpy(dep_URL_full, dep_URL);
        }

        msg = download_modpack_recursive(dep_URL_full, fcmp, mcb, pbcb, recursion + 1);

        if (msg != NULL) {
          return msg;
        }
      }
    }

    dep++;
    
  } while (dep_name != NULL);


  total_files = 0;
  sec = secfile_sections_by_name_prefix(control, "files_");

  if (sec != NULL) {
    size_t sec_count = section_list_size(sec);
    size_t i;
    int filenbr = 0;

    for (i = 0; i < sec_count; i++) {
      const char *sec_name = section_name(section_list_get(sec, i));

      do {
        src_name = secfile_lookup_str_default(control, NULL,
                                              "%s.list%d.src",
                                              sec_name, total_files);

        if (src_name != NULL) {
          total_files++;
        }
      } while (src_name != NULL);
    }

    if (pbcb != NULL) {
      /* Control file already downloaded */
      pbcb(1, total_files + 1);
    }

    for (i = 0; i < sec_count; i++) {
      const char *sec_name = section_name(section_list_get(sec, i));
      const char *baseURLpart;
      int URL_len;
      char baseURL[2048];
      int j;

      baseURLpart = secfile_lookup_str(control, "%s.baseURL", sec_name);

      if (baseURLpart[0] == '.') {
        char URLstart[start_idx];

        strncpy(URLstart, URL, start_idx - 1);
        URLstart[start_idx - 1] = '\0';
        fc_snprintf(baseURL, sizeof(baseURL), "%s%s",
                    URLstart, baseURLpart + 1);
      } else {
        sz_strlcpy(baseURL, baseURLpart);
      }

      /* Remove potential ending '/' as one will get added later. */
      URL_len = strlen(baseURL);
      if (baseURL[URL_len - 1] == '/') {
        baseURL[URL_len - 1] = '\0';
      }

      for (j = 0;
           (src_name = secfile_lookup_str_default(control, NULL,
                                                  "%s.list%d.src", sec_name, j)) != NULL;
           j++) {
        const char *dest_name;

#ifndef DIR_SEPARATOR_IS_DEFAULT
        char *dest_name_copy;
#else  /* DIR_SEPARATOR_IS_DEFAULT */
#define dest_name_copy dest_name
#endif /* DIR_SEPARATOR_IS_DEFAULT */

        int k;
        bool illegal_filename = FALSE;

        dest_name = secfile_lookup_str_default(control, NULL,
                                               "%s.list%d.dest", sec_name, j);

        if (dest_name == NULL || dest_name[0] == '\0') {
          /* Missing dest name is ok, we just default to src_name */
          dest_name = src_name;
        }

#ifndef DIR_SEPARATOR_IS_DEFAULT
        dest_name_copy = fc_malloc(strlen(dest_name) + 1);
#endif /* DIR_SEPARATOR_IS_DEFAULT */

        for (k = 0; dest_name[k] != '\0'; k++) {
          if (dest_name[k] == '.' && dest_name[k + 1] == '.') {
            if (mcb != NULL) {
              char buf[2048];

              fc_snprintf(buf, sizeof(buf), _("Illegal path for %s"),
                          dest_name);
              mcb(buf);
            }
            partial_failure = TRUE;
            illegal_filename = TRUE;
          }

#ifndef DIR_SEPARATOR_IS_DEFAULT
          if (dest_name[k] == '/') {
            dest_name_copy[k] = DIR_SEPARATOR_CHAR;
          } else {
            dest_name_copy[k] = dest_name[k];
          }
#endif /* DIR_SEPARATOR_IS_DEFAULT */
        }

#ifndef DIR_SEPARATOR_IS_DEFAULT
        dest_name_copy[k] = '\0';
#endif /* DIR_SEPARATOR_IS_DEFAULT */

        if (!illegal_filename) {
          char fileURL[2048];

          fc_snprintf(local_name, sizeof(local_name),
                      "%s" DIR_SEPARATOR "%s", local_dir, dest_name_copy);

#ifndef DIR_SEPARATOR_IS_DEFAULT
          free(dest_name_copy);
#endif /* DIR_SEPARATOR_IS_DEFAULT */

          if (!make_dir_for_file(local_name)) {
            secfile_destroy(control);
            return _("Cannot create required directories");
          }

          if (mcb != NULL) {
            char buf[2048];

            fc_snprintf(buf, sizeof(buf), _("Downloading %s"), src_name);
            mcb(buf);
          }

          fc_snprintf(fileURL, sizeof(fileURL), "%s/%s", baseURL, src_name);
          log_debug("Download \"%s\" as \"%s\".", fileURL, local_name);
          if (!netfile_download_file(fileURL, local_name, nf_cb, mcb)) {
            if (mcb != NULL) {
              char buf[2048];

              fc_snprintf(buf, sizeof(buf), _("Failed to download %s"),
                          src_name);
              mcb(buf);
            }
            partial_failure = TRUE;
          }
        } else {
#ifndef DIR_SEPARATOR_IS_DEFAULT
          free(dest_name_copy);
#endif /* DIR_SEPARATOR_IS_DEFAULT */
        }

        filenbr++;

        if (pbcb != NULL) {
          /* Count download of control file also */
          pbcb(filenbr + 1, total_files + 1);
        }
      }
    }
  }

  section_list_destroy(sec);

  if (partial_failure) {
    secfile_destroy(control);

    return _("Some parts of the modpack failed to install.");
  }

  mpdb_update_modpack(mpname, type, mpver);

  secfile_destroy(control);

  return NULL;
}

/**********************************************************************//**
  Download modpack list
**************************************************************************/
const char *download_modpack_list(const struct fcmp_params *fcmp,
                                  modpack_list_setup_cb cb,
                                  dl_msg_callback mcb)
{
  struct section_file *list_file;
  const char *list_capstr;
  int modpack_count;
  const char *msg;
  const char *mp_name;
  int start_idx;

  list_file = netfile_get_section_file(fcmp->list_url, nf_cb, mcb);

  if (list_file == NULL) {
    return _("Cannot fetch and parse modpack list");
  }

  for (start_idx = strlen(fcmp->list_url);
       start_idx > 0 && fcmp->list_url[start_idx - 1] != '/';
       start_idx--) {
    /* Nothing */
  }

  if (start_idx <= 0) {
    return _("Invalid modpack list URL");
  }

  list_capstr = secfile_lookup_str(list_file, "info.options");
  if (list_capstr == NULL) {
    secfile_destroy(list_file);
    return _("Modpack list has no capability string");
  }

  if (!has_capabilities(MODLIST_CAPSTR, list_capstr)) {
    log_error("Incompatible modpack list file:");
    log_error("  list file options: %s", list_capstr);
    log_error("  supported options: %s", MODLIST_CAPSTR);

    secfile_destroy(list_file);

    return _("Modpack list is incompatible");  
  }

  msg = secfile_lookup_str_default(list_file, NULL, "info.message");

  if (msg != NULL) {
    mcb(msg);
  }

  modpack_count = 0;
  do {
    const char *mpURL;
    const char *mpver;
    const char *mplic;
    const char *mp_type_str;
    const char *mp_subtype;
    const char *mp_notes;

    mp_name = secfile_lookup_str_default(list_file, NULL,
                                         "modpacks.list%d.name", modpack_count);
    mpver = secfile_lookup_str_default(list_file, NULL,
                                       "modpacks.list%d.version",
                                       modpack_count);
    mplic = secfile_lookup_str_default(list_file, NULL,
                                       "modpacks.list%d.license",
                                       modpack_count);
    mp_type_str = secfile_lookup_str_default(list_file, NULL,
                                             "modpacks.list%d.type",
                                             modpack_count);
    mp_subtype = secfile_lookup_str_default(list_file, NULL,
                                            "modpacks.list%d.subtype",
                                            modpack_count);
    mpURL = secfile_lookup_str_default(list_file, NULL,
                                       "modpacks.list%d.URL", modpack_count);
    mp_notes = secfile_lookup_str_default(list_file, NULL,
                                          "modpacks.list%d.notes", modpack_count);

    if (mp_name != NULL && mpURL != NULL) {
      char mpURL_full[2048];
      enum modpack_type type = modpack_type_by_name(mp_type_str, fc_strcasecmp);

      if (!modpack_type_is_valid(type)) {
        log_error("Illegal modpack type \"%s\"", mp_type_str ? mp_type_str : "NULL");
      }
      if (mpver == NULL) {
        mpver = "-";
      }
      if (mp_subtype == NULL) {
        mp_subtype = "-";
      }

      if (mpURL[0] == '.') {
        char URLstart[start_idx];

        strncpy(URLstart, fcmp->list_url, start_idx - 1);
        URLstart[start_idx - 1] = '\0';
        fc_snprintf(mpURL_full, sizeof(mpURL_full), "%s%s",
                    URLstart, mpURL + 1);
      } else {
        sz_strlcpy(mpURL_full, mpURL);
      }
      
      cb(mp_name, mpURL_full, mpver, mplic, type, _(mp_subtype), mp_notes);
    }
    modpack_count++;
  } while (mp_name != NULL);

  return NULL;
}
