//
// Copyright (C) 1999-2004 Toshikaz Hirabayashi
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// TOSHIKAZ HIRABAYASHI BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
// OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Except as contained in this notice, the name of Toshikaz Hirabayashi shall
// not be used in advertising or otherwise to promote the sale, use or other
// dealings in this Software without prior written authorization from
// Toshikaz Hirabayashi.

#include <WScom.h>
#include <WSDfile.h>
#include <x11/WSDunixExecute.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <WSCbaseList.h>
#include <WSDfileSystem.h>
#include <errno.h>
#ifndef MacOS
#include <sys/poll.h>
#endif //MacOS
#include <x11/WSxcom.h>
#include <WSClocaleSet.h>

WSMFclassInit(WSDunixExecute,WSDexecute);

WSDexecute* _unix_execute_chandler(){
  return new WSDunixExecute;
}
class _unix_execute_init {
  public: _unix_execute_init(){
    WSDexecute::setCreateInstanceHandler((void*)_unix_execute_chandler);
  };
};
static _unix_execute_init  _unix_execute_init_execute;

WSDunixExecute::WSDunixExecute(){
  _id = 0;
  _sem = NULL;
}
WSDunixExecute::~WSDunixExecute(){
  if (_sem != NULL){
    pthread_sem_t* sem = (pthread_sem_t*)_sem;
    _sem = NULL;
    pthread_sem_unlock_np(sem);
  }

  if (_pid != 0){
    killProcess();
  }
  if (_handle != NULL){
    pclose((FILE*)_handle);
    _handle = NULL;
    return;
  }
}
void WSDunixExecute::_callback(WSDunixExecute* obj){
  if (obj->_data_receive != NULL){
    obj->_data_receive(obj);
    WSGIappObjectList()->execUpdate();
  }
//printf("WSDunixExecute::_callback _sem = 0x%x\n",obj->_sem);
  if (obj->_sem != NULL){
//printf("WSDunixExecute::_callback.. unlock and _sem = NULL \n");
    pthread_sem_t* sem = (pthread_sem_t*)obj->_sem;
    obj->_sem = NULL;
    pthread_sem_unlock_np(sem);
  }
}
long WSDunixExecute::execute(char* exename,long mode){
  if (exename == NULL){
    return WS_ERR;
  }
//printf("WSDunixExecute::execute #%s#\n",exename);
  _exe_name = WSGIappFileSystem()->adjustFileName(exename);
  _exe_name.delHeadSpace();
  char* exe_name = _exe_name.getString(WSGIappLocaleSet()->getSystemLocaleEncoding());
  char* dir_name = _dir_name.getString(WSGIappLocaleSet()->getSystemLocaleEncoding());

#ifdef MacOS
  WSCstring tmp1;
  WSCstring tmp2;
  if (WSGIappLocaleSet()->getSystemLocaleEncoding() == WS_EN_UTF8){
extern char* utf8_to_macutf8(char*);
    char* fn2 = utf8_to_macutf8(
       _exe_name.getString(WSGIappLocaleSet()->getSystemLocaleEncoding()));
    if (fn2 != NULL){
      tmp1.setString(fn2);
      delete fn2;
      exe_name = tmp1.getString();
    }

    char* fn3 = utf8_to_macutf8(
       _dir_name.getString(WSGIappLocaleSet()->getSystemLocaleEncoding()));
    if (fn3 != NULL){
      tmp2.setString(fn3);
      delete fn3;
      dir_name = tmp2.getString();
    }
  }
#endif

  if (strcmp((char*)dir_name,"")){
    int ret = chdir((char*)dir_name);
    if (ret != 0){
      return WS_ERR;
    }
  }
//printf("WSDunixExecute::execute2 #%s#\n",(char*)_exe_name);
  if (mode == WS_EXEC_RDWR || mode == WS_EXEC_RD){
    if (mode == WS_EXEC_RD){
      _handle = popen(exe_name,"r");
    }else if (mode == WS_EXEC_WR){
      _handle = popen(exe_name,"w");
    }else{
      _handle = popen(exe_name,"rw");
    }
    if (_handle == NULL){
      return WS_ERR;
    }
    if (mode != WS_EXEC_WR && _data_receive){
//      fcntl(fileno((FILE*)_handle),F_SETFL,O_NDELAY);
#if 0
      _xid = XtAppAddInput(WSGIxwinAppDev()->appContext(),
                fileno((FILE*)_handle),
                (void*)(XtInputReadMask | XtInputWriteMask),
                 _callback,(void*)this);
#endif
      pthread_t thr;
      int ret = pthread_create(&thr,NULL,_read_thread_,this);
      if (ret != 0){
        return WS_ERR;
      }
      pthread_detach(thr);
    }
    _mode = mode;
    return WS_NO_ERR;
  }

  long num = _exe_name.getWords();
  if (num == 1){
#if 0
    if (file != NULL){
      struct stat sret;
      if (stat(file,&sret) == -1){
        return WS_ERR;
      }else if ((sret.st_mode & S_IFMT) == S_IFDIR){
        return WS_ERR;
      }else if (!(sret.st_mode & S_IFREG) || access(file,X_OK) != 0){
         return WS_ERR;
      }
    }else{
      return WS_ERR;
    }
#endif
    _pid  = (int)fork();
    if (_pid == 0){
      int ret = execl((char*)exe_name,exename,NULL);
      if (ret == -1){
        exit(127);
      }else{
        exit(0);
      }
    }
  }else if (num > 1){
//printf("file=#%s#\n",(char*)_exe_name);
    WSCstring t1(exe_name);
    char** args = new char*[num+1];
    long i;
    for(i=0; i<num; i++){
      args[i] = WSGFstrdup((char*)t1.getWord(i));
//printf("args[%d]=%s\n",i,args[i]);
    }
    args[num] = NULL;
    _pid  = (int)fork();
    if (_pid == 0){
      int ret = execv(args[0],args);
      if (ret == -1){
        exit(127);
      }else{
        exit(0);
      }
    }
    for(i=0; i<num; i++){
      delete args[i];
    }
    delete args;
    int status;
    waitpid(_pid,&status,WNOHANG);
    if (WEXITSTATUS(status) == 127){
      return WS_ERR;
    }
  }
  return WS_NO_ERR;
}
long WSDunixExecute::killProcess(){
  if (_handle != NULL){
    pclose((FILE*)_handle);
    _handle = NULL;
    _id++;
    return WS_NO_ERR;
  }
  if (_pid == 0){
    _id++;
    return WS_NO_ERR;
  }
  kill(_pid,SIGKILL);
  while(1){
    int dust;
    if ( (int)waitpid(_pid,&dust,0) < 0){
       continue;
    }
    break;
  }
  _pid = 0;
  _id++;
  if (_sem != NULL){
//printf("WSDunixExecute::killProcess.. unlock and _sem = NULL \n");
    pthread_sem_t* sem = (pthread_sem_t*)_sem;
    _sem = NULL;
    pthread_sem_unlock_np(sem);
  }
  return WS_NO_ERR;
}

long WSDunixExecute::read(char* buf,long len){
  if (_mode == WS_EXEC_RD || _mode == WS_EXEC_RDWR){
    if (_handle != NULL){
      int fdno = fileno((FILE*)_handle);
      int ret = ::read(fdno,buf,len);
      return ret;
    }
  }
  return -1;
}
char* WSDunixExecute::gets(char* buf,long len){
//printf("WSDunixExecute::gets here.\n");
  if (_mode == WS_EXEC_RD || _mode == WS_EXEC_RDWR){
    if (_handle != NULL){
      return fgets(buf,len,(FILE*)_handle);
    }
  }
  return 0;
}


long WSDunixExecute::write(char* buf,long len){
  if (_mode == WS_EXEC_WR || _mode == WS_EXEC_RDWR){
    if (_handle != NULL){
      int fdno = fileno((FILE*)_handle);
      return ::write(fdno,buf,len);
    }
  }
  return -1;
}
void* WSDunixExecute::_read_thread_(void* ptr){
  WSDunixExecute* _this = (WSDunixExecute*)ptr;

  void* handle = _this->_handle;

  while(1){
    if (_this->_handle != handle){
      return NULL;
    }
#ifndef MacOS
    WSCulong timeout = 0xffffffff;
    struct pollfd fds;
    fds.fd = fileno((FILE*)_this->_handle);
    fds.events = POLLIN;
    fds.revents = 0;
    long ret = poll( &fds, 1, timeout );

    if (ret <0){
      return NULL;
    }
    if (fds.revents & POLLIN || ret >0){
#else //MacOS
    struct timeval tm;
    tm.tv_sec = 0xffff;
    tm.tv_usec = 0;
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(fileno((FILE*)_this->_handle),&read_fds);
    long ret = select(FD_SETSIZE,&read_fds,NULL,NULL,&tm);
    if (ret < 0){
      continue;
    }
    if (ret == 0){
      continue;
    }
    if (1){
#endif //MacOS
      if (_this->_handle != handle){
        return NULL;
      }
      _read_event_send_type data;
      data.obj = _this;
      data.id = _this->_id;
      if (handle != _this->_handle || 
          data.id != _this->_id){
        return NULL;
      }

      pthread_sem_t* sem = new pthread_sem_t;
      _this->_sem = (void*)sem;
      pthread_sem_init_np(sem);
      pthread_sem_lock_np(sem);

      // TODO send message to the event loop.

      pthread_sem_lock_np(sem);
      pthread_sem_unlock_np(sem);
      pthread_sem_destroy_np(sem);
      delete sem;
    }else{
      break;
    }
  }
  return 0;
}
long WSDunixExecute::getId(){
  return _id;
}


