/* Copyright (c) 2001 John E. Davis
 * This file is part of the S-Lang library.
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Perl Artistic License.
 */
#include <stdio.h>
#include <slang.h>

#include <unistd.h>
#include <string.h>
#include <termios.h>
#include <errno.h>

SLANG_MODULE(termios);

static int Termios_Type_Id;

static int check_and_set_errno (int e)
{
#ifdef EINTR
   if (e == EINTR)
     return 0;
#endif
   (void) SLerrno_set_errno (e);
   return -1;
}

static int do_syscall_0 (int (*fun)(int), SLFile_FD_Type *f)
{
   int fd;
   int ret;

   if (-1 == SLfile_get_fd (f, &fd))
     return -1;
   
   while ((-1 == (ret = (*fun) (fd)))
	  && (0 == check_and_set_errno (errno)))
     ;
   
   return ret;
}

static int do_syscall_1 (int (*fun)(int, int), SLFile_FD_Type *f, int arg)
{
   int fd;
   int ret;

   if (-1 == SLfile_get_fd (f, &fd))
     return -1;
   
   while ((-1 == (ret = (*fun) (fd, arg)))
	  && (0 == check_and_set_errno (errno)))
     ;
   
   return ret;
}

static int do_syscall_struct_1 (int (*fun)(int, void *), SLFile_FD_Type *f, void *v)
{
   int fd;
   int ret;

   if (-1 == SLfile_get_fd (f, &fd))
     return -1;
   
   while ((-1 == (ret = (*fun) (fd, v)))
	  && (0 == check_and_set_errno (errno)))
     ;
   
   return ret;
}

static int do_syscall_struct_2 (int (*fun)(int, int, void *), SLFile_FD_Type *f, int i, void *v)
{
   int fd;
   int ret;

   if (-1 == SLfile_get_fd (f, &fd))
     return -1;
   
   while ((-1 == (ret = (*fun) (fd, i, v)))
	  && (0 == check_and_set_errno (errno)))
     ;
   
   return ret;
}

#define DO_SYSCALL_0(fun, f) do_syscall_0((int(*)(int))(fun),(f))
#define DO_SYSCALL_1(fun, f, i) do_syscall_1((int(*)(int,int))(fun),(f),(i))
#define DO_SYSCALL_STRUCT_1(fun, f, s) \
     do_syscall_struct_1((int(*)(int, void*))(fun), (f), (void*)(s))
#define DO_SYSCALL_STRUCT_2(fun, f, i, s) \
     do_syscall_struct_2((int(*)(int, int, void*))(fun), (f), (i), (void*)(s))


static int tcdrain_intrin (SLFile_FD_Type *f)
{
   return DO_SYSCALL_0 (tcdrain, f);
}

static int tcflow_intrin (SLFile_FD_Type *f, int *action)
{
   return DO_SYSCALL_1 (tcflow, f, *action);
}

static int tcflush_intrin (SLFile_FD_Type *f, int *action)
{
   return DO_SYSCALL_1 (tcflush, f, *action);
}

static int tcgetpgrp_intrin (SLFile_FD_Type *f)
{
   return DO_SYSCALL_0 (tcgetpgrp, f);
}

static int tcsetpgrp_intrin (SLFile_FD_Type *f, int *id)
{
   return DO_SYSCALL_1 (tcgetpgrp, f, *id);
}

static int tcsendbreak_intrin (SLFile_FD_Type *f, int *action)
{
   return DO_SYSCALL_1 (tcsendbreak, f, *action);
}

static void destroy_termios (unsigned char type, VOID_STAR f)
{
   (void) type;
   SLfree ((char *) f);
}

static SLang_MMT_Type *allocate_termios (struct termios *s)
{
   struct termios *s1;
   SLang_MMT_Type *mmt;

   s1 = (struct termios *) SLmalloc (sizeof (struct termios));
   if (s1 == NULL)
     return NULL;
   
   memcpy (s1, s, sizeof (struct termios));
   if (NULL == (mmt = SLang_create_mmt (Termios_Type_Id, (VOID_STAR) s1)))
     SLfree ((char *) s1);
   return mmt;
}

static void tcgetattr_intrin (SLFile_FD_Type *f)
{
   struct termios s;
   SLang_MMT_Type *mmt;

   if (-1 == DO_SYSCALL_STRUCT_1(tcgetattr,f,&s))
     {
	SLang_push_null ();
	return;
     }
   
   mmt = allocate_termios (&s);	       /* NULL ok */
   if (-1 == SLang_push_mmt (mmt))
     SLang_free_mmt (mmt);
}

static int tcsetattr_intrin (SLFile_FD_Type *f, int *when, struct termios *s)
{
   return DO_SYSCALL_STRUCT_2(tcsetattr,f,*when,s);
}

static int termios_get_oflag (struct termios *s)
{
   return s->c_oflag;
}
static int termios_get_iflag (struct termios *s)
{
   return s->c_iflag;
}
static int termios_get_cflag (struct termios *s)
{
   return s->c_cflag;
}
static int termios_get_lflag (struct termios *s)
{
   return s->c_lflag;
}

static void termios_get_cc (struct termios *s)
{
   SLang_Array_Type *at;
   int dims = NCCS;
   int i;
   unsigned char *at_data;

   at = SLang_create_array (SLANG_UCHAR_TYPE, 0, NULL, &dims, 1);
   if (at == NULL)
     return;
   at_data = (unsigned char *) at->data;

   for (i = 0; i < NCCS; i++)
     at_data[i] = (unsigned char) s->c_cc[i];
   
   (void) SLang_push_array (at, 1);
}


static void termios_set_oflag (struct termios *s, int *flag)
{
   s->c_oflag = *flag;
}
static void termios_set_iflag (struct termios *s, int *flag)
{
   s->c_iflag = *flag;
}
static void termios_set_cflag (struct termios *s, int *flag)
{
   s->c_cflag = *flag;
}
static void termios_set_lflag (struct termios *s, int *flag)
{
   s->c_lflag = *flag;
}

static void termios_set_cc (void)
{
   SLang_Array_Type *at;
   SLang_MMT_Type *mmt;
   struct termios *s;
   unsigned char *at_data;
   int i;

   if (-1 == SLang_pop_array_of_type (&at, SLANG_UCHAR_TYPE))
     return;
   if (NULL == (mmt = SLang_pop_mmt (Termios_Type_Id)))
     goto free_and_return;

   s = SLang_object_from_mmt (mmt);
   if (at->num_elements != NCCS)
     {
	SLang_verror (SL_TYPE_MISMATCH, 
		      "Expecting UChar_Type[%d]", NCCS);
	goto free_and_return;
     }

   at_data = (unsigned char *) at->data;
   for (i = 0; i < NCCS; i++)
     s->c_cc[i] = at_data[i];

   /* drop */

   free_and_return:
   SLang_free_array (at);
   SLang_free_mmt (mmt);
}


static int termios_dereference (unsigned char type, VOID_STAR addr)
{
   struct termios *s;
   SLang_MMT_Type *mmt;
   
   (void) type;
   mmt = *(SLang_MMT_Type **) addr;
   if (NULL == (s = SLang_object_from_mmt (mmt)))
     return -1;
   
   mmt = allocate_termios (s);
   if (-1 == SLang_push_mmt (mmt))
     {
	SLang_free_mmt (mmt);
	return -1;
     }
   
   return 0;
}

   
#define DUMMY_TERMIOS_TYPE 255
#define T DUMMY_TERMIOS_TYPE
#define F SLANG_FILE_FD_TYPE
#define I SLANG_INT_TYPE
#define V SLANG_VOID_TYPE
static SLang_Intrin_Fun_Type Termios_Intrinsics [] =
{
   MAKE_INTRINSIC_1("tcdrain", tcdrain_intrin, I, F),
   MAKE_INTRINSIC_2("tcflow", tcflow_intrin, I, F, I),
   MAKE_INTRINSIC_2("tcflush", tcflush_intrin, I, F, I),
   MAKE_INTRINSIC_1("tcgetpgrp", tcgetpgrp_intrin, I, F),
   MAKE_INTRINSIC_2("tcsetpgrp", tcsetpgrp_intrin, I, F, I),
   MAKE_INTRINSIC_2("tcsendbreak", tcsendbreak_intrin, I, F, I),
   MAKE_INTRINSIC_1("tcgetattr", tcgetattr_intrin, V, F),
   MAKE_INTRINSIC_3("tcsetattr", tcsetattr_intrin, I, F, I, T),
   MAKE_INTRINSIC_1("termios_get_oflag", termios_get_oflag, I, T),
   MAKE_INTRINSIC_1("termios_get_iflag", termios_get_iflag, I, T),
   MAKE_INTRINSIC_1("termios_get_cflag", termios_get_cflag, I, T),
   MAKE_INTRINSIC_1("termios_get_lflag", termios_get_lflag, I, T),
   MAKE_INTRINSIC_1("termios_get_cc", termios_get_cc, V, T),
   MAKE_INTRINSIC_2("termios_set_oflag", termios_set_oflag, V, T, I),
   MAKE_INTRINSIC_2("termios_set_iflag", termios_set_iflag, V, T, I),
   MAKE_INTRINSIC_2("termios_set_cflag", termios_set_cflag, V, T, I),
   MAKE_INTRINSIC_2("termios_set_lflag", termios_set_lflag, V, T, I),
   MAKE_INTRINSIC_0("termios_set_cc", termios_set_cc, V),
   
   SLANG_END_INTRIN_FUN_TABLE
};
#undef T
#undef I
#undef F
#undef V

static SLang_IConstant_Type Termios_Consts [] =
{
   MAKE_ICONSTANT("TCOOFF", TCOOFF),
   MAKE_ICONSTANT("TCOON", TCOON),
   MAKE_ICONSTANT("TCIOFF", TCIOFF),
   MAKE_ICONSTANT("TCION", TCION),
   MAKE_ICONSTANT("TCIFLUSH", TCIFLUSH),
   MAKE_ICONSTANT("TCOFLUSH", TCOFLUSH),
   MAKE_ICONSTANT("TCIOFLUSH", TCIOFLUSH),
   MAKE_ICONSTANT("TCSANOW", TCSANOW),
   MAKE_ICONSTANT("TCSADRAIN", TCSADRAIN),
   MAKE_ICONSTANT("TCSAFLUSH", TCSAFLUSH),		    
   MAKE_ICONSTANT("BRKINT", BRKINT),
   MAKE_ICONSTANT("IGNBRK", IGNBRK),
   MAKE_ICONSTANT("IGNPAR", IGNPAR),
   MAKE_ICONSTANT("PARMRK", PARMRK),
   MAKE_ICONSTANT("INPCK", INPCK),
   MAKE_ICONSTANT("ISTRIP", ISTRIP),
   MAKE_ICONSTANT("INLCR", INLCR),
   MAKE_ICONSTANT("IGNCR", IGNCR),
   MAKE_ICONSTANT("ICRNL", ICRNL),
   MAKE_ICONSTANT("IXON", IXON),
   MAKE_ICONSTANT("IXOFF", IXOFF),
   MAKE_ICONSTANT("CLOCAL", CLOCAL),
   MAKE_ICONSTANT("CREAD", CREAD),
   MAKE_ICONSTANT("CSIZE", CSIZE),
   MAKE_ICONSTANT("CSTOPB", CSTOPB),
   MAKE_ICONSTANT("HUPCL", HUPCL),
   MAKE_ICONSTANT("PARENB", PARENB),
   MAKE_ICONSTANT("PARODD", PARODD),
   MAKE_ICONSTANT("ECHO", ECHO),
   MAKE_ICONSTANT("ECHOE", ECHOE),
   MAKE_ICONSTANT("ECHOK", ECHOK),
   MAKE_ICONSTANT("ECHONL", ECHONL),
   MAKE_ICONSTANT("ICANON", ICANON),
   MAKE_ICONSTANT("ISIG", ISIG),
   MAKE_ICONSTANT("NOFLSH", NOFLSH),
   MAKE_ICONSTANT("TOSTOP", TOSTOP),
   MAKE_ICONSTANT("IEXTEN", IEXTEN),
   MAKE_ICONSTANT("VEOF", VEOF),
   MAKE_ICONSTANT("VEOL", VEOL),
   MAKE_ICONSTANT("VERASE", VERASE),
   MAKE_ICONSTANT("VINTR", VINTR),
   MAKE_ICONSTANT("VKILL", VKILL),
   MAKE_ICONSTANT("VQUIT", VQUIT),
   MAKE_ICONSTANT("VSUSP", VSUSP),
   MAKE_ICONSTANT("VSTART", VSTART),
   MAKE_ICONSTANT("VSTOP", VSTOP),
#ifdef ultrix   /* Ultrix gets _POSIX_VDISABLE wrong! */
# define NULL_VALUE -1
#else
# ifdef _POSIX_VDISABLE
#  define NULL_VALUE _POSIX_VDISABLE
# else
#  define NULL_VALUE 255
# endif
#endif
   MAKE_ICONSTANT("VDISABLE", NULL_VALUE),

   SLANG_END_ICONST_TABLE
};

static void patchup_intrinsic_table (SLang_Intrin_Fun_Type *table, 
				     unsigned char dummy, unsigned char type)
{
   while (table->name != NULL)
     {
	unsigned int i, nargs;
	unsigned char *args;
	
	nargs = table->num_args;
	args = table->arg_types;
	for (i = 0; i < nargs; i++)
	  {
	     if (args[i] == dummy)
	       args[i] = type;
	  }
	
	/* For completeness */
	if (table->return_type == dummy)
	  table->return_type = type;

	table++;
     }
}


static int register_termios_type (void)
{
   SLang_Class_Type *cl;

   if (NULL == (cl = SLclass_allocate_class ("Termios_Type")))
     return -1;

   if (-1 == SLclass_set_destroy_function (cl, destroy_termios))
     return -1;
   cl->cl_dereference = termios_dereference;

   /* By registering as SLANG_VOID_TYPE, slang will dynamically allocate a
    * type.
    */
   if (-1 == SLclass_register_class (cl, SLANG_VOID_TYPE, sizeof (struct termios), SLANG_CLASS_TYPE_MMT))
     return -1;

   Termios_Type_Id = SLclass_get_class_id (cl);
   patchup_intrinsic_table (Termios_Intrinsics, DUMMY_TERMIOS_TYPE, Termios_Type_Id);

   return 0;
}

int init_termios_module_ns (char *ns_name)
{
   SLang_NameSpace_Type *ns;
   
   ns = SLns_create_namespace (ns_name);
   if (ns == NULL)
     return -1;

   if (-1 == register_termios_type ())
     return -1;

   if ((-1 == SLns_add_intrin_fun_table (ns, Termios_Intrinsics, "__TERMIOS__"))
       || (-1 == SLns_add_iconstant_table (ns, Termios_Consts, NULL)))
     return -1;

   return 0;
}

/* This function is optional */
void deinit_termios_module (void)
{
}
