/* Copyright (C) 2025-2026 Free Software Foundation, Inc.
   Contributed by Thomas Koenig, Nicolas Koenig, Andre Vehreschild

This file is part of the GNU Fortran Shmem Coarray Library (caf_shmem).

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

Caf_shmem 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/>.  */

#include "libgfortran.h"
#include "supervisor.h"
#include "sync.h"
#include "teams_mgmt.h"
#include "thread_support.h"

#include <string.h>

static inline void
lock_table (sync_t *si)
{
  caf_shmem_mutex_lock (&si->cis->sync_images_table_lock);
}

static inline void
unlock_table (sync_t *si)
{
  caf_shmem_mutex_unlock (&si->cis->sync_images_table_lock);
}

void
sync_init (sync_t *si, shared_memory sm)
{
  *si = (sync_t) {
    &this_image.supervisor->sync_shared,
    SHMPTR_AS (int *, this_image.supervisor->sync_shared.sync_images_table, sm),
    SHMPTR_AS (caf_shmem_condvar *,
	       this_image.supervisor->sync_shared.sync_images_cond_vars, sm)};
}

void
sync_init_supervisor (sync_t *si, alloc *ai)
{
  const int num_images = local->total_num_images;
  const size_t table_size_in_bytes = sizeof (int) * num_images * num_images;

  si->cis = &this_image.supervisor->sync_shared;

  initialize_shared_mutex (&si->cis->event_lock);
  initialize_shared_condition (&si->cis->event_cond, num_images);

  initialize_shared_mutex (&si->cis->sync_images_table_lock);

  si->cis->sync_images_table
    = allocator_shared_malloc (alloc_get_allocator (ai), table_size_in_bytes);
  si->cis->sync_images_cond_vars
    = allocator_shared_malloc (alloc_get_allocator (ai),
			       sizeof (caf_shmem_condvar) * num_images);

  si->table = SHMPTR_AS (int *, si->cis->sync_images_table, ai->mem);
  si->triggers
    = SHMPTR_AS (caf_shmem_condvar *, si->cis->sync_images_cond_vars, ai->mem);

  for (int i = 0; i < num_images; i++)
    initialize_shared_condition (&si->triggers[i], num_images);

  memset (si->table, 0, table_size_in_bytes);
}

void
sync_table (sync_t *si, int *images, int size)
{
  /* The variable `table` is an N x N matrix, where N is the number of all
     images.  The position (i, j) (where i and j are always the real images
     index, i.e. after team de-mapping) tells whether image i has seen the same
     number of synchronisation calls to sync_table like j.  When table(i,j) ==
     table(j,i) then the sync for i with this image is completed (here j is the
     real image index of the current image).  When this holds for all i in the
     current set of images (or all images, if the set is empty), then sync table
     command is completed.
     */
  volatile int *table = si->table;
  int i;

  lock_table (si);
  if (size > 0)
    {
      const size_t img_c = caf_current_team->u.image_info->image_map_size;
      for (i = 0; i < size; ++i)
	{
	  ++table[images[i] + img_c * this_image.image_num];
	  caf_shmem_cond_signal (&si->triggers[images[i]]);
	}
      for (;;)
	{
	  for (i = 0; i < size; ++i)
	    if (this_image.supervisor->images[images[i]].status == IMAGE_OK
		&& table[images[i] + this_image.image_num * img_c]
		     > table[this_image.image_num + images[i] * img_c])
	      break;
	  if (i == size)
	    break;
	  caf_shmem_cond_wait (&si->triggers[this_image.image_num],
			     &si->cis->sync_images_table_lock);
	}
    }
  else
    {
      int *map = caf_current_team->u.image_info->image_map;
      size = caf_current_team->u.image_info->image_count.count;
      for (i = 0; i < size; ++i)
	{
	  if (this_image.supervisor->images[map[i]].status != IMAGE_OK)
	    continue;
	  ++table[map[i] + size * this_image.image_num];
	  caf_shmem_cond_signal (&si->triggers[map[i]]);
	}
      for (;;)
	{
	  for (i = 0; i < size; ++i)
	    if (this_image.supervisor->images[map[i]].status == IMAGE_OK
		&& table[map[i] + size * this_image.image_num]
		     > table[this_image.image_num + map[i] * size])
	      break;
	  if (i == size)
	    break;
	  caf_shmem_cond_wait (&si->triggers[this_image.image_num],
			     &si->cis->sync_images_table_lock);
	}
    }
  unlock_table (si);
}

void
sync_all (void)
{
  counter_barrier_wait (&caf_current_team->u.image_info->image_count);
}

void
sync_team (caf_shmem_team_t team)
{
  counter_barrier_wait (&team->u.image_info->image_count);
}

void
lock_event (sync_t *si)
{
  caf_shmem_mutex_lock (&si->cis->event_lock);
}

void
unlock_event (sync_t *si)
{
  caf_shmem_mutex_unlock (&si->cis->event_lock);
}

void
event_post (sync_t *si)
{
  caf_shmem_cond_broadcast (&si->cis->event_cond);
}

void
event_wait (sync_t *si)
{
  caf_shmem_cond_wait (&si->cis->event_cond, &si->cis->event_lock);
}
