/*
 *  Copyright (c) 1992, 1994 John E. Davis  (davis@amy.tch.harvard.edu)
 *  All Rights Reserved.
 */
#include <stdio.h>
#include <string.h>
#include <limits.h>
#ifndef sequent
#include <stdlib.h>
#endif


#ifdef unix
#ifndef sequent
# include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#ifdef SYSV
#include <sys/fcntl.h>
#endif
#endif

#ifdef __os2__
#include <fcntl.h>
#include <io.h>
#include <sys/types.h>
#include <sys/stat.h>
 
 typedef struct HOLDFEA *PHOLDFEA;
 PHOLDFEA QueryEAs (char *name);
 int WriteEAs (char *name, PHOLDFEA pHoldFEA);
#endif

#ifdef msdos
#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
#endif
#ifdef __GO32__
#include <fcntl.h>
#endif

/* Was anything missed? */
#ifndef O_RDONLY
#ifdef VMS
#include <file.h>
#else
#include <fcntl.h>
#endif
#endif

#include "config.h"
#include "buffer.h"
#include "file.h"
#include "misc.h"
#include "sysdep.h"
#include "paste.h"
#include "slang.h"
#include "ins.h"
#include "ledit.h"
#include "mem.h"

#if defined (msdos) || defined (__os2_16__)
#define MAX_LINE_LEN 1024
#else
#define MAX_LINE_LEN 64 * 1024
#endif

#ifdef VMS
static int vms_max_rec_size;
#include <stat.h>
#include <rms.h>
static int VMS_write_rfm_fixed;
int vms_stupid_open(char *file)
{
   struct stat s;
   char rat_buf[80], rfm_buf[80], mrs_buf[40], *rfm = "var";
   unsigned short mode = 0, c;

   VMS_write_rfm_fixed = 0;

   strcpy(rfm_buf, "rfm=");
   
   
   if (0 == stat(file, &s))
     {
	strcpy(rat_buf, "rat");
	c = s.st_fab_rat;
	if (c & FAB$M_FTN)  strcat(rat_buf, ",ftn");
	if (c & FAB$M_CR)  strcat(rat_buf, ",cr");
	if (c & FAB$M_PRN)  strcat(rat_buf, ",prn");
	if (c & FAB$M_BLK)  strcat(rat_buf, ",blk");
	if (rat_buf[3] != 0) rat_buf[3] = '='; else *rat_buf = 0;
	
	

	c = s.st_fab_rfm;
	switch(c)
	  {
	   case FAB$C_UDF:  rfm = "udf"; break;
	   case FAB$C_FIX:  
	     rfm = "fix"; 
	     VMS_write_rfm_fixed = 1;
	     break;
	   case FAB$C_VAR:  rfm = "var"; break;
	   case FAB$C_VFC:  rfm = "vfc"; break;
	   case FAB$C_STM:  rfm = "stm"; break;
	   case FAB$C_STMLF:  rfm = "stmlf"; break;
	   case FAB$C_STMCR:  rfm = "stmcr"; break;
	  }
	mode = s.st_mode & 0777;
     }
   else strcpy (rat_buf, "rat=cr");
   
   strcat(rfm_buf, rfm);
   
   if (vms_max_rec_size <= 0) vms_max_rec_size = 255;
   sprintf(mrs_buf, "mrs=%d", vms_max_rec_size);
      
   if (*rfm == 's')		       /* stream */
     {
	return creat(file, mode, rfm_buf);
     }
   else
     {
	if (*rat_buf) return creat(file, mode, rfm_buf, mrs_buf, rat_buf);
	else return creat(file, mode, rfm_buf, mrs_buf);
     }
}
#endif

int Require_Final_Newline = 0;

/* 0 = read, 1 = write , 2 = append... */
int sys_open(char *file, int acces)
{
   int fp = -1;
   int flags;
   unsigned int mode;
#ifdef VMS
   char *p, neew[256];
#endif
   
#ifdef O_BINARY
	mode = O_BINARY;
#else
	mode = 0;
#endif

   flags = file_status(file);
   if ((flags < 0) || (flags > 1)) return(fp);
   
   /* on VMS I cheat since I do not want to deal with RMS at this point */
#ifdef VMS
   VMS_write_rfm_fixed = 0;
   strcpy(neew, file);
   p = neew; while (*p) if (*p == ';') *p = 0; else p++;
   
   if (acces == 0) fp = open(file, O_RDONLY, "ctx=rec","mbf=8","mbc=16","rop=RAH","shr=upi,get,put");
   else if (acces == 1)
     {
	fp = vms_stupid_open(neew);
     }
   
   else if (acces == 2) fp = open(file, O_WRONLY | O_APPEND | O_CREAT | mode);
#else
   
   switch(acces)
     {
      case 0: flags =  O_RDONLY; 
	break;
      case 1: flags =  O_WRONLY | O_CREAT | O_TRUNC;
	break;
      case 2: flags =  O_WRONLY | O_CREAT | O_APPEND; break;
      default: return(fp);
     }
   
   flags |= mode;
   
#if defined (msdos) || defined (__os2__)
   fp = open(file, flags, S_IREAD | S_IWRITE); 
#else
   fp = open(file, flags, 0666); 
#endif
#endif /* VMS */
   return(fp);
}

/* Leaves Point at last line inserted */
/* returns -1 if unable to open file,
           -2 if memory allocation error
           otherwise returns number of lines read */

char *file_type(char *file)
{
   char *p, *psave;
   if ((file == (char *) NULL) || (*file == 0)) return( (char *) NULL);

   file = extract_file(file);
   p = file; while (*p != 0) p++;
   psave = p;
   while((p != file) && (*p != '.')) p--;
   if (*p == '.') p++;
   if (p == file) return(psave); else return(p);
}

void set_file_modes()
{
   char *type;

   if (CBuf == NULL) return;
   CBuf->c_time = sys_time();
   if (CBuf->file[0])
     {
	CBuf->flags |= AUTO_SAVE_BUFFER;
	CBuf->hits = 0;
	type = file_type(CBuf->file);
     }
   else type = (char *) NULL;

   if (type == (char *) NULL) CBuf->modes = NO_MODE;
   else if (SLang_run_hooks("mode_hook", type, NULL));

   else if (!strcmp("c", type)) CBuf->modes = C_MODE;
   else if (!strcmp("h", type)) CBuf->modes = C_MODE;
   else if (!strcmp("txt", type)) CBuf->modes = WRAP_MODE;
   else if (!strcmp("tex", type)) CBuf->modes = WRAP_MODE;
   else if (!strcmp("doc", type)) CBuf->modes = WRAP_MODE;
   else CBuf->modes = NO_MODE;
}

int read_file(char *file)
{
   int fp;
   int n, status;

   if ((fp = sys_open(file, 0)) < 0)
     {
	status = file_status(file);
	if (!status) return(-1);  /* file does not exist */
	return(-2); /* exists but not readable */
     }

   n = read_file_pointer(fp);
   close(fp);
   eob();
   if ('\n' == *(CLine->data + Point)) make_line(2);

   VFile_Mode = VFILE_TEXT;
   return n;
}

int insert_file_pointer(VFILE *vp)
{
   int n = 0;
   unsigned int num;
   unsigned char *vbuf;
   
   Suspend_Screen_Update = 1;
   while(NULL != (vbuf = (unsigned char *) vgets(vp, &num)))
     {
	n++;
	if (SLang_Error) break;
	quick_insert(vbuf, (int) num);
     }
   return(n);
}

int insert_file(char *file)
{
   VFILE *vp;
   int n;
   
   if (NULL == (vp = vopen(file, 0, VFile_Mode))) return(-1);
   n = insert_file_pointer(vp);
   vclose(vp);
   return(n);
}


#define WRITE jed_write1

#if defined(unix) || defined(VMS)
#define BUFSIZE 0x7FFF
#else
#define BUFSIZE 512
#endif

static int Output_Buffer_Size = BUFSIZE;
static char output_buffer[BUFSIZE];
static char *output_bufferp;
static char *output_bufferp_max;


/* definitely perform the write.  Return number of chars written */
int jed_write1(int fd, char *b, unsigned int n)
{
#ifndef msdos   
   int len;
   unsigned int total = 0;
   while (total < n)
     {
	if ((len = write (fd, b, n - total)) <= 0) break;
	total += (unsigned int) len;
	b += len;
     }
   return total;
#else
   int num = -1;
   asm mov ah, 40h
   asm mov bx, fd
   asm mov cx, n
   asm push ds
   asm lds dx, dword ptr b
   asm int 21h
   asm pop ds
   asm jc L1
   asm mov num, ax		       /* number of bytes written */
   L1: 
   return(num);
#endif
}



/* RMS wants to start a NEW record after a write so just forget it! */
/* maybe do write-- return number of chars possibly written */

int jed_write(int fd, char *b, unsigned int n)
{
   int num, max, nl_flag = 0;
   unsigned int nsave = n;

#ifdef VMS
   if (VMS_write_rfm_fixed == 0)
     {
	output_bufferp = output_buffer;
	return jed_write1(fd, b, n);
     }
   
#endif

   /* amount of space left in buffer */
   /* copy whats in b to the output buffer */
   while(n)
     {
	num = (int) (output_bufferp_max - output_bufferp);
	if (n > num)
	  {
	     max = num;
	     MEMCPY(output_bufferp, b, max);
	     output_bufferp += max;
	  }

#if defined(pc_system) || defined(__os2__)
	else if ((*(b + (n - 1)) == '\n') && (VFile_Mode == VFILE_TEXT))
	  {
	     max = n - 1;
	     MEMCPY(output_bufferp, b, max);
	     output_bufferp += max;
	     *output_bufferp++ = '\r';
	     max++;

	     /* can only write the \r */
	     if (n == num) nl_flag = 1; else *output_bufferp++ = '\n';
	  }
#endif
	else
	  {
	     max = n;
	     MEMCPY(output_bufferp, b, max);
	     output_bufferp += max;
	  }
	
	if (output_bufferp == output_bufferp_max)
	  {
	     output_bufferp = output_buffer;
	     if (Output_Buffer_Size != WRITE(fd, output_buffer, Output_Buffer_Size)) return(-1);
	     if (nl_flag)
	       {
		  nl_flag = 0;
		  *output_bufferp++ = '\n';
	       }
	  }
	b += max;
	n -= max;
     }
   return(nsave);
}

/* returns -1 on failure */
int write_region_to_fp(int fp)
{
   register int pnt, len;
   register Line *first, *last;
   int last_pnt, n = 0;
   char *err = "Write Failed!";

#ifndef VMS
   char nl = '\n';
#endif
   
   output_bufferp = output_buffer;
   output_bufferp_max = output_buffer + BUFSIZE;
   Output_Buffer_Size = BUFSIZE;

#ifdef VMS
   if (VMS_write_rfm_fixed && (vms_max_rec_size <= BUFSIZE))
     {
	Output_Buffer_Size = vms_max_rec_size;
     }
   else VMS_write_rfm_fixed = 0;
#endif   
   if (!check_region(&Number_One)) return(-1);
   last = CLine; last_pnt = Point;

   pop_mark(&Number_One);
   first = CLine; pnt = Point;

   /* first should never be null without hitting last first.  If this
      ever happens, check_region failed. */
   while (first != last)
     {
	len = first->len - pnt;
	if (len != jed_write(fp, (char *) (first->data + pnt), len))
	  {
	     msg_error(err);
	  }
	
	/* This goes here inside the loop because it is possible for external
	   events to set error_buffer */
	pnt = 0;
	if (SLang_Error) break;
	first = first->next;
	n++;
     }

   if (!SLang_Error && (last_pnt != 0))
     {
	len = last_pnt - pnt;
	if (len != jed_write(fp, (char *) (last->data + pnt), len))
	  {
	     msg_error(err);
	  }
	n++;
     }
#ifndef VMS
   if ((Require_Final_Newline) && (CBuf->end == last))
     {
	eob(); if (Point) jed_write(fp, &nl, 1);
     }
#endif
   

   /* Now flush output buffer if necessary */
   
   len = (int) (output_bufferp - output_buffer);
   if (!SLang_Error && len) if (len != WRITE(fp, output_buffer, len))
     {
	msg_error(err);
     }
   
   output_bufferp = output_buffer;

   
   pop_spot();
   VFile_Mode = VFILE_TEXT;
   if (SLang_Error) return(-1);
   return(n);
}

/* write current buffer to open file pointer. Return number of lines */

int write_region(char *file)
{
   int fp;
   int n;
   char msg[256];

   if (!check_region(&Number_Zero)) return(-1);
   if ((fp = sys_open(file, 1)) < 0)
     {
	sprintf(msg, "Unable to open %s for writing.", file);
	msg_error(msg);
	return(-1);
     }
   n = write_region_to_fp(fp);
   close(fp);

   return(n);
}



/* returns -1 on failure and number of lines on success */

int write_file(char *file)
{
   Mark *m;
   int n = -1;
   int fnl;
   
   
#ifdef VMS
   register Line *l = CBuf->beg;
   register int len = 0, max = 0;
   
   while (l != NULL)
     {
	len = l->len;
	if (len > max) max = len;
	l = l->next;
     }
   vms_max_rec_size = max;
#endif
   
   push_spot();
   bob();
   push_mark();  m = CBuf->marks;
   eob();
   fnl = Require_Final_Newline;
   if (CBuf->flags & BINARY_FILE) 
     {
	VFile_Mode = VFILE_BINARY;
	Require_Final_Newline = 0;

#ifdef VMS
	vms_max_rec_size = 512;
#endif
     }

   n = write_region(file);
   
   Require_Final_Newline = fnl;
   VFile_Mode = VFILE_TEXT;
   if (m == CBuf->marks) pop_mark(&Number_Zero);
   pop_spot();
   return(n);
}

int append_to_file(char *file)
{
   int fp;
   int n;

   if ((fp = sys_open(file, 2)) < 0) return(-1);
   n = write_region_to_fp(fp);
   close(fp);
   check_buffers();
   return(n);
}

int make_autosave_filename(char *save, char *dir, char *file)
{
   char *s;
   int dat;

   if (*file == 0) return(0);
      
   
   if (SLang_run_hooks("make_autosave_filename", dir, file) && !SLang_Error)
     {
	if (SLang_pop_string(&s, &dat)) return(0);
	strncpy(save, s, 254);
	save[255] = 0;
	if (dat == 1) FREE(s);
     }
   else
     {
#ifdef unix
	sprintf(save, "%s#%s#", dir, file);
#else
#if defined (msdos) || defined (__os2__)
	sprintf(save, "%s#%s", dir, file);
#else
	sprintf(save, "%s_$%s;1", dir, file);
#endif
#endif
     }
   return(1);
}

int write_file_with_backup(char *dir, char *file)
{
   char neew[256]; char save[256];
   int n;
   int mode, do_mode;
   short uid, gid;
#ifndef VMS
   char *old;
   int ok = 0, bu, do_free = 0;
#endif
#ifdef __os2__
   PHOLDFEA EAs;
#endif
   
   if (*file == 0) return(-1);

   sprintf(neew, "%s%s", dir, file);
   
   if (!make_autosave_filename(save, dir, file)) *save = 0;

   do_mode = sys_chmod(neew, 0, &mode, &uid, &gid);
   if ((do_mode < 0) ||  (do_mode > 1)) return(-1);
#ifndef VMS
   
#ifdef __os2__
   EAs = QueryEAs (neew);
#endif
   
   bu = 1;
   if (((CBuf->flags & NO_BACKUP_FLAG) == 0)
       && SLang_run_hooks("make_backup_filename", dir, file))
     {
	if (((bu = !SLang_pop_string(&old, &do_free)) != 0) && (*old))
	  {
	     unlink(old);
	     ok = !rename(neew, old);
	  }
     }
#endif
   
   if (-1 != (n = write_file(neew)))
     {
	if (*save)
	  {
	     sys_delete_file(save);
	  }
	     
	if (do_mode) /* must be an existing file, so preserve mode */
	  {
	     sys_chmod (neew, 1, &mode, &uid, &gid);
#ifdef __os2__
	     WriteEAs (neew, EAs);
#endif
	  }
	CBuf->c_time = sys_time();
     }
#ifndef VMS
   else if (ok && bu && *old) rename(old, neew); /* error -- put it back */
   if ((do_free == 1) && bu) FREE(old);
#endif
   return(n);
}

/* warning-- this saves on the narrowed part of buffer.
   Here, I widen first.  I need a save_restriction type of thing because
   I do not narrow back.
   */
void auto_save_buffer(Buffer *b)
{
   char tmp[256];
   Buffer *old_buf;
   unsigned int vfm = VFile_Mode;
   
   if (b == NULL) return;
   b->hits = 0;
   if ((b->narrow != NULL) || !(b->flags & BUFFER_TRASHED)) return;

   old_buf = CBuf;
   switch_to_buffer(b);
   
   if (b->flags & BINARY_FILE)  VFile_Mode = VFILE_BINARY; 
   else VFile_Mode = VFILE_TEXT;

   if (b->flags & AUTO_SAVE_BUFFER)
     {
	if (make_autosave_filename(tmp, b->dir, b->file))
	  {
	     flush_message("autosaving..."); 
	     sys_delete_file(tmp);
	     write_file(tmp);
	     message("autosaving...done");
	  }
      }
   else if (b->flags & AUTO_SAVE_JUST_SAVE)
     {
	if (write_file_with_backup(b->dir, b->file) >= 0)
	  {
	     b->flags &= ~BUFFER_TRASHED;
	  }
     }
   switch_to_buffer(old_buf);
   VFile_Mode = vfm;
}

void auto_save_all()
{
    Buffer *b;

   if (NULL == (b = CBuf)) return;
   do
     {
	while (b->narrow != NULL) widen_buffer(b);
	if (*b->file != 0) auto_save_buffer(b);
	b = b->next;
     }
   while (b != CBuf);
}

#ifdef unix
#ifndef __GO32__
int is_link(char *f, char *f1)
{
   struct stat s;
   int l;
   char work[256];
   
   if ((f[l = strlen(f)] == '\\') && (l > 1))
     {
	strcpy(work, f);
	f = work;
	f[l - 1] = 0;
     }
   

   if (( lstat(f, &s) == -1 ) 
       /* || ((s.st_mode & S_IFMT)  S_IFLNK)) */
       || (((s.st_mode & S_IFMT) & S_IFLNK) == 0))
     return(0);
   
   if (-1 == (l = readlink(f, f1, 512))) return(0);
   f1[l] = 0;
   return(1);
}

/* we do not worry about the dir--- only the filename */
char *expand_link(char *f)
{
   char work[256], lnk[256];
   char *d = expand_filename(f);
   
   if (is_link(d, lnk)) 
     {
	strcpy(work, d);
	*(extract_file(work)) = 0;
	strcat(work, lnk);
	d = expand_filename(work);
     }
   
   return (d);
}

#endif
#endif

void visit_file(char *dir, char *file)
{
#ifndef __os2__
   if (strcmp(file, CBuf->file) || strcmp(dir, CBuf->dir))
#else
   if (strcmpi(file, CBuf->file) || strcmp(dir, CBuf->dir))
#endif
     {
	if (NULL == find_buffer(file)) strcpy(CBuf->name, file); 
	else uniquely_name_buffer(file);
   
	strcpy(CBuf->dir, dir);
	strcpy(CBuf->file, file);
     }
   
   CBuf->c_time = sys_time();

   check_buffers();
}

void fixup_dir(char *dir)
{
#ifndef VMS
   if (*dir == 0) return;
#ifdef pc_system
   if (dir[strlen(dir) - 1] != '\\') strcat(dir,"\\");
#else
   if (dir[strlen(dir) - 1] != '/') strcat(dir,"/");
#endif
#endif
}

char *dir_file_merge(char *dir, char *file)
/*
;;  returns result of merging dir with file. If file is empty, dir is
;;  considered to be whole file.
*/
{
   char name[512];

   strcpy (name, dir);
   if ((file != NULL) && *file)
     {
	fixup_dir(name);
	strcat(name, file);
     }
   return expand_filename(name);
}

int file_status(char *file)
/*
;;  Returns a coded integer about file.  If the file does not exist, 0 is
;;  returned.  Meaning:
;;
;;     2 file is a directory
;;     1 file exists
;;     0 file does not exist.
;;    -1 no access.
;;    -2 path invalid
;;    -3 unknown error
*/
{
   int mode = 0;
   short uid, gid;
   return sys_chmod(file, 0, &mode, &uid, &gid);
}

int file_changed_on_disk(char *file)
{
   unsigned long t;
   Buffer *buf;
   if (NULL == (buf = find_file_buffer(file))) return(0);
   t = sys_file_mod_time(file);
   return(t > buf->c_time);
}

int file_time_cmp(char *file1, char *file2)
{
   unsigned long t1, t2;
   
   t1 = sys_file_mod_time(file1);
   t2 = sys_file_mod_time(file2);
   /* These times are modification  times from 1970.  Hence, the bigger 
      number represents the more recent change.  Let '>' denote 
      'more recent than'.  This function returns:
          1:  file1 > file2
	  0:  file 1 == file2
	  -1: otherwise. 
       So: */
   if (t1 == t2) return(0);
   if (t1 > t2) return(1); 
   return(-1);
}

void auto_save(void)
/*
;;  autosave current buffer if marked for autosave.  To autosave all buffers
;;  use autosvall
;;
*/
{
   auto_save_buffer(CBuf);
}

void check_buffer(Buffer *b)
{
   if ((*b->file != 0)
       && file_changed_on_disk(dir_file_merge(b->dir, b->file)))
     {
	b->flags |= FILE_MODIFIED;
     }
   else b->flags &= ~FILE_MODIFIED;
}

void check_buffers()
{
   Buffer *b = CBuf;
   do
     {
	check_buffer(b);
	b = b->next;
     }
   while (b != CBuf);
}

void set_file_trans(int *mode)
{
   if (*mode) VFile_Mode = VFILE_BINARY; else VFile_Mode = VFILE_TEXT;
}

int read_file_pointer(int fp)
{
   int n = 1;
   unsigned int num;
   unsigned char *vbuf;
   VFILE *vp;
   
   if (SLang_Error || (vp = vstream(fp, MAX_LINE_LEN, VFile_Mode)) == NULL) return(-1);
   
   if (NULL == (vbuf = (unsigned char *) vgets(vp, &num))) return(0);
   
   if (CLine->space < num)
     {
	remake_line(CLine->len + num + 1);
     }
   MEMCPY((char *) CLine->data, (char *) vbuf, (int) num);
   CLine->len = num;
   
   while(NULL != (vbuf = (unsigned char *) vgets(vp, &num)))
     {
	n++;
	if ((num == 1) && (*vbuf == '\n')) make_line(num); else make_line(num + 1);
	MEMCPY((char *) CLine->data, (char *) vbuf, (int) num);
	CLine->len = num;
	if (SLang_Error) break;
     }
   if (vp->buf != NULL) FREE(vp->buf);
   FREE(vp);
   return n;
}


