/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
 *
 * file-remote.c
 * Copyright (C) 2014  Michael Natterer <mitch@gimp.org>
 *
 * Based on: URI plug-in, GIO/GVfs backend
 * Copyright (C) 2008  Sven Neumann <sven@gimp.org>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "libgimpbase/gimpbase.h"

#include "core/core-types.h"

#include "core/gimp.h"
#include "core/gimpprogress.h"

#include "file-remote.h"

#include "gimp-intl.h"


typedef enum
{
  DOWNLOAD,
  UPLOAD
} RemoteCopyMode;

typedef struct
{
  GimpProgress *progress;
  GCancellable *cancellable;
  gboolean      cancel;
  GMainLoop    *main_loop;
  GError       *error;
} RemoteMount;

typedef struct
{
  RemoteCopyMode  mode;
  GimpProgress   *progress;
  GCancellable   *cancellable;
  gboolean        cancel;
  GTimeVal        last_time;
} RemoteProgress;


static void       file_remote_mount_volume_ready (GFile           *file,
                                                  GAsyncResult    *result,
                                                  RemoteMount     *mount);
static void       file_remote_mount_file_cancel  (GimpProgress    *progress,
                                                  RemoteMount     *mount);

static GFile    * file_remote_get_temp_file      (Gimp            *gimp,
                                                  GFile           *file);
static gboolean   file_remote_copy_file          (Gimp            *gimp,
                                                  GFile           *src_file,
                                                  GFile           *dest_file,
                                                  RemoteCopyMode   mode,
                                                  GimpProgress    *progress,
                                                  GError         **error);
static void       file_remote_copy_file_cancel   (GimpProgress    *progress,
                                                  RemoteProgress  *remote_progress);

static void       file_remote_progress_callback  (goffset          current_num_bytes,
                                                  goffset          total_num_bytes,
                                                  gpointer         user_data);


/*  public functions  */

gboolean
file_remote_mount_file (Gimp          *gimp,
                        GFile         *file,
                        GimpProgress  *progress,
                        GError       **error)
{
  GMountOperation *operation;
  RemoteMount      mount = { 0, };

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
  g_return_val_if_fail (G_IS_FILE (file), FALSE);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  mount.progress  = progress;
  mount.main_loop = g_main_loop_new (NULL, FALSE);

  operation = gimp_get_mount_operation (gimp, progress);

  if (progress)
    {
      gimp_progress_start (progress, TRUE, _("Mounting remote volume"));

      mount.cancellable = g_cancellable_new ();

      g_signal_connect (progress, "cancel",
                        G_CALLBACK (file_remote_mount_file_cancel),
                        &mount);
    }

  g_file_mount_enclosing_volume (file, G_MOUNT_MOUNT_NONE,
                                 operation, mount.cancellable,
                                 (GAsyncReadyCallback) file_remote_mount_volume_ready,
                                 &mount);

  g_main_loop_run (mount.main_loop);
  g_main_loop_unref (mount.main_loop);

  if (progress)
    {
      g_signal_handlers_disconnect_by_func (progress,
                                            file_remote_mount_file_cancel,
                                            &mount);

      g_object_unref (mount.cancellable);

      gimp_progress_end (progress);
    }

  g_object_unref (operation);

  if (mount.error)
    {
      if (mount.error->domain != G_IO_ERROR ||
          mount.error->code   != G_IO_ERROR_ALREADY_MOUNTED)
        {
          g_propagate_error (error, mount.error);
          return FALSE;
        }
      else
        {
          g_clear_error (&mount.error);
        }
    }

  return TRUE;
}

GFile *
file_remote_download_image (Gimp          *gimp,
                            GFile         *file,
                            GimpProgress  *progress,
                            GError       **error)
{
  GFile *local_file;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
  g_return_val_if_fail (G_IS_FILE (file), NULL);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  local_file = file_remote_get_temp_file (gimp, file);

  if (! file_remote_copy_file (gimp, file, local_file, DOWNLOAD,
                               progress, error))
    {
      g_object_unref (local_file);
      return NULL;
    }

  return local_file;
}

GFile *
file_remote_upload_image_prepare (Gimp          *gimp,
                                  GFile         *file,
                                  GimpProgress  *progress,
                                  GError       **error)
{
  GFile *local_file;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
  g_return_val_if_fail (G_IS_FILE (file), NULL);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  local_file = file_remote_get_temp_file (gimp, file);

  return local_file;
}

gboolean
file_remote_upload_image_finish (Gimp          *gimp,
                                 GFile         *file,
                                 GFile         *local_file,
                                 GimpProgress  *progress,
                                 GError       **error)
{
  g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
  g_return_val_if_fail (G_IS_FILE (file), FALSE);
  g_return_val_if_fail (G_IS_FILE (local_file), FALSE);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (! file_remote_copy_file (gimp, local_file, file, UPLOAD,
                               progress, error))
    {
      return FALSE;
    }

  return TRUE;
}


/*  private functions  */

static void
file_remote_mount_volume_ready (GFile        *file,
                                GAsyncResult *result,
                                RemoteMount  *mount)
{
  g_file_mount_enclosing_volume_finish (file, result, &mount->error);

  g_main_loop_quit (mount->main_loop);
}

static void
file_remote_mount_file_cancel (GimpProgress *progress,
                               RemoteMount  *mount)
{
  mount->cancel = TRUE;

  g_cancellable_cancel (mount->cancellable);
}

static GFile *
file_remote_get_temp_file (Gimp  *gimp,
                           GFile *file)
{
  gchar *basename;
  GFile *temp_file = NULL;

  basename = g_path_get_basename (gimp_file_get_utf8_name (file));

  if (basename)
    {
      const gchar *ext = strchr (basename, '.');

      if (ext && strlen (ext))
        temp_file = gimp_get_temp_file (gimp, ext + 1);

      g_free (basename);
    }

  if (! temp_file)
    temp_file = gimp_get_temp_file (gimp, "xxx");

  return temp_file;
}

static gboolean
file_remote_copy_file (Gimp            *gimp,
                       GFile           *src_file,
                       GFile           *dest_file,
                       RemoteCopyMode   mode,
                       GimpProgress    *progress,
                       GError         **error)
{
  RemoteProgress  remote_progress = { 0, };
  gboolean        success;
  GError         *my_error = NULL;

  remote_progress.mode     = mode;
  remote_progress.progress = progress;

  if (progress)
    {
      gimp_progress_start (progress, TRUE, _("Opening remote file"));

      remote_progress.cancellable = g_cancellable_new ();

      g_signal_connect (progress, "cancel",
                        G_CALLBACK (file_remote_copy_file_cancel),
                        &remote_progress);

      success = g_file_copy (src_file, dest_file, G_FILE_COPY_OVERWRITE,
                             remote_progress.cancellable,
                             file_remote_progress_callback, &remote_progress,
                             &my_error);

      g_signal_handlers_disconnect_by_func (progress,
                                            file_remote_copy_file_cancel,
                                            &remote_progress);

      g_object_unref (remote_progress.cancellable);

      gimp_progress_set_value (progress, 1.0);
      gimp_progress_end (progress);
    }
  else
    {
      success = g_file_copy (src_file, dest_file, G_FILE_COPY_OVERWRITE,
                             NULL, NULL, NULL,
                             &my_error);
    }

  return success;
}

static void
file_remote_copy_file_cancel (GimpProgress   *progress,
                              RemoteProgress *remote_progress)
{
  remote_progress->cancel = TRUE;

  g_cancellable_cancel (remote_progress->cancellable);
}

static void
file_remote_progress_callback (goffset  current_num_bytes,
                               goffset  total_num_bytes,
                               gpointer user_data)
{
  RemoteProgress *progress = user_data;
  GTimeVal        now;

  /*  update the progress only up to 10 times a second  */
  g_get_current_time (&now);

  if (progress->last_time.tv_sec &&
      ((now.tv_sec - progress->last_time.tv_sec) * 1000 +
       (now.tv_usec - progress->last_time.tv_usec) / 1000) < 100)
    return;

  progress->last_time = now;

  if (total_num_bytes > 0)
    {
      const gchar *format;
      gchar       *done  = g_format_size (current_num_bytes);
      gchar       *total = g_format_size (total_num_bytes);

      switch (progress->mode)
        {
        case DOWNLOAD:
          format = _("Downloading image (%s of %s)");
          break;

        case UPLOAD:
          format = _("Uploading image (%s of %s)");
          break;

        default:
          gimp_assert_not_reached ();
        }

      gimp_progress_set_text (progress->progress, format, done, total);
      g_free (total);
      g_free (done);

      gimp_progress_set_value (progress->progress,
                               (gdouble) current_num_bytes /
                               (gdouble) total_num_bytes);
    }
  else
    {
      const gchar *format;
      gchar       *done = g_format_size (current_num_bytes);

      switch (progress->mode)
        {
        case DOWNLOAD:
          format = _("Downloaded %s of image data");
          break;

        case UPLOAD:
          format = _("Uploaded %s of image data");
          break;

        default:
          gimp_assert_not_reached ();
        }

      gimp_progress_set_text (progress->progress, format, done);
      g_free (done);

      gimp_progress_pulse (progress->progress);
    }

  while (! progress->cancel && g_main_context_pending (NULL))
    g_main_context_iteration (NULL, FALSE);
}
