#include "dosstream.h"
#include <iostream.h>
#include <streambuf.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <wm_lib/system.h>
#include <string.h>

#include "client.h"
#include "daemon.h"

LOCAL const key_t key_base = 6661000;
LOCAL const size_t DEF_SIZE = 4096;

struct daemonbuf: public streambuf {
  // data
  key_t read_key, write_key;  // key for shm area of read, write
  int read_id, write_id;
  uint32 read_pos, write_pos; // position in dos file
  uint16 handle;
  
  // public functions
  daemonbuf (void);
  virtual ~daemonbuf (void);
  daemonbuf * open (const char * path, int mode);
  daemonbuf * open (char drive, const char * path, int mode);
  daemonbuf * close (void);
  int overflow (int c = EOF);
  int underflow (void);
 private:
  void do_allocate (size_t write_size, size_t read_size);
};
  

dosstreambase::dosstreambase (const char * path, int mode) {
  daemonbuf * tmpbuf = (new daemonbuf)->open (path, mode);
  ios::init (tmpbuf);
}

dosstreambase::dosstreambase (void) {
  ios::init(new daemonbuf());
}

dosstreambase::dosstreambase (char drive, const char * path, int mode) {
  daemonbuf * tmpbuf = (new daemonbuf)->open (drive, path, mode);
  ios::init (tmpbuf);
}

void dosstreambase::open (const char * path, int mode) {
  daemonbuf * tmpbuf = (daemonbuf *)rdbuf();
  tmpbuf->open (path, mode);
}

void dosstreambase::open (char drive, const char * path, int mode) {
  daemonbuf * tmpbuf = (daemonbuf *)rdbuf();
  tmpbuf->open (drive, path, mode);
}

void dosstreambase::close (void) {
  ((daemonbuf *)rdbuf())->close();
}

/****************************************************************************
 *                                                                          *
 * Daemonbuf implementation details                                         *
 *                                                                          *
 ****************************************************************************/

LOCAL void * shmalloc (int & id, key_t & key, size_t size, int flags) {

  do {
    id = shmget (key, size, flags);
    if (id == -1) key++;
  } while (id == -1);

  return (void *)shmat (id, 0, 0);
}
  

// allocate or re-allocate the shm buffer area for the files
// copy the contents of the current buffers into the new ones,
// and flush any writes, or discard any reads that won't fit into 
// the new buffers.
void daemonbuf::do_allocate (size_t write_size, size_t read_size) {

  sync(); // flush any writes
  
  
  int flags = IPC_CREAT | IPC_EXCL | 0666; // rw for ugo
  write_key = key_base;
  read_key = key_base;
  char * buf = shmalloc (write_id, write_key, write_size, flags);

  if (buf == NULL) {
    // nothing we can really do yet
    cerr << "daemonbuf() shmalloc(write) failed!!!" << endl;
  }
  
  // copy the data into the new buf and dispose of the old memory
  if (pbase() != NULL) {
    memcpy (buf, pbase(), min (write_size, epptr() - pbase()));
    shmctl (write_id, IPC_RMID, NULL);
    shmdt (pbase());
  }

  setp (buf, buf + write_size);
  
  buf = shmalloc (read_id, read_key, read_size, flags);

  read_key = key_base;
  if (buf == NULL) {
    // nothing we can really do yet
    cerr << "daemonbuf() shmalloc(read) failed!!!" << endl;
  }
  
  // copy the data into the new buf and dispose of the old memory
  if (Gbase() != NULL) {
    memcpy (buf, Gbase(), min (read_size, (egptr() - Gbase())));
    shmctl (read_id, IPC_RMID, NULL);
    shmdt (Gbase());
  }

  setg (buf, buf, buf + read_size);
  
}

daemonbuf::daemonbuf (void) {
  read_key = write_key = key_base;
  read_id = write_id = -1;
  read_pos = write_pos = -1;
  do_allocate (DEF_SIZE, DEF_SIZE);
}

daemonbuf::~daemonbuf (void) {
  if (handle >= 0)
    close();
  shmctl (write_id, IPC_RMID, NULL);
  shmdt (pbase());
  shmctl (read_id, IPC_RMID, NULL);
  shmdt (Gbase());
}

LOCAL uint16 do_dosopen (int mode) {
  switch (mode) {
   case ios::in:
    LO(ax) = 0;
    do_allocate (0, DEF_SIZE);
    break;
    
   case ios::out:
    LO(ax) = (1 << 0);
    do_allocate (DEF_SIZE, 0);
    break;
    
   case ios::out | ios::in:
    LO(ax) = (1 << 1);
    do_allocate (DEF_SIZE, DEF_SIZE);
    break;
  }
  HI(ax) = 0x3d; // open file
  DOS_DX = dcom.off + offsetof (daemon_comms, buf);
  Daemon_int86 (0x21);  
  // FIXME: check for errors!!!

  return DOS_AX;
}
  
daemonbuf * daemonbuf::open (const char * path, int mode) {
  daemon_comms & dcom = server->daemon_comm;
  strcpy (dcom.buf, path);
  handle = do_dosopen (mode);
  return this;
}

daemonbuf * daemonbuf::open (char drive, const char * path, int mode) {
  daemon_comms & dcom = server->daemon_comm;
  sprintf (dcom.buf, "%c:%s", drive, path);
  handle = do_dosopen(mode);
  return this;
}

daemonbuf * daemonbuf::close (void) {
  sync();
  
  DOS_AX = 0x3e00;
  DOS_BX = handle;
  Daemon_int86 (0x21);
  handle = -1;
}

//  write out any buffers
int daemonbuf::sync() {
  DOS_CMD = DAEMON_GETBUF;
  call_dosemu();
  uint16 dosbuf = DOS_BX;
  uint16 doslen = DOS_CX;
  return 0;
}
