#include <stdio.h>
#include "externs.h"
#include "window.h"
#include "line.h"
#include "display.h"

Window *WIN;
Window *TOP_WIN;
int COLUMN = 1;
int BEEP_MINI_B = 0;
int MINIBUFFER_SELECTED = 0;
int CURS_ROW;
int CURS_COL;
int RESTORE_WIDTH_TO = 0;
unsigned char *CURS_POS;

int read_string(char *str)
{
    char ch;
    int i;

    i = strlen(str);
    do 
      {
          ch = getkey();
          if ((ch == '\b') || (ch == 127))
            {
                if (i > 0)
                  {
                      if (str[--i] < 32) fputs("\b \b",stdout); /* erase the ^ */
                      fputs("\b \b",stdout);
                  }
                else fputc('\007',stdout);
            }
          else if ((ch < 32) && (ch != 10) && (ch != 13))
            {
		if ((ch == '\007') || (ch == '\025')) /* ^G or ^U aborts */
		   {
		      fputc('\007',stdout);
		      return(-1);    /* ^G quits */
		   }
		if (ch == '\033')  /* Allow KP ENTER as a terminator */
		   {
		      ch = getkey();
		      if (ch == 'O')
			 {
			    ch = getkey();
			    if (ch == 'M')
			       {
				  str[i] = '\0';
				  return(i);
			       }
			    else
			       {
			       fputc('^',stdout);
			       fputc('[',stdout);
			       fputc(str[i++] = 'O', stdout);
			       fputc(str[i++] = ch, stdout);
			       }
			 }
		      else
			 {
			    fputc('^',stdout);
			    fputc('[',stdout);
			    fputc(str[i++] = ch, stdout);
			 }
		   }
		else
		   {
		      str[i++] = ch;
		      fputc('^',stdout);
		      fputc(ch + '@',stdout);
		   }
            }
          else if (ch == '`')   /* quoted insert */
            {
                ch = getkey();
                str[i++] = ch;
                if ((ch < ' ') || (ch == 127)) 
                  {
                      if (ch == 127) ch = '?'; else ch = ch + '@';
                      fputc('^',stdout);
                  }
                fputc(ch,stdout);
            }
	  else if ((ch != 10) && (ch != 13))
            {
                str[i++] = ch;
                fputc(ch,stdout);
            }
          
          fflush(stdout);
      }
    while ((ch != 10) && (ch != 13));
      
    str[i] = '\0';
    return(i);
}


void message(char *what, int how)
{
    strcpy(MINI_BUF,what);
    if (how) BEEP_MINI_B = 1; else BEEP_MINI_B = 0;
}

void select_minibuffer()
{
    if (MINIBUFFER_SELECTED) return;
    MINIBUFFER_SELECTED = 1;
    set_scroll_region(1,SCREEN_HEIGHT);
    goto_rc(SCREEN_HEIGHT,1);
    fflush(stdout);
}

void exit_minibuffer()
{
    if (!MINIBUFFER_SELECTED) return;
    MINIBUFFER_SELECTED = 0;
    set_scroll_region(WIN->top,WIN->bot);
    fflush(stdout);
}

/* put out string, expanding control chars */
void nicely_puts(char *str, FILE *fp)
{
    while (*str != '\0')
      {
          if ((*str < ' ') || (*str == 127))
            {
                fputc('^',fp);
                if (*str != 127) fputc(*str + '@',fp); else fputc('?',fp);
            }
          else fputc(*str,fp);
          str++;
      }
}

void put_message()
{
    select_minibuffer();
    delete_line(1);
    if (BEEP_MINI_B) fputc('\007',stdout);
    BEEP_MINI_B = 0;
    if (*MINI_BUF != '\0') nicely_puts((char *) MINI_BUF, stdout);
    exit_minibuffer();
}

/* puts 'what in the minibuffer to be edited. */
/* returns number of chars read */
int read_from_minibuffer(char *prompt, char *what)
{
    int i;
    char str[132];
    
    select_minibuffer();
    fputs(prompt,stdout);
    if (*what != '\0')
      nicely_puts(what,stdout);
    fflush(stdout);
    strcpy(str,what);
    i = read_string(str);
    if (i > 0) strcpy(what,str);
    delete_line(1);
    exit_minibuffer();
    return(i);
}

    
void clear_minibuffer()
{
    MINI_BUF[0] = '\0';
    BEEP_MINI_B = 0;
    put_message();
}


int get_scroll(int *line)
{
    /* line is the line we want at the topo of the window if possible */
    int dtop, dbot,n,top,bot,wsize;

    top = WIN->beg_line;
    wsize = WIN->bot - WIN->top; /* actually 1 less than window size */
    bot = top + wsize;

    if ((*line == 1) && (top == 1))
      {
          message("Top of Buffer.",1);
          return(0);
      }
    
    /* handles endof file in a window */
    if ((bot > NUM_LINES) && *line > C_LINE)
      {
          *line = top;
          message("End of Buffer.",1);
          return(0);
      }
    
    if (NUM_LINES <= wsize)     /* short file */
      {
          *line = 1;
          return(0);
      }

    dtop = top - 1;
    dbot = NUM_LINES - bot;
    n = *line - top;

    if ((n>0) && (dbot < n))
      {
          n = dbot;
          *line = top + n;
          if (!n) message("End of buffer.",1);
      }
    else if ((n < 0) && (dtop + n < 0))
      {
          n = -dtop;
          if (!n) message("Top of buffer.",1);
          *line = n + top;
      }
    return(n);
}


void update_window(int line)
{
    int n,max_n, save_line, save_col, npos;
    unsigned char *save_pos;

    if (COLUMN != WIN->col)
      {
          if (COLUMN < 1) COLUMN = 1;
          if (COLUMN != WIN->col)
            {
                save_pos = CURS_POS; save_line = CURS_ROW; save_col = CURS_COL;
                redraw_window();
                update_status(0);
                WIN->curs_pos = CURS_POS = save_pos;
                WIN->curs_line = CURS_ROW = save_line;
                WIN->curs_col = CURS_COL = save_col;
            }
          return;
      }

    
    n = get_scroll(&line);
    max_n = WIN->bot - WIN->top;
    if (abs(n) > max_n)
      {
          goto_line(line);
          redraw_window();
          update_status(0);
          return;
      }
    if (!n) return;

    goto_rc(1,1);
    forward_line(n);
    WIN->beg_pos = C_POS;
    WIN->beg_line = C_LINE;

    if (n>0)
      {
          npos = 1;
          delete_nlines(n);
          goto_rc(WIN->bot - WIN->top - n + 2,1);
          forward_line(max_n - n + 1);
      }
    else
      {
          npos = 0;
          CURS_ROW = 1; CURS_COL = 1; CURS_POS = C_POS;
          n = -n;
          reverse_index(n);
      }
    n = n - 1;
    display_line();
    while(n--)
      {
          forward_line(1);
          fputc('\n',stdout);
          display_line();
      }
    if (npos) 
      {
          
          CURS_ROW = C_LINE - WIN->beg_line + 1;
          CURS_COL = 1; CURS_POS = C_POS;
      }
    
    C_POS = WIN->beg_pos;
    C_LINE = WIN->beg_line;
    update_status(0);
    fflush(stdout);
}

/* updates current window as well as scroll lock ones */
/* Although current window is update in an absolute fashion, scroll locked
   ones are updated in a relative fashion */
void update_windows(int line)
{
    int dline,flg;
    Window *w;
    
    dline = line - C_LINE;
    update_window(line);
    if (!WIN->lock) return;
    flg = 0;
    w = WIN;
    while(WIN = WIN->next, WIN != w)
      {
          if (WIN->lock)
            {
                flg = 1;
                set_window(WIN);
                line = C_LINE + dline;
                update_window(line);
            }
      }
    WIN = w;
    if (flg) set_window(WIN);
}


void redraw_window()
{
    int n,t;
    t = WIN->top;
    if (t == 1) clear_window();
    goto_rc(1, 1);
    n = WIN->bot - WIN->top;
    if ((C_LINE + n) > NUM_LINES) goto_line(NUM_LINES - n);
    WIN->curs_pos = CURS_POS = WIN->beg_pos = C_POS;
    WIN->beg_line = C_LINE;
    WIN->col = COLUMN;
    WIN->curs_col = CURS_COL = 1;
    WIN->curs_line = CURS_ROW = 1;
    if (t != 1) delete_line(1);
    display_line();
    while(n--)
      {
          fputc('\n',stdout);
          if (t != 1) delete_line(1);
          if (forward_line(1)) display_line();
      }
    
        
    C_POS = WIN->beg_pos;
    C_LINE = WIN->beg_line;
}

/* associates current window with current buffer */
void save_win_flags(Window *w)
{
    w->flags = 0;
    if (MOST_V_OPT) w->flags |= _MOST_V_OPT;
    if (MOST_B_OPT) w->flags |= _MOST_B_OPT;
    if (MOST_T_OPT) w->flags |= _MOST_T_OPT;
    if (MOST_W_OPT) w->flags |= _MOST_W_OPT;
    if (SQUEEZE_LINES) w->flags |= _MOST_SQ_OPT;
    w->n_lines = NUM_LINES;
    w->display = MOST_S_OPT;
}

void window_buffer()
{
    WIN->beg_line = C_LINE;
    WIN->beg_pos = C_POS;
    WIN->col = COLUMN;
    WIN->buf = BUF;
    MOST_S_OPT = 0;
    save_win_flags(WIN);
}

void clear_window()
{
    int i,n;
    i = WIN->top;
    n = WIN->bot - WIN->top;
    if (i == 1)
      {
          goto_rc(WIN->bot - WIN->top + 1,SCREEN_WIDTH);
          clr_bos();
      }
    else
      {
          goto_rc(i - WIN->top + 1,1);
          delete_line(1);
          while(n--)
            {
                fputc('\n',stdout);
                delete_line(1);
            }
      }
    
    goto_rc(i - WIN->top + 1,1);
    fflush(stdout);
}

void restore_win_flags()
{
    MOST_V_OPT = WIN->flags & _MOST_V_OPT;
    MOST_B_OPT = WIN->flags & _MOST_B_OPT;
    MOST_T_OPT = WIN->flags & _MOST_T_OPT;
    MOST_W_OPT = WIN->flags & _MOST_W_OPT;
    SQUEEZE_LINES = WIN->flags & _MOST_SQ_OPT;
    NUM_LINES = WIN->n_lines;
    MOST_S_OPT = WIN->display;
}


Window *make_window(int r1,int r2)
{
    int i;
    Window *new;
    new = (Window *) malloc(sizeof(Window));
    new->status = (char *) malloc(135);
    for (i = 0; i <= SCREEN_WIDTH; i++) new->status[i] = '\0';
    new->col = COLUMN;
    new->top = r1;
    new->bot = r2;
    new->lock = 0;
    save_win_flags(new);
    return(new);
}

void init_display()
{
    int i;
    fputs("\033[?6h",stdout);
    TOP_WIN = WIN = make_window(1,SCREEN_HEIGHT - 2);
    WIN->prev = WIN->next = WIN;
    cls();
    set_scroll_region(WIN->top, WIN->bot);
    goto_rc(1,1);
    fflush(stdout);
}

void reset_display()
{
    set_scroll_region(1,SCREEN_HEIGHT);
    fputs("\033[?6l",stdout);   /* normal origin mode */
    goto_rc(SCREEN_HEIGHT,1);
    if (RESTORE_WIDTH_TO)
      {
          set_width(RESTORE_WIDTH_TO, 0);
          RESTORE_WIDTH_TO = 0;
      }
    
    fflush(stdout);
}

update_status1(int new_status)
{
    char str[30], ch, *strp;
    static char new[135];
    int i,ii,r,x,line_p = 60;

    r = WIN->bot + 1;
    goto_rc(r,1);

    i = ii = 0;
    new[ii++] = '-';
    if (WIN->lock) new[ii++] = '*'; else new[ii++] = '-';
    strp = " MOST: ";
    while(*strp != '\0') new[ii++] = *strp++;
    
    while(ch = WIN->buf->file[i++], ch != '\0') new[ii++] = ch;

    while(ii < line_p) new[ii++] = ' ';

    x = (C_LINE + WIN->bot - WIN->top) * 100; x = x / NUM_LINES;

    /* for files with end of file above the bottom row (due to window manipulations) */
    if (x > 100) x = 100;
    sprintf(str,"(%d,%d) %d%%",C_LINE,COLUMN,x);
    i = 0; while(ch = str[i++], ch != '\0') new[ii++] = ch;

    while(ii < SCREEN_WIDTH) new[ii++] = '-';
    new[SCREEN_WIDTH] = '\0';
    set_attribute(7);
    if (new_status)
      fputs(new,stdout);
    else
      smart_puts(new,WIN->status,stdout);
    set_attribute(0);
    strcpy(WIN->status,new);
}

void update_status(int new_status)
{

    C_LINE = WIN->beg_line;
    C_POS = WIN->beg_pos;
    set_scroll_region(1,SCREEN_HEIGHT);
    update_status1(new_status);
    set_scroll_region(WIN->top,WIN->bot);
    fflush(stdout);
}

/* splits window-- no screen update, does not affect scrolling region */
int split_window()
{
    Window *new, *old;
    int b2,t2,b1, line;

    b2 = WIN->bot;
    b1 = (WIN->bot + WIN->top) / 2 - 1;
    t2 = b1 + 2;
    if ((b1 == WIN->top) || (t2 == b2)) return(0);

    /* line is top line of new window. */
    line = WIN->beg_line + t2 - WIN->top;
    old = WIN;
    WIN->bot = b1;
    new = make_window(t2,b2);
    /* add to ring */
    WIN->next->prev = new;
    new->next = WIN->next;
    new->prev = WIN;
    WIN->next = new;

    new->beg_line = line;
    new->buf = BUF;
    /* new window status line is at same position as old */
    strcpy(new->status,WIN->status);
    return(1);
}

    
void two_windows()
{
    int line;
    Window *new, *old;
    if (!split_window()) return;

    old = WIN;
    new = WIN->next;
    line = new->beg_line;
    if (line + new->bot - new->top > NUM_LINES)
      {
          other_window(1);
          /* since split window left new window undefined... */
          C_POS = old->beg_pos;
          C_LINE = old->beg_line;
          if (NUM_LINES <= new->bot - new->top + 1)
            {
                C_LINE = new->beg_line = 1;
                C_POS = new->beg_pos = BUF->beg;
                redraw_window();
                update_status(0);
            }
          else if (line > NUM_LINES)
            {
                goto_line(NUM_LINES - new->bot + new->top);
                WIN->beg_pos = C_POS;
                WIN->beg_line = C_LINE;
                redraw_window();
                update_status(0);
            }    
          else
            {
                goto_line(line);
                WIN->beg_pos = C_POS;
                WIN->beg_line = C_LINE;
                update_window(NUM_LINES - new->bot + new->top);
            }
          WIN->curs_line = 1;
          WIN->curs_col = COLUMN;
          WIN->curs_pos = C_POS;
          other_window(-1);
      }
    else
      {
          WIN = new;
          (void) forward_line(line - old->beg_line);
          new->beg_pos = C_POS;
          new->beg_line = C_LINE;
          new->curs_line = 1;
          new->curs_col = COLUMN;
          new->curs_pos = C_POS;
          update_status(0);
          WIN = old;
      }    
    update_status(1);
}

void expand_window1(int dn)
{
    int l, save_top, save_line;
    unsigned char *save_pos;

    /* l is line after last line of current window (where status line is) */
    l = WIN->beg_line + WIN->bot - WIN->top + 1;
    save_top = WIN->top;
    WIN->top = WIN->bot + 1;
    WIN->bot = WIN->bot + dn;
    set_scroll_region(WIN->top, WIN->bot);
    if (l > NUM_LINES)
      {
          clear_window();
      }
    else
      {
          /* should add something here for smarter scrolling...
             if ((WIN->next->BUF == BUF) && (l >= WIN->next->beg_line)
             && (l <= (WIN->next->beg_line + WIN->next)
             */
                
          save_line = C_LINE;
          save_pos = C_POS;
          goto_line(l);
          redraw_window();
          WIN->beg_line = C_LINE = save_line;
          WIN->beg_pos = C_POS = save_pos;
      }
    WIN->top = save_top;
    set_scroll_region(WIN->top, WIN->bot);
}

void one_window()
{
    Window *w, *tmp;
    int diff;
    
    if (WIN->next == WIN) return;
    w = WIN;
    WIN = WIN->next;
    /* delete other windows preserving the ring! */
    while(WIN != w)
      {
          free_window_buffer(); /* needs a ring */
          tmp = WIN->next;
          /* if this is the bottom window, save its status line */
          if (tmp == TOP_WIN) strcpy(w->status,WIN->status);
          tmp->prev = WIN->prev;
          WIN->prev->next = tmp;
          free(WIN);
          WIN = tmp;
      }
    WIN = w;
    /* slide the window to the top and expand it */
    diff = WIN->top - 1;
    if (diff)
      {
          set_scroll_region(1,SCREEN_HEIGHT - 2);
          goto_rc(1,1);
          delete_nlines(diff);
          WIN->top = 1;
          WIN->bot -=  diff;
          TOP_WIN = WIN;
      }
    expand_window1(SCREEN_HEIGHT - 2 - WIN->bot);
    update_status(0);
}

          
void set_window(Window *w)
{
    WIN = w;
    CURS_ROW = WIN->curs_line;
    CURS_COL = WIN->curs_col;
    CURS_POS = WIN->curs_pos;
    C_LINE = WIN->beg_line;
    C_POS = WIN->beg_pos;
    COLUMN = WIN->col;
    BUF = WIN->buf;
    switch_to_buffer(BUF);
    set_scroll_region(WIN->top,WIN->bot);
    restore_win_flags();
    fflush(stdout);
}

void other_window(int n)
{
    if (!n) return;
    WIN->beg_pos = C_POS;
    WIN->curs_pos = CURS_POS;
    WIN->curs_line = CURS_ROW;
    WIN->curs_col = CURS_COL;
    WIN->beg_line = C_LINE;
    save_win_flags(WIN);
    if (n < 0)
      while (n++) WIN = WIN->prev;
    else
      while (n--) WIN = WIN->next;
    set_window(WIN);
}



/* kills window by moving lower window up */
delete_as_top_window()
{
    int t1,t2,b1,b2;
    t1 = WIN->top;
    t2 = WIN->next->top;
    b1 = WIN->bot;
    b2 = WIN->next->bot;
    WIN->prev->next = WIN->next;
    WIN->next->prev = WIN->prev;

    /* scroll contents of window below to top */
    set_scroll_region(t1,b2);
    goto_rc(1,1);
    delete_nlines(t2 - t1);
    other_window(1);
    WIN->top = t1;
    WIN->bot = b2 - t2 + t1;
    expand_window1(b2 - WIN->bot);
    update_status(0);
}

/* free buffer for this window if no other windows are viewing it. */
free_window_buffer()
{
    Window *w;
    int f = 1;
    w = WIN;
    WIN = w->next;
    while((WIN != w) && f)
      {
          if (!strcmp(WIN->buf->file,w->buf->file)) f = 0;
          WIN = WIN->next;
      }
    WIN = w;
    if (f) free(WIN->buf);
}


delete_window()
{
    int new_b, old_b;
    Window *w;
    
    w = WIN;
    if (WIN->next == WIN) return;
    free_window_buffer();
    if (WIN->next != TOP_WIN)
      {
          if (WIN == TOP_WIN)
            {
                delete_as_top_window();
                TOP_WIN = WIN;  /* not anymore, this one is */
            }
          else
            delete_as_top_window();
          
          free(w);
          return;
      }
    old_b = WIN->top - 2;
    new_b = WIN->bot;
    other_window(-1);
    expand_window1(new_b - old_b);
    strcpy(WIN->status,w->status); /* share the same line */
    
    WIN->next = w->next;
    WIN->next->prev = WIN;
    free(w);
    update_status(0);
}

void redraw_display()
{
    Window *w;
    int n,t;
    fputs("\033[?6h",stdout);   /* relative origins */
    set_scroll_region(1,SCREEN_HEIGHT);
    cls();
    save_win_flags(WIN);
    w = WIN;
    do
      {
          WIN = WIN->next;
          t = WIN->top;
          goto_rc(t, 1);
          C_POS = WIN->beg_pos;
          C_LINE = WIN->beg_line;
          COLUMN = WIN->col;
          switch_to_buffer(WIN->buf);
          restore_win_flags();
          n = WIN->bot - WIN->top;
          display_line();
          while(n--)
            {
                fputc('\n',stdout);
                if (forward_line(1)) display_line();
            }
          C_LINE = WIN->beg_line;
          C_POS = WIN->beg_pos;
          update_status1(1);
      }
    while(WIN != w);
    set_window(w);
}

void toggle_lock()
{
    WIN->lock = !(WIN->lock);
    update_status(0);
}

