/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  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.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/time.h>


#include "swindow/sosx/SEventOSX.h"
#include "swindow/sosx/SExtern.h"

static int nonblockHack (long id);
static int sEventBSDSignal =0;
static void initSignals (bool init);

extern "C"
{
  typedef void(*signalHandler_t)(int);
  static  void signalHandler (int signum);
};


SClientOSX::SClientOSX (SEventSource* s, SEventTarget* t, ClientType _type) : SClient(s,t,_type)
{
  cocoaTimer = 0;
}

SClientOSX::~SClientOSX() 
{
}
/**
 * This is a single implementation of the event handler
 */
SEventOSX::SEventOSX(void) : SEventHandlerImpl()
{
    initSignals(true);
    cocoaInit();
}

SEventOSX::~SEventOSX()
{
    initSignals(false);
}

void
SEventOSX::addJob (SJob* s, SEventTarget* t) 
{
    fprintf (stderr, "SEventOSX::addJob not implemented\n");
}

void
SEventOSX::addServer (SServerStream* s, SEventTarget* t) 
{
    fprintf (stderr, "SEventOSX::addServer not implemented\n");
}

void
SEventOSX::addTimer (STimer* tim, SEventTarget* t) 
{
  if (sourceHashtable.get (tim))
  {
    fprintf (stderr, "attempted to add save event source (timer) twice.\n");
    return;
  }
  SEventSource* s = tim;
  SClientOSX* c = new SClientOSX (s, t, SClientOSX::TIMER);
  CHECK_NEW (c);

  /* Check if there is already somebody there...*/
  targetHashtable.put (t, c, false);
  sourceHashtable.put (s, c, false);
  clientHashtable.put((const SString&)c->id, c);
  c->cocoaData = cocoaAddTimer (this, c, (float)(unsigned long)c->value/1000.0);
}

void
SEventOSX::addInput (SInputStream* s, SEventTarget* t) 
{
    fprintf (stderr, "SEventOSX::addInput not implemented\n");
}

/*
NSFileHandle does not provide the functionality we need:

Run Loop Considerations

When using a file handle object to communicate asynchronously with a socket, you must initiate the corresponding operations from a thread with an active run loop. Although the read, accept, and wait operations themselves are performed asynchronously on background threads, the file handle uses a run loop source to monitor the operations and notify your code appropriately. Therefore, you must call those methods from your application’s main thread or from any thread where you have configured a run loop and are using it to process events.
*/

void
SEventOSX::addOutput (SOutputStream* outs, SEventTarget* t, const SString& m) 
{
  if (sourceHashtable.get (outs))
  {
    fprintf (stderr, "attempted to add save event source (output) twice.\n");
    return;
  }
  SEventSource* s = outs;
  SClientOSX* c = new SClientOSX (s, t, SClientOSX::WRITER);
  CHECK_NEW (c);
  c->data = m;
 
  nonblockHack (c->value);
  /* Check if there is already somebody there...*/
  targetHashtable.put (t, c, false);
  sourceHashtable.put (s, c, false);
  clientHashtable.put((const SString&)c->id,c);
}

void
SEventOSX::remove (SEventTarget* t) 
{
  //fprintf (stderr, "SEventOSX::remove event target\n");
  while (true)
  {
    SClientOSX* cl = targetHashtable.get (t);
    if (cl == 0)
    {
      //fprintf (stderr, "SEventBSD::remove - target already got removed.\n");
      return;
    }
    remove (cl);
  }
}

void
SEventOSX::remove (SEventSource* s) 
{
  //fprintf (stderr, "SEventOSX::remove event source\n");
  while (true)
  {
    SClientOSX* cl = sourceHashtable.get (s);
    if (cl == 0)
    {
      //fprintf (stderr, "SEventBSD::remove - source already got removed.\n");
      return;
    }
    //fprintf (stderr, "SEventOSX::remove event source\n");
    remove (cl);
  }
}

void
SEventOSX::remove (SClientOSX* c)
{
  //fprintf (stderr, "SEventOSX::remove event client\n");
  if (c==0)
  {
    fprintf (stderr, "SEventBSD::remove tried to remove zero client.\n");
    return;
  }
  SString id = SString((const SString&)c->id);
  //fprintf (stderr, "remove-remove %*.*s\n", SSARGS(id));
  if (clientHashtable.get (id)==0) return;
  switch (c->getType())
  {
  case SClientOSX::WRITER: 

    break;
  case SClientOSX::READER: 
  case SClientOSX::SERVER: 
    break;
  case SClientOSX::TIMER: 
//SGC
//fprintf (stderr, "removing timer %lx\n", (unsigned long)c->cocoaData);
    if (c->cocoaData) {
        cocoaRemoveTimer(c->cocoaData);
    }
    c->cocoaData = 0;
    break;
  case SClientOSX::JOB: 
    fprintf (stderr, "removing job not implemented\n");
/*
    cindex=jobClient.find (c);
    if (cindex<0)
    {
       // this should not be an error - it removed itself 
       //fprintf (stderr, "SEventBSD::remove can not find job.\n");
       break;
    }
    jobVector.remove (cindex);
    jobClient.remove (cindex);
*/
    break;
  }
  /* if there are multiple matches, search the one that 
     needs to be removed. */
  SClientOSXVector addbackTarget;
  /** targets **/
  while (targetHashtable.get (c->target))
  {
     SClientOSX* ab = targetHashtable.get (c->target);
     if (ab == c)
     {
       targetHashtable.remove (c->target);
       break;
     }
     addbackTarget.append (ab);
     targetHashtable.remove (c->target);
  }

  unsigned int i;
  /* adding back stuff. */
  for (i=0; i<addbackTarget.size(); i++)
  {
    targetHashtable.put (addbackTarget[i]->target, 
              addbackTarget[i], false);
  }

  /** sources **/
  SClientOSXVector addbackSource;
  while (sourceHashtable.get (c->source))
  {
    SClientOSX* ab = sourceHashtable.get (c->source);
    if (ab == c)
    {
      sourceHashtable.remove (c->source);
      break;
    }
    addbackSource.append (ab);
    sourceHashtable.remove (ab->source);
  }
  for (i=0; i<addbackSource.size(); i++)
  {
    sourceHashtable.put (addbackSource[i]->source, 
          addbackSource[i], false);
  }

  clientHashtable.remove ((const SString&)c->id);
  delete c;
  return;

}

void
SEventOSX::start() 
{
    //fprintf (stderr, "SEventOSX::start\n");
    cocoaRun();
    //fprintf (stderr, "SEventOSX::start end\n");
}


void
SEventOSX::exit() 
{
    fprintf (stderr, "SEventOSX::exit\n");
    cocoaExitApplication ();
}
// return negative on error,
// 0 if more data
// 1 if finished.
// 2 if finished, no remove.
int
SEventOSX::process(SClientOSX* client) {
    switch (client->getType()) {
    case SClient::WRITER:
        {
            sEventBSDSignal = 0;
            SEventSource *s=client->source;
            SEventTarget *t=client->target;
            int n = ::write(client->value, 
                &client->data.array()[client->progress], 
                client->data.size()-client->progress);
//fprintf (stderr, "writing %d on %d\n", n, (int)client->value);
            if (n<0 && (errno == EINTR || errno==EAGAIN || errno==EWOULDBLOCK)) n = 0;
            if (sEventBSDSignal!=0) {
                sEventBSDSignal=0; n=-1;
            }
            if (n<0)
            {
                remove(client);
                t->error(s);
                return -1;
            }
            else if ((unsigned int) n  == (client->data.size() - client->progress))
            {
                remove (client);
                t->write(s);
                return 1;
            }
            else
            {
                client->progress += n;
                return 0;
            }
        }
        break;
    case SClient::JOB:
        break;
    case SClient::READER:
        break;
    case SClient::SERVER:
    case SClient::TIMER:
        break;
    }
    return 2;
}

/**
 * Check clients and call them, return true to call again.
 */
bool
SEventOSX::next() 
{
//fprintf (stderr, "SEventOSX::next\n");
    SStringVector allClients;
    int moreData = 0;
    for (unsigned int i=0; i<clientHashtable.size(); i++) {
        for (unsigned int j=0; j<clientHashtable.size(); j++) {
            SClientOSX* client = clientHashtable.get (i,j);
            if (client == 0) continue;
            SString id = SString((const SString&)client->id);
            allClients.append(id);
        }
    }
    for (unsigned int i=0; i<allClients.size(); i++) {
        SClientOSX* client = clientHashtable.get(allClients[i]);
        if (client == 0) continue;
        int ret = process (client); 
        // more data
        if (ret == 0) {
            moreData++;
        }
    }
    // we dont call the gui next loop for simplicity.
    return moreData != 0;
}

bool
SEventOSX::fireTimer(SClientOSX* client) 
{
    SString id = SString((const SString&)client->id);
//fprintf (stderr, "fireTimer %*.*s\n", SSARGS(id));
    if (clientHashtable.get (id)==0)
    {
        fprintf (stderr, "SEventOSX::fireTimer client disappeared\n");
        return false;
    }
    SEventSource *s=client->source;
    SEventTarget *t=client->target;
    if (!t->timeout(s)){
//fprintf (stderr, "fireTimer-remove %*.*s\n", SSARGS(id));
        //fprintf (stderr, "fireTimer false\n");
        // removed by timeout itself.
        if (clientHashtable.get (id)!=0) {
            remove(client);
        }
        return false;
    }
    return true;
}

extern "C"
{
  typedef void(*signalHandler_t)(int);
  static  void signalHandler (int signum);
};

/**
 * Be aware: on some systems signalHandler does not take an int argument
 */
extern "C" {
  static  void
  signalHandler (int signum)
  {
  #if 0
      switch (signum)
      {
      case SIGPIPE:
          fprintf (stderr, "SEventOSX: disconnected - SIGPIPE\n");
          break;

      case SIGALRM:
          fprintf (stderr, "SEventOSX: timeout - SIGALRM\n");
          break;
  
      default:
          fprintf (stderr, "SEventOSX: signal %d\n", signum);
      }
      sEventBSDSignal = signum;
  #endif
     sEventBSDSignal = 1;
     return;
  }
}



static void
initSignals(bool init)
{
  static bool inited = false;
  if (inited && init) return;
  static struct sigaction   oact;
  struct sigaction          act;

  if (init)
  {
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    /* Hack: on some systems signalHandler does not take an int argument */
    *((signalHandler_t*) &act.sa_handler) = (signalHandler_t) signalHandler;
#ifdef SA_INTERRUPT
    act.sa_flags |= SA_INTERRUPT;
#endif
    sigaction (SIGALRM, &act, &oact);

    act.sa_handler = SIG_IGN;
    sigaction (SIGPIPE, &act, &oact);
    inited = true;
  }
  else
  {
    sigaction (SIGPIPE, &oact, &act);
    sigaction (SIGALRM, &oact, &act);
    inited = false;
  }
}

static int
nonblockHack (long id)
{
  int opts = fcntl((int)id,F_GETFL);
  if (opts < 0)
  {
    //fprintf (stderr, "SEventBSD:nonblockHack[%d] %s\n", errno, strerror (errno));
    return -1;
  }
  opts = (opts | O_NONBLOCK);
  if (fcntl((int)id,F_SETFL,opts) < 0)
 {
    //fprintf (stderr, "SEventBSD:nonblockHack[%d] %s\n", errno, strerror (errno));
    return -1;
 }
 return 0;
}

