/* libguestfs
 * Copyright (C) 2011 Red Hat Inc.
 *
 * This library 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 of the License, or (at your option) any later version.
 *
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "c-ctype.h"

#include "guestfs.h"
#include "guestfs-internal.h"

int
guestfs_set_event_callback (guestfs_h *g,
                            guestfs_event_callback cb,
                            uint64_t event_bitmask,
                            int flags,
                            void *opaque)
{
  int event_handle;

  if (flags != 0) {
    error (g, "flags parameter should be passed as 0 to this function");
    return -1;
  }

  /* We cast size_t to int which is not always safe for large numbers,
   * and in any case if a program is registering a huge number of
   * callbacks then we'd want to look at using an alternate data
   * structure in place of a linear list.
   */
  if (g->nr_events >= 1000) {
    error (g, "too many event callbacks registered");
    return -1;
  }

  event_handle = (int) g->nr_events;
  g->events =
    safe_realloc (g, g->events,
                  (g->nr_events+1) * sizeof (struct event));
  g->nr_events++;

  g->events[event_handle].event_bitmask = event_bitmask;
  g->events[event_handle].cb = cb;
  g->events[event_handle].opaque = opaque;
  g->events[event_handle].opaque2 = NULL;

  return event_handle;
}

void
guestfs_delete_event_callback (guestfs_h *g, int event_handle)
{
  if (event_handle < 0 || event_handle >= (int) g->nr_events)
    return;

  /* Set the event_bitmask to 0, which will ensure that this callback
   * cannot match any event and therefore cannot be called.
   */
  g->events[event_handle].event_bitmask = 0;

  /* If the program continually allocated and then deallocated event
   * handlers, it would eventually run into the limit (of 1000) in the
   * function above.  Avoid that here.  However this doesn't "fix" the
   * problem that this structure is not well-suited to handling large
   * numbers of event handlers.
   */
  if ((unsigned) event_handle == g->nr_events-1)
    g->nr_events--;
}

/* Functions to generate an event with various payloads. */

void
guestfs_int_call_callbacks_void (guestfs_h *g, uint64_t event)
{
  size_t i;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0)
      g->events[i].cb (g, g->events[i].opaque, event, i, 0, NULL, 0, NULL, 0);

  /* All events with payload type void are discarded if no callback
   * was registered.
   */
}

void
guestfs_int_call_callbacks_message (guestfs_h *g, uint64_t event,
				    const char *buf, size_t buf_len)
{
  size_t i, count = 0;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0) {
      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
                       buf, buf_len, NULL, 0);
      count++;
    }

  /* Emulate the old-style handlers. */

  /* Callers can override print-on-stderr simply by registering a
   * callback (so count > 0).
   */
  if (count > 0)
    return;

  if ((event == GUESTFS_EVENT_APPLIANCE ||
       event == GUESTFS_EVENT_LIBRARY ||
       event == GUESTFS_EVENT_WARNING ||
       event == GUESTFS_EVENT_TRACE) &&
      (g->verbose ||
       event == GUESTFS_EVENT_WARNING ||
       event == GUESTFS_EVENT_TRACE)) {
    bool from_appliance = event == GUESTFS_EVENT_APPLIANCE;
    size_t i0;

    /* APPLIANCE =>  <buf>
     * LIBRARY =>    libguestfs: <buf>\n
     * WARNING =>    libguestfs: warning: <buf>\n
     * TRACE =>      libguestfs: trace: [<ID>:] <buf>\n  (RHBZ#673479)
     */

    if (event != GUESTFS_EVENT_APPLIANCE)
      fputs ("libguestfs: ", stderr);

    if (event == GUESTFS_EVENT_WARNING)
      fputs ("warning: ", stderr);

    if (event == GUESTFS_EVENT_TRACE) {
      fputs ("trace: ", stderr);
      if (STRNEQ (g->identifier, "")) {
        fputs (g->identifier, stderr);
        fputs (": ", stderr);
      }
    }

    /* Special or non-printing characters in the buffer must be
     * escaped (RHBZ#731744).  The buffer can contain any 8 bit
     * character, even \0.
     *
     * Handling of \n and \r characters is complex:
     *
     * Case 1: Messages from the appliance: These messages already
     * contain \n and \r characters at logical positions, so we just
     * echo those out directly.
     *
     * Case 2: Messages from other sources: These messages should NOT
     * contain \n or \r.  If they do, it is escaped.  However we also
     * need to print a real end of line after these messages.
     *
     * RHBZ#802109: Because stderr is usually not buffered, avoid
     * single 'putc' calls (which translate to a 1 byte write), and
     * try to send longest possible strings in single fwrite calls
     * (thanks to Jim Meyering for the basic approach).
     */
#define NO_ESCAPING(c)							\
    (c_isprint ((c)) || (from_appliance && ((c) == '\n' || (c) == '\r')))

    for (i = 0; i < buf_len; ++i) {
      if (NO_ESCAPING (buf[i])) {
        i0 = i;
        while (i < buf_len && NO_ESCAPING (buf[i]))
          ++i;
        fwrite (&buf[i0], 1, i-i0, stderr);
        /* Adjust i so that next time around the loop, the next
         * non-printing character will be displayed.
         */
        if (i < buf_len)
          --i;
      } else {
        switch (buf[i]) {
        case '\0': fputs ("\\0", stderr); break;
        case '\a': fputs ("\\a", stderr); break;
        case '\b': fputs ("\\b", stderr); break;
        case '\f': fputs ("\\f", stderr); break;
        case '\n': fputs ("\\n", stderr); break;
        case '\r': fputs ("\\r", stderr); break;
        case '\t': fputs ("\\t", stderr); break;
        case '\v': fputs ("\\v", stderr); break;
        default:
          fprintf (stderr, "\\x%x", (unsigned char) buf[i]);
        }
      }
    }

    if (!from_appliance)
      putc ('\n', stderr);
  }
}

void
guestfs_int_call_callbacks_array (guestfs_h *g, uint64_t event,
				  const uint64_t *array, size_t array_len)
{
  size_t i;

  for (i = 0; i < g->nr_events; ++i)
    if ((g->events[i].event_bitmask & event) != 0)
      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
                       NULL, 0, array, array_len);

  /* All events with payload type array are discarded if no callback
   * was registered.
   */
}

/**
 * Emulate old-style callback API.
 *
 * There were no event handles, so multiple callbacks per event were
 * not supported.  Calling the same C<guestfs_set_*_callback> function
 * would replace the existing event.  Calling it with C<cb == NULL>
 * meant that the caller wanted to remove the callback.
 */
static void
replace_old_style_event_callback (guestfs_h *g,
                                  guestfs_event_callback cb,
                                  uint64_t event_bitmask,
                                  void *opaque,
                                  void *opaque2)
{
  size_t i;

  /* Use 'cb' pointer as a sentinel to replace the existing callback
   * for this event if one was registered previously.  Else append a
   * new event.
   */

  for (i = 0; i < g->nr_events; ++i)
    if (g->events[i].cb == cb) {
      if (opaque2 == NULL) {
        /* opaque2 (the original callback) is NULL, which in the
         * old-style API meant remove the callback.
         */
        guestfs_delete_event_callback (g, i);
        return;
      }

      goto replace;
    }

  if (opaque2 == NULL)
    return; /* see above */

  /* i == g->nr_events */
  g->events =
    safe_realloc (g, g->events,
                  (g->nr_events+1) * sizeof (struct event));
  g->nr_events++;

 replace:
  g->events[i].event_bitmask = event_bitmask;
  g->events[i].cb = cb;
  g->events[i].opaque = opaque;
  g->events[i].opaque2 = opaque2;
}

static void
log_message_callback_wrapper (guestfs_h *g,
                              void *opaque,
                              uint64_t event,
                              int event_handle,
                              int flags,
                              const char *buf, size_t buf_len,
                              const uint64_t *array, size_t array_len)
{
  guestfs_log_message_cb cb = g->events[event_handle].opaque2;
  /* Note that the old callback declared the message buffer as
   * (char *, int).  I sure hope message buffers aren't too large
   * and that callers aren't writing to them. XXX
   */
  cb (g, opaque, (char *) buf, (int) buf_len);
}

void
guestfs_set_log_message_callback (guestfs_h *g,
                                  guestfs_log_message_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, log_message_callback_wrapper,
                                    GUESTFS_EVENT_APPLIANCE,
                                    opaque, cb);
}

static void
subprocess_quit_callback_wrapper (guestfs_h *g,
                                  void *opaque,
                                  uint64_t event,
                                  int event_handle,
                                  int flags,
                                  const char *buf, size_t buf_len,
                                  const uint64_t *array, size_t array_len)
{
  guestfs_subprocess_quit_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_subprocess_quit_callback (guestfs_h *g,
                                      guestfs_subprocess_quit_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, subprocess_quit_callback_wrapper,
                                    GUESTFS_EVENT_SUBPROCESS_QUIT,
                                    opaque, cb);
}

static void
launch_done_callback_wrapper (guestfs_h *g,
                              void *opaque,
                              uint64_t event,
                              int event_handle,
                              int flags,
                              const char *buf, size_t buf_len,
                              const uint64_t *array, size_t array_len)
{
  guestfs_launch_done_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_launch_done_callback (guestfs_h *g,
                                  guestfs_launch_done_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, launch_done_callback_wrapper,
                                    GUESTFS_EVENT_LAUNCH_DONE,
                                    opaque, cb);
}

static void
close_callback_wrapper (guestfs_h *g,
                        void *opaque,
                        uint64_t event,
                        int event_handle,
                        int flags,
                        const char *buf, size_t buf_len,
                        const uint64_t *array, size_t array_len)
{
  guestfs_close_cb cb = g->events[event_handle].opaque2;
  cb (g, opaque);
}

void
guestfs_set_close_callback (guestfs_h *g,
                            guestfs_close_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, close_callback_wrapper,
                                    GUESTFS_EVENT_CLOSE,
                                    opaque, cb);
}

static void
progress_callback_wrapper (guestfs_h *g,
                           void *opaque,
                           uint64_t event,
                           int event_handle,
                           int flags,
                           const char *buf, size_t buf_len,
                           const uint64_t *array, size_t array_len)
{
  guestfs_progress_cb cb = g->events[event_handle].opaque2;
  assert (array_len >= 4);
  cb (g, opaque, (int) array[0], (int) array[1], array[2], array[3]);
}

void
guestfs_set_progress_callback (guestfs_h *g,
                               guestfs_progress_cb cb, void *opaque)
{
  replace_old_style_event_callback (g, progress_callback_wrapper,
                                    GUESTFS_EVENT_PROGRESS,
                                    opaque, cb);
}
