/* edit.c */

#include "config.h"
#include "object.h"
#include "interface.h"
#include "construct.h"
#include "globals.h"
#include "file.h"

struct edit_s *find_buf(struct object *obj) {
  struct edit_s *curr;

  curr=edit_list;
  while (curr) {
    if (curr->obj==obj) return curr;
    curr=curr->next;
  }
  return NULL;
}

void delete_line(struct edit_s *buf, unsigned long line) {
  struct edit_buf *curr,*next;
  unsigned int num_bufs,index,count;

  count=buf->num_lines-line;
  --(buf->num_lines);
  num_bufs=(line-1)/NUM_ELINES;
  index=(line-1)%NUM_ELINES;
  curr=buf->buf;
  while (num_bufs--) curr=curr->next;
  if (curr->buf[index]) FREE(curr->buf[index]);
  while (count--) {
    if (index==NUM_ELINES-1) {
      curr->buf[index]=curr->next->buf[0];
      index=0;
      curr=curr->next;
    } else
      curr->buf[index]=curr->buf[++index];
  }
}

void insert_line(struct edit_s *buf, unsigned long line, char *new_line) {
  struct edit_buf *curr;
  unsigned long index,count,num_bufs;
  char *tmp,*tmp2;

  count=buf->num_lines-line;
  ++(buf->num_lines);
  curr=buf->buf;
  if (!curr) {
    buf->buf=MALLOC(sizeof(struct edit_buf));
    buf->buf->next=NULL;
    curr=buf->buf;
  }
  index=(line)%NUM_ELINES;
  num_bufs=(line)/NUM_ELINES;
  while (num_bufs--) {
    if (!(curr->next)) {
      curr->next=MALLOC(sizeof(struct edit_buf));
      curr->next->next=NULL;
    }
    curr=curr->next;
  }
  tmp=curr->buf[index];
  curr->buf[index]=copy_string(new_line);
  while (count--) {
    if (index==NUM_ELINES-1) {
      if (!(curr->next)) {
        curr->next=MALLOC(sizeof(struct edit_buf));
        curr->next->next=NULL;
      }
      tmp2=curr->next->buf[0];
      curr->next->buf[0]=tmp;
      tmp=tmp2;
      index=0;
      curr=curr->next;
    } else {
      tmp2=curr->buf[++index];
      curr->buf[index]=tmp;
      tmp=tmp2;
    }
  }
}

void empty_buf(struct edit_s *buf) {
  struct edit_buf *curr,*next;
  unsigned long count;

  count=buf->num_lines;
  curr=buf->buf;
  if (buf->path) {
    FREE(buf->path);
    buf->path=NULL;
  }
  while (count) delete_line(buf,count--);
  curr=buf->buf;
  while (curr) {
    next=curr->next;
    FREE(curr);
    curr=next;
  }
  buf->buf=NULL;
  buf->num_lines=0;
  buf->is_changed=0;
  buf->curr_line=0;
}

void read_into_buf(struct edit_s *buf, char *filename) {
  FILE *f;
  unsigned long count,index;
  struct edit_buf *curr;
  char sbuf[MAX_STR_LEN];
  int len;

  if (!(f=open_file(filename,"r",buf->obj))) {
    send_device(buf->obj,"couldn't read ");
    send_device(buf->obj,filename);
    send_device(buf->obj,"\n");
    return;
  }
  count=0;
  empty_buf(buf);
  buf->buf=MALLOC(sizeof(struct edit_buf));
  curr=buf->buf;
  curr->next=NULL;
  index=0;
  while (fgets(sbuf,MAX_STR_LEN,f)) {
    count++;
    len=strlen(sbuf);
    if (len)
      if (sbuf[len-1]=='\n')
        sbuf[len-1]='\0';
    curr->buf[index]=copy_string(sbuf);
    index++;
    if (index==NUM_ELINES) {
      index=0;
      curr->next=MALLOC(sizeof(struct edit_buf));
      curr->next->next=NULL;
      curr=curr->next;
    }
  }
  buf->num_lines=count;
  buf->path=copy_string(filename);
  buf->curr_line=0;
  buf->is_changed=0;
  buf->inserting=0;
  close_file(f);
  send_device(buf->obj,"read ");
  send_device(buf->obj,filename);
  send_device(buf->obj,"\n");
}    

void write_to_buf(struct edit_s *buf, char *filename) {
  FILE *f;
  unsigned long count,index;
  struct edit_buf *curr;

  if (!(f=open_file(filename,"w",buf->obj))) {
    send_device(buf->obj,"couldn't write ");
    send_device(buf->obj,filename);
    send_device(buf->obj,"\n");
    return;
  }
  count=0;
  index=0;
  curr=buf->buf;
  while (count++<buf->num_lines) {
    fputs(curr->buf[index],f);
    fputc('\n',f);
    index++;
    if (index==NUM_ELINES) {
      index=0;
      curr=curr->next;
    }
  }
  buf->is_changed=0;
  close_file(f);
  send_device(buf->obj,"wrote ");
  send_device(buf->obj,filename);
  send_device(buf->obj,"\n");
}

void add_to_edit(struct object *obj, char *file) {
  struct edit_s *curr;

  if (obj->flags & IN_EDITOR) return;
  if (!(obj->flags & CONNECTED)) return;
  obj->flags|=IN_EDITOR;
  curr=MALLOC(sizeof(struct edit_s));
  curr->obj=obj;
  curr->num_lines=0;
  curr->buf=NULL;
  curr->path=NULL;
  curr->curr_line=0;
  curr->inserting=0;
  curr->is_changed=0;
  curr->next=edit_list;
  edit_list=curr;
  send_device(obj,"entering editor\n");
  if (file) read_into_buf(curr,file);
}

void remove_from_edit(struct object *obj) {
  struct edit_s *prev,*curr;

  if (!(obj->flags & IN_EDITOR)) return;
  obj->flags&=~IN_EDITOR;
  curr=edit_list;
  prev=NULL;
  while (curr) {
    if (curr->obj==obj) {
      if (prev)
        prev->next=curr->next;
      else
        edit_list=curr->next;
      break;
    }
    curr=curr->next;
  }
  if (!curr) return;
  empty_buf(curr);
  FREE(curr);
}

void split_arg(struct edit_s *buf, unsigned long *arg1, unsigned long *arg2,
               char *arg) {
  int count,len,has_hyphen;
  char *a1,*a2;
  
  count=0;
  a1=arg;
  a2="";
  has_hyphen=0;
  len=strlen(arg);
  while (count<len) {
    if (arg[count]=='-') {
      arg[count]='\0';
      has_hyphen=1;
      a2=&(arg[count+1]);
      break;
    }
    count++;
  }
  if (has_hyphen) {
    if (*a1)
      *arg1=atol(a1);
    else
      *arg1=1;
    if (*a2)
      *arg2=atol(a2);
    else
      *arg2=buf->num_lines;
  } else
    if (*arg) {
      *arg1=atol(arg);
      *arg2=*arg1;
    } else {
      *arg1=buf->curr_line;
      *arg2=*arg1;
    }
}

void do_edit_command(struct object *obj, char *s) {
  char *cmd,*arg;
  unsigned long line1,line2;
  int loop,len,index,num_bufs,count;
  struct edit_s *buf;
  struct edit_buf *curr;
  char sbuf[ITOA_BUFSIZ+3];

  buf=find_buf(obj);
  if (!buf) return;
  if (buf->curr_line>buf->num_lines) buf->curr_line=buf->num_lines;
  if (buf->inserting) {
    if (!strcmp(s,".")) {
      buf->inserting=0;
      send_device(buf->obj,"exiting insertion mode\n");
  } else {
      insert_line(buf,buf->curr_line,s);
      ++(buf->curr_line);
    }
    return;
  }
  loop=0;
  len=strlen(s);
  cmd=s;
  arg="";
  while (loop<len) {
    if (s[loop]==' ') {
      s[loop]='\0';
      arg=&(s[loop+1]);
      break;
    }
    loop++;
  }
  if (!strcmp(cmd,"l") || !strcmp(cmd,"list")) {
    split_arg(buf,&line1,&line2,arg);
    if (!line1) return;
    if (line1<1) line1=1;
    if (line2>buf->num_lines) line2=buf->num_lines;
    count=line2-line1+1;
    if (count<1) return;
    index=(line1-1)%NUM_ELINES;
    num_bufs=(line1-1)/NUM_ELINES;
    curr=buf->buf;
    while (num_bufs--) curr=curr->next;
    while (count--) {
      sprintf(sbuf,"%ld: ",(long) line1++);
      send_device(buf->obj,sbuf);
      send_device(buf->obj,curr->buf[index]);
      send_device(buf->obj,"\n");
      index++;
      if (index==NUM_ELINES) {
        index=0;
        curr=curr->next;
      }
    }
    return;
  }
  if (!strcmp(cmd,"q") || !strcmp(cmd,"quit")) {
    if (buf->is_changed) {
      send_device(buf->obj,"file has been modified\n");
      return;
    }
    send_device(buf->obj,"exiting editor\n");
    remove_from_edit(buf->obj);
    return;
  }
  if (!strcmp(cmd,"i") || !strcmp(cmd,"insert")) {
    if (*arg) {
      line1=atol(arg);
      if (line1<0 || line1>buf->num_lines) {
        send_device(buf->obj,"line number out of range\n");
        return;
      }
      buf->curr_line=line1;
    }
    buf->is_changed=1;
    buf->inserting=1;
    send_device(buf->obj,"entering insertion mode\n");
    return;
  }
  if (!strcmp(cmd,"r") || !strcmp(cmd,"read")) {
    if (!arg) {
      send_device(buf->obj,"no filename specified\n");
      return;
    }
    empty_buf(buf);
    read_into_buf(buf,arg);
    return;
  }
  if (!strcmp(cmd,"w") || !strcmp(cmd,"write")) {
    if (!*arg && !buf->path) {
      send_device(buf->obj,"no filename specified\n");
      return;
    }
    if (*arg)
      buf->path=copy_string(arg);
    write_to_buf(buf,buf->path);
    return;
  }
  if (!strcmp(cmd,"x") || !strcmp(cmd,"exit")) {
    empty_buf(buf);
    send_device(buf->obj,"exiting editor\n");
    remove_from_edit(buf->obj);
    return;
  }
  if (!strcmp(cmd,"d") || !strcmp(cmd,"delete")) {
    split_arg(buf,&line1,&line2,arg);
    if (line1>buf->num_lines || line1<line2 || line1==0) return;
    count=line2-line1+1;
    while (count--) delete_line(buf,line1);
    send_device(buf->obj,"deleted\n");
    return;
  }
  if (!strcmp(cmd,"?") || !strcmp(cmd,"h") || !strcmp(cmd,"help")) {
    send_device(buf->obj,"list <#>-<#>             lists file. line #'s "
                         "optional\n");
    send_device(buf->obj,"quit                     quit editor unless file "
                         "not saved\n");
    send_device(buf->obj,"insert <#>               insert line at directly "
                         "after specified #\n");
    send_device(buf->obj,"delete <#>-<#>           deletes lines. #'s "
                         "optional\n");
    send_device(buf->obj,"read filename            reads a file\n");
    send_device(buf->obj,"write <filename>         writes a file\n");
    send_device(buf->obj,"exit                     quit editor\n");
    send_device(buf->obj,"help                     this help screen\n");
    send_device(buf->obj,"status                   editor status\n");
    return;
  }
  if (!strcmp(cmd,"s") || !strcmp(cmd,"status")) {
    send_device(buf->obj,"file being edited: ");
    if (buf->path)
      send_device(buf->obj,buf->path);
    else
      send_device(buf->obj,"<unknown>");
    send_device(buf->obj,"\nnumber of lines: ");
    sprintf(sbuf,"%ld",(long) buf->num_lines);
    send_device(buf->obj,sbuf);
    send_device(buf->obj,"\ncurrent line: ");
    sprintf(sbuf,"%ld",(long) buf->curr_line);
    send_device(buf->obj,sbuf);
    if (buf->is_changed)
      send_device(buf->obj,"\nfile has been modified\n");
    else
      send_device(buf->obj,"\nfile has not been modified\n");
    return;
  }
  send_device(buf->obj,"syntax error. type 'help' for help\n");
}