/* vim: set sw=2 ts=2 sts=2 et: */
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * autoar-format-filter.c
 * Functions related to archive formats and filters
 *
 * Copyright (C) 2013  Ting-Wei Lan
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 */

#include "config.h"

#include "autoar-format-filter.h"

#include <archive.h>
#include <gio/gio.h>
#include <glib.h>

/**
 * SECTION:autoar-format-filter
 * @Short_description: Utilities for handling archive formats and filters
 * @Title: autoar-format-filter
 * @Include: gnome-autoar/autoar.h
 *
 * autoar-format-filter is a collection of functions providing information
 * of archive formats and filters.
 **/

typedef struct _AutoarFormatDescription AutoarFormatDescription;
typedef struct _AutoarFilterDescription AutoarFilterDescription;

struct _AutoarFormatDescription
{
  AutoarFormat format;
  int libarchive_format;
  char *extension;
  char *keyword;
  char *mime_type;
  char *description;
  AutoarFormatFunc libarchive_read;
  AutoarFormatFunc libarchive_write;
};

struct _AutoarFilterDescription
{
  AutoarFilter filter;
  int libarchive_filter;
  char *extension;
  char *keyword;
  char *mime_type;
  char *description;
  AutoarFilterFunc libarchive_read;
  AutoarFilterFunc libarchive_write;
};

static AutoarFormatDescription autoar_format_description[] = {
  { AUTOAR_FORMAT_ZIP,       ARCHIVE_FORMAT_ZIP,                 "zip",  "zip",
    "application/zip",       "Zip archive",
    archive_read_support_format_zip,
    archive_write_set_format_zip },

  { AUTOAR_FORMAT_TAR,       ARCHIVE_FORMAT_TAR_PAX_RESTRICTED,  "tar",  "tar",
    "application/x-tar",     "Tar archive (restricted pax)",
    archive_read_support_format_tar,
    archive_write_set_format_pax_restricted },

  { AUTOAR_FORMAT_CPIO,      ARCHIVE_FORMAT_CPIO_POSIX,          "cpio", "cpio",
    "application/x-cpio",    "CPIO archive",
    archive_read_support_format_cpio,
    archive_write_set_format_cpio },

  { AUTOAR_FORMAT_7ZIP,      ARCHIVE_FORMAT_7ZIP,                "7z",   "7z-compressed",
    "application/x-7z-compressed", "7-zip archive",
    archive_read_support_format_7zip,
    archive_write_set_format_7zip },

  { AUTOAR_FORMAT_AR_BSD,    ARCHIVE_FORMAT_AR_BSD,              "a",    "ar",
    "application/x-ar",      "AR archive (BSD)",
    archive_read_support_format_ar,
    archive_write_set_format_ar_bsd },

  { AUTOAR_FORMAT_AR_SVR4,   ARCHIVE_FORMAT_AR_GNU,              "a",    "ar",
    "application/x-ar",      "AR archive (SVR4)",
    archive_read_support_format_ar,
    archive_write_set_format_ar_svr4 },

  { AUTOAR_FORMAT_CPIO_NEWC, ARCHIVE_FORMAT_CPIO_SVR4_NOCRC,     "cpio", "sv4cpio",
    "application/x-sv4cpio", "SV4 CPIO archive",
    archive_read_support_format_cpio,
    archive_write_set_format_cpio_newc },

  { AUTOAR_FORMAT_GNUTAR,    ARCHIVE_FORMAT_TAR_GNUTAR,          "tar",  "tar",
    "application/x-tar",     "Tar archive (GNU tar)",
    archive_read_support_format_gnutar,
    archive_write_set_format_gnutar },

  { AUTOAR_FORMAT_ISO9660,   ARCHIVE_FORMAT_ISO9660,             "iso",  "cd-image",
    "application/x-cd-image", "Raw CD Image",
    archive_read_support_format_iso9660,
    archive_write_set_format_iso9660 },

  { AUTOAR_FORMAT_PAX,       ARCHIVE_FORMAT_TAR_PAX_INTERCHANGE, "tar",  "tar",
    "application/x-tar",     "Tar archive (pax)",
    archive_read_support_format_tar,
    archive_write_set_format_pax },

  { AUTOAR_FORMAT_USTAR,     ARCHIVE_FORMAT_TAR_USTAR,           "tar",  "tar",
    "application/x-tar",     "Tar archive (ustar)",
    archive_read_support_format_tar,
    archive_write_set_format_ustar },

  { AUTOAR_FORMAT_XAR,       ARCHIVE_FORMAT_XAR,                 "xar",  "xar",
    "application/x-xar",     "Xar archive",
    archive_read_support_format_xar,
    archive_write_set_format_xar }
};

static AutoarFilterDescription autoar_filter_description[] = {
  { AUTOAR_FILTER_NONE,      ARCHIVE_FILTER_NONE,                "",     "",
    "",                      "None",
    archive_read_support_filter_none,
    archive_write_add_filter_none },

  { AUTOAR_FILTER_COMPRESS,  ARCHIVE_FILTER_COMPRESS,            "Z",    "compress",
    "application/x-compress", "UNIX-compressed",
    archive_read_support_filter_compress,
    archive_write_add_filter_compress },

  { AUTOAR_FILTER_GZIP,      ARCHIVE_FILTER_GZIP,                "gz",   "gzip",
    "application/gzip",      "Gzip",
    archive_read_support_filter_gzip,
    archive_write_add_filter_gzip },

  { AUTOAR_FILTER_BZIP2,     ARCHIVE_FILTER_BZIP2,               "bz2",  "bzip",
    "application/x-bzip",    "Bzip2",
    archive_read_support_filter_bzip2,
    archive_write_add_filter_bzip2 },

  { AUTOAR_FILTER_XZ,        ARCHIVE_FILTER_XZ,                  "xz",   "xz",
    "application/x-xz",      "XZ",
    archive_read_support_filter_xz,
    archive_write_add_filter_xz },

  { AUTOAR_FILTER_LZMA,      ARCHIVE_FILTER_LZMA,                "lzma", "lzma",
    "application/x-lzma",    "LZMA",
    archive_read_support_filter_lzma,
    archive_write_add_filter_lzma },

  { AUTOAR_FILTER_LZIP,      ARCHIVE_FILTER_LZIP,                "lz",   "lzip",
    "application/x-lzip",    "Lzip",
    archive_read_support_filter_lzip,
    archive_write_add_filter_lzip },

  { AUTOAR_FILTER_LZOP,      ARCHIVE_FILTER_LZOP,                "lzo",  "lzop",
    "application/x-lzop",    "LZO",
    archive_read_support_filter_lzop,
    archive_write_add_filter_lzop },

  { AUTOAR_FILTER_GRZIP,     ARCHIVE_FILTER_GRZIP,               "grz",  "grzip",
    "application/x-grzip",   "GRZip",
    archive_read_support_filter_grzip,
    archive_write_add_filter_grzip },

  { AUTOAR_FILTER_LRZIP,     ARCHIVE_FILTER_LRZIP,               "lrz",  "lrzip",
    "application/x-lrzip",   "Long Range ZIP (lrzip)",
    archive_read_support_filter_lrzip,
    archive_write_add_filter_lrzip },

  { AUTOAR_FILTER_ZSTD,       ARCHIVE_FILTER_ZSTD,               "zst",  "zstd",
    "application/zstd",      "Zstandard",
    archive_read_support_filter_zstd,
    archive_write_add_filter_zstd }
};

/**
 * autoar_format_last:
 *
 * Gets the maximal allowed values of #AutoarFormat
 *
 * Returns: maximal allowed values of #AutoarFormat
 **/
int
autoar_format_last (void)
{
  return AUTOAR_FORMAT_LAST;
}

/**
 * autoar_format_is_valid:
 * @format: an #AutoarFormat
 *
 * Checks whether an #AutoarFormat is valid.
 *
 * Returns: %TRUE if the value of @format is valid
 **/
gboolean
autoar_format_is_valid (AutoarFormat format)
{
  return (format > 0 && format < AUTOAR_FORMAT_LAST);
}

/**
 * autoar_format_get_mime_type:
 * @format: an #AutoarFormat
 *
 * Gets the MIME type of the format from the internal static data.
 *
 * Returns: (transfer none): an MIME type
 **/
const char*
autoar_format_get_mime_type (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format) , NULL);
  return autoar_format_description[format - 1].mime_type;
}

/**
 * autoar_format_get_extension:
 * @format: an #AutoarFormat
 *
 * Gets the file name extension of the format from the internal static data.
 *
 * Returns: (transfer none): a file name extension
 **/
const char*
autoar_format_get_extension (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  return autoar_format_description[format - 1].extension;
}

/**
 * autoar_format_get_description:
 * @format: an #AutoarFormat
 *
 * Gets description of the format from the internal static data.
 *
 * Returns: (transfer none): description about the format
 **/
const char*
autoar_format_get_description (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  return autoar_format_description[format - 1].description;
}

/**
 * autoar_format_get_format_libarchive:
 * @format: an #AutoarFormat
 *
 * Gets the format code used by libarchive. You can use the return value
 * as the argument for archive_read_support_format_by_code() and
 * archive_write_set_format(). However, some format cannot be set using
 * these two functions because of problems inside libarchive. Use
 * autoar_format_get_libarchive_read() and
 * autoar_format_get_libarchive_write() to get the function pointer
 * is the more reliable way to set format on the archive object.
 *
 * Returns: an integer
 **/
int
autoar_format_get_format_libarchive (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format), -1);
  return autoar_format_description[format - 1].libarchive_format;
}

/**
 * autoar_format_get_description_libarchive:
 * @format: an #AutoarFormat
 *
 * Gets description of the format from libarchive. This function creates
 * and destroys an archive object in order to get the description string.
 *
 * Returns: (transfer full): description about the format. Free the returned
 * string with g_free().
 **/
gchar*
autoar_format_get_description_libarchive (AutoarFormat format)
{
  struct archive* a;
  gchar *str;

  g_return_val_if_fail (autoar_format_is_valid (format), NULL);

  a = archive_write_new ();
  archive_write_set_format (a, autoar_format_description[format - 1].libarchive_format);
  str = g_strdup (archive_format_name (a));
  archive_write_free (a);

  return str;
}

/**
 * autoar_format_get_libarchive_read: (skip)
 * @format: an #AutoarFormat
 *
 * Gets the function used to set format on the object returned by
 * archive_read_new().
 *
 * Returns: function pointer to the setter function provided by libarchive
 **/
AutoarFormatFunc
autoar_format_get_libarchive_read (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  return autoar_format_description[format - 1].libarchive_read;
}

/**
 * autoar_format_get_libarchive_write: (skip)
 * @format: an #AutoarFormat
 *
 * Gets the function used to set format on the object returned by
 * archive_write_new().
 *
 * Returns: function pointer to the setter function provided by libarchive
 **/
AutoarFormatFunc
autoar_format_get_libarchive_write (AutoarFormat format)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  return autoar_format_description[format - 1].libarchive_write;
}

/**
 * autoar_filter_last:
 *
 * Gets the maximal allowed values of #AutoarFilter
 *
 * Returns: maximal allowed values of #AutoarFilter
 **/
int
autoar_filter_last (void)
{
  return AUTOAR_FILTER_LAST;
}

/**
 * autoar_filter_is_valid:
 * @filter: an #AutoarFilter
 *
 * Checks whether an #AutoarFilter is valid.
 *
 * Returns: %TRUE if the value of @filter is valid
 **/
gboolean
autoar_filter_is_valid (AutoarFilter filter)
{
  return (filter > 0 && filter < AUTOAR_FILTER_LAST);
}

/**
 * autoar_filter_get_mime_type:
 * @filter: an #AutoarFilter
 *
 * Gets the MIME type of the filter from the internal static data.
 *
 * Returns: (transfer none): an MIME type
 **/
const char*
autoar_filter_get_mime_type (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);
  return autoar_filter_description[filter - 1].mime_type;
}

/**
 * autoar_filter_get_extension:
 * @filter: an #AutoarFilter
 *
 * Gets the file name extension of the filter from the internal static data.
 *
 * Returns: (transfer none): a file name extension
 **/
const char*
autoar_filter_get_extension (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);
  return autoar_filter_description[filter - 1].extension;
}

/**
 * autoar_filter_get_description:
 * @filter: an #AutoarFilter
 *
 * Gets description of the filter from the internal static data.
 *
 * Returns: (transfer none): description about the filter
 **/
const char*
autoar_filter_get_description (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);
  return autoar_filter_description[filter - 1].description;
}

/**
 * autoar_filter_get_filter_libarchive:
 * @filter: an #AutoarFilter
 *
 * Gets the filter code used by libarchive. You can use the return value
 * as the argument for archive_write_add_filter().
 *
 * Returns: an integer
 **/
int
autoar_filter_get_filter_libarchive (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), -1);
  return autoar_filter_description[filter - 1].libarchive_filter;
}

/**
 * autoar_filter_get_description_libarchive:
 * @filter: an #AutoarFilter
 *
 * Gets description of the filter from libarchive. This function creates
 * and destroys an archive object in order to get the description string.
 *
 * Returns: (transfer full): description about the filter. Free the returned
 * string with g_free().
 **/
gchar*
autoar_filter_get_description_libarchive (AutoarFilter filter)
{
  struct archive *a;
  gchar *str;

  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);

  a = archive_write_new ();
  archive_write_add_filter (a, autoar_filter_description[filter - 1].libarchive_filter);
  str = g_strdup (archive_filter_name (a, 0));
  archive_write_free (a);

  return str;
}

/**
 * autoar_filter_get_libarchive_read: (skip)
 * @filter: an #AutoarFilter
 *
 * Gets the function used to add filter on the object returned by
 * archive_read_new().
 *
 * Returns: function pointer to the setter function provided by libarchive
 **/
AutoarFilterFunc
autoar_filter_get_libarchive_read (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);
  return autoar_filter_description[filter - 1].libarchive_read;
}

/**
 * autoar_filter_get_libarchive_write: (skip)
 * @filter: an #AutoarFilter
 *
 * Gets the function used to add filter on the object returned by
 * archive_write_new().
 *
 * Returns: function pointer to the setter function provided by libarchive
 **/
AutoarFilterFunc
autoar_filter_get_libarchive_write (AutoarFilter filter)
{
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);
  return autoar_filter_description[filter - 1].libarchive_write;
}

/**
 * autoar_format_filter_get_mime_type:
 * @format: an #AutoarFormat
 * @filter: an #AutoarFilter
 *
 * Gets the MIME type for an archive @format compressed by
 * @filter. This function always succeed, but it is not guaranteed
 * that the returned MIME type exists and can be recognized by applications.
 * Some combination of format and filter seldom exists in application,
 * so this function can only generate the string based on some
 * non-standard rules.
 *
 * Returns: (transfer full): an MIME type. Free the returned
 * string with g_free().
 **/
gchar*
autoar_format_filter_get_mime_type (AutoarFormat format,
                                    AutoarFilter filter)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);

  switch (filter) {
    case AUTOAR_FILTER_NONE:
      return g_strdup (autoar_format_description[format - 1].mime_type);
    case AUTOAR_FILTER_COMPRESS:
      return g_strconcat ("application/x-",
                          autoar_format_description[format - 1].keyword,
                          "z", NULL);
    case AUTOAR_FILTER_GZIP:
      return g_strconcat ("application/x-compressed-",
                          autoar_format_description[format - 1].keyword,
                          NULL);
    default:
      return g_strconcat ("application/x-",
                          autoar_filter_description[filter - 1].keyword,
                          "-compressed-",
                          autoar_format_description[format - 1].keyword,
                          NULL);
  }
}

/**
 * autoar_format_filter_get_extension:
 * @format: an #AutoarFormat
 * @filter: an #AutoarFilter
 *
 * Gets the file name extension for an archive @format compressed by
 * @filter. The first character of the returned string is always '.'
 *
 * Returns: (transfer full): a file name extension. Free the returned string
 * with g_free().
 **/
gchar*
autoar_format_filter_get_extension (AutoarFormat format,
                                    AutoarFilter filter)
{
  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);

  return g_strconcat (".",
                      autoar_format_description[format - 1].extension,
                      autoar_filter_description[filter - 1].extension[0] ? "." : "",
                      autoar_filter_description[filter - 1].extension,
                      NULL);
}

/**
 * autoar_format_filter_get_description:
 * @format: an #AutoarFormat
 * @filter: an #AutoarFilter
 *
 * Gets the description for an archive @format compressed by
 * @filter using #GContentType and autoar_format_filter_get_mime_type().
 *
 * Returns: (transfer full): description about the archive. Free the returned
 * string with g_free().
 **/
gchar*
autoar_format_filter_get_description (AutoarFormat format,
                                      AutoarFilter filter)
{
  gchar *mime_type;
  gchar *description;

  g_return_val_if_fail (autoar_format_is_valid (format), NULL);
  g_return_val_if_fail (autoar_filter_is_valid (filter), NULL);

  mime_type = autoar_format_filter_get_mime_type (format, filter);
  description = g_content_type_get_description (mime_type);
  g_free (mime_type);

  return description;
}
