{% block header %}
# cimport C libraries
from libc.stdlib cimport atoi, exit
from libc.stdio cimport printf, snprintf, fflush, stdout
from libc.string cimport strcpy, memset, memcpy
from libc.signal cimport SIGINT, SIGUSR1, SIGALRM, SIGBUS, SIGSEGV, SIGQUIT
from libc.stdint cimport int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t
from posix.signal cimport kill, sigaction, sigaction_t, sigset_t, sigemptyset, sigaddset, sigfillset
from posix.unistd cimport getppid, pause, close, getpid
from posix.mman cimport shm_open, mmap, munmap, PROT_READ, PROT_WRITE, MAP_SHARED, mlockall, MCL_CURRENT, MCL_FUTURE, munlockall, shm_unlink
from posix.types cimport pid_t
cimport posix.stat 
from posix.fcntl cimport O_RDWR, O_CREAT, O_TRUNC
from libcpp cimport bool
from posix.time cimport clock_gettime, CLOCK_MONOTONIC_RAW, timespec, nanosleep

import sys, signal
import SharedArray as sa
from cpython.exc cimport PyErr_CheckSignals
import random as random
import math
import numpy as np
import posix_ipc
import time

# import LiCoRICE utils
from module_utils import create_shared_array

cdef extern from "semaphore.h":
  enum: __SIZEOF_SEM_T
  cdef union sem_union:
    char __size[__SIZEOF_SEM_T]
    long int __align
  ctypedef sem_union sem_t
  sem_t *sem_open(const char *name, int oflag)
  int sem_wait(sem_t *)
  int sem_post(sem_t *)
  int sem_close(sem_t *sem)
  int sem_getvalue(sem_t *sem, int *val)
#not used yet, just seeing if it would work
cdef extern from "unistd.h":
  int getpagesize()
cdef extern from "stdatomic.h":
  enum memory_order:
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
  void atomic_thread_fence(memory_order)

cdef extern from "utilityFunctions.h":
  void init_utils(void (*pHandleExit)(int exitStatus), sigset_t *pExitMask)
  void die(char *errorStr)
  void open_shared_mem(uint8_t **ppmem, const char *pName, int numBytes, int shm_flags, int mmap_flags)
  void set_sighandler(int signum, void *psh, sigset_t *block_mask)
  void make_realtime()

import numpy as np
cimport numpy as np
cimport cython

cdef extern from "constants.h":
  enum: NUM_PAGES_IN_GB
  enum: MAX_PATH_LEN
  enum: NUM_CHILDREN
  enum: BYTES_IN_GB
  cdef const char *SMEM0_PATHNAME
  enum: PAGESIZE
  enum: LATENCY
  enum: NUM_NON_SOURCES
  enum: NUM_SEM_SIGS
  enum: NUM_INTERNAL_SIGS
  size_t ROUND_UP(int X, int Y)
  enum: BUF_VARS_LEN
  enum: SHM_SIZE
  enum: NUM_TICKS_OFFSET
  enum: BUF_VARS_OFFSET
  enum: SEM_NAME_LEN

cdef pid_t pid
cdef pid_t ppid
cdef char pathName[MAX_PATH_LEN]
cdef public sigset_t exitMask

# shared memory vars
cdef size_t shm_size
cdef uint8_t *pmem
cdef int64_t *pNumTicks
cdef sem_t *pTickUpSem
cdef sem_t *pTickDownSem
cdef sem_t *pSigSems[NUM_SEM_SIGS]
cdef char semNameBuf[SEM_NAME_LEN]
cdef uint32_t *pBufVars
{%- for sig, dtype in out_sig_types.items() %}
cdef uint32_t *{{sig}}BufVars
{% endfor %}
{%- for sig, dtype in in_sig_types.items() %}
cdef uint32_t *{{sig}}BufVars
{% endfor %}

{% endblock %}

{% block handlers %}

cdef void handle_exit(int exitStatus):
  global pmem, shm_size, pTickUpSem, pTickDownSem, pSigSems
  {{destruct_code}}
{%- if platform_system == "Linux" %}
  sa_names = [sig.name for sig in sa.list()]
  {%- for sig in (out_signals.keys()) %}
  if b"{{sig}}" in sa_names:
    sa.delete("shm://{{sig}}")
  else:
    printf("Could not delete shared array {{sig}}.\n")
  {%- endfor %}
{%- else %}
  {%- for sig in (out_signals.keys()) %}
  sa.delete("shm://{{sig}}")
  {%- endfor %}
{%- endif %}
  munmap(pmem, shm_size)
  munlockall()

  if (sem_close(pTickUpSem) == -1):
    printf("Could not close source up semaphore. \n")
  if (sem_close(pTickDownSem) == -1):
    printf("Could not close source down semaphore. \n")
{%- for sig_sem in sig_sems %}
  if (sem_close(pSigSems[{{sig_sem[1]}}]) == -1):
    printf("Could not close signal semaphore. \n")
{%- endfor %}

  exit(exitStatus)

cdef void int_handler(int signum):
  pass

cdef void exit_handler(int signum):
  printf("EXIT HANDLER for {{name}}\n")
  handle_exit(0)

cdef void mem_error_handler(int signum):
  printf("Child memory error: signum %d\n", signum)
  printf("Child ID: %d\n", {{module_num}})
  handle_exit(1)

{%- endblock %}

{% block setup -%}

# runtime code
# intialize utilityFunctions
sigfillset(&exitMask)
init_utils(&handle_exit, &exitMask)
ppid = getppid()
pid = getpid()
printf("child %d id: %d\n", {{module_num}}, pid)

# open and map shared parent memory
shm_size = ROUND_UP(SHM_SIZE, PAGESIZE)
open_shared_mem(
  &pmem, SMEM0_PATHNAME, shm_size, O_RDWR, PROT_READ | PROT_WRITE
)
pNumTicks = <int64_t *>(pmem + NUM_TICKS_OFFSET)
pBufVars = <uint32_t *>(pmem + BUF_VARS_OFFSET)

pTickUpSem = sem_open("/tick_up_sem_{{non_source_num}}", 0)
pTickDownSem = sem_open("/tick_down_sem_{{non_source_num}}", 0)
{%- for sig_sem in sig_sems %}
snprintf(semNameBuf, SEM_NAME_LEN, "/sig_sem_%d", {{sig_sem[1]}})
pSigSems[{{sig_sem[1]}}] = sem_open(semNameBuf, 0)

{%- endfor %}

{#This forces creation of processes in graph level order #}
in_sigs = {}
{%- if platform_system == "Linux"  %}
created_mem = sa.list()
  {%- for sig,args in in_signals.items() %}
  {# Extra error checking on Linux #}
if any([b'{{sig}}' == x[0] for x in created_mem]):
  in_sigs["{{sig}}"] = sa.attach("shm://{{sig}}")
  {{sig}}Raw = in_sigs["{{sig}}"]
  {{sig}}BufVars = pBufVars + {{sig_nums[sig] * buf_vars_len}}
    {%- if args["max_packets_per_tick"] == 1 %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[8]]
    {%- else %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[8]:{{sig}}BufVars[9]]
    {%- endif %}
else:
  print("{{sig}} not created")
  die("signal not created")
  {%- endfor %}
{%- else %}
  {%- for sig,args in in_signals.items() %}
in_sigs["{{sig}}"] = sa.attach("shm://{{sig}}")
{{sig}}Raw = in_sigs["{{sig}}"]
{{sig}}BufVars = pBufVars + {{sig_nums[sig] * buf_vars_len}}
    {%- if args["max_packets_per_tick"] == 1 %}
{{sig}} = {{sig}}Raw[{{sig}}BufVars[8]]
    {%- else %}
{{sig}} = {{sig}}Raw[{{sig}}BufVars[8]:{{sig}}BufVars[9]]
    {%- endif %}
  {%- endfor %}
{%- endif %}

out_sigs = {}
{% for sig, args in out_signals.items() %}
  {%- if platform_system == "Linux"  %}
if any([b'{{sig}}' == x[0] for x in created_mem]):
  sa.delete("shm://{{sig}}")
  die("numpy signal {{sig}} already created")
else:
  out_sigs["{{sig}}"] = create_shared_array(
    "shm://{{sig}}", {{args['sig_shape']}}, dtype=np.{{args['dtype']}}
  )
  # make sure SharedArray==2.0.4 -> try to transfer to 3.0.0
  out_sigs["{{sig}}"].base.mlock()
  {{sig}}Raw = out_sigs["{{sig}}"]
  {{sig}}BufVars = pBufVars + {{sig_nums[sig] * buf_vars_len}}
    {%- if args["max_packets_per_tick"] == 1 %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[8]]
    {%- else %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[8]:{{sig}}BufVars[9]]
    {%- endif %}
  {{sig}}BufVars[5] = {{args["packet_size"]}}
  {{sig}}BufVars[6] = {{args["max_packets_per_tick"] * args["packet_size"]}}
  {{sig}}BufVars[7] = {{args["buf_tot_numel"]}}
  {%- else %}
out_sigs["{{sig}}"] = create_shared_array(
  "shm://{{sig}}", {{args['sig_shape']}}, dtype=np.{{args['dtype']}}
)
# make sure SharedArray==2.0.4 -> try to transfer to 3.0.0
out_sigs["{{sig}}"].base.mlock()
{{sig}}Raw = out_sigs["{{sig}}"]
{{sig}}BufVars = pBufVars + {{sig_nums[sig] * buf_vars_len}}
    {%- if args["max_packets_per_tick"] == 1 %}
{{sig}} = {{sig}}Raw[{{sig}}BufVars[8]]
    {%- else %}
{{sig}} = {{sig}}Raw[{{sig}}BufVars[8]:{{sig}}BufVars[9]]
    {%- endif %}
{{sig}}BufVars[5] = {{args["packet_size"]}}
{{sig}}BufVars[6] = {{args["max_packets_per_tick"] * args["packet_size"]}}
{{sig}}BufVars[7] = {{args["buf_tot_numel"]}}
  {%- endif %}
{% endfor %}

# set signal mask
sigfillset(&exitMask)
# handle signals
set_sighandler(SIGINT, &int_handler, &exitMask)
set_sighandler(SIGUSR1, &exit_handler, &exitMask)
set_sighandler(SIGBUS, &mem_error_handler, &exitMask)
set_sighandler(SIGSEGV, &mem_error_handler, &exitMask)

{% if default_params -%}
{{default_sig_name}} = {{default_params}}
{%- endif %}

{% if numba -%}

# Numba constructor
import {{numba_mod_name}}
{{numba_mod_name}}.{{numba_func_name}}({{numba_inst_inputs}})
{%- endif %}

{{construct_code}}

{%- if platform_system != "Darwin" %}
make_realtime()
{%- endif %}

kill(ppid,SIGUSR1)

cdef int semVal
while(True):
  sem_wait(pTickUpSem)
  #clock_gettime(CLOCK_MONOTONIC_RAW, &ts)
  #printf("tick %lu, module {{name}} triggered at %f.\n", pNumTicks[0], ts.tv_nsec/1000000.)
{% for name, sem_num in in_sig_sems %}
  sem_wait(pSigSems[{{sem_num}}])
{%- endfor %}
  # time.sleep(0.001)
#  clock_gettime(CLOCK_MONOTONIC_RAW, &ts)
#  printf("\ttick %lu, module {{name}} deps ready at %f.\n", pNumTicks[0], ts.tv_nsec/1000000.)
{% for sig,args in in_signals.items() %}
  {%- if args["max_packets_per_tick"] == 1 %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[8]]
  {%- else %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[0]:{{sig}}BufVars[1]]
  {%- endif %}
  {{sig}}Len = {{sig}}BufVars[1] - {{sig}}BufVars[0]
{% endfor %}
{% for sig,args in out_signals.items() %}
  {{sig}} = {{sig}}Raw[{{sig}}BufVars[10]]
{% endfor %}
{%- if numba %}
  {{numba_mod_name}}.{{numba_func_name}}({{numba_func_inputs}})
{% else %}
  {{user_code}}
{%- endif %}

{% for dep_name, dep_tuple in out_sig_dependency_info.items() %}
  # printf("module 1 : %u %u %u %u %u %u %u %u  \n", {{dep_name}}BufVars[0], {{dep_name}}BufVars[1], {{dep_name}}BufVars[2], {{dep_name}}BufVars[3], {{dep_name}}BufVars[4], {{dep_name}}BufVars[5], {{dep_name}}BufVars[6], {{dep_name}}BufVars[7])
  {{dep_name}}BufVars[2] += {{dep_name}}BufVars[5]
  {{dep_name}}BufVars[10] += 1
  if ({{dep_name}}BufVars[7] <= ({{dep_name}}BufVars[2] + {{dep_name}}BufVars[6])):
    {{dep_name}}BufVars[4] = {{dep_name}}BufVars[2]
    {{dep_name}}BufVars[12] = {{dep_name}}BufVars[10]
  elif ({{dep_name}}BufVars[2] > {{dep_name}}BufVars[4]):
    {{dep_name}}BufVars[4] = {{dep_name}}BufVars[2] 
    {{dep_name}}BufVars[12] = {{dep_name}}BufVars[10]
  {{dep_name}}BufVars[3] += {{dep_name}}BufVars[5]
  {{dep_name}}BufVars[11] += 1
  # printf("module 2 : %u %u %u %u %u %u %u %u  \n", {{dep_name}}BufVars[0], {{dep_name}}BufVars[1], {{dep_name}}BufVars[2], {{dep_name}}BufVars[3], {{dep_name}}BufVars[4], {{dep_name}}BufVars[5], {{dep_name}}BufVars[6], {{dep_name}}BufVars[7])

  # update bufVars
  {{dep_name}}BufVars[1] = {{dep_name}}BufVars[2]
  {{dep_name}}BufVars[9] = {{dep_name}}BufVars[10]
  {{dep_name}}BufVars[0] = {{dep_name}}BufVars[1] - {{dep_name}}BufVars[3]
  {{dep_name}}BufVars[8] = {{dep_name}}BufVars[9] - {{dep_name}}BufVars[11]
  if ({{dep_name}}BufVars[7] < ({{dep_name}}BufVars[2] + {{dep_name}}BufVars[6])):
    {{dep_name}}BufVars[2] = 0
    {{dep_name}}BufVars[10] = 0
  {{dep_name}}BufVars[3] = 0
  {{dep_name}}BufVars[11] = 0  
  # printf("module 3 : %u %u %u %u %u %u %u %u  \n", {{dep_name}}BufVars[0], {{dep_name}}BufVars[1], {{dep_name}}BufVars[2], {{dep_name}}BufVars[3], {{dep_name}}BufVars[4], {{dep_name}}BufVars[5], {{dep_name}}BufVars[6], {{dep_name}}BufVars[7])

  {% for i in range(dep_tuple[0]) %}
  sem_post(pSigSems[{{dep_tuple[1]}}])
  {%- endfor %}
{%- endfor %}

  atomic_thread_fence(memory_order_seq_cst)
  sem_post(pTickDownSem)
  #clock_gettime(CLOCK_MONOTONIC_RAW, &ts)
  #printf("tick %lu, module {{name}} done at %f.\n", pNumTicks[0], ts.tv_nsec/1000000.)

{% endblock %}
