/*
 *  Copyright (c) 1992, 1994 John E. Davis  (davis@amy.tch.harvard.edu)
 *  All Rights Reserved.
 */
#include <stdio.h>
#include "config.h"
#include "sysdep.h"

#include <ssdef.h>
#include <rmsdef.h>
#include <dvidef.h>
#include <jpidef.h>
#include <descrip.h>
#include <iodef.h>
#include <ttdef.h>
#include <rms.h>
#include <errno.h>

/* #include <libdef.h>  */
/* #include <unixlib.h> */

typedef struct {                /* I/O status block     */
        short i_cond;           /* Condition value      */
        short i_xfer;           /* Transfer count     */
        long  i_info;           /* Device information     */
} iosb;

typedef struct {                /* Terminal characteristics   */
        char  t_class;          /* Terminal class     */
        char  t_type;           /* Terminal type      */
        short t_width;          /* Terminal width in characters   */
        long  t_mandl;          /* Terminal's mode and length   */
        long  t_extend;         /* Extended terminal characteristics  */
}  termchar;

short TTY_CHANNEL_GLOBAL;


typedef struct
{
   short buflen;
   short item_code;
   int *buf_addr;
   int *len_addr;
} item_list_3;
   
static int tty_inited;

void vms_exit_handler(int not_used)
{
   if (tty_inited == 0) exit(0);

   auto_save_all();
   reset_display(); 
   reset_tty(); 
   exit(1);
}

/*
 *      Exit Handler Control Block
 */
static struct argument_block
  {
      int forward_link;
      int (*exit_routine)();
      int arg_count;
      int *status_address;
      int exit_status;
  }
exit_block =
  {
      0,
      NULL,
      1,
      &exit_block.exit_status,
      0
  };

void vms_cancel_exithandler()
{
   SYS$CANEXH(exit_block);
}



int vms_input_buffer;

static struct vms_ast_iosb
{
    short status;
    short offset;
    short termlen;
    short term;
} vms_ast_iosb;

extern void vms_que_key_ast();
static int vms_event_flag;
static int timer_event_flag;
static int event_flag_mask;
static int ast_stop_input = 0;
static int waiting_for_ast;

int getkey_ast(int x)
{
   unsigned int c = 1000;
/*  moved to end 
   if (waiting_for_ast)  SYS$SETEF (vms_event_flag);
   waiting_for_ast = 0;
*/
   if (vms_ast_iosb.offset)
     {
	c = (unsigned int) vms_input_buffer;
     }

   if (c <= 255)
     {
	if (c == Abort_Char)
	  {
	     if (Ignore_User_Abort == 0) SLang_Error = 2;
	     KeyBoard_Quit = 1;
	  }
	
	while (Input_Buffer_Active);
	Input_Buffer_Active = 1;

	if (INPUT_BUFFER_LEN < MAX_INPUT_BUFFER_LEN - 3) 
	  INPUT_BUFFER[INPUT_BUFFER_LEN++] = c;
	
	Input_Buffer_Active = 0;
     }
   if (waiting_for_ast)  SYS$SETEF (vms_event_flag);
   waiting_for_ast = 0;
   vms_que_key_ast();
   return (1);
}

void vms_que_key_ast()
{
   static int trmmsk [2] = { 0, 0 };
   int status;

   if (ast_stop_input) return;
   status = SYS$QIO (0, TTY_CHANNEL_GLOBAL,
		     IO$_READVBLK + IO$M_NOECHO | IO$_TTYREADALL,
		     &vms_ast_iosb, getkey_ast, 1,
		     &vms_input_buffer, 1, 0, trmmsk, 0, 0);
}

static char TTY_Name[8];
static int This_Process_Pid;

void init_tty()
{
   int tmp, name_len, status, lastppid, ppid;
   item_list_3 itmlst[] =
     {
	{sizeof(int), JPI$_PID, &This_Process_Pid, &tmp},
	{7, JPI$_TERMINAL, (int *) TTY_Name, &name_len},
	{0, 0, 0, 0}
     };
   $DESCRIPTOR ( Term, TTY_Name);
   
   tty_inited = 1;
   ppid = 0, lastppid = -1;
   
   /* Here I get this process pid then I get the master process pid
      and use the controlling terminal of that process. */
   while (1)
     {
	status = SYS$GETJPIW(0,       /* event flag */
			     &ppid,   /* pid address */
			     0,       /* proc name address */
			     itmlst,
			     0, 0, 0);

	if (status != SS$_NORMAL) 
	  {
	     fprintf(stderr, "PID: %X, status: %X\n", This_Process_Pid, status);
	     exit(1);
	  }

	if (lastppid == ppid) break;
	lastppid = ppid;

	itmlst[0].item_code =  JPI$_MASTER_PID;
	itmlst[0].buf_addr =  &ppid;
     }
   
   if (Batch) return;
   if (X_Init_Term_Hook != NULL)
     {
	(void) (*X_Init_Term_Hook) ();
	return;
     }
   
   Term.dsc$w_length = name_len;
   status = sys$assign ( &Term, &TTY_CHANNEL_GLOBAL, 0, 0 );
   if (status != SS$_NORMAL)
     {
	fprintf(stderr,"Unable to assign input channel\n");
	fprintf(stderr,"PID: %X, DEV %s, status: %d\n", This_Process_Pid, TTY_Name, status);
	exit(0);
     }

   if (NULL == exit_block.exit_routine)
     {
	exit_block.exit_routine = (int (*)()) vms_exit_handler;
	SYS$DCLEXH(&exit_block);
     }

   /* allocate an event flag and clear it--- used by ast routines */
   if (!vms_event_flag) LIB$GET_EF (&vms_event_flag);
   SYS$CLREF (vms_event_flag);
   if (!timer_event_flag) LIB$GET_EF (&timer_event_flag);
   SYS$CLREF (timer_event_flag);
   event_flag_mask = ((unsigned) 1 << (vms_event_flag % 32));
   event_flag_mask |= ((unsigned) 1 << (timer_event_flag % 32));

   waiting_for_ast = 0;
   ast_stop_input = 0;
   
   /* Enable the Application Keypad */
   send_string_to_term("\033=\033[?1l");   /* application keys/cursor keys */
   vms_que_key_ast();   /* set up the key ast */
}

void sys_flush(int fd)
{
}

static int keypad_state = 0;
void reset_tty()
{

   if (Batch) return;
   if (!tty_inited) return;
   tty_inited = 0;
   if (X_Init_Term_Hook != NULL)
     {
	if (X_Reset_Term_Hook != NULL) (*X_Reset_Term_Hook) ();
	return;
     }
   if (keypad_state) send_string_to_term("\033=\033[?1l");
   else send_string_to_term("\033>");
}

unsigned char sys_getkey()
{
   unsigned char c;
   int tsecs;

   if (KeyBoard_Quit) return((int) Abort_Char);
   
   /* Under DECWIndows, I do not know how to timeout so this will have to do
      for now */
   if (X_Read_Hook != NULL) return (c = X_Read_Hook ());

  /* On VMS, the keyboard ast routine should be stuffing the buffer, so
   do nothing except sleep */

   /* clear the flag which ast will set */
   waiting_for_ast = 0;
   if (INPUT_BUFFER_LEN) return(my_getkey());
   tsecs = 450;   /* 45 seconds */
   
   while (!input_pending(&tsecs))
     {
	/* update status line incase user is displaying time */
	if (Display_Time)
	  {
	     JWindow->trashed = 1;
	     update((Line *) NULL, 0, 1);
	  }
     }
   c = my_getkey();
   return(c);
}

/* waits *secs tenth of seconds for input */
int input_pending(int *secs)
{
   unsigned long daytim[2];
   
   if (Batch) return(0);
   if (INPUT_BUFFER_LEN) return(INPUT_BUFFER_LEN);
   
   if (X_Input_Pending_Hook != NULL) 
     {
	if ((*X_Input_Pending_Hook) ()) return 1;
	/* I need to make this work with DECWIndows */
	return 0;
     }
   
   
   if (*secs) 
     {
	/* takes a quad word time.  If negative, use a relative time. */
	daytim[1] = 0xFFFFFFFF;
	daytim[0] = -(*secs * 1000 * 1000);   /* 1000 * 1000 is a tenth of a sec */
	
	SYS$CLREF (vms_event_flag);
	/* SYS$CLREF (timer_event_flag);  SYS$SETIMR call clears this */

	/* set up a flag for the ast so it knows to set the event flag */
	waiting_for_ast = 1;
	
	SYS$SETIMR(timer_event_flag, daytim, 0, 1);
		   
	/* this will return when ast does its job or timer expires */
	SYS$WFLOR (vms_event_flag, event_flag_mask);
	waiting_for_ast = 0;
	
	/* cancel the timer */
	SYS$CANTIM(1, 3);   /* 3 is user mode */
     }
   return (INPUT_BUFFER_LEN);
}

/*  This is to get the size of the terminal  */
void get_term_dimensions(int *cols, int *rows)
{
   int status, junk;
   iosb iostatus;
   $DESCRIPTOR(devnam, TTY_Name);
   item_list_3 itmlst[] = 
     {
	{sizeof(*rows), DVI$_TT_PAGE, rows, &junk},
	{sizeof(*cols), DVI$_DEVBUFSIZ, cols, &junk},
	{sizeof(keypad_state), DVI$_TT_APP_KEYPAD, &keypad_state, &junk},
	{0, 0, 0, 0}		       /* end of list */
     };

   if (X_Get_Term_Size_Hook != NULL)
     {
	(*X_Get_Term_Size_Hook)(cols, rows);
	return;
     }
   
   if (*rows <= 0) *rows = Screen_Height;
   if (*cols <= 0) *cols = Screen_Width;

   /* Get current terminal characteristics */
   status = sys$getdviw(0,           /* Wait on event flag zero  */
			0,           /* Channel to input terminal  */
			&devnam,     /* device name */
			&itmlst,	  /* Item descriptor List */
			&iostatus,   /* Status after operation */
			0, 0,        /* No AST service   */
			0);          /* nullarg */

   if (status&1) status = iostatus.i_cond;
   /* Jump out if bad status */
   if ((status & 1) == 0) exit(status);
}

/* returns 0 on failure, 1 on sucess */
int sys_delete_file(char *filename)
{
    return (1 + delete(filename));   /* 0: sucess; -1 failure */
}

int sys_rename(char *from, char *to)
{
    return(-1);
}

/* This routine converts unix type names to vms names */
int locate(char ch, char *string)
{
    int i;
    char c;

    i = 0;
    while (c = string[i++], (c != ch) && (c != '\0'));
    if (c == ch) return(i); else return (0);
}

char *unix2vms(char *file)
{
    int i,device,j,first,last;
    static char vms_name[80];
    char ch;

    if (locate('[',file)) return(file); /* vms_name syntax */
    if (!locate('/',file)) return(file); /* vms_name syntax */

    /* search for the ':' which means a device is present */
    device = locate(':',file);

    i = 0;
    if (device)
      {
          while (ch = file[i], i < device) vms_name[i++] = ch;
      }
    j = i;

    /* go from the  end looking for a '/' and mark it */
    i = strlen(file) - 1;
    while(ch = file[i], ch != '/' && i-- >= 0);
    if (ch == '/')
      {
          file[i] = ']';
          last = 0;
      }
    else last = 1;

    i = j;
    vms_name[j++] = '[';
    vms_name[j++] = '.';
    first = 0;
    while(ch = file[i++], ch != '\0')
      {
          switch (ch)
            {
              case '.':
                if (last) vms_name[j++] = '.';
                if (last) break;
                ch = file[i++];
                if (ch == '.')
                  {
                      if (!first) j--;  /* overwrite the dot */
                      vms_name[j++] = '-';
                  }
                else if (ch == '/'); /*  './' combinations-- do nothing */
                else if (ch == ']')
                  {
                      last = 1;
                      if (vms_name[j-1] == '.') j--;
                      vms_name[j++] = ']';
                  }

                else vms_name[j++] = '.';
                break;
              case '/':
                if (first)
                  {
                      vms_name[j++] = '.';
                  }
                else
                  {
                      first = 1;
                      /* if '/' is first char or follows a colon do nothing */
                      if ((i!=1) && (file[i-2] != ':'))
                        {
                            vms_name[j++] = '.';
                        }
                      else j--; /* overwrite the '.' following '[' */
                  }
                break;
              case ']':
                last = 1;
                if (vms_name[j-1] == '.') j--;
                vms_name[j++] = ']';
                break;
              default:
                vms_name[j++] = ch;
            }
      }
    return (vms_name);
}

/* these two from emacs source */
void define_logical_name (char *varname, char *string)
{
    struct dsc$descriptor_s strdsc =
      {strlen (string), DSC$K_DTYPE_T, DSC$K_CLASS_S, string};
    struct dsc$descriptor_s envdsc =
      {strlen (varname), DSC$K_DTYPE_T, DSC$K_CLASS_S, varname};
    struct dsc$descriptor_s lnmdsc =
      {7, DSC$K_DTYPE_T, DSC$K_CLASS_S, "LNM$JOB"};

    LIB$SET_LOGICAL (&envdsc, &strdsc, &lnmdsc, 0, 0);
}

void delete_logical_name (char *varname)
{
    struct dsc$descriptor_s envdsc =
      {strlen (varname), DSC$K_DTYPE_T, DSC$K_CLASS_S, varname};
    struct dsc$descriptor_s lnmdsc =
      {7, DSC$K_DTYPE_T, DSC$K_CLASS_S, "LNM$JOB"};

    LIB$DELETE_LOGICAL (&envdsc, &lnmdsc);
}

int do_attach_cmd()
{
   unsigned long pid;
   char *pidstr;

   if((pidstr = getenv("JED_ATTACH_TO")) != NULL)
     {
	delete_logical_name("JED_ATTACH_TO");
	(void) sscanf(pidstr,"%X",&pid);
	if (lib$attach(&pid) == SS$_NORMAL)
	  return(1);
        else
          return(0);
     }
   else return(0);
}

unsigned long SHELL_PID = 0;

/* here we try to attach to the parent otherwise just spawn a new one */
void sys_suspend()
{
   unsigned long parent_pid;
   unsigned long status = 0;
   char str[80];

   /* stop the keyboard ast */
   SYS$SETAST (0);
   SYS$CLREF (vms_event_flag);
   waiting_for_ast = 1;
   ast_stop_input = 1;

   /* cancel all i/o on this channel */
   SYS$CANCEL (TTY_CHANNEL_GLOBAL);
   SYS$SETAST (1);
   SYS$WAITFR (vms_event_flag);
   waiting_for_ast = 0;

   parent_pid = getppid();

   /* try to attach to different process */
   if (do_attach_cmd()) status = SS$_NORMAL;

   else if (parent_pid && parent_pid != 0xffffffff)
   /* we attach to parent */
      status = lib$attach(&parent_pid);

   else if (SHELL_PID && SHELL_PID != 0xffffffff)
   /* try to attach to previous shell */
      status = lib$attach (&SHELL_PID);

   if (status != SS$_NORMAL)		/* others fail so spawn a new shell */
     {
	status = 0;
	send_string_to_term("Spawning Subprocess...\n");
	fflush(stdout);
	status = lib$spawn(0,0,0,0,0,&SHELL_PID,0);
	/* if we attach back, status may come back unchanged */
	if (!(status & 1))  /* Thanks to Hunter Goatley for this suggestion */
	  {              
	     sprintf(str,"Unable to spawn subprocess. Error = X%X (%d)", status, status);
	     msg_error(str);
	     return;
	  }
     }

/*   if((file = getenv("JED_FILE_NAME")) != NULL)
     {
	find_file_cmd(file);
	delete_logical_name ("JED_FILE_NAME");
     } */
}

/* returns 0 on success, -1 on syntax error
   -2 on bad dir, -3 on bad device, 1 if something else */
int vms_parse_file(char *old)
{
   struct FAB fab = cc$rms_fab;
   struct NAM nam = cc$rms_nam;
   char neew[256];
   int status;

   fab.fab$l_fna = old;
   fab.fab$b_fns = strlen(old);
   fab.fab$b_dns = 0;
   fab.fab$w_ifi = 0;
   fab.fab$l_nam = &nam;
   nam.nam$l_esa = neew;
   nam.nam$b_ess = 255;
   nam.nam$b_nop = NAM$V_SYNCHK;   /* syntax only */
   nam.nam$l_rlf = 0;

   status = sys$parse(&fab);
   neew[nam.nam$b_esl] = 0;
   strcpy(old, neew);
   while(*old != 0)
     {
	if ((*old == ';') && (*(old + 1) == 0)) *old = 0;
	else
	  {
	     if ((*old >= 'A') && (*old <= 'Z')) *old |= 0x20;
	     old++;
	  }
     }

   switch(status)
     {
	case RMS$_NORMAL: return 0;
	case RMS$_SYN: return -1;
	case RMS$_DNF: return -2;
	case RMS$_DEV: return -3;
	return 1;
     }
}

char *expand_filename(char *file)
{
   char *p, *p1;
   static char work[300];
   int len;

   strcpy(work, get_cwd()); strcat(work, file); file = work;

    /*  start at end and look for ']' then look for ':' */
    if (0 == (len = strlen(file))) return(file);
    p = file + (len - 1);
    while (p >= file) if (*p-- == ':')
      {
	 while((p >= file) && (*p != ':') && (*p != ']')) p--;
	 p++;
	 p1 = file;
         while(*p) *p1++ = *p++;
	 *p1 = 0;
	 break;
      }

    /* now remove "][" combinations */
    p = p1 = file;
    while (*p != 0)
      {
	 *p1 = *p;

	 if ((*p == ']') && (*(p + 1) == '['))
	   {
	      *p1 = '.';
	      p++;
	   }
	 p1++;
	 p++;
      }
    *p1 = 0;
    switch(vms_parse_file(file))
      {
	 case -1: msg_error("Filename syntax error."); break;
	 case -2: msg_error("Directory does not exist!");
	          msg_error (file); break;
	 case -3: msg_error("Bad device name."); break;
	 case 0: break;
	 default: msg_error("Unknown filename error!");
      }
   p = file;
   while(*p != 0) if (*p++ == ']')
     {
	 if ((*p == '.') && (*(p + 1) == 0)) *p = 0;
         break;
      }
   return(file);

}

int vms_expand_filename(char *file,char *expanded_file)
{
    static int context = 0;
    static char inputname[256] = "";
    $DESCRIPTOR(file_desc,inputname);
    $DESCRIPTOR(default_dsc,"SYS$DISK:[]*.*;");
    static struct dsc$descriptor_s  result =
	    {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, NULL};

    if (strcmp(inputname, file))
      {
	  if (context)
	    {
		lib$find_file_end(&context);
	    }
	  context = 0;
	  strcpy(inputname, file);
	  file_desc.dsc$w_length = strlen(inputname);
      }

    if (RMS$_NORMAL == lib$find_file(&file_desc,&result,&context,
	           		     &default_dsc,0,0,&Number_Zero))
      {
	  MEMCPY(expanded_file, result.dsc$a_pointer, result.dsc$w_length);
	  expanded_file[result.dsc$w_length] = '\0';
          return (1);
      }
    else
      {
          /* expanded_file[0] = '\0'; */      /* so file comes back as zero width */
          return(0);
      }
}

static int context = 0;

static char inputname[256] = "";
$DESCRIPTOR(file_desc,inputname);
$DESCRIPTOR(default_dsc,"SYS$DISK:[]*.*;");

static char *VMS_Star;

int sys_findnext(char *file)
{
   char *f;
   static struct dsc$descriptor_s  result = {0, DSC$K_DTYPE_T, DSC$K_CLASS_D, NULL};

   if (RMS$_NORMAL == lib$find_file(&file_desc,&result,&context,
				    &default_dsc,0,0,&Number_Zero))
     {
	MEMCPY(file, result.dsc$a_pointer, result.dsc$w_length);
	file[result.dsc$w_length] = 0;
	f = &file[result.dsc$w_length];
	if (*VMS_Star)
	  {
	     while ((f > file) && (*f != ';')) f--;
	     if (*f == ';') *f = 0;
	  }

	return (1);
     }
   else return(0);
}

int sys_findfirst(char *thefile)
{
   char *f, *f1, *s2 = "*.*";
   char *file;

   file = thefile;
   f = extract_file(file);

   while (*f && (*f != '*')) f++;

   VMS_Star = "";

   /* let user choose what to match with or take mine */
   if (! *f)
     {
	f = extract_file(file);
	while (*f && (*f != '.')) f++;
	if (*f) VMS_Star = "*"; else VMS_Star = s2;

	file = expand_filename(thefile);
	f = f1 = extract_file(file);
	if (VMS_Star == s2)
	  {
	     while (*f && (*f != '.')) f++;
	     *f = 0;
	  }
	else
	  {
	     while (*f1 && (*f1 != ';')) f1++;
	     *f1 = 0;
	  }
	strcpy(thefile, file);
     }

   strcpy(inputname, file);
   if (*VMS_Star) strcat(inputname, VMS_Star);
   file_desc.dsc$w_length = strlen(inputname);

   if (context) lib$find_file_end(&context);
   context = 0;
   return sys_findnext(thefile);
}

#include <stat.h>

/* returns 0 if file does not exist, 1 if it is not a dir, 2 if it is */
int sys_chmod(char *file, int what, int *mode, short *uid, short *gid)
{
   struct stat buf;
   int m;

   if (stat(file, &buf) < 0) switch (errno)
     {
	case EACCES: return(-1); /* es = "Access denied."; break; */
	case ENOENT: return(0);  /* ms = "File does not exist."; */
	case ENOTDIR: return(-2); /* es = "Invalid Path."; */
	default: return(-3); /* "stat: unknown error."; break;*/
     }

   m = buf.st_mode;
   (void) uid; (void) gid;

   *mode = m & 0777;

   if (m & S_IFDIR) return (2);
   return(1);
}

#if 0
   switch(vms_parse_file(file))
     {
	case 0: break;
	case -1:		       /* Filename syntax error. */
	case -2:   return(-2);	       /* Directory does not exist! */
	case 1:
	case -3:   return(-3);	       /* Bad device name. */
      default: return(-3);
     }
   return(1);


#endif

unsigned long sys_file_mod_time(char *file)
{
   struct stat buf;

   if (stat(file, &buf) < 0) return(0);
   return((unsigned long) buf.st_mtime);
}


int rmdir (char *d)
{
   return(-1);
}


