//    This is part of the iostream library, providing input/output for C++.
//    Copyright (C) 1991 Per Bothner.
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Library General Public
//    License as published by the Free Software Foundation; either
//    version 2 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Library General Public License for more details.
//
//    You should have received a copy of the GNU Library General Public
//    License along with this library; if not, write to the Free
//    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "ioprivat.h"
#include "editbuf.h"
#include <stddef.h>
#ifdef __GNUG__
#pragma implementation
#endif

/* NOTE: Some of the code here is taken from GNU emacs */
/* Hence this file falls under the GNU License! */

// Invariants for edit_streambuf:
// An edit_streambuf is associated with a specific edit_string,
// which again is a sub-string of a specific edit_buffer.
// An edit_streambuf is always in either get mode or put mode, never both.
// In get mode, gptr() is the current position,
// and pbase(), pptr(), and epptr() are all NULL.
// In put mode, pptr() is the current position,
// and eback(), gptr(), and egptr() are all NULL.
// Any edit_streambuf that is actively doing insertion (as opposed to
// replacing) // must have its pptr() pointing to the start of the gap.
// Only one edit_streambuf can be actively inserting into a specific
// edit_buffer; the edit_buffer's _writer field points to that edit_streambuf.
// That edit_streambuf "owns" the gap, and the actual start of the
// gap is the pptr() of the edit_streambuf; the edit_buffer::_gap_start pointer
// will only be updated on an edit_streambuf::overflow().

int edit_streambuf::truncate()
{
    str->buffer->delete_range(str->buffer->tell((buf_char*)pptr()),
			      str->buffer->tell(str->end));
    return 0;
}

#ifdef OLD_STDIO
inline void  disconnect_gap_from_file(edit_buffer* buffer, FILE* fp)
{
    if (buffer->gap_start_ptr != &fp->__bufp)
	return;
    buffer->gap_start_normal = fp->__bufp;
    buffer->gap_start_ptr = &buffer->gap_start_normal;
}
#endif

void edit_streambuf::flush_to_buffer(edit_buffer* buffer)
{
    if (pptr() > buffer->_gap_start && pptr() < buffer->gap_end())
	buffer->_gap_start = pptr();
}

void edit_streambuf::disconnect_gap_from_file(edit_buffer* buffer)
{
    if (buffer->_writer != this) return;
    flush_to_buffer(buffer);
    setp(pptr(),pptr());
    buffer->_writer = NULL;    
}

buf_index edit_buffer::tell(buf_char *ptr)
{
    if (ptr <= gap_start())
	return ptr - data;
    else
	return ptr - gap_end() + size1();
}

#if 0
buf_index buf_cookie::tell()
{
    return str->buffer->tell(file->__bufp);
}
#endif

buf_index edit_buffer::tell(edit_mark*mark)
{
    return tell(data + mark->index_in_buffer(this));
}

// adjust the position of the gap

void edit_buffer::move_gap(buf_offset pos)
{
  if (pos < size1())
    gap_left (pos);
  else if (pos > size1())
    gap_right (pos);
}

void edit_buffer::gap_left (size_t pos)
{
  register buf_char *to, *from;
  register long i;
  long new_s1;

  i = size1();
  from = gap_start();
  to = from + gap_size();
  new_s1 = size1();

  /* Now copy the characters.  To move the gap down,
     copy characters up.  */

  for (;;)
    {
      /* I gets number of characters left to copy.  */
      i = new_s1 - pos;
      if (i == 0)
	break;
#if 0
      /* If a quit is requested, stop copying now.
	 Change POS to be where we have actually moved the gap to.  */
      if (QUITP)
	{
	  pos = new_s1;
	  break;
	}
#endif
      /* Move at most 32000 chars before checking again for a quit.  */
      if (i > 32000)
	i = 32000;
      new_s1 -= i;
      while (--i >= 0)
	*--to = *--from;
    }

  /* Adjust markers, and buffer data structure, to put the gap at POS.
     POS is where the loop above stopped, which may be what was specified
     or may be where a quit was detected.  */
  adjust_markers (pos << 1, size1() << 1, gap_size(), data);
#ifndef OLD_STDIO
  _gap_start = data + pos;
#else
  if (gap_start_ptr == &gap_start_normal)
	gap_start_normal = data + pos;
#endif
  __gap_end_pos = to - data;
/*  QUIT;*/
}

void edit_buffer::gap_right (size_t pos)
{
  register buf_char *to, *from;
  register long i;
  long new_s1;

  i = size1();
  to = gap_start();
  from = i + gap_end();
  new_s1 = i;

  /* Now copy the characters.  To move the gap up,
     copy characters down.  */

  while (1)
    {
      /* I gets number of characters left to copy.  */
      i = pos - new_s1;
      if (i == 0)
	break;
#if 0
      /* If a quit is requested, stop copying now.
	 Change POS to be where we have actually moved the gap to.  */
      if (QUITP)
	{
	  pos = new_s1;
	  break;
	}
#endif
      /* Move at most 32000 chars before checking again for a quit.  */
      if (i > 32000)
	i = 32000;
      new_s1 += i;
      while (--i >= 0)
	*to++ = *from++;
    }

  adjust_markers ((size1() + gap_size()) << 1, (pos + gap_size()) << 1,
	- gap_size(), data);
#ifndef OLD_STDIO
  _gap_start = data+pos;
#else
  if (gap_start_ptr == &gap_start_normal)
	gap_start_normal = data + pos;
#endif
  __gap_end_pos = from - data;
/*  QUIT;*/
}

/* make sure that the gap in the current buffer is at least k
   characters wide */

void edit_buffer::make_gap(buf_offset k)
{
  register buf_char *p1, *p2, *lim;
  buf_char *old_data = data;
  int long s1 = size1();

  if (gap_size() >= k)
    return;

  /* Get more than just enough */
  if (buf_size > 1000) k += 2000;
  else k += /*200;*/ 20; // for testing!

  p1 = (buf_char *) realloc (data, s1 + size2() + k);
  if (p1 == 0)
    abort(); /*memory_full ();*/

  k -= gap_size();			/* Amount of increase.  */

  /* Record new location of text */
  data = p1;

  /* Transfer the new free space from the end to the gap
     by shifting the second segment upward */
  p2 = data + buf_size;
  p1 = p2 + k;
  lim = p2 - size2();
  while (lim < p2)
    *--p1 = *--p2;

  /* Finish updating text location data */
  __gap_end_pos += k;

#ifndef OLD_STDIO
  _gap_start = data + s1;
#else
  if (gap_start_ptr == &gap_start_normal)
	gap_start_normal = data + s1;
#endif

  /* adjust markers */
  adjust_markers (s1 << 1, (buf_size << 1) + 1, k, old_data);
  buf_size += k;
}

/* Add `amount' to the position of every marker in the current buffer
   whose current position is between `from' (exclusive) and `to' (inclusive).
   Also, any markers past the outside of that interval, in the direction
   of adjustment, are first moved back to the near end of the interval
   and then adjusted by `amount'.  */

void edit_buffer::adjust_markers(register mark_pointer low,
				 register mark_pointer high,
				 long amount, buf_char *old_data)
{
  register struct edit_mark *m;
  register mark_pointer mpos;
  /* convert to mark_pointer */
  amount <<= 1;

  if (_writer)
      _writer->disconnect_gap_from_file(this);

  for (m = mark_list(); m != NULL; m = m->chain)
    {
      mpos = m->_pos;
      if (amount > 0)
	{
	  if (mpos > high && mpos < high + amount)
	    mpos = high + amount;
	}
      else
	{
	  if (mpos > low + amount && mpos <= low)
	    mpos = low + amount;
	}
      if (mpos > low && mpos <= high)
	mpos += amount;
      m->_pos = mpos;
    }

    // Now adjust files
    edit_streambuf *file;

    for (file = files; file != NULL; file = file->next) {
	mpos = file->current() - old_data;
	if (amount > 0)
	{
	  if (mpos > high && mpos < high + amount)
	    mpos = high + amount;
	}
	else
	{
	  if (mpos > low + amount && mpos <= low)
	    mpos = low + amount;
	}
	if (mpos > low && mpos <= high)
	    mpos += amount;
	char* new_pos = data + mpos;
	file->set_current(new_pos, file->is_reading());
    }
}

#if 0
stdio_
   __off == index at start of buffer (need only be valid after seek ? )
   __buf ==

if read/read_delete/overwrite mode:
     __endp <= min(*gap_start_ptr, edit_string->end->ptr(buffer))

if inserting:
     must have *gap_start_ptr == __bufp && *gap_start_ptr+gap == __endp
     file->edit_string->end->ptr(buffer) == *gap_start_ptr+end
if write_mode:
     if before gap
#endif

int edit_streambuf::underflow()
{
    if (!(_mode & ios::in))
	return EOF;
    struct edit_buffer *buffer = str->buffer;
    if (!is_reading()) { // Must switch from put to get mode.
	disconnect_gap_from_file(buffer);
	set_current(pptr(), 1);
    }
    buf_char *str_end = str->end->ptr(buffer);
  retry:
    if (gptr() < egptr()) {
	return *gptr();
    }
    if ((buf_char*)gptr() == str_end)
	return EOF;
    if (str_end <= buffer->gap_start()) {
	setg(eback(), gptr(), str_end);
	goto retry;
    }
    if (gptr() < buffer->gap_start()) {
	setg(eback(), gptr(), buffer->gap_start());
	goto retry;
    }
    if (gptr() == buffer->gap_start()) {
	disconnect_gap_from_file(buffer);
//	fp->__offset += fp->__bufp - fp->__buffer;
	setg(buffer->gap_end(), buffer->gap_end(), str_end);
    }
    else
	setg(eback(), gptr(), str_end);
    goto retry;
}

int edit_streambuf::overflow(int ch)
{
    if (_mode == ios::in)
	return EOF;
    struct edit_buffer *buffer = str->buffer;
    flush_to_buffer(buffer);
    if (ch == EOF)
	return 0;
    if (is_reading()) { // Must switch from get to put mode.
	set_current(gptr(), 0);
    }
    buf_char *str_end = str->end->ptr(buffer);
  retry:
    if (pptr() < epptr()) {
	*pptr() = ch;
	pbump(1);
	return (unsigned char)ch;
    }
    if ((buf_char*)pptr() == str_end || inserting()) {
	/* insert instead */
	if (buffer->_writer)
	    buffer->_writer->flush_to_buffer(); // Redundant?
	buffer->_writer = NULL;
	if  (pptr() >= buffer->gap_end())
	    buffer->move_gap(pptr() - buffer->gap_size());
	else
	    buffer->move_gap(pptr());
	buffer->make_gap(1);
	setp(buffer->gap_start(), buffer->gap_end());
	buffer->_writer = this;
	*pptr() = ch;
	pbump(1);
	return (unsigned char)ch;
    }
    if (str_end <= buffer->gap_start()) {
	// Entire string is left of gap.
	setp(pptr(), str_end);
    }
    else if (pptr() < buffer->gap_start()) {
	// Current pos is left of gap.
	setp(pptr(), buffer->gap_start());
	goto retry;
    }
    else if (pptr() == buffer->gap_start()) {
	// Current pos is at start of gap; move to end of gap.
//	disconnect_gap_from_file(buffer);
	setp(buffer->gap_end(), str_end);
//	__offset += __bufp - __buffer;
    }
    else {
	// Otherwise, current pos is right of gap.
	setp(pptr(), str_end);
    }
    goto retry;
}

void edit_streambuf::set_current(char *new_pos, int reading)
{
    if (reading) {
	setg(new_pos, new_pos, new_pos);
	setp(NULL, NULL);
    }
    else {
	setg(NULL, NULL, NULL);
	setp(new_pos, new_pos);
    }
}

// Called by fseek(fp, pos, whence) if fp is bound to a edit_buffer.

streampos edit_streambuf::seekoff(streamoff offset, _seek_dir dir,
				  int mode=ios::in|ios::out)
{
    struct edit_buffer *buffer = str->buffer;
    disconnect_gap_from_file(buffer);
    buf_index cur_pos = buffer->tell((buf_char*)current());;
    buf_index start_pos = buffer->tell(str->start);
    buf_index end_pos = buffer->tell(str->end);
    switch (dir) {
      case ios::beg:
	offset += start_pos;
	break;
      case ios::cur:
	offset += cur_pos;
	break;
      case ios::end:
	offset += end_pos;
	break;
    }
    if (offset < start_pos || offset > end_pos)
	return EOF;
    buf_char *new_pos = buffer->data + offset;
    buf_char* gap_start = buffer->gap_start();
    if (new_pos > gap_start) {
	buf_char* gap_end = buffer->gap_end();
	new_pos += gap_end - gap_start;
	if (new_pos >= buffer->data + buffer->buf_size) abort(); // Paranoia.
    }
    set_current(new_pos, is_reading());
    return EOF;
}

#if 0
int buf_seek(void *arg_cookie, fpos_t * pos, int whence)
{
    struct buf_cookie *cookie = arg_cookie;
    FILE *file = cookie->file;
    struct edit_buffer *buffer = cookie->str->buffer;
    buf_char *str_start = cookie->str->start->ptr(buffer);
    disconnect_gap_from_file(buffer, cookie->file);
    fpos_t cur_pos, new_pos;
    if (file->__bufp <= *buffer->gap_start_ptr
	|| str_start >= buffer->__gap_end)
	cur_pos = str_start - file->__bufp;
    else
	cur_pos =
	    (*buffer->gap_start_ptr - str_start) + (file->__bufp - __gap_end);
    end_pos = ...;
    switch (whence) {
      case SEEK_SET:
	new_pos = *pos;
	break;
      case SEEK_CUR:
	new_pos = cur_pos + *pos;
	break;
      case SEEK_END:
	new_pos = end_pos + *pos;
	break;
    }
    if (new_pos > end_pos) {
	seek to end_pos;
	insert_nulls(new_pos - end_pos);
	return;
    }
    if (str_start + new_pos <= *gap_start_ptr &* *gap_start_ptr < end) {
	__buffer = str_start;
        __off = 0;
	__bufp = str_start + new_pos;
	file->__get_limit =
	    *buffer->gap_start_ptr; /* what if gap_start_ptr == &bufp ??? */
    } else if () {
	
    }
    *pos = new_pos;
}
#endif

/* Delete characters from `from' up to (but not incl) `to' */

void edit_buffer::delete_range (buf_index from, buf_index to)
{
  register long numdel;

  if ((numdel = to - from) <= 0)
    return;

  /* Make sure the gap is somewhere in or next to what we are deleting */
  if (from > size1())
    gap_right (from);
  if (to < size1())
    gap_left (to);

  /* Relocate all markers pointing into the new, larger gap
     to point at the end of the text before the gap.  */
  adjust_markers ((to + gap_size()) << 1, (to + gap_size()) << 1,
	- numdel - gap_size(), data);

   __gap_end_pos = to + gap_size();
  _gap_start = data + from;
}

void edit_buffer::delete_range(struct edit_mark *start, struct edit_mark *end)
{
    delete_range(tell(start), tell(end));
}

void buf_delete_chars(struct edit_buffer *buf, struct edit_mark *mark, size_t count)
{
 abort();
}

edit_streambuf::edit_streambuf(edit_string* bstr, int mode)
{
    _mode = mode;
    str = bstr;
    edit_buffer* buffer = bstr->buffer;
    next = buffer->files;
    buffer->files = this;
    char* buf_ptr = bstr->start->ptr(buffer);
    _inserting = 0;
//    setb(buf_ptr, buf_ptr, 0);
    set_current(buf_ptr, !(mode & ios::out+ios::trunc+ios::app));
    if (_mode & ios::trunc)
	truncate();
    if (_mode & ios::ate)
	seekoff(0, ios::end);
}

// Called by fclose(fp) if fp is bound to a edit_buffer.

#if 0
static int buf_close(void *arg)
{
    register struct buf_cookie *cookie = arg;
    struct edit_buffer *buffer = cookie->str->buffer;
    struct buf_cookie **ptr;
    for (ptr = &buffer->files; *ptr != cookie; ptr = &(*ptr)->next) ;
    *ptr = cookie->next;
    disconnect_gap_from_file(buffer, cookie->file);
    free (cookie);
    return 0;
}
#endif

edit_streambuf::~edit_streambuf()
{
    if (_mode == ios::out)
	truncate();
    // Unlink this from list of files associated with bstr->buffer.
    edit_streambuf **ptr = &str->buffer->files;
    for (; *ptr != this; ptr = &(*ptr)->next) { }
    *ptr = next;

    disconnect_gap_from_file(str->buffer);
}

edit_buffer::edit_buffer()
{
    buf_size = /*200;*/ 15; /* for testing! */
    data = malloc(buf_size);
    files = NULL;
#ifndef OLD_STDIO
    _gap_start = data;
    _writer = NULL;
#else
    gap_start_normal = data;
    gap_start_ptr = &gap_start_normal;
#endif
    __gap_end_pos = buf_size;
    start_mark.chain = &end_mark;
    start_mark._pos = 0;
    end_mark.chain = NULL;
    end_mark._pos = 2 * buf_size + 1;
}

// Allocate a new mark, which is adjusted by 'delta' bytes from 'this'.
// Restrict new mark to lie within 'str'.

edit_mark::edit_mark(struct edit_string *str, long delta)
{
    struct edit_buffer *buf = str->buffer;
    chain = buf->start_mark.chain;
    buf->start_mark.chain = this;
    mark_pointer size1 = buf->size1() << 1;
    size_t gap_size = buf->gap_size() << 1;
    delta <<= 1;

    // check if new and old marks are opposite sides of gap
    if (_pos <= size1 && _pos + delta > size1)
	delta += gap_size;
    else if (_pos >= size1 + gap_size && _pos + delta < size1 + gap_size)
	delta -= gap_size;

    _pos = _pos + delta;
    if (_pos < str->start->_pos & ~1)
	_pos = (str->start->_pos & ~ 1) + (_pos & 1);
    else if (_pos >= str->end->_pos)
	_pos = (str->end->_pos & ~ 1) + (_pos & 1);
}

// A (slow) way to find the buffer a mark belongs to.

edit_buffer * edit_mark::buffer()
{
    struct edit_mark *mark;
    for (mark = this; mark->chain != NULL; mark = mark->chain) ;
    // Assume that the last mark on the chain is the end_mark.
    return (edit_buffer *)((char*)mark - offsetof(edit_buffer, end_mark));
}

edit_mark::~edit_mark()
{
    // Must unlink mark from chain of owning buffer
    struct edit_buffer *buf = buffer();
    if (this == &buf->start_mark || this == &buf->end_mark) abort();
    edit_mark **ptr;
    for (ptr = &buf->start_mark.chain; *ptr != this; ptr = &(*ptr)->chain) ;
    *ptr = this->chain;
}

size_t edit_string::length() const
{
    ptrdiff_t delta = end->ptr(buffer) - start->ptr(buffer);
    if (end->ptr(buffer) <= buffer->gap_start() ||
	start->ptr(buffer) >= buffer->gap_end())
	return delta;
    return delta - buffer->gap_size();
}

buf_char * edit_string::copy_bytes(size_t *lenp) const
{
    char *new_str;
    size_t len1, len2;
    buf_char *start1, *start2;
    start1 = start->ptr(buffer);
    if (end->ptr(buffer) <= buffer->gap_start()
	|| start->ptr(buffer) >= buffer->gap_end()) {
	len1 = end->ptr(buffer) - start1;
	len2 = 0;
	start2 = NULL; // To avoid a warning from g++.
    }
    else {
	len1 = buffer->gap_start() - start1;
	start2 = buffer->gap_end();
	len2 = end->ptr(buffer) - start2;
    }
    new_str = (char*)malloc(len1 + len2 + 1);
    memcpy(new_str, start1, len1);
    if (len2 > 0) memcpy(new_str + len1, start2, len2);
    new_str[len1+len2] = '\0';
    *lenp = len1+len2;
    return new_str;
}

// Replace the buf_chars in 'this' with ones from 'src'.
// Equivalent to deleting this, then inserting src, except tries
// to leave marks in place: Marks whose offset from the start
// of 'this' is less than 'src->length()' will still have the
// same offset in 'this' when done.

void edit_string::assign(struct edit_string *src)
{
    edit_streambuf dst_file(this, ios::out);
    if (buffer == src->buffer /*&& ???*/) { /* overly conservative */
	size_t src_len;
	buf_char *new_str;
	new_str = src->copy_bytes(&src_len);
	dst_file.sputn(new_str, src_len);
	free (new_str);
    } else {
	edit_streambuf src_file(src, ios::in);
	for ( ; ; ) {
	    int ch = src_file.sbumpc();
	    if (ch == EOF) break;
	    dst_file.sputc(ch);
	}
    }
}
