/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program 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.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#define _DEFAULT_SOURCE
#include "os.h"

#if defined(_TD_WINDOWS_64) || defined(_TD_WINDOWS_32)

/*
 * windows implementation
 */

#include <windows.h>

bool taosCheckPthreadValid(pthread_t thread) { return thread.p != NULL; }

void taosResetPthread(pthread_t* thread) { thread->p = 0; }

int64_t taosGetPthreadId(pthread_t thread) {
#ifdef PTW32_VERSION
  return pthread_getw32threadid_np(thread);
#else
  return (int64_t)thread;
#endif
}

int64_t taosGetSelfPthreadId() { return GetCurrentThreadId(); }

bool taosComparePthread(pthread_t first, pthread_t second) { return first.p == second.p; }

int32_t taosGetPId() { return GetCurrentProcessId(); }

int32_t taosGetAppName(char* name, int32_t* len) {
  char filepath[1024] = {0};

  GetModuleFileName(NULL, filepath, MAX_PATH);
  char* sub = strrchr(filepath, '.');
  if (sub != NULL) {
    *sub = '\0';
  }
  strcpy(name, filepath);

  if (len != NULL) {
    *len = (int32_t)strlen(filepath);
  }

  return 0;
}

int32_t tsem_wait(tsem_t* sem) {
  int ret = 0;
  do {
    ret = sem_wait(sem);
  } while (ret != 0 && errno == EINTR);
  return ret;
}

#elif defined(_TD_DARWIN_64)

/*
 * darwin implementation
 */

#include <libproc.h>

// #define SEM_USE_PTHREAD
// #define SEM_USE_POSIX
#define SEM_USE_SEM

#ifdef SEM_USE_SEM
#include <mach/mach_error.h>
#include <mach/mach_init.h>
#include <mach/semaphore.h>
#include <mach/task.h>
#include <pthread.h>

static pthread_t      sem_thread;
static pthread_once_t sem_once;
static task_t         sem_port;
static volatile int   sem_inited = 0;
static semaphore_t    sem_exit;

static void *sem_thread_routine(void *arg) {
  (void)arg;
  setThreadName("sem_thrd");

  sem_port = mach_task_self();
  kern_return_t ret = semaphore_create(sem_port, &sem_exit, SYNC_POLICY_FIFO, 0);
  if (ret != KERN_SUCCESS) {
    fprintf(stderr, "==%s[%d]%s()==failed to create sem_exit\n", basename(__FILE__), __LINE__, __func__);
    sem_inited = -1;
    return NULL;
  }
  sem_inited = 1;
  semaphore_wait(sem_exit);
  return NULL;
}

static void once_init(void) {
  int r = 0;
  r = pthread_create(&sem_thread, NULL, sem_thread_routine, NULL);
  if (r) {
    fprintf(stderr, "==%s[%d]%s()==failed to create thread\n", basename(__FILE__), __LINE__, __func__);
    return;
  }
  while (sem_inited == 0) {
    ;
  }
}
#endif

struct tsem_s {
#ifdef SEM_USE_PTHREAD
  pthread_mutex_t  lock;
  pthread_cond_t   cond;
  volatile int64_t val;
#elif defined(SEM_USE_POSIX)
  size_t        id;
  sem_t *       sem;
#elif defined(SEM_USE_SEM)
  semaphore_t sem;
#else   // SEM_USE_PTHREAD
  dispatch_semaphore_t sem;
#endif  // SEM_USE_PTHREAD

  volatile unsigned int valid : 1;
};

int tsem_init(tsem_t *sem, int pshared, unsigned int value) {
  // fprintf(stderr, "==%s[%d]%s():[%p]==creating\n", basename(__FILE__), __LINE__, __func__, sem);
  if (*sem) {
    fprintf(stderr, "==%s[%d]%s():[%p]==already initialized\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  struct tsem_s *p = (struct tsem_s *)calloc(1, sizeof(*p));
  if (!p) {
    fprintf(stderr, "==%s[%d]%s():[%p]==out of memory\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }

#ifdef SEM_USE_PTHREAD
  int r = pthread_mutex_init(&p->lock, NULL);
  do {
    if (r) break;
    r = pthread_cond_init(&p->cond, NULL);
    if (r) {
      pthread_mutex_destroy(&p->lock);
      break;
    }
    p->val = value;
  } while (0);
  if (r) {
    fprintf(stderr, "==%s[%d]%s():[%p]==not created\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
#elif defined(SEM_USE_POSIX)
  static size_t tick = 0;
  do {
    size_t id = atomic_add_fetch_64(&tick, 1);
    if (id == SEM_VALUE_MAX) {
      atomic_store_64(&tick, 0);
      id = 0;
    }
    char name[NAME_MAX - 4];
    snprintf(name, sizeof(name), "/t%ld", id);
    p->sem = sem_open(name, O_CREAT | O_EXCL, pshared, value);
    p->id = id;
    if (p->sem != SEM_FAILED) break;
    int e = errno;
    if (e == EEXIST) continue;
    if (e == EINTR) continue;
    fprintf(stderr, "==%s[%d]%s():[%p]==not created[%d]%s\n", basename(__FILE__), __LINE__, __func__, sem, e,
            strerror(e));
    abort();
  } while (p->sem == SEM_FAILED);
#elif defined(SEM_USE_SEM)
  pthread_once(&sem_once, once_init);
  if (sem_inited != 1) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal resource init failed\n", basename(__FILE__), __LINE__, __func__, sem);
    errno = ENOMEM;
    return -1;
  }
  kern_return_t ret = semaphore_create(sem_port, &p->sem, SYNC_POLICY_FIFO, value);
  if (ret != KERN_SUCCESS) {
    fprintf(stderr, "==%s[%d]%s():[%p]==semophore_create failed\n", basename(__FILE__), __LINE__, __func__, sem);
    // we fail-fast here, because we have less-doc about semaphore_create for the moment
    abort();
  }
#else   // SEM_USE_PTHREAD
  p->sem = dispatch_semaphore_create(value);
  if (p->sem == NULL) {
    fprintf(stderr, "==%s[%d]%s():[%p]==not created\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
#endif  // SEM_USE_PTHREAD

  p->valid = 1;

  *sem = p;

  return 0;
}

int tsem_wait(tsem_t *sem) {
  if (!*sem) {
    fprintf(stderr, "==%s[%d]%s():[%p]==not initialized\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  struct tsem_s *p = *sem;
  if (!p->valid) {
    fprintf(stderr, "==%s[%d]%s():[%p]==already destroyed\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
#ifdef SEM_USE_PTHREAD
  if (pthread_mutex_lock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  p->val -= 1;
  if (p->val < 0) {
    if (pthread_cond_wait(&p->cond, &p->lock)) {
      fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
      abort();
    }
  }
  if (pthread_mutex_unlock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  return 0;
#elif defined(SEM_USE_POSIX)
  return sem_wait(p->sem);
#elif defined(SEM_USE_SEM)
  return semaphore_wait(p->sem);
#else   // SEM_USE_PTHREAD
  return dispatch_semaphore_wait(p->sem, DISPATCH_TIME_FOREVER);
#endif  // SEM_USE_PTHREAD
}

int tsem_post(tsem_t *sem) {
  if (!*sem) {
    fprintf(stderr, "==%s[%d]%s():[%p]==not initialized\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  struct tsem_s *p = *sem;
  if (!p->valid) {
    fprintf(stderr, "==%s[%d]%s():[%p]==already destroyed\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
#ifdef SEM_USE_PTHREAD
  if (pthread_mutex_lock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  p->val += 1;
  if (p->val <= 0) {
    if (pthread_cond_signal(&p->cond)) {
      fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
      abort();
    }
  }
  if (pthread_mutex_unlock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  return 0;
#elif defined(SEM_USE_POSIX)
  return sem_post(p->sem);
#elif defined(SEM_USE_SEM)
  return semaphore_signal(p->sem);
#else   // SEM_USE_PTHREAD
  return dispatch_semaphore_signal(p->sem);
#endif  // SEM_USE_PTHREAD
}

int tsem_destroy(tsem_t *sem) {
  // fprintf(stderr, "==%s[%d]%s():[%p]==destroying\n", basename(__FILE__), __LINE__, __func__, sem);
  if (!*sem) {
    // fprintf(stderr, "==%s[%d]%s():[%p]==not initialized\n", basename(__FILE__), __LINE__, __func__, sem);
    // abort();
    return 0;
  }
  struct tsem_s *p = *sem;
  if (!p->valid) {
    // fprintf(stderr, "==%s[%d]%s():[%p]==already destroyed\n", basename(__FILE__), __LINE__, __func__, sem);
    // abort();
    return 0;
  }
#ifdef SEM_USE_PTHREAD
  if (pthread_mutex_lock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  p->valid = 0;
  if (pthread_cond_destroy(&p->cond)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  if (pthread_mutex_unlock(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
  if (pthread_mutex_destroy(&p->lock)) {
    fprintf(stderr, "==%s[%d]%s():[%p]==internal logic error\n", basename(__FILE__), __LINE__, __func__, sem);
    abort();
  }
#elif defined(SEM_USE_POSIX)
  char name[NAME_MAX - 4];
  snprintf(name, sizeof(name), "/t%ld", p->id);
  int r = sem_unlink(name);
  if (r) {
    int e = errno;
    fprintf(stderr, "==%s[%d]%s():[%p]==unlink failed[%d]%s\n", basename(__FILE__), __LINE__, __func__, sem, e,
            strerror(e));
    abort();
  }
#elif defined(SEM_USE_SEM)
  semaphore_destroy(sem_port, p->sem);
#else   // SEM_USE_PTHREAD
#endif  // SEM_USE_PTHREAD

  p->valid = 0;
  free(p);

  *sem = NULL;
  return 0;
}

bool taosCheckPthreadValid(pthread_t thread) {
  uint64_t id = 0;
  int      r = pthread_threadid_np(thread, &id);
  return r ? false : true;
}

int64_t taosGetSelfPthreadId() {
  uint64_t id;
  pthread_threadid_np(0, &id);
  return (int64_t)id;
}

int64_t taosGetPthreadId(pthread_t thread) { return (int64_t)thread; }

void taosResetPthread(pthread_t *thread) { *thread = NULL; }

bool taosComparePthread(pthread_t first, pthread_t second) { return pthread_equal(first, second) ? true : false; }

int32_t taosGetPId() { return (int32_t)getpid(); }

int32_t taosGetAppName(char *name, int32_t *len) {
  char buf[PATH_MAX + 1];
  buf[0] = '\0';
  proc_name(getpid(), buf, sizeof(buf) - 1);
  buf[PATH_MAX] = '\0';
  size_t n = strlen(buf);
  if (len) *len = n;
  if (name) strcpy(name, buf);
  return 0;
}

#else

/*
 * linux implementation
 */

#include <sys/syscall.h>
#include <unistd.h>

bool taosCheckPthreadValid(pthread_t thread) { return thread != 0; }

int64_t taosGetSelfPthreadId() {
  static __thread int id = 0;
  if (id != 0) return id;
  id = syscall(SYS_gettid);
  return id;
}

int64_t taosGetPthreadId(pthread_t thread) { return (int64_t)thread; }
void    taosResetPthread(pthread_t* thread) { *thread = 0; }
bool    taosComparePthread(pthread_t first, pthread_t second) { return first == second; }
int32_t taosGetPId() { return getpid(); }

int32_t taosGetAppName(char* name, int32_t* len) {
  const char* self = "/proc/self/exe";
  char        path[PATH_MAX] = {0};

  if (readlink(self, path, PATH_MAX) <= 0) {
    return -1;
  }

  path[PATH_MAX - 1] = 0;
  char* end = strrchr(path, '/');
  if (end == NULL) {
    return -1;
  }

  ++end;

  strcpy(name, end);

  if (len != NULL) {
    *len = strlen(name);
  }

  return 0;
}

int32_t tsem_wait(tsem_t* sem) {
  int ret = 0;
  do {
    ret = sem_wait(sem);
  } while (ret != 0 && errno == EINTR);
  return ret;
}

#endif
