/*
 *  GNOME Logs - View and search logs
 *  Copyright (C) 2013  Red Hat, Inc.
 *  Copyright (C) 2015  Ekaterina Gerasimova
 *
 *  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 <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include "gl-util.h"

#include <glib/gi18n.h>
#include <systemd/sd-id128.h>
#include <systemd/sd-journal.h>

/**
 * GlUtilTimestamps:
 * @GL_UTIL_TIMESTAMPS_SAME_DAY: the timestamps have the same year, month and
 * day
 * @GL_UTIL_TIMESTAMPS_SAME_YEAR: the timestamps have the same year, but
 * different months and days
 * @GL_UTIL_TIMESTAMPS_DIFFERENT_YEAR: the timestamps have different years,
 * months and days
 *
 * Date string comparison result, used for formatting a date into an
 * appropriate string.
 */
typedef enum
{
    GL_UTIL_TIMESTAMPS_SAME_DAY,
    GL_UTIL_TIMESTAMPS_SAME_YEAR,
    GL_UTIL_TIMESTAMPS_DIFFERENT_YEAR
} GlUtilTimestamps;

static const gchar DESKTOP_SCHEMA[] = "org.gnome.desktop.interface";
static const gchar CLOCK_FORMAT[] = "clock-format";

/**
 * gl_util_on_css_provider_parsing_error:
 * @provider: the provider that had a parsing error
 * @section: section the error happened in
 * @error: the parsing error
 * @user_data: user data set when the signal handler was connected
 *
 * Handle the GtkCssProvider::parsing-error signal and print a warning based on
 * @section and @error.
 */
void
gl_util_on_css_provider_parsing_error (GtkCssProvider *provider,
                                       GtkCssSection *section,
                                       GError *error,
                                       G_GNUC_UNUSED gpointer user_data)
{
    g_critical ("Error while parsing CSS style (line: %u, character: %u): %s",
                gtk_css_section_get_end_line (section) + 1,
                gtk_css_section_get_end_position (section) + 1,
                error->message);
}

/**
 * compare_timestamps:
 * @a: a date
 * @b: a date to compare with
 *
 * Compare @a to @b and return how similar the dates are.
 *
 * Returns: a value from GlUtilTimestamps
 */
static GlUtilTimestamps
compare_timestamps (GDateTime *a,
                    GDateTime *b)
{
    gint ayear, amonth, aday;
    gint byear, bmonth, bday;

    g_date_time_get_ymd (a, &ayear, &amonth, &aday);
    g_date_time_get_ymd (b, &byear, &bmonth, &bday);

    if (ayear != byear)
    {
        return GL_UTIL_TIMESTAMPS_DIFFERENT_YEAR;
    }

    /* Same year, month and day. */
    if ((amonth == bmonth) && (aday == bday))
    {
        return GL_UTIL_TIMESTAMPS_SAME_DAY;
    }

    /* Same year, but differing by month or day. */
    return GL_UTIL_TIMESTAMPS_SAME_YEAR;
}

/**
 * gl_util_timestamp_to_display:
 * @microsecs: number of microseconds since the Unix epoch in UTC
 * @now: the time to compare with
 * @format: clock format (12 or 24 hour)
 *
 * Return a human readable time, corresponding to @microsecs, using an
 * appropriate @format after comparing it to @now and discarding unnecessary
 * elements (for example, return only time if the date is today).
 *
 * Returns: a newly-allocated human readable string which represents @microsecs
 */
gchar *
gl_util_timestamp_to_display (guint64 microsecs,
                              GDateTime *now,
                              GlUtilClockFormat format,
                              gboolean show_second)
{
    GDateTime *datetime;
    GDateTime *local;
    gchar *time = NULL;

    datetime = g_date_time_new_from_unix_utc (microsecs / G_TIME_SPAN_SECOND);

    if (datetime == NULL)
    {
        g_warning ("Error converting timestamp to time value");
        goto out;
    }

    local = g_date_time_to_local (datetime);

    switch (format)
    {
        case GL_UTIL_CLOCK_FORMAT_12HR:
            switch (compare_timestamps (local, now))
            {
                case GL_UTIL_TIMESTAMPS_SAME_DAY:
                    if (show_second)
                    {
                        /* Translators: timestamp format for events on the
                         * current day, showing the time with seconds in
                         * 12-hour format. */
                        time = g_date_time_format (local, _("%l:%M:%S %p"));
                    }
                    else
                    {
                        /* Translators: timestamp format for events on the
                         * current day, showing the time without seconds in
                         * 12-hour format. */
                        time = g_date_time_format (local, _("%l:%M %p"));
                    }
                    break;
                case GL_UTIL_TIMESTAMPS_SAME_YEAR:
                    if (show_second)
                    {
                        time = g_date_time_format (local,
                               /* Translators: timestamp format for events in
                                * the current year, showing the abbreviated
                                * month name, day of the month and the time
                                * with seconds in 12-hour format. */
                                                   _("%b %e %l:%M:%S %p"));
                    }
                    else
                    {
                        /* Translators: timestamp format for events in the
                         * current year, showing the abbreviated month name,
                         * day of the month and the time without seconds in
                         * 12-hour format. */
                        time = g_date_time_format (local, _("%b %e %l:%M %p"));
                    }
                    break;
                case GL_UTIL_TIMESTAMPS_DIFFERENT_YEAR:
                    if (show_second)
                    {
                        time = g_date_time_format (local,
                               /* Translators: timestamp format for events in
                                * a different year, showing the abbreviated
                                * month name, day of the month, year and the
                                * time with seconds in 12-hour format. */
                                                   _("%b %e %Y %l:%M:%S %p"));
                    }
                    else
                    {
                        time = g_date_time_format (local,
                               /* Translators: timestamp format for events in
                                * a different year, showing the abbreviated
                                * month name day of the month, year and the
                                * time without seconds in 12-hour format. */
                                                   _("%b %e %Y %l:%M %p"));
                    }
                    break;
                default:
                    g_assert_not_reached ();
            }

            break;
        case GL_UTIL_CLOCK_FORMAT_24HR:
            switch (compare_timestamps (local, now))
            {
                case GL_UTIL_TIMESTAMPS_SAME_DAY:
                    if (show_second)
                    {
                        /* Translators: timestamp format for events on the
                         * current day, showing the time with seconds in
                         * 24-hour format. */
                        time = g_date_time_format (local, _("%H:%M:%S"));
                    }
                    else
                    {
                        /* Translators: timestamp format for events on the
                         * current day, showing the time without seconds in
                         * 24-hour format. */
                        time = g_date_time_format (local, _("%H:%M"));
                    }
                    break;
                case GL_UTIL_TIMESTAMPS_SAME_YEAR:
                    if (show_second)
                    {
                        /* Translators: timestamp format for events in the
                         * current year, showing the abbreviated month name,
                         * day of the month and the time with seconds in
                         * 24-hour format. */
                        time = g_date_time_format (local, _("%b %e %H:%M:%S"));
                    }
                    else
                    {
                        /* Translators: timestamp format for events in the
                         * current year, showing the abbreviated month name,
                         * day of the month and the time without seconds in
                         * 24-hour format. */
                        time = g_date_time_format (local, _("%b %e %H:%M"));
                    }
                    break;
                case GL_UTIL_TIMESTAMPS_DIFFERENT_YEAR:
                    if (show_second)
                    {
                        time = g_date_time_format (local,
                               /* Translators: timestamp format for events in
                                * a different year, showing the abbreviated
                                * month name, day of the month, year and the
                                * time with seconds in 24-hour format. */
                                                   _("%b %e %Y %H:%M:%S"));
                    }
                    else
                    {
                        /* Translators: timestamp format for events in a
                         * different year, showing the abbreviated month name,
                         * day of the month, year and the time without seconds
                         * in 24-hour format. */
                        time = g_date_time_format (local, _("%b %e %Y %H:%M"));
                    }
                    break;
                default:
                    g_assert_not_reached ();
            }

            break;
        default:
            g_assert_not_reached ();
    }

    g_date_time_unref (datetime);
    g_date_time_unref (local);

    if (time == NULL)
    {
        g_warning ("Error converting datetime to string");
    }

out:
    return time;
}

gint
gl_util_get_uid (void)
{
    GCredentials *creds;
    uid_t uid;

    creds = g_credentials_new ();
    uid = g_credentials_get_unix_user (creds, NULL);

    g_object_unref (creds);

    return uid;
}

gchar *
gl_util_boot_time_to_display (guint64 realtime_first,
                              guint64 realtime_last)
{
    gchar *time_first;
    gchar *time_last;
    gchar *time_display;
    GDateTime *now;
    GSettings *settings;
    GlUtilClockFormat clock_format;

    /* TODO: Monitor and propagate any GSettings changes. */
    settings = g_settings_new (DESKTOP_SCHEMA);
    clock_format = g_settings_get_enum (settings, CLOCK_FORMAT);

    g_object_unref (settings);

    now = g_date_time_new_now_local ();
    time_first = gl_util_timestamp_to_display (realtime_first,
                                               now, clock_format, FALSE);
    time_last = gl_util_timestamp_to_display (realtime_last,
                                              now, clock_format, FALSE);

    /* Transltors: the first string is the earliest timestamp of the boot,
     * and the second string is the newest timestamp. An example string might
     * be '08:10 - 08:30' */
    time_display = g_strdup_printf (_("%s – %s"), time_first, time_last);

    g_date_time_unref (now);
    g_free (time_first);
    g_free (time_last);

    return time_display;
}

/**
 * Determine journal storage type:
 *
 * Test existence of possible journal storage paths.
 *
 * Returns: a value from GlJournalStorage
 */
GlJournalStorage
gl_util_journal_storage_type (void)
{
    if (g_file_test ("/run/log/journal", G_FILE_TEST_EXISTS))
    {
        return GL_JOURNAL_STORAGE_VOLATILE;
    }
    else
    {
        return GL_JOURNAL_STORAGE_PERSISTENT;
    }

    if ((!g_file_test ("/run/log/journal", G_FILE_TEST_EXISTS)) && (!g_file_test ("/var/log/journal", G_FILE_TEST_EXISTS)))
    {
        return GL_JOURNAL_STORAGE_NONE;
    }
}

gboolean
gl_util_can_read_system_journal (GlJournalStorage storage_type)
{
    GFile *file;
    GFileInfo *info;
    gint ret;
    gchar *path;
    gchar ids[33];
    sd_id128_t machine;

    ret = sd_id128_get_machine (&machine);
    if (ret < 0)
    {
        g_critical ("Error getting machine id: %s", g_strerror (-ret));
    }
    sd_id128_to_string (machine, ids);

    if (storage_type == GL_JOURNAL_STORAGE_PERSISTENT)
    {
        path = g_build_filename ("/var/log/journal", ids, "system.journal", NULL);
    }
    else if (storage_type == GL_JOURNAL_STORAGE_VOLATILE)
    {
        path = g_build_filename ("/run/log/journal", ids, "system.journal", NULL);
    }
    else
    {
        path = "/dev/null";
    }

    file = g_file_new_for_path (path);
    info = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
                              G_FILE_QUERY_INFO_NONE, NULL, NULL);

    g_free (path);
    g_object_unref (file);

    if (g_file_info_get_attribute_boolean (info,
                                           G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
    {
        g_object_unref (info);

        return TRUE;
    }
    else
    {
        g_object_unref (info);

        return FALSE;
    }
}

gboolean
gl_util_can_read_user_journal (void)
{
    GFile *file;
    GFileInfo *info;
    gint ret;
    gchar *path;
    gchar ids[33];
    gchar *filename;
    gchar *uid;
    uid_t user_id;
    sd_id128_t machine;
    GError *error = NULL;
    GCredentials *credentials;

    credentials = g_credentials_new ();
    user_id = g_credentials_get_unix_user (credentials, &error);
    if (error != NULL)
    {
        g_debug ("Unable to get uid: %s", error->message);
        g_error_free (error);
    }
    uid = g_strdup_printf ("%d", user_id);
    filename = g_strconcat ("/user-", uid, ".journal", NULL);

    ret = sd_id128_get_machine (&machine);
    if (ret < 0)
    {
        g_critical ("Error getting machine id: %s", g_strerror (-ret));
    }
    sd_id128_to_string (machine, ids);

    path = g_build_filename ("/var/log/journal", ids, filename, NULL);

    file = g_file_new_for_path (path);
    info = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_READ,
                              G_FILE_QUERY_INFO_NONE, NULL, NULL);

    g_free (uid);
    g_free (path);
    g_free (filename);
    g_object_unref (file);
    g_object_unref (credentials);

    if (g_file_info_get_attribute_boolean (info,
                                           G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
    {
        g_object_unref (info);

        return TRUE;
    }
    else
    {
        g_object_unref (info);

        return FALSE;
    }
}

/**
 * This function is orignally written in gtk/gtkfontbutton.c of gtk+ project.
 */
gchar *
pango_font_description_to_css (PangoFontDescription *desc)
{
  GString *s;
  PangoFontMask set;

  s = g_string_new ("{ ");

  set = pango_font_description_get_set_fields (desc);
  if (set & PANGO_FONT_MASK_FAMILY)
    {
      g_string_append (s, "font-family: ");
      g_string_append (s, pango_font_description_get_family (desc));
      g_string_append (s, "; ");
    }
  if (set & PANGO_FONT_MASK_STYLE)
    {
      switch (pango_font_description_get_style (desc))
        {
        case PANGO_STYLE_NORMAL:
          g_string_append (s, "font-style: normal; ");
          break;
        case PANGO_STYLE_OBLIQUE:
          g_string_append (s, "font-style: oblique; ");
          break;
        case PANGO_STYLE_ITALIC:
          g_string_append (s, "font-style: italic; ");
          break;
        }
    }
  if (set & PANGO_FONT_MASK_VARIANT)
    {
      switch (pango_font_description_get_variant (desc))
        {
        case PANGO_VARIANT_NORMAL:
          g_string_append (s, "font-variant: normal; ");
          break;
        case PANGO_VARIANT_SMALL_CAPS:
          g_string_append (s, "font-variant: small-caps; ");
          break;
        }
    }
  if (set & PANGO_FONT_MASK_WEIGHT)
    {
      switch (pango_font_description_get_weight (desc))
        {
        case PANGO_WEIGHT_THIN:
          g_string_append (s, "font-weight: 100; ");
          break;
        case PANGO_WEIGHT_ULTRALIGHT:
          g_string_append (s, "font-weight: 200; ");
          break;
        case PANGO_WEIGHT_LIGHT:
        case PANGO_WEIGHT_SEMILIGHT:
          g_string_append (s, "font-weight: 300; ");
          break;
        case PANGO_WEIGHT_BOOK:
        case PANGO_WEIGHT_NORMAL:
          g_string_append (s, "font-weight: 400; ");
          break;
        case PANGO_WEIGHT_MEDIUM:
          g_string_append (s, "font-weight: 500; ");
          break;
        case PANGO_WEIGHT_SEMIBOLD:
          g_string_append (s, "font-weight: 600; ");
          break;
        case PANGO_WEIGHT_BOLD:
          g_string_append (s, "font-weight: 700; ");
          break;
        case PANGO_WEIGHT_ULTRABOLD:
          g_string_append (s, "font-weight: 800; ");
          break;
        case PANGO_WEIGHT_HEAVY:
        case PANGO_WEIGHT_ULTRAHEAVY:
          g_string_append (s, "font-weight: 900; ");
          break;
        }
    }
  if (set & PANGO_FONT_MASK_STRETCH)
    {
      switch (pango_font_description_get_stretch (desc))
        {
        case PANGO_STRETCH_ULTRA_CONDENSED:
          g_string_append (s, "font-stretch: ultra-condensed; ");
          break;
        case PANGO_STRETCH_EXTRA_CONDENSED:
          g_string_append (s, "font-stretch: extra-condensed; ");
          break;
        case PANGO_STRETCH_CONDENSED:
          g_string_append (s, "font-stretch: condensed; ");
          break;
        case PANGO_STRETCH_SEMI_CONDENSED:
          g_string_append (s, "font-stretch: semi-condensed; ");
          break;
        case PANGO_STRETCH_NORMAL:
          g_string_append (s, "font-stretch: normal; ");
          break;
        case PANGO_STRETCH_SEMI_EXPANDED:
          g_string_append (s, "font-stretch: semi-expanded; ");
          break;
        case PANGO_STRETCH_EXPANDED:
          g_string_append (s, "font-stretch: expanded; ");
          break;
        case PANGO_STRETCH_EXTRA_EXPANDED:
          g_string_append (s, "font-stretch: extra-expanded; ");
          break;
        case PANGO_STRETCH_ULTRA_EXPANDED:
          g_string_append (s, "font-stretch: ultra-expanded; ");
          break;
        }
    }
  if (set & PANGO_FONT_MASK_SIZE)
    {
      g_string_append_printf (s, "font-size: %dpt", pango_font_description_get_size (desc) / PANGO_SCALE);
    }

  g_string_append (s, "}");

  return g_string_free (s, FALSE);
}
