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

#include "config.h"
#include "malloc.h"
#include "buffer.h"
#include "ins.h"
#include "line.h"
#include "paste.h"
#include "screen.h"
#include "misc.h"
#include "cmds.h"

Buffer *Paste_Buffer;
Buffer *Rectangle_Buffer;



/* This is used by the narrow command so that multiple windows are 
   handled properly. The assumption is that we are dealing with a canonical
   region */
      
void touch_windows(void)
{
   Window_Type *w;
   Line *l;
   unsigned int n;
   
   w = Window;
   Window = Window->next;
   while (Window != w)
     {
	if (CBuf == Window->buffer)
	  {
	     /* The mark is set with line at top of buffer */
	     l = CBuf->marks->line;
	     n = CBuf->marks->n;
	     while ((l != NULL) && (l != Window->mark.line)) l = l->next, n++;
	     if (l == NULL) 
	       {
		  Window->mark.line = CLine;
		  Window->mark.point = Point;
		  Window->mark.n = n - 1;
	       }
	     touch_window();
	  }
	Window = Window->next;
     }
   pop_mark(&Number_Zero);
}


int line_in_buffer(Line *line)
{
   Line *l;
   
   l = CBuf->beg;
   while (l != NULL) if (l == line) return(1); else l = l->next;
   return(0);
}


/* with prefix argument, pop marks */
int set_mark_cmd()
{
   Mark *m;
   if (Repeat_Factor != NULL)
     {
	while (CBuf->marks != NULL) pop_mark(&Number_Zero);
	Repeat_Factor = NULL;
	return(1);
     }
   
   if (CBuf->marks == NULL)
     {
	push_mark();
     }
   m = CBuf->marks;
   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   if (m->vis == 0) 
     {
	m->vis = 1;
	CBuf->vis_marks++;
     }
   
   /* if (m != CBuf->marks) m->next = CBuf->marks;
    CBuf->marks = m; */
   if (Last_Key_Function == (VOID *) set_mark_cmd) message("Mark Set.");
   return(1);
}

int push_spot()
{
    Mark *m;

   if (CBuf->spot_ptr < SPOT_ARRAY_SIZE)
     {
	m = &CBuf->spot_array[CBuf->spot_ptr++];
     }
   else if (NULL == (m = (Mark *) MALLOC(sizeof(Mark))))
      {
	  exit_error("push_spot: malloc error!");
      }

   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   m->next = CBuf->spots;
   CBuf->spots = m;
   return(1);
}

int push_mark()
{
    Mark *m;

   if (CBuf->mark_ptr < SPOT_ARRAY_SIZE)
     {
	m = &CBuf->mark_array[CBuf->mark_ptr++];
     }
   else if (NULL == (m = (Mark *) MALLOC(sizeof(Mark))))
     {
	exit_error("push_mark: malloc error!");
     }
   /*
    if (NULL == (m = (Mark *) MALLOC(sizeof(Mark))))
      {
	 msg_error("push_mark: malloc error!");
	 return(0);
      } */

   m->line = CLine;
   m->point = Point;
   m->n = LineNum + CBuf->nup;
   m->next = CBuf->marks;
   m->vis = 0;
   CBuf->marks = m;
   return(1);
}

void goto_mark(Mark *m)
{
   Line *l;
   
   l = m->line;
   LineNum = m->n;
	
   if (LineNum <= CBuf->nup) bob();
   else if (LineNum > CBuf->nup + Max_LineNum) eob();
   else
     {
	CLine = l;
	Point = m->point;
	LineNum -= CBuf->nup;
     }
}

int pop_mark(int *go)
{
   Mark *m;
   
   m = CBuf->marks;
   if (m == NULL) return(0);

   if (*go) goto_mark(m);
   if (m->vis)
     {
	CBuf->vis_marks--;
	/* touch screen since region may be highlighted */
	if (CBuf->vis_marks == 0) touch_screen();
     }
   
   CBuf->marks = m->next;
   if (m == &CBuf->mark_array[CBuf->mark_ptr - 1])
     {
	CBuf->mark_ptr--;
     }
   else FREE(m);
   return(1);
}

int mark_spot()
{
    push_spot();
    message("Spot Marked.");
    return(1);
}

int pop_spot()
{
   Mark *m;
   Line *l;
   
   m = CBuf->spots;
   if (m == NULL) return(0);
   l = m->line;

   LineNum = m->n;
   if (LineNum <= CBuf->nup) bob(); else if (LineNum > CBuf->nup + Max_LineNum) eob();
   else
     {
	CLine = l;
	Point = m->point;
	LineNum -= CBuf->nup;
     }
   CBuf->spots = m->next;
   
   if (m == &CBuf->spot_array[CBuf->spot_ptr - 1])
     {
	CBuf->spot_ptr--;
     }
   else FREE(m);
   return(1);
}

int exchange_point_mark(void)
{
   Line *save_line, *l;
   int save_point;
   unsigned int save_n;
   Mark *m;
   
   if ((m = CBuf->marks) == NULL) return(0);
   
   save_point = Point;
   save_line = CLine;
   save_n = LineNum + CBuf->nup;
   
   l = m->line;
	
   LineNum = m->n;
	
   if (LineNum <= CBuf->nup) bob();
   else if (LineNum > CBuf->nup + Max_LineNum) eob();
   else
     {
	CLine = l;
	Point = m->point;
	LineNum -= CBuf->nup;
     }
   m->point = save_point; m->line = save_line; m->n = save_n;
   return(1);
}

/* returns 0 if the mark is not set and gives error.  Exchanges point and mark
   to produce valid region.  A valid region is one with mark
   earlier in the buffer than point.  Always call this if using a region
   which reqires point > mark.  Also, push spot first then pop at end. */
int check_region(int *push)
{
   register Line *beg, *tthis = CLine;
   int pbeg;

   if (CBuf->marks == NULL)
     {
	msg_error("Set mark first.");
	return(0);
     }

   if (*push) push_spot();
   beg = CBuf->marks->line;
   pbeg = CBuf->marks->point;

   if (beg == CLine)
     {
	if (pbeg <= Point) return(1);
     }

   else
     {
	while((beg != NULL) && (beg != tthis)) beg = beg->next;
	if (beg == tthis) return(1);
     }

   exchange_point_mark();
   return(1);
}


int widen_buffer(Buffer *b)
{
   Narrow_Type *n;
   Buffer *save = CBuf;
   
   if (NULL == (n = b->narrow)) return(0);
   
   /* make sure buffer ends in final newline */
   
   switch_to_buffer(b);
   push_spot();
   eob();
   if ((n->end != NULL)
       && (!CLine->len || ('\n' != *(CLine->data + (CLine->len - 1)))))
     ins('\n');
   
   pop_spot();
   
   if (n->end != NULL) n->end->prev = b->end;
   if (n->beg != NULL) n->beg->next = b->beg;
   b->end->next = n->end;
   b->beg->prev = n->beg;
   b->beg = n->beg1;
   if (n->end != NULL) b->end = n->end1;
   
   Max_LineNum += n->ndown + n->nup;
   LineNum += n->nup;
   
   /* adjust absolute offsets */
   b->nup -= n->nup;
   b->ndown -= n->ndown;
   b->narrow = n->next;
   
   FREE(n);
   switch_to_buffer(save);
   return(1);
}
   
int widen()
{
   return widen_buffer(CBuf);
}
   

/* not really a region of points but a region of lines. */
int narrow_to_region()
{
   Line *beg;
   Narrow_Type *nt;
   
   if (NULL == (nt = (Narrow_Type *) MALLOC(sizeof(Narrow_Type))))
     {
	msg_error("Malloc Error during narrow.");
	return(0);
     }
   
   if (!check_region(&Number_One)) return(0);       /* spot pushed */

   push_spot();
   pop_mark(&Number_One);
   push_mark();			       /* popped and used in touch_windows! */
   beg = CLine;
   nt->nup = LineNum - 1;
   
   pop_spot();  /* eor now */
   
   nt->ndown = Max_LineNum - LineNum;

   Max_LineNum = LineNum = LineNum - nt->nup;
   CBuf->nup += nt->nup;
   CBuf->ndown += nt->ndown;
   
   
   nt->next = CBuf->narrow;
   CBuf->narrow = nt;
   nt->beg = beg->prev;
   nt->end = CLine->next;
   nt->beg1 = CBuf->beg;
   nt->end1 = CBuf->end;
   
   CBuf->beg = beg;
   CBuf->end = CLine;
   beg->prev = NULL;
   CLine->next = NULL;
   
   pop_spot();
   touch_windows();
   return(1);
}

int yank()
{
    if (Paste_Buffer == NULL) return(0);
    insert_buffer(Paste_Buffer);
    return(1);
}

int copy_region_to_buffer(Buffer *b)
{
   int first_point, last_point, n, tmpm;
   Line *first, *last;
   Buffer *save_buffer;

   if (b->flags & READ_ONLY) 
     {
	msg_error(Read_Only_Error);
	return (0);
     }
   
   if (!check_region(&Number_One)) return(0);  /* spot pushed */
   last = CLine;
   last_point = Point;

   tmpm = 1; pop_mark(&tmpm);
   if (b == CBuf)
     {
	msg_error("A buffer cannot be inserted upon itself.");
	pop_spot();
	return(0);
     }

   first = CLine;
   first_point = Point;

   save_buffer = CBuf;
   switch_to_buffer(b);

   /* go through standard routines for undo comapatability */
   Suspend_Screen_Update = 1;
   if (first == last)
     {
	n = last_point - first_point;
	quick_insert(first->data + first_point, n);
     }
   else 
     {
	n = first->len - first_point;
	quick_insert(first->data + first_point, n);
	while (first = first->next, first != last)
	  {
	     quick_insert(first->data, first->len);
	  }
	quick_insert(first->data, last_point);
     }
   touch_screen();
   switch_to_buffer(save_buffer);
   pop_spot();
   return(1);
}

int copy_to_pastebuffer()
{
   /* delete paste buffer */
   if (Paste_Buffer != NULL) delete_buffer(Paste_Buffer);
   Paste_Buffer = make_buffer();
   strcpy(Paste_Buffer->name, " <paste>");

   copy_region_to_buffer(Paste_Buffer);
   return(0);
}

int delete_region()
{
   int beg_point, tmpm, end_point, n;
   Line *beg, *end;
   
   CHECK_READ_ONLY
    if (!check_region(&Number_Zero)) return(0);

   /* make this go through standard ins/del routines to ensure undo */
   
   end = CLine; end_point = Point;
   push_spot();
   tmpm = 1; pop_mark(&tmpm);
   beg = CLine; beg_point = Point;
   pop_spot();
   
   Point = 0;
   
   if (end != beg) 
     {
	deln(&end_point);
	
	/* go back because we do not want to mess with Line structures
	   changing on us --- shouldn't happen anyway */
	
	while (CLine = CLine->prev, LineNum--, CLine != beg)
	  {
	     eol(); 
	     n = Point + 1;
	     Point = 0;
	     generic_deln(&n);
	  }
	eol();
	n = Point - beg_point + 1;  /* the \n char */
	Point = beg_point;
     }
   else 
     {
	Point = beg_point;
	n = end_point - Point;
     }
   
   generic_deln(&n);
   return(1);
}

int kill_region()
{
   int tmpm = 1;
   
   CHECK_READ_ONLY

   /* need two marks for this one */
   push_spot();
   if (!pop_mark(&tmpm))
     {
	check_region(&Number_Zero);
	pop_spot();
	return(0);
     }
   push_mark();
   push_mark();
   pop_spot();

   copy_to_pastebuffer();
   delete_region();
   return(1);
}

static char *Rect_Error = "Rectangle has 0 width.";
int insert_rectangle()
{
   int c1;
   Line *rline;

   CHECK_READ_ONLY
   if (Rectangle_Buffer == NULL) return(0);

   c1 = calculate_column();
   rline = Rectangle_Buffer->beg;
   while (rline != NULL)
     {
	goto_column(&c1);
	quick_insert(rline->data, rline->len);
	if (CLine->next == NULL)
	  {
	     eol();
	     newline();
	  }
	else 
	  {
	     CLine = CLine->next;
	     LineNum++;
	  }
	
	rline = rline->next;
     }
   return(1);
}

int open_rectangle()
{
   int c1, n, c2, tmpm;
   Line *save_line;
   
   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0); /* push_spot(); performed */

   c1 = calculate_column();
   save_line = CLine;
   tmpm = 1; pop_mark(&tmpm);
   c2 = calculate_column();
   n = c2 - c1;
   if (n < 0)
     {
	n = -n;
	c1 = c2;
     }

   while(1)
     {
	goto_column(&c1);
	ins_char_n_times(' ', n);
	if (CLine == save_line) break;
	CLine = CLine->next;
	LineNum++;
     }
   pop_spot();
   return(1);
}

/* rectangle commands */
int copy_rectangle()
{
    Line *save_line, *line, *beg;
    int c1, c2, dc, tmp, tmpm;
    unsigned char *p1, *p2, *data;

    if (!check_region(&Number_One)) return(0);       /* spot pushed */
    /* delete Rectangle buffer */
    if (Rectangle_Buffer != NULL) delete_buffer(Rectangle_Buffer);

    Rectangle_Buffer = make_buffer();
    strcpy(Rectangle_Buffer->name, " <rect>");
    c2 = calculate_column();
    save_line = CLine;

    tmpm = 1; pop_mark(&tmpm);
    c1 = calculate_column();
    if (c1 == c2)
      {
	 msg_error(Rect_Error);
	 pop_spot();
	 return(0);
      }
    if (c1 > c2)
      {
	 tmp = c1;
	 c1 = c2;
	 c2 = tmp;
	 goto_column(&c1);
      }

    /* go through the region copying rectanglar blocks to Rectanglebuffer */
    dc = c2 - c1;
    line = beg = make_line1(dc);
    beg->prev = NULL;
    while (1)
      {
	 p1 = data = line->data;
	 p2 = data + dc;
	 while (p1 < p2) *p1++ = ' ';
	 line->len = dc;

	 if (c1 == goto_column1(c1))
	   {
	      p1 = CLine->data + Point;
	      (void) goto_column1(c2);
	      p2 = CLine->data + Point;

	      while(p1 < p2) *data++ = *p1++;
	   }
	 if (CLine == save_line) break;
	 CLine = CLine->next;
	 LineNum++;

	 line->next = make_line1(dc);
	 line->next->prev = line;
	 line = line->next;
      }

    line->next = NULL;

    Rectangle_Buffer->line = Rectangle_Buffer->beg = beg;
    Rectangle_Buffer->end = line;
    Rectangle_Buffer->point = 0;

    pop_spot();
    return(0);
}

int kill_rectangle()
{
    Line *save_line, *line, *beg;
   int c1, c2, dc, tmp, n, tmpm;
   unsigned char *p1, *p2, *data;

   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0);

   /* delete Rectangle buffer */
   if (Rectangle_Buffer != NULL) delete_buffer(Rectangle_Buffer);

   Rectangle_Buffer = make_buffer();
   strcpy(Rectangle_Buffer->name, " <rect>");
   c2 = calculate_column();
   save_line = CLine;

   tmpm = 1; pop_mark(&tmpm);
    c1 = calculate_column();
    if (c1 == c2)
      {
	 msg_error(Rect_Error);
	 pop_spot();
	 return(0);
      }
    if (c1 > c2)
      {
	 tmp = c1;
	 c1 = c2;
	 c2 = tmp;
	 goto_column(&c1);
      }

    /* go through the region copying rectanglar blocks to Rectanglebuffer */
    dc = c2 - c1;
    line = beg = make_line1(dc);
    beg->prev = NULL;
    while (1)
      {
	 p1 = data = line->data;
	 p2 = data + dc;
	 while (p1 < p2) *p1++ = ' ';
	 line->len = dc;

	 if (c1 == goto_column1(c1))
	   {
	      p1 = CLine->data + Point;
	      (void) goto_column1(c2);
	      p2 = CLine->data + Point;
	      Point = (int) (p1 - CLine->data);
	      n = (int) (p2 - p1);
	      while(n-- > 0)
	        {
		   *data++ = *p1;
		   del();
		}
	   }
	 if (CLine == save_line) break;
	 CLine = CLine->next;  LineNum++;

	 line->next = make_line1(dc);
	 line->next->prev = line;
	 line = line->next;
      }

    line->next = NULL;

    Rectangle_Buffer->line = Rectangle_Buffer->beg = beg;
    Rectangle_Buffer->end = line;
    Rectangle_Buffer->point = 0;

    pop_spot();
    return(0);
}

int blank_rectangle()
{
   int c1, n, c2, pnt, tmpm;
   Line *save_line;
   int nn;
   

   CHECK_READ_ONLY
   if (!check_region(&Number_One)) return(0); /* push_spot(); performed */

   c1 = calculate_column();
   save_line = CLine;
   tmpm = 1; pop_mark(&tmpm);
   c2 = calculate_column();
   n = c2 - c1;
   if (n < 0)
     {
	n = -n;
	c1 = c2;
     }

   while(1)
     {
	goto_column(&c1);
	pnt = Point;
	eol();
	nn = Point - pnt;
	if (nn > n) nn = n;
	Point = pnt;
	
	deln(&nn);
	ins_char_n_times( ' ', nn);
	
	if (CLine == save_line) break;
	CLine = CLine->next;
	LineNum++;
     }
   pop_spot();
   mark_buffer_modified(&Number_One);
   return(1);
}

