/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=2 sts=2 et: */
/*
 *  Copyright © 2012 Igalia S.L.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "ephy-file-monitor.h"

#include "ephy-debug.h"

#include <string.h>

#define RELOAD_DELAY            250 /* ms */
#define RELOAD_DELAY_MAX_TICKS  40  /* RELOAD_DELAY * RELOAD_DELAY_MAX_TICKS = 10 s */

struct _EphyFileMonitorPrivate {
  GFileMonitor *monitor;
  gboolean monitor_directory;
  guint reload_scheduled_id;
  guint reload_delay_ticks;

  EphyWebView *view;
};    

enum {
  PROP_0,

  PROP_VIEW
};

#define EPHY_FILE_MONITOR_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_FILE_MONITOR, EphyFileMonitorPrivate))

G_DEFINE_TYPE (EphyFileMonitor, ephy_file_monitor, G_TYPE_OBJECT)

static void
ephy_file_monitor_cancel (EphyFileMonitor *monitor)
{
  EphyFileMonitorPrivate *priv;

  g_return_if_fail (EPHY_IS_FILE_MONITOR (monitor));

  priv = monitor->priv;

  if (priv->monitor != NULL) {
    LOG ("Cancelling file monitor");

    g_file_monitor_cancel (G_FILE_MONITOR (priv->monitor));
    g_object_unref (priv->monitor);
    priv->monitor = NULL;
  }

  if (priv->reload_scheduled_id != 0) {
    LOG ("Cancelling scheduled reload");

    g_source_remove (priv->reload_scheduled_id);
    priv->reload_scheduled_id = 0;
  }

  priv->reload_delay_ticks = 0;
}

static gboolean
ephy_file_monitor_reload_cb (EphyFileMonitor *monitor)
{
  EphyFileMonitorPrivate *priv = monitor->priv;

  if (priv->reload_delay_ticks > 0) {
    priv->reload_delay_ticks--;

    /* Run again. */
    return TRUE;
  }

  if (ephy_web_view_is_loading (priv->view)) {
    /* Wait a bit to reload if we're still loading! */
    priv->reload_delay_ticks = RELOAD_DELAY_MAX_TICKS / 2;

    /* Run again. */
    return TRUE;
  }

  priv->reload_scheduled_id = 0;

  LOG ("Reloading file '%s'", ephy_web_view_get_address (priv->view));
  webkit_web_view_reload (WEBKIT_WEB_VIEW (priv->view));

  /* Don't run again. */
  return FALSE;
}

static void
ephy_file_monitor_changed_cb (GFileMonitor *monitor,
                              GFile *file,
                              GFile *other_file,
                              GFileMonitorEvent event_type,
                              EphyFileMonitor *file_monitor)
{
  gboolean should_reload;
  EphyFileMonitorPrivate *priv = file_monitor->priv;

  switch (event_type) {
    /* These events will always trigger a reload: */
    case G_FILE_MONITOR_EVENT_CHANGED:
    case G_FILE_MONITOR_EVENT_CREATED:
      should_reload = TRUE;
      break;

    /* These events will only trigger a reload for directories: */
    case G_FILE_MONITOR_EVENT_DELETED:
    case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
      should_reload = priv->monitor_directory;
      break;

    /* These events don't trigger a reload: */
    case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
    case G_FILE_MONITOR_EVENT_UNMOUNTED:
    case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
    default:
      should_reload = FALSE;
      break;
  }

  if (should_reload) {
    /* We make a lot of assumptions here, but basically we know
     * that we just have to reload, by construction.
     * Delay the reload a little bit so we don't endlessly
     * reload while a file is written.
     */
    if (priv->reload_delay_ticks == 0)
      priv->reload_delay_ticks = 1;
    else {
      /* Exponential backoff. */
      priv->reload_delay_ticks = MIN (priv->reload_delay_ticks * 2,
                                      RELOAD_DELAY_MAX_TICKS);
    }

    if (priv->reload_scheduled_id == 0) {
      priv->reload_scheduled_id =
        g_timeout_add (RELOAD_DELAY,
                       (GSourceFunc)ephy_file_monitor_reload_cb, file_monitor);
    }
  }
}

void
ephy_file_monitor_update_location (EphyFileMonitor *file_monitor,
                                   const char *address)
{
  EphyFileMonitorPrivate *priv;
  gboolean local;
  char *anchor;
  char *url;
  GFile *file;
  GFileType file_type;
  GFileInfo *file_info;

  g_return_if_fail (EPHY_IS_FILE_MONITOR (file_monitor));
  g_return_if_fail (address != NULL);

  priv = file_monitor->priv;

  ephy_file_monitor_cancel (file_monitor);

  local = g_str_has_prefix (address, "file://");
  if (local == FALSE)
    return;

  /* strip off anchors */
  anchor = strchr (address, '#');
  if (anchor != NULL)
    url = g_strndup (address, anchor - address);
  else
    url = g_strdup (address);

  file = g_file_new_for_uri (url);
  file_info = g_file_query_info (file,
                                 G_FILE_ATTRIBUTE_STANDARD_TYPE,
                                 0, NULL, NULL);
  if (file_info == NULL) {
    g_object_unref (file);
    g_free (url);
    return;
  }

  file_type = g_file_info_get_file_type (file_info);
  g_object_unref (file_info);

  if (file_type == G_FILE_TYPE_DIRECTORY) {
    priv->monitor = g_file_monitor_directory (file, 0, NULL, NULL);
    g_signal_connect (priv->monitor, "changed",
                      G_CALLBACK (ephy_file_monitor_changed_cb),
                      file_monitor);
    priv->monitor_directory = TRUE;
    LOG ("Installed monitor for directory '%s'", url);
  }
  else if (file_type == G_FILE_TYPE_REGULAR) {
    priv->monitor = g_file_monitor_file (file, 0, NULL, NULL);
    g_signal_connect (priv->monitor, "changed",
                      G_CALLBACK (ephy_file_monitor_changed_cb),
                      file_monitor);
    priv->monitor_directory = FALSE;
    LOG ("Installed monitor for file '%s'", url);
  }

  g_object_unref (file);
  g_free (url);
}

static void
ephy_file_monitor_dispose (GObject *object)
{
  ephy_file_monitor_cancel (EPHY_FILE_MONITOR (object));

  G_OBJECT_CLASS (ephy_file_monitor_parent_class)->dispose (object);
}

static void
ephy_file_monitor_get_property (GObject *object,
                                guint prop_id,
                                GValue *value,
                                GParamSpec *pspec)
{
  EphyFileMonitorPrivate *priv = EPHY_FILE_MONITOR (object)->priv;

  switch (prop_id) {
    case PROP_VIEW:
      g_value_set_object (value, priv->view);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 
      break;
  }
}

static void
ephy_file_monitor_set_property (GObject *object,
                                guint prop_id,
                                const GValue *value,
                                GParamSpec *pspec)
{
  EphyFileMonitorPrivate *priv = EPHY_FILE_MONITOR (object)->priv;

  switch (prop_id) {
    case PROP_VIEW:
      priv->view = g_value_get_object (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 
      break;
  }
}

static void
ephy_file_monitor_class_init (EphyFileMonitorClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = ephy_file_monitor_dispose;
  gobject_class->get_property = ephy_file_monitor_get_property;
  gobject_class->set_property = ephy_file_monitor_set_property;

  g_object_class_install_property (gobject_class,
                                   PROP_VIEW,
                                   g_param_spec_object ("view",
                                                        "View",
                                                        "The file monitor's associated view",
                                                        EPHY_TYPE_WEB_VIEW,
                                                        G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_CONSTRUCT_ONLY));

  g_type_class_add_private (gobject_class, sizeof (EphyFileMonitorPrivate));
}

static void
ephy_file_monitor_init (EphyFileMonitor *monitor)
{
  monitor->priv = EPHY_FILE_MONITOR_GET_PRIVATE (monitor);
}

EphyFileMonitor *
ephy_file_monitor_new (EphyWebView *view)
{
  return g_object_new (EPHY_TYPE_FILE_MONITOR,
                       "view", view,
                       NULL);
}
