//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: thread.cpp,v 1.1.1.1 2003/10/29 10:05:18 wschweer Exp $
//
//  (C) Copyright 2001 Werner Schweer (ws@seh.de)
//=========================================================

#include "thread.h"
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/poll.h>
#include <fcntl.h>

#include "globals.h"
#include "errno.h"

//---------------------------------------------------------
//   Thread
//---------------------------------------------------------

Thread::~Thread()
      {
      }

//---------------------------------------------------------
//   serverloop
//---------------------------------------------------------

static void* loop(void* mops)
      {
      Thread* t = (Thread*) mops;
      t->loop();
      return 0;
      }

//---------------------------------------------------------
//   start
//---------------------------------------------------------

void Thread::start(void* ptr)
      {
      userPtr = ptr;
      pthread_attr_t* attributes = 0;
      doSetuid();
      if (realTimeScheduling) {
            struct sched_param rt_param;
            memset(&rt_param, 0, sizeof(rt_param));
            rt_param.sched_priority = realTimePriority;

            attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t));
            pthread_attr_init(attributes);

            if (pthread_attr_setschedpolicy(attributes, SCHED_FIFO)) {
                  printf("cannot set FIFO scheduling class for RT thread\n");
                  }
            if (pthread_attr_setschedparam (attributes, &rt_param)) {
                  printf("Cannot set scheduling priority for RT thread (%s)\n", strerror(errno));
                  }
            if (pthread_attr_setscope (attributes, PTHREAD_SCOPE_SYSTEM)) {
                  printf("Cannot set scheduling scope for RT thread\n");
                  }
            }
      pthread_mutex_lock(&lock);

      if (pthread_create(&thread, attributes, ::loop, this))
            perror("creating thread failed:");

      pthread_cond_wait(&ready, &lock);
      pthread_mutex_unlock(&lock);

      undoSetuid();
      }

//---------------------------------------------------------
//   stop
//---------------------------------------------------------

void Thread::stop(bool force)
      {
      if (force) {
            pthread_cancel(thread);
            threadStop();
            }
      _running = false;
      if (pthread_join(thread, 0)) {
            // perror("Failed to join sequencer thread");
            }
      }

//---------------------------------------------------------
//   Thread
//    prio = 0    no realtime scheduling
//---------------------------------------------------------

Thread::Thread(int prio, const char* s)
      {
      userPtr          = 0;
      _name            = s;
      realTimePriority = prio;
      pfd              = 0;
      npfd             = 0;
      maxpfd           = 0;
      _running         = false;
      _pollWait        = -1;

      // create message channels
      int filedes[2];         // 0 - reading   1 - writing
      if (pipe(filedes) == -1) {
            perror("thread:creating pipe4");
            exit(-1);
            }
      toThreadFdr = filedes[0];
      toThreadFdw = filedes[1];

      if (pipe(filedes) == -1) {
            perror("thread: creating pipe5");
            exit(-1);
            }
      fromThreadFdr = filedes[0];
      fromThreadFdw = filedes[1];

      pthread_mutex_init(&lock, 0);
      pthread_cond_init(&ready, 0);
      }

//---------------------------------------------------------
//   addPollFd
//---------------------------------------------------------

void Thread::addPollFd(int fd, int action, void (*handler)(void*,void*), void* p, void* q)
      {
      for (iPoll i = plist.begin(); i != plist.end(); ++i) {
            if ((i->fd == fd) && (i->action == action))
                  return;
            }

      plist.push_back(Poll(fd, action, handler, p, q));

      if (npfd == maxpfd) {
            int n = (maxpfd == 0) ? 4 : maxpfd * 2;
            //TODO: delete old pfd
            pfd   = new struct pollfd[n];
            maxpfd = n;
            }
      ++npfd;
      int idx = 0;
      for (iPoll i = plist.begin(); i != plist.end(); ++i, ++idx) {
            pfd[idx].fd     = i->fd;
            pfd[idx].events = i->action;
            }
      }

//---------------------------------------------------------
//   removePollFd
//---------------------------------------------------------

void Thread::removePollFd(int fd, int action)
      {
      for (iPoll i = plist.begin(); i != plist.end(); ++i) {
            if (i->fd == fd && i->action == action) {
                  plist.erase(i);
                  --npfd;
                  break;
                  }
            }
      int idx = 0;
      for (iPoll i = plist.begin(); i != plist.end(); ++i, ++idx) {
            pfd[idx].fd     = i->fd;
            pfd[idx].events = i->action;
            }
      }

//---------------------------------------------------------
//   loop
//---------------------------------------------------------

void Thread::loop()
      {
      if (mlockall(MCL_CURRENT|MCL_FUTURE))
            perror("WARNING: Cannot lock memory:");
      undoSetuid();

#define BIG_ENOUGH_STACK (1024*1024*1)
      char buf[BIG_ENOUGH_STACK];
      for (int i = 0; i < BIG_ENOUGH_STACK; i++)
            buf[i] = i;
#undef BIG_ENOUGH_STACK

      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);

      int policy;
      if ((policy = sched_getscheduler (0)) < 0) {
            printf("Thread: Cannot get current client scheduler: %s\n", strerror(errno));
            }

      if (debugMsg)
            printf("Thread <%s> set to %s priority %d\n",
               _name, policy == SCHED_FIFO ? "SCHED_FIFO" : "SCHED_OTHER",
                realTimePriority);

      pthread_mutex_lock(&lock);
      _running = true;
      pthread_cond_signal(&ready);
      pthread_mutex_unlock(&lock);

      threadStart(userPtr);
      while (_running) {
            if (debugMode)          // DEBUG
                  _pollWait = 10;   // ms
            int n = poll(pfd, npfd, _pollWait);
            if (n < 0) {
                  if (errno == EINTR)
                        continue;
                  fprintf(stderr, "poll failed: %s\n", strerror(errno));
                  exit(-1);
                  }
            if (n == 0) {       // timeout
                  defaultTick();
                  continue;
                  }
            struct pollfd* p = &pfd[0];
            for (iPoll ip = plist.begin(); ip != plist.end(); ++ip, ++p) {
                  if (ip->action & p->revents)
                        (ip->handler)(ip->param1, ip->param2);
                  }
            }
      threadStop();
      }

//---------------------------------------------------------
//   send
//    send request from gui to thread
//    wait until request is processed
//---------------------------------------------------------

bool Thread::sendMsg(const ThreadMsg* m)
      {
      static char c = 0;
      ++c;
      if (_running) {
            int rv = write(toThreadFdw, &m, sizeof(ThreadMsg*));
            if (rv != sizeof(ThreadMsg*)) {
                  perror("Thread::sendMessage(): write pipe failed");
                  return true;
                  }

            ThreadMsg* rm;
            // wait for sequencer to finish operation
            rv = read(fromThreadFdr, &rm, sizeof(ThreadMsg*));
            if (rv != sizeof(ThreadMsg*)) {
                  perror("Thread::sendMessage(): read pipe failed");
                  return true;
                  }
            }
      else {
            // if thread is not running (during initialization)
            // process commands directly:
            processMsg(m);
            }
      return false;
      }

//---------------------------------------------------------
//   send
//    send request from gui to thread
//    do __not__ wait until request is processed
//---------------------------------------------------------

bool Thread::sendMsg1(const void* m, int n)
      {
      int rv = write(toThreadFdw, m, n);
      if (rv != n) {
            perror("Thread::sendMessage1(): write pipe failed");
            return true;
            }
      return false;
      }

//---------------------------------------------------------
//   readMsg
//    sequencer reads one gui message
//---------------------------------------------------------

void Thread::readMsg()
      {
      ThreadMsg* p;
      if (read(toThreadFdr, &p, sizeof(p)) != sizeof(p)) {
            perror("Thread::readMessage(): read pipe failed");
            exit(-1);
            }
      processMsg(p);
      int rv = write(fromThreadFdw, p, sizeof(p));
      if (rv != sizeof(p))
            perror("Thread::readMessage(): write pipe failed");
      }

//---------------------------------------------------------
//   readMsg
//    sequencer reads one gui message
//---------------------------------------------------------

void Thread::readMsg1(int size)
      {
      char buffer[size];
      int n = read(toThreadFdr, buffer, size);
      if (n != size) {
            fprintf(stderr, "Thread::readMessage1(): read pipe failed, get %d, expected %d: %s\n",
               n, size, strerror(errno));
            exit(-1);
            }
      processMsg1(buffer);
      }

