/* Copyright (C) 2018-2024 Free Software Foundation, Inc.
   Contributed by Nicolas Koenig

   This file is part of the GNU Fortran runtime library (libgfortran).

   Libgfortran 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, or (at your option)
   any later version.

   Libgfortran 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.

   Under Section 7 of GPL version 3, you are granted additional
   permissions described in the GCC Runtime Library Exception, version
   3.1, as published by the Free Software Foundation.

   You should have received a copy of the GNU General Public License and
   a copy of the GCC Runtime Library Exception along with this program;
   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
   <http://www.gnu.org/licenses/>.  */

#ifndef ASYNC_H
#define ASYNC_H

/* Async I/O will not work on targets which do not support
   __gthread_cond_t and __gthread_equal / __gthread_self.  Check
   this.  */

#if defined(__GTHREAD_HAS_COND) && defined(__GTHREADS_CXX0X)
#define ASYNC_IO 1
#else
#define ASYNC_IO 0
#endif

/* Defining DEBUG_ASYNC will enable somewhat verbose debugging
   output for async I/O.  */

#define DEBUG_ASYNC
#undef DEBUG_ASYNC

#ifdef DEBUG_ASYNC

/* Define this if you want to use ANSI color escape sequences in your
   debugging output.  */

#define DEBUG_COLOR

#ifdef DEBUG_COLOR
#define MPREFIX "\033[30;46mM:\033[0m "
#define TPREFIX "\033[37;44mT:\033[0m "
#define RPREFIX "\033[37;41mR:\033[0m "
#define DEBUG_RED "\033[31m"
#define DEBUG_ORANGE "\033[33m"
#define DEBUG_GREEN "\033[32m"
#define DEBUG_DARKRED "\033[31;2m"
#define DEBUG_PURPLE "\033[35m"
#define DEBUG_NORM "\033[0m"
#define DEBUG_REVERSE_RED "\033[41;37m"
#define DEBUG_BLUE "\033[34m"

#else

#define MPREFIX "M: "
#define TPREFIX "T: "
#define RPREFIX ""
#define DEBUG_RED ""
#define DEBUG_ORANGE ""
#define DEBUG_GREEN ""
#define DEBUG_DARKRED ""
#define DEBUG_PURPLE ""
#define DEBUG_NORM ""
#define DEBUG_REVERSE_RED ""
#define DEBUG_BLUE ""

#endif

#define DEBUG_PRINTF(...) fprintf (stderr,__VA_ARGS__)

#define IN_DEBUG_QUEUE(mutex) ({		\
      __label__ end;				\
      aio_lock_debug *curr = aio_debug_head;	\
      while (curr) {				\
	if (curr->m == mutex) {			\
	  goto end;				\
	}					\
	curr = curr->next;			\
      }						\
    end:;					\
      curr;					\
    })

#define TAIL_DEBUG_QUEUE ({			\
      aio_lock_debug *curr = aio_debug_head;	\
      while (curr && curr->next) {		\
	curr = curr->next;			\
      }						\
      curr;					\
    })

#define CHECK_LOCK(mutex, status) do {					\
    aio_lock_debug *curr;						\
    INTERN_LOCK (&debug_queue_lock);					\
    if (__gthread_mutex_trylock (mutex)) {				\
      if ((curr = IN_DEBUG_QUEUE (mutex))) {				\
	sprintf (status, DEBUG_RED "%s():%d" DEBUG_NORM, curr->func, curr->line); \
      } else								\
	sprintf (status, DEBUG_RED "unknown" DEBUG_NORM);			\
    }									\
    else {								\
      __gthread_mutex_unlock (mutex);					\
      sprintf (status, DEBUG_GREEN "unlocked" DEBUG_NORM);			\
    }									\
    INTERN_UNLOCK (&debug_queue_lock);					\
  }while (0)

#define T_ERROR(func, ...) do {				\
    int t_error_temp;					\
    t_error_temp = func(__VA_ARGS__);			\
    if (t_error_temp)					\
      ERROR (t_error_temp, "args: " #__VA_ARGS__ "\n");	\
  } while (0)

#define NOTE(str, ...) do{						\
    char note_str[200];							\
    sprintf (note_str, "%s" DEBUG_PURPLE "NOTE: " DEBUG_NORM str, aio_prefix, ##__VA_ARGS__); \
    DEBUG_PRINTF ("%-90s %20s():%-5d\n", note_str, __FUNCTION__, __LINE__); \
  }while (0);

#define ERROR(errnum, str, ...) do{					\
    char note_str[200];							\
    sprintf (note_str, "%s" DEBUG_REVERSE_RED "ERROR:" DEBUG_NORM " [%d] " str, aio_prefix, \
	    errnum, ##__VA_ARGS__);					\
    DEBUG_PRINTF ("%-68s %s():%-5d\n", note_str, __FUNCTION__, __LINE__);	\
  }while (0)

#define MUTEX_DEBUG_ADD(mutex) do {		\
    aio_lock_debug *n;				\
    n = malloc (sizeof(aio_lock_debug));	\
    n->prev = TAIL_DEBUG_QUEUE;			\
    if (n->prev)				\
      n->prev->next = n;			\
    n->next = NULL;				\
    n->line = __LINE__;				\
    n->func = __FUNCTION__;			\
    n->m = mutex;				\
    if (!aio_debug_head) {			\
      aio_debug_head = n;			\
    }						\
  } while (0)

#define UNLOCK(mutex) do {						\
    aio_lock_debug *curr;						\
    DEBUG_PRINTF ("%s%-75s %20s():%-5d %18p\n", aio_prefix, DEBUG_GREEN "UNLOCK: " DEBUG_NORM #mutex, \
		 __FUNCTION__, __LINE__, (void *) mutex);		\
    INTERN_LOCK (&debug_queue_lock);					\
    curr = IN_DEBUG_QUEUE (mutex);					\
    if (curr)								\
      {									\
	if (curr->prev)							\
	  curr->prev->next = curr->next;				\
	if (curr->next) {						\
	  curr->next->prev = curr->prev;				\
	  if (curr == aio_debug_head)					\
	    aio_debug_head = curr->next;				\
	} else {							\
	  if (curr == aio_debug_head)					\
	    aio_debug_head = NULL;					\
	}								\
	free (curr);							\
      }									\
    INTERN_UNLOCK (&debug_queue_lock);					\
    INTERN_UNLOCK (mutex);						\
  }while (0)

#define TRYLOCK(mutex) ({						\
			 char status[200];				\
			 int res;					\
			 aio_lock_debug *curr;				\
			 res = __gthread_mutex_trylock (mutex);		\
			 INTERN_LOCK (&debug_queue_lock);		\
			 if (res) {					\
			   if ((curr = IN_DEBUG_QUEUE (mutex))) {	\
			     sprintf (status, DEBUG_RED "%s():%d" DEBUG_NORM, curr->func, curr->line);	\
			   } else					\
			     sprintf (status, DEBUG_RED "unknown" DEBUG_NORM);	\
			 }						\
			 else {						\
			   sprintf (status, DEBUG_GREEN "unlocked" DEBUG_NORM);	\
			   MUTEX_DEBUG_ADD (mutex);			\
			 }						\
			 DEBUG_PRINTF ("%s%-44s prev: %-35s %20s():%-5d %18p\n", aio_prefix, \
				      DEBUG_DARKRED "TRYLOCK: " DEBUG_NORM #mutex, status, __FUNCTION__, __LINE__, \
				      (void *) mutex);			\
			 INTERN_UNLOCK (&debug_queue_lock);		\
			 res;						\
    })

#define LOCK(mutex) do {						\
    char status[200];							\
    CHECK_LOCK (mutex, status);						\
    DEBUG_PRINTF ("%s%-42s prev: %-35s %20s():%-5d %18p\n", aio_prefix,	\
		 DEBUG_RED "LOCK: " DEBUG_NORM #mutex, status, __FUNCTION__, __LINE__, (void *) mutex); \
    INTERN_LOCK (mutex);							\
    INTERN_LOCK (&debug_queue_lock);					\
    MUTEX_DEBUG_ADD (mutex);						\
    INTERN_UNLOCK (&debug_queue_lock);					\
    DEBUG_PRINTF ("%s" DEBUG_RED "ACQ:" DEBUG_NORM " %-30s %78p\n", aio_prefix, #mutex, mutex); \
  } while (0)

#ifdef __GTHREAD_RWLOCK_INIT
#define RWLOCK_DEBUG_ADD(rwlock) do {		\
    aio_rwlock_debug *n;				\
    n = xmalloc (sizeof (aio_rwlock_debug));	\
    n->prev = TAIL_RWLOCK_DEBUG_QUEUE;			\
    if (n->prev)				\
      n->prev->next = n;			\
    n->next = NULL;				\
    n->line = __LINE__;				\
    n->func = __FUNCTION__;			\
    n->rw = rwlock;				\
    if (!aio_rwlock_debug_head) {			\
      aio_rwlock_debug_head = n;			\
    }						\
  } while (0)

#define CHECK_RDLOCK(rwlock, status) do {					\
    aio_rwlock_debug *curr;						\
    INTERN_WRLOCK (&debug_queue_rwlock);					\
    if (__gthread_rwlock_tryrdlock (rwlock)) {				\
      if ((curr = IN_RWLOCK_DEBUG_QUEUE (rwlock))) {				\
	sprintf (status, DEBUG_RED "%s():%d" DEBUG_NORM, curr->func, curr->line); \
      } else								\
	sprintf (status, DEBUG_RED "unknown" DEBUG_NORM);			\
    }									\
    else {								\
      __gthread_rwlock_unlock (rwlock);					\
      sprintf (status, DEBUG_GREEN "rwunlocked" DEBUG_NORM);			\
    }									\
    INTERN_RWUNLOCK (&debug_queue_rwlock);					\
  }while (0)

#define CHECK_WRLOCK(rwlock, status) do {					\
    aio_rwlock_debug *curr;						\
    INTERN_WRLOCK (&debug_queue_rwlock);					\
    if (__gthread_rwlock_trywrlock (rwlock)) {				\
      if ((curr = IN_RWLOCK_DEBUG_QUEUE (rwlock))) {				\
	sprintf (status, DEBUG_RED "%s():%d" DEBUG_NORM, curr->func, curr->line); \
      } else								\
	sprintf (status, DEBUG_RED "unknown" DEBUG_NORM);			\
    }									\
    else {								\
      __gthread_rwlock_unlock (rwlock);					\
      sprintf (status, DEBUG_GREEN "rwunlocked" DEBUG_NORM);			\
    }									\
    INTERN_RWUNLOCK (&debug_queue_rwlock);					\
  }while (0)

#define TAIL_RWLOCK_DEBUG_QUEUE ({			\
      aio_rwlock_debug *curr = aio_rwlock_debug_head;	\
      while (curr && curr->next) {		\
	curr = curr->next;			\
      }						\
      curr;					\
    })

#define IN_RWLOCK_DEBUG_QUEUE(rwlock) ({		\
      __label__ end;				\
      aio_rwlock_debug *curr = aio_rwlock_debug_head;	\
      while (curr) {				\
	if (curr->rw == rwlock) {			\
	  goto end;				\
	}					\
	curr = curr->next;			\
      }						\
    end:;					\
      curr;					\
    })

#define RDLOCK(rwlock) do {						\
    char status[200];							\
    CHECK_RDLOCK (rwlock, status);						\
    DEBUG_PRINTF ("%s%-42s prev: %-35s %20s():%-5d %18p\n", aio_prefix,	\
		 DEBUG_RED "RDLOCK: " DEBUG_NORM #rwlock, status, __FUNCTION__, __LINE__, (void *) rwlock); \
    INTERN_RDLOCK (rwlock);							\
    INTERN_WRLOCK (&debug_queue_rwlock);					\
    RWLOCK_DEBUG_ADD (rwlock);						\
    INTERN_RWUNLOCK (&debug_queue_rwlock);					\
    DEBUG_PRINTF ("%s" DEBUG_RED "ACQ:" DEBUG_NORM " %-30s %78p\n", aio_prefix, #rwlock, rwlock); \
  } while (0)

#define WRLOCK(rwlock) do {						\
    char status[200];							\
    CHECK_WRLOCK (rwlock, status);						\
    DEBUG_PRINTF ("%s%-42s prev: %-35s %20s():%-5d %18p\n", aio_prefix,	\
		 DEBUG_RED "WRLOCK: " DEBUG_NORM #rwlock, status, __FUNCTION__, __LINE__, (void *) rwlock); \
    INTERN_WRLOCK (rwlock);							\
    INTERN_WRLOCK (&debug_queue_rwlock);					\
    RWLOCK_DEBUG_ADD (rwlock);						\
    INTERN_RWUNLOCK (&debug_queue_rwlock);					\
    DEBUG_PRINTF ("%s" DEBUG_RED "ACQ:" DEBUG_NORM " %-30s %78p\n", aio_prefix, #rwlock, rwlock); \
  } while (0)

#define RWUNLOCK(rwlock) do {						\
    aio_rwlock_debug *curr;						\
    DEBUG_PRINTF ("%s%-75s %20s():%-5d %18p\n", aio_prefix, DEBUG_GREEN "RWUNLOCK: " DEBUG_NORM #rwlock, \
		 __FUNCTION__, __LINE__, (void *) rwlock);		\
    INTERN_WRLOCK (&debug_queue_rwlock);					\
    curr = IN_RWLOCK_DEBUG_QUEUE (rwlock);					\
    if (curr)								\
      {									\
	if (curr->prev)							\
	  curr->prev->next = curr->next;				\
	if (curr->next) {						\
	  curr->next->prev = curr->prev;				\
	  if (curr == aio_rwlock_debug_head)					\
	    aio_rwlock_debug_head = curr->next;				\
	} else {							\
	  if (curr == aio_rwlock_debug_head)					\
	    aio_rwlock_debug_head = NULL;					\
	}								\
	free (curr);							\
      }									\
    INTERN_RWUNLOCK (&debug_queue_rwlock);					\
    INTERN_RWUNLOCK (rwlock);						\
  } while (0)

#define RD_TO_WRLOCK(rwlock)	\
  RWUNLOCK (rwlock);	\
  WRLOCK (rwlock);
#endif

#define DEBUG_LINE(...) __VA_ARGS__

#else
#define DEBUG_PRINTF(...) {}
#define CHECK_LOCK(au, mutex, status) {}
#define NOTE(str, ...) {}
#define DEBUG_LINE(...)
#define T_ERROR(func, ...) func(__VA_ARGS__)
#define LOCK(mutex) INTERN_LOCK (mutex)
#define UNLOCK(mutex) INTERN_UNLOCK (mutex)
#define TRYLOCK(mutex) (__gthread_mutex_trylock (mutex))
#ifdef __GTHREAD_RWLOCK_INIT
#define RDLOCK(rwlock) INTERN_RDLOCK (rwlock)
#define WRLOCK(rwlock) INTERN_WRLOCK (rwlock)
#define RWUNLOCK(rwlock) INTERN_RWUNLOCK (rwlock)
#define RD_TO_WRLOCK(rwlock)	\
  RWUNLOCK (rwlock);	\
  WRLOCK (rwlock);
#endif
#endif

#ifndef __GTHREAD_RWLOCK_INIT
#define RDLOCK(rwlock) LOCK (rwlock)
#define WRLOCK(rwlock) LOCK (rwlock)
#define RWUNLOCK(rwlock) UNLOCK (rwlock)
#define RD_TO_WRLOCK(rwlock) do {} while (0)
#endif

#define INTERN_LOCK(mutex) T_ERROR (__gthread_mutex_lock, mutex);

#define INTERN_UNLOCK(mutex) T_ERROR (__gthread_mutex_unlock, mutex);

#define INTERN_RDLOCK(rwlock) T_ERROR (__gthread_rwlock_rdlock, rwlock)
#define INTERN_WRLOCK(rwlock) T_ERROR (__gthread_rwlock_wrlock, rwlock)
#define INTERN_RWUNLOCK(rwlock) T_ERROR (__gthread_rwlock_unlock, rwlock)

#if ASYNC_IO

/* au->lock has to be held when calling this macro.  */

#define SIGNAL(advcond) do{						\
    (advcond)->pending = 1;						\
    DEBUG_PRINTF ("%s%-75s %20s():%-5d %18p\n", aio_prefix, DEBUG_ORANGE "SIGNAL: " DEBUG_NORM \
		 #advcond, __FUNCTION__, __LINE__, (void *) advcond);	\
    T_ERROR (__gthread_cond_broadcast, &(advcond)->signal);			\
  } while (0)

/* Has to be entered with mutex locked.  */

#define WAIT_SIGNAL_MUTEX(advcond, condition, mutex) do{		\
    __label__ finish;		       					\
    DEBUG_PRINTF ("%s%-75s %20s():%-5d %18p\n", aio_prefix, DEBUG_BLUE "WAITING: " DEBUG_NORM \
		 #advcond, __FUNCTION__, __LINE__, (void *) advcond);	\
    if ((advcond)->pending || (condition))				\
      goto finish;							\
    while (1)								\
      {									\
	int err_ret = __gthread_cond_wait(&(advcond)->signal, mutex);	\
	if (err_ret) internal_error (NULL, "WAIT_SIGNAL_MUTEX failed");	\
	if (condition)							\
	  {								\
	    DEBUG_PRINTF ("%s%-75s %20s():%-5d %18p\n", aio_prefix, DEBUG_ORANGE \
			  "REC: " DEBUG_NORM				\
			  #advcond,  __FUNCTION__, __LINE__, (void *)advcond); \
	    break;				      			\
	  }								\
      }									\
  finish:								\
    (advcond)->pending = 0;						\
    UNLOCK (mutex);							\
  } while (0)

/* au->lock has to be held when calling this macro.  */

#define REVOKE_SIGNAL(advcond) do{		\
    (advcond)->pending = 0;			\
  } while (0)

#else

#define SIGNAL(advcond) do{} while(0)
#define WAIT_SIGNAL_MUTEX(advcond, condition, mutex) do{} while(0)
#define REVOKE_SIGNAL(advcond) do{} while(0)

#endif

#if ASYNC_IO
DEBUG_LINE (extern __thread const char *aio_prefix);

DEBUG_LINE (typedef struct aio_lock_debug{
  __gthread_mutex_t *m;
  int line;
  const char *func;
  struct aio_lock_debug *next;
  struct aio_lock_debug *prev;
} aio_lock_debug;)

DEBUG_LINE (typedef struct aio_rwlock_debug{
  __gthread_rwlock_t *rw;
  int line;
  const char *func;
  struct aio_rwlock_debug *next;
  struct aio_rwlock_debug *prev;
} aio_rwlock_debug;)

DEBUG_LINE (extern aio_lock_debug *aio_debug_head;)
DEBUG_LINE (extern __gthread_mutex_t debug_queue_lock;)
DEBUG_LINE (extern aio_rwlock_debug *aio_rwlock_debug_head;)
DEBUG_LINE (extern __gthread_rwlock_t debug_queue_rwlock;)

/* Thread - local storage of the current unit we are looking at. Needed for
   error reporting.  */

extern __thread gfc_unit *thread_unit;
#endif

enum aio_do {
  AIO_INVALID = 0,
  AIO_DATA_TRANSFER_INIT,
  AIO_TRANSFER_SCALAR,
  AIO_TRANSFER_ARRAY,
  AIO_WRITE_DONE,
  AIO_READ_DONE,
  AIO_CLOSE
};

typedef union transfer_args
{
  struct
  {
    void (*transfer) (struct st_parameter_dt *, bt, void *, int, size_t, size_t);
    bt arg_bt;
    void *data;
    int i;
    size_t s1;
    size_t s2;
  } scalar;
  struct
  {
    gfc_array_char *desc;
    int kind;
    gfc_charlen_type charlen;
  } array;
} transfer_args;

struct adv_cond
{
#if ASYNC_IO
  int pending;
  __gthread_cond_t signal;
#endif
};

typedef struct async_unit
{
  __gthread_mutex_t io_lock;   /* Lock for doing actual I/O. */
  __gthread_mutex_t lock;      /* Lock for manipulating the queue structure.  */
  bool empty;
  struct
  {
    int waiting;
    int low;
    int high;
    struct adv_cond done;
  } id;

#if ASYNC_IO
  struct adv_cond work;
  struct adv_cond emptysignal;
  struct st_parameter_dt *pdt;
  __gthread_t thread;
  struct transfer_queue *head;
  struct transfer_queue *tail;

  struct {
    const char *message;
    st_parameter_common *cmp;
    bool has_error;
    int last_good_id;
    int family;
    bool fatal_error;
  } error;
#endif
} async_unit;

void init_async_unit (gfc_unit *);
internal_proto (init_async_unit);

bool async_wait (st_parameter_common *, async_unit *);
internal_proto (async_wait);

bool async_wait_id (st_parameter_common *, async_unit *, int);
internal_proto (async_wait_id);

bool collect_async_errors (st_parameter_common *, async_unit *);
internal_proto (collect_async_errors); 

void async_close (async_unit *);
internal_proto (async_close);

void enqueue_transfer (async_unit * au, transfer_args * arg, enum aio_do);
internal_proto (enqueue_transfer);

void enqueue_done (async_unit *, enum aio_do type);
internal_proto (enqueue_done);

int enqueue_done_id (async_unit *, enum aio_do type);
internal_proto (enqueue_done_id);

void enqueue_init (async_unit *);
internal_proto (enqueue_init);

void enqueue_data_transfer_init (async_unit *, st_parameter_dt *, int);
internal_proto (enqueue_data_transfer_init);

void enqueue_close (async_unit *);
internal_proto (enqueue_close);

#endif
