/* dex-posix-aio-future.c
 *
 * Copyright 2022 Christian Hergert <christian@sourceandstack.com>
 *
 * 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.1 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 General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include <errno.h>
#include <fcntl.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <gio/gio.h>

#include "dex-fd-private.h"
#include "dex-future-private.h"
#include "dex-posix-aio-backend-private.h"
#include "dex-posix-aio-future-private.h"

typedef enum _DexPosixAioFutureKind
{
  DEX_POSIX_AIO_FUTURE_CLOSE = 1,
  DEX_POSIX_AIO_FUTURE_OPEN,
  DEX_POSIX_AIO_FUTURE_READ,
  DEX_POSIX_AIO_FUTURE_WRITE,
} DexPosixAioFutureKind;

struct _DexPosixAioFuture
{
  DexFuture              parent_instance;
  GMainContext          *main_context;
  DexPosixAioContext    *aio_context;
  DexPosixAioFutureKind  kind;
  int                    errsv;
  union {
    struct {
      DexFD             *fd;
      int                res;
    } close;
    struct {
      char              *path;
      int                flags;
      int                mode;
      int                res;
    } open;
    struct {
      int                fd;
      gpointer           buffer;
      gsize              count;
      goffset            offset;
      gssize             res;
    } read;
    struct {
      int                fd;
      gconstpointer      buffer;
      gsize              count;
      goffset            offset;
      gssize             res;
    } write;
  };
};

typedef struct _DexPosixAioFutureClass
{
  DexFutureClass parent_class;
} DexPosixAioFutureClass;

DEX_DEFINE_FINAL_TYPE (DexPosixAioFuture, dex_posix_aio_future, DEX_TYPE_FUTURE)

#undef DEX_TYPE_POSIX_AIO_FUTURE
#define DEX_TYPE_POSIX_AIO_FUTURE dex_posix_aio_future_type

static void
dex_posix_aio_future_finalize (DexObject *object)
{
  DexPosixAioFuture *posix_aio_future = DEX_POSIX_AIO_FUTURE (object);

  if (posix_aio_future->kind == DEX_POSIX_AIO_FUTURE_CLOSE)
    g_clear_pointer (&posix_aio_future->close.fd, dex_fd_free);
  else if (posix_aio_future->kind == DEX_POSIX_AIO_FUTURE_OPEN)
    g_clear_pointer (&posix_aio_future->open.path, g_free);

  g_clear_pointer ((GSource **)&posix_aio_future->aio_context, g_source_unref);
  g_clear_pointer (&posix_aio_future->main_context, g_main_context_unref);

  DEX_OBJECT_CLASS (dex_posix_aio_future_parent_class)->finalize (object);
}

static void
dex_posix_aio_future_class_init (DexPosixAioFutureClass *posix_aio_future_class)
{
  DexObjectClass *object_class = DEX_OBJECT_CLASS (posix_aio_future_class);

  object_class->finalize = dex_posix_aio_future_finalize;
}

static void
dex_posix_aio_future_init (DexPosixAioFuture *posix_aio_future)
{
}

static DexPosixAioFuture *
dex_posix_aio_future_new (DexPosixAioFutureKind  kind,
                          DexPosixAioContext    *aio_context)
{
  DexPosixAioFuture *posix_aio_future;
  GMainContext *main_context;

  if ((main_context = g_source_get_context ((GSource *)aio_context)))
    g_main_context_ref (main_context);

  posix_aio_future = (DexPosixAioFuture *)dex_object_create_instance (DEX_TYPE_POSIX_AIO_FUTURE);
  posix_aio_future->kind = kind;
  posix_aio_future->aio_context = (DexPosixAioContext *)g_source_ref ((GSource *)aio_context);
  posix_aio_future->main_context = main_context;

  return posix_aio_future;
}

DexPosixAioFuture *
dex_posix_aio_future_new_close (DexPosixAioContext *posix_aio_context,
                                int                 fd)
{
  DexPosixAioFuture *posix_aio_future;

  posix_aio_future = dex_posix_aio_future_new (DEX_POSIX_AIO_FUTURE_CLOSE, posix_aio_context);
  posix_aio_future->close.fd = g_memdup2 (&fd, sizeof fd);
  posix_aio_future->close.res = -1;

  return posix_aio_future;
}

DexPosixAioFuture *
dex_posix_aio_future_new_open (DexPosixAioContext *posix_aio_context,
                               const char          *path,
                               int                  flags,
                               int                  mode)
{
  DexPosixAioFuture *posix_aio_future;

  posix_aio_future = dex_posix_aio_future_new (DEX_POSIX_AIO_FUTURE_OPEN, posix_aio_context);
  posix_aio_future->open.path = g_strdup (path);
  posix_aio_future->open.flags = flags;
  posix_aio_future->open.mode = mode;
  posix_aio_future->open.res = -1;

  return posix_aio_future;
}

DexPosixAioFuture *
dex_posix_aio_future_new_read (DexPosixAioContext *posix_aio_context,
                               int                 fd,
                               gpointer            buffer,
                               gsize               count,
                               goffset             offset)
{
  DexPosixAioFuture *posix_aio_future;

  posix_aio_future = dex_posix_aio_future_new (DEX_POSIX_AIO_FUTURE_READ, posix_aio_context);
  posix_aio_future->read.fd = fd;
  posix_aio_future->read.buffer = buffer;
  posix_aio_future->read.count = count;
  posix_aio_future->read.offset = offset;
  posix_aio_future->read.res = -1;

  return posix_aio_future;
}

DexPosixAioFuture *
dex_posix_aio_future_new_write (DexPosixAioContext *posix_aio_context,
                                int                 fd,
                                gconstpointer       buffer,
                                gsize               count,
                                goffset             offset)
{
  DexPosixAioFuture *posix_aio_future;

  posix_aio_future = dex_posix_aio_future_new (DEX_POSIX_AIO_FUTURE_WRITE, posix_aio_context);
  posix_aio_future->write.fd = fd;
  posix_aio_future->write.buffer = buffer;
  posix_aio_future->write.count = count;
  posix_aio_future->write.offset = offset;
  posix_aio_future->write.res = -1;

  return posix_aio_future;
}

void
dex_posix_aio_future_run (DexPosixAioFuture *posix_aio_future)
{
  g_return_if_fail (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future));

  errno = 0;

  switch (posix_aio_future->kind)
    {
    case DEX_POSIX_AIO_FUTURE_CLOSE:
      posix_aio_future->close.res =
        close (dex_fd_steal (posix_aio_future->close.fd));

      /* Silence unused-result */
      (void)posix_aio_future->close.res;
      break;

    case DEX_POSIX_AIO_FUTURE_OPEN:
      posix_aio_future->open.res =
        open (posix_aio_future->open.path,
              posix_aio_future->open.flags,
              posix_aio_future->open.mode);

      /* Silence unused-result */
      (void)posix_aio_future->open.res;
      break;

    case DEX_POSIX_AIO_FUTURE_READ:
      if (posix_aio_future->read.offset >= 0)
        posix_aio_future->read.res =
          pread (posix_aio_future->read.fd,
                 posix_aio_future->read.buffer,
                 posix_aio_future->read.count,
                 posix_aio_future->read.offset);
      else
        posix_aio_future->read.res =
          read (posix_aio_future->read.fd,
                posix_aio_future->read.buffer,
                posix_aio_future->read.count);

      /* Silence unused-result */
      (void)posix_aio_future->read.res;
      break;

    case DEX_POSIX_AIO_FUTURE_WRITE:
      if (posix_aio_future->write.offset >= 0)
        posix_aio_future->write.res =
          pwrite (posix_aio_future->write.fd,
                  posix_aio_future->write.buffer,
                  posix_aio_future->write.count,
                  posix_aio_future->write.offset);
      else
        posix_aio_future->write.res =
          write (posix_aio_future->write.fd,
                 posix_aio_future->write.buffer,
                 posix_aio_future->write.count);

      /* Silence unused-result */
      (void)posix_aio_future->write.res;
      break;

    default:
      g_assert_not_reached ();
    }

  posix_aio_future->errsv = errno;
}

static void
dex_posix_aio_future_complete_boolean (DexPosixAioFuture *posix_aio_future,
                                       int                res)
{
  g_assert (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future));

  if (res < 0)
    dex_future_complete (DEX_FUTURE (posix_aio_future),
                         NULL,
                         g_error_new_literal (G_IO_ERROR,
                                              g_io_error_from_errno (posix_aio_future->errsv),
                                              g_strerror (posix_aio_future->errsv)));
  else
    dex_future_complete (DEX_FUTURE (posix_aio_future),
                         &(GValue) { G_TYPE_BOOLEAN, {{.v_int = TRUE}}},
                         NULL);
}

static void
dex_posix_aio_future_complete_fd (DexPosixAioFuture *posix_aio_future,
                                  int                res)
{
  g_assert (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future));

  if (res < 0)
    dex_future_complete (DEX_FUTURE (posix_aio_future),
                         NULL,
                         g_error_new_literal (G_IO_ERROR,
                                              g_io_error_from_errno (posix_aio_future->errsv),
                                              g_strerror (posix_aio_future->errsv)));
  else
    {
      GValue value = G_VALUE_INIT;

      g_value_init (&value, DEX_TYPE_FD);
      g_value_take_boxed (&value, g_memdup2 (&res, sizeof res));
      dex_future_complete_steal (DEX_FUTURE (posix_aio_future), &value, NULL);
    }
}

static void
dex_posix_aio_future_complete_int64 (DexPosixAioFuture *posix_aio_future,
                                     gint64             res)
{
  g_assert (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future));

  if (res < 0)
    dex_future_complete (DEX_FUTURE (posix_aio_future),
                         NULL,
                         g_error_new_literal (G_IO_ERROR,
                                              g_io_error_from_errno (posix_aio_future->errsv),
                                              g_strerror (posix_aio_future->errsv)));
  else
    dex_future_complete (DEX_FUTURE (posix_aio_future),
                         &(GValue) { G_TYPE_INT64, {{.v_int64 = res}}},
                         NULL);
}

void
dex_posix_aio_future_complete (DexPosixAioFuture *posix_aio_future)
{
  g_return_if_fail (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future));

  switch (posix_aio_future->kind)
    {
    case DEX_POSIX_AIO_FUTURE_CLOSE:
      dex_posix_aio_future_complete_boolean (posix_aio_future,
                                             posix_aio_future->close.res);
      break;

    case DEX_POSIX_AIO_FUTURE_OPEN:
      dex_posix_aio_future_complete_fd (posix_aio_future, posix_aio_future->open.res);
      break;

    case DEX_POSIX_AIO_FUTURE_READ:
      dex_posix_aio_future_complete_int64 (posix_aio_future, posix_aio_future->read.res);
      break;

    case DEX_POSIX_AIO_FUTURE_WRITE:
      dex_posix_aio_future_complete_int64 (posix_aio_future, posix_aio_future->write.res);
      break;

    default:
      g_assert_not_reached ();
    }
}

DexPosixAioContext *
dex_posix_aio_future_get_aio_context (DexPosixAioFuture *posix_aio_future)
{
  g_return_val_if_fail (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future), NULL);

  return posix_aio_future->aio_context;
}

GMainContext *
dex_posix_aio_future_get_main_context (DexPosixAioFuture *posix_aio_future)
{
  g_return_val_if_fail (DEX_IS_POSIX_AIO_FUTURE (posix_aio_future), NULL);

  return posix_aio_future->main_context;
}
