/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

sigset_t ints_off;
pthread_mutex_t _pr_heapLock;
pthread_key_t current_thread_key;
pthread_key_t current_cpu_key;
pthread_key_t last_thread_key;
pthread_key_t intsoff_key;

PRInt32 _pr_md_pthreads_created, _pr_md_pthreads_failed;
PRInt32 _pr_md_pthreads = 1;

void _MD_EarlyInit(void) {
  extern PRInt32 _nspr_noclock;

  if (pthread_key_create(&current_thread_key, NULL) != 0) {
    perror("pthread_key_create failed");
    exit(1);
  }
  if (pthread_key_create(&current_cpu_key, NULL) != 0) {
    perror("pthread_key_create failed");
    exit(1);
  }
  if (pthread_key_create(&last_thread_key, NULL) != 0) {
    perror("pthread_key_create failed");
    exit(1);
  }
  if (pthread_key_create(&intsoff_key, NULL) != 0) {
    perror("pthread_key_create failed");
    exit(1);
  }

  sigemptyset(&ints_off);
  sigaddset(&ints_off, SIGALRM);
  sigaddset(&ints_off, SIGIO);
  sigaddset(&ints_off, SIGCLD);

  /*
   * disable clock interrupts
   */
  _nspr_noclock = 1;
}

void _MD_InitLocks() {
  if (pthread_mutex_init(&_pr_heapLock, NULL) != 0) {
    perror("pthread_mutex_init failed");
    exit(1);
  }
}

PR_IMPLEMENT(void) _MD_FREE_LOCK(struct _MDLock* lockp) {
  PRIntn _is;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  if (me && !_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(_is);
  }
  pthread_mutex_destroy(&lockp->mutex);
  if (me && !_PR_IS_NATIVE_THREAD(me)) {
    _PR_FAST_INTSON(_is);
  }
}

PR_IMPLEMENT(PRStatus) _MD_NEW_LOCK(struct _MDLock* lockp) {
  PRStatus rv;
  PRIntn is;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  if (me && !_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  rv = pthread_mutex_init(&lockp->mutex, NULL);
  if (me && !_PR_IS_NATIVE_THREAD(me)) {
    _PR_FAST_INTSON(is);
  }
  return (rv == 0) ? PR_SUCCESS : PR_FAILURE;
}

PRWord* _MD_HomeGCRegisters(PRThread* t, int isCurrent, int* np) {
  if (isCurrent) {
    (void)setjmp(CONTEXT(t));
  }
  *np = sizeof(CONTEXT(t)) / sizeof(PRWord);
  return (PRWord*)CONTEXT(t);
}

PR_IMPLEMENT(void)
_MD_SetPriority(_MDThread* thread, PRThreadPriority newPri) {
  /*
   * XXX - to be implemented
   */
  return;
}

PR_IMPLEMENT(PRStatus) _MD_InitThread(struct PRThread* thread) {
  struct sigaction sigact;

  if (thread->flags & _PR_GLOBAL_SCOPE) {
    thread->md.pthread = pthread_self();
#if 0
        /*
         * set up SIGUSR1 handler; this is used to save state
         * during PR_SuspendAll
         */
        sigact.sa_handler = save_context_and_block;
        sigact.sa_flags = SA_RESTART;
        /*
         * Must mask clock interrupts
         */
        sigact.sa_mask = timer_set;
        sigaction(SIGUSR1, &sigact, 0);
#endif
  }

  return PR_SUCCESS;
}

PR_IMPLEMENT(void) _MD_ExitThread(struct PRThread* thread) {
  if (thread->flags & _PR_GLOBAL_SCOPE) {
    _MD_CLEAN_THREAD(thread);
    _MD_SET_CURRENT_THREAD(NULL);
  }
}

PR_IMPLEMENT(void) _MD_CleanThread(struct PRThread* thread) {
  if (thread->flags & _PR_GLOBAL_SCOPE) {
    pthread_mutex_destroy(&thread->md.pthread_mutex);
    pthread_cond_destroy(&thread->md.pthread_cond);
  }
}

PR_IMPLEMENT(void) _MD_SuspendThread(struct PRThread* thread) {
  PRInt32 rv;

  PR_ASSERT((thread->flags & _PR_GLOBAL_SCOPE) && _PR_IS_GCABLE_THREAD(thread));
#if 0
    thread->md.suspending_id = getpid();
    rv = kill(thread->md.id, SIGUSR1);
    PR_ASSERT(rv == 0);
    /*
     * now, block the current thread/cpu until woken up by the suspended
     * thread from it's SIGUSR1 signal handler
     */
    blockproc(getpid());
#endif
}

PR_IMPLEMENT(void) _MD_ResumeThread(struct PRThread* thread) {
  PRInt32 rv;

  PR_ASSERT((thread->flags & _PR_GLOBAL_SCOPE) && _PR_IS_GCABLE_THREAD(thread));
#if 0
    rv = unblockproc(thread->md.id);
#endif
}

PR_IMPLEMENT(void) _MD_SuspendCPU(struct _PRCPU* thread) {
  PRInt32 rv;

#if 0
    cpu->md.suspending_id = getpid();
    rv = kill(cpu->md.id, SIGUSR1);
    PR_ASSERT(rv == 0);
    /*
     * now, block the current thread/cpu until woken up by the suspended
     * thread from it's SIGUSR1 signal handler
     */
    blockproc(getpid());
#endif
}

PR_IMPLEMENT(void) _MD_ResumeCPU(struct _PRCPU* thread) {
#if 0
    unblockproc(cpu->md.id);
#endif
}

#define PT_NANOPERMICRO 1000UL
#define PT_BILLION 1000000000UL

PR_IMPLEMENT(PRStatus)
_pt_wait(PRThread* thread, PRIntervalTime timeout) {
  int rv;
  struct timeval now;
  struct timespec tmo;
  PRUint32 ticks = PR_TicksPerSecond();

  if (timeout != PR_INTERVAL_NO_TIMEOUT) {
    tmo.tv_sec = timeout / ticks;
    tmo.tv_nsec = timeout - (tmo.tv_sec * ticks);
    tmo.tv_nsec = PR_IntervalToMicroseconds(PT_NANOPERMICRO * tmo.tv_nsec);

    /* pthreads wants this in absolute time, off we go ... */
    (void)GETTIMEOFDAY(&now);
    /* that one's usecs, this one's nsecs - grrrr! */
    tmo.tv_sec += now.tv_sec;
    tmo.tv_nsec += (PT_NANOPERMICRO * now.tv_usec);
    tmo.tv_sec += tmo.tv_nsec / PT_BILLION;
    tmo.tv_nsec %= PT_BILLION;
  }

  pthread_mutex_lock(&thread->md.pthread_mutex);
  thread->md.wait--;
  if (thread->md.wait < 0) {
    if (timeout != PR_INTERVAL_NO_TIMEOUT) {
      rv = pthread_cond_timedwait(&thread->md.pthread_cond,
                                  &thread->md.pthread_mutex, &tmo);
    } else
      rv = pthread_cond_wait(&thread->md.pthread_cond,
                             &thread->md.pthread_mutex);
    if (rv != 0) {
      thread->md.wait++;
    }
  } else {
    rv = 0;
  }
  pthread_mutex_unlock(&thread->md.pthread_mutex);

  return (rv == 0) ? PR_SUCCESS : PR_FAILURE;
}

PR_IMPLEMENT(PRStatus)
_MD_wait(PRThread* thread, PRIntervalTime ticks) {
  if (thread->flags & _PR_GLOBAL_SCOPE) {
    _MD_CHECK_FOR_EXIT();
    if (_pt_wait(thread, ticks) == PR_FAILURE) {
      _MD_CHECK_FOR_EXIT();
      /*
       * wait timed out
       */
      _PR_THREAD_LOCK(thread);
      if (thread->wait.cvar) {
        /*
         * The thread will remove itself from the waitQ
         * of the cvar in _PR_WaitCondVar
         */
        thread->wait.cvar = NULL;
        thread->state = _PR_RUNNING;
        _PR_THREAD_UNLOCK(thread);
      } else {
        _pt_wait(thread, PR_INTERVAL_NO_TIMEOUT);
        _PR_THREAD_UNLOCK(thread);
      }
    }
  } else {
    _PR_MD_SWITCH_CONTEXT(thread);
  }
  return PR_SUCCESS;
}

PR_IMPLEMENT(PRStatus)
_MD_WakeupWaiter(PRThread* thread) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRInt32 pid, rv;
  PRIntn is;

  PR_ASSERT(_pr_md_idle_cpus >= 0);
  if (thread == NULL) {
    if (_pr_md_idle_cpus) {
      _MD_Wakeup_CPUs();
    }
  } else if (!_PR_IS_NATIVE_THREAD(thread)) {
    /*
     * If the thread is on my cpu's runq there is no need to
     * wakeup any cpus
     */
    if (!_PR_IS_NATIVE_THREAD(me)) {
      if (me->cpu != thread->cpu) {
        if (_pr_md_idle_cpus) {
          _MD_Wakeup_CPUs();
        }
      }
    } else {
      if (_pr_md_idle_cpus) {
        _MD_Wakeup_CPUs();
      }
    }
  } else {
    PR_ASSERT(_PR_IS_NATIVE_THREAD(thread));
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_INTSOFF(is);
    }

    pthread_mutex_lock(&thread->md.pthread_mutex);
    thread->md.wait++;
    rv = pthread_cond_signal(&thread->md.pthread_cond);
    PR_ASSERT(rv == 0);
    pthread_mutex_unlock(&thread->md.pthread_mutex);

    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
  }
  return PR_SUCCESS;
}

/* These functions should not be called for AIX */
PR_IMPLEMENT(void)
_MD_YIELD(void) { PR_NOT_REACHED("_MD_YIELD should not be called for AIX."); }

PR_IMPLEMENT(PRStatus)
_MD_CreateThread(PRThread* thread, void (*start)(void*),
                 PRThreadPriority priority, PRThreadScope scope,
                 PRThreadState state, PRUint32 stackSize) {
  PRIntn is;
  int rv;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  pthread_attr_t attr;

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }

  if (pthread_mutex_init(&thread->md.pthread_mutex, NULL) != 0) {
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
    return PR_FAILURE;
  }

  if (pthread_cond_init(&thread->md.pthread_cond, NULL) != 0) {
    pthread_mutex_destroy(&thread->md.pthread_mutex);
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
    return PR_FAILURE;
  }
  thread->flags |= _PR_GLOBAL_SCOPE;

  pthread_attr_init(&attr); /* initialize attr with default attributes */
  if (pthread_attr_setstacksize(&attr, (size_t)stackSize) != 0) {
    pthread_mutex_destroy(&thread->md.pthread_mutex);
    pthread_cond_destroy(&thread->md.pthread_cond);
    pthread_attr_destroy(&attr);
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
    return PR_FAILURE;
  }

  thread->md.wait = 0;
  rv = pthread_create(&thread->md.pthread, &attr, start, (void*)thread);
  if (0 == rv) {
    _MD_ATOMIC_INCREMENT(&_pr_md_pthreads_created);
    _MD_ATOMIC_INCREMENT(&_pr_md_pthreads);
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
    return PR_SUCCESS;
  } else {
    pthread_mutex_destroy(&thread->md.pthread_mutex);
    pthread_cond_destroy(&thread->md.pthread_cond);
    pthread_attr_destroy(&attr);
    _MD_ATOMIC_INCREMENT(&_pr_md_pthreads_failed);
    if (!_PR_IS_NATIVE_THREAD(me)) {
      _PR_FAST_INTSON(is);
    }
    PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, rv);
    return PR_FAILURE;
  }
}

PR_IMPLEMENT(void)
_MD_InitRunningCPU(struct _PRCPU* cpu) {
  extern int _pr_md_pipefd[2];

  _MD_unix_init_running_cpu(cpu);
  cpu->md.pthread = pthread_self();
  if (_pr_md_pipefd[0] >= 0) {
    _PR_IOQ_MAX_OSFD(cpu) = _pr_md_pipefd[0];
#ifndef _PR_USE_POLL
    FD_SET(_pr_md_pipefd[0], &_PR_FD_READ_SET(cpu));
#endif
  }
}

void _MD_CleanupBeforeExit(void) {
#if 0
    extern PRInt32    _pr_cpus_exit;

    _pr_irix_exit_now = 1;
    if (_pr_numCPU > 1) {
        /*
         * Set a global flag, and wakeup all cpus which will notice the flag
         * and exit.
         */
        _pr_cpus_exit = getpid();
        _MD_Wakeup_CPUs();
        while(_pr_numCPU > 1) {
            _PR_WAIT_SEM(_pr_irix_exit_sem);
            _pr_numCPU--;
        }
    }
    /*
     * cause global threads on the recycle list to exit
     */
    _PR_DEADQ_LOCK;
    if (_PR_NUM_DEADNATIVE != 0) {
        PRThread *thread;
        PRCList *ptr;

        ptr = _PR_DEADNATIVEQ.next;
        while( ptr != &_PR_DEADNATIVEQ ) {
            thread = _PR_THREAD_PTR(ptr);
            _MD_CVAR_POST_SEM(thread);
            ptr = ptr->next;
        }
    }
    _PR_DEADQ_UNLOCK;
    while(_PR_NUM_DEADNATIVE > 1) {
        _PR_WAIT_SEM(_pr_irix_exit_sem);
        _PR_DEC_DEADNATIVE;
    }
#endif
}
