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

#include "config.h"
#include "buffer.h"
#include "screen.h"
#include "window.h"
#include "display.h"
#include "sysdep.h"
#include "file.h"
#include "keymap.h"
#include "ledit.h"
#include "misc.h"
#include "ins.h"
#include "slang.h"
#include "paste.h"
#include "mem.h"

char Error_Buffer[256];
char Message_Buffer[256];
volatile int KeyBoard_Quit;
int Exit_From_MiniBuffer;


typedef struct
{
   jmp_buf b;
} jmp_buf_struct;

extern jmp_buf_struct Jump_Buffer, *Jump_Buffer_Ptr;

#define MACRO_SIZE 256
char Macro_Buffer[MACRO_SIZE];
char *Macro_Buffer_Ptr = Macro_Buffer;
char *Macro_Ptr_Max = Macro_Buffer;
int Defining_Keyboard_Macro = 0;
int Executing_Keyboard_Macro = 0;

struct
{
   int def;
   int exe;
} Macro_State;

MiniInfo_Type Mini_Info;

void macro_store_key(char ch)
{
   /* I need to put something here to increase the size of the Macro_Buffer
      in case of overflow */

   if (Macro_Buffer_Ptr - Macro_Buffer >= MACRO_SIZE) msg_error("Macro Size exceeded.");
   else *Macro_Buffer_Ptr++ = ch;
}

void restore_macro_state(void)
{
   Defining_Keyboard_Macro = Macro_State.def;
   Executing_Keyboard_Macro = Macro_State.exe;
}

void set_macro_state(void)
{
   Macro_State.def = Defining_Keyboard_Macro;
   Macro_State.exe = Executing_Keyboard_Macro;
}

/* if in the mini buffer and if during keyboard macro, allow user to enter
   different text each time macro is executed */

int macro_query()
{
   char *s;
   int n;
   
   /* macro state is already set */
   Defining_Keyboard_Macro = 0;
   Executing_Keyboard_Macro = 0;

   if (!IS_MINIBUFFER)
     {
	if (NULL == (s = read_from_minibuffer("Enter String:", (char *) NULL, NULL, &n))) return(0);
	ins_chars((unsigned char *) s, n);
	FREE(s);
     }

   /* exit from mini restores the state */
   return(1);
}

int jed_getkey()
{
   int ch, diff, ch1;
   static int mini_flag = 0;
   
   if (Read_This_Character != NULL)
     {
	ch = *Read_This_Character++;
	if (ch == 0) Read_This_Character = NULL;
	else
	  {
	     if ((ch1 = *Read_This_Character) == 0) 
	       {
		  Read_This_Character = NULL;
		  return (ch);
	       }
	     
	     
	     if (ch == '^')
	       {
		  if (ch1 == '?') ch = 127; 
		  else ch = ch1 - '@';
		  Read_This_Character++;
	       }
	     else if (ch == '\\') 
	       {
		  ch = ch1;
		  Read_This_Character++;
	       }
	     if (*Read_This_Character == 0) Read_This_Character = NULL;
	     return(ch);
	  }
	
     }
   
   if (Executing_Keyboard_Macro)
     {
	if (Macro_Buffer_Ptr < Macro_Ptr_Max) return (*Macro_Buffer_Ptr++);
	Executing_Keyboard_Macro = 0;
	update((Line *) NULL, 0, 0);
     }

   diff = (int) (Key_Bufferp - Key_Buffer);
   if (Key_TimeOut_Flag && 
       ((mini_flag && (diff > 0))
	|| !input_pending(&Number_Ten)))
     {
	message(Key_Buffer);
	strcat(Message_Buffer, "-");
	update((Line *)NULL, 0, 0);
	message(" ");	       /* ensures that previous message will 
				get erased. */
	mini_flag = 1;
     }
   else (mini_flag = 0);
   
   ch = my_getkey();
   if (diff < 13) 
     {
	*Key_Bufferp++ = (char) ch;
	*Key_Bufferp = 0;
     }
   else msg_error("KeyBuffer overflow!");
   
   if (Defining_Keyboard_Macro) 
     {
	/* starting a new keysequence to save this point */
	if (diff == 0)
	  {
	     Macro_Ptr_Max = Macro_Buffer_Ptr;
	  }
	
	macro_store_key(ch);
     }
   return(ch);
}


int msg_error(char *msg)
{
   flush_input();
   JWindow->trashed = 1;
   Defining_Keyboard_Macro = Executing_Keyboard_Macro = 0;
   Read_This_Character = NULL;
   set_macro_state();
   if (Batch) fprintf(stderr, "%s\n", msg);

   if (!SLang_Error) SLang_Error = 1;
   if (Error_Buffer[0] != 0) return(0);
   strcpy(Error_Buffer, msg);
   return(1);
}

/* later I will do more with this-- for now, keep last one */
void message(char *msg)
{
   if (Executing_Keyboard_Macro) return;
   if (Batch) fprintf(stderr, "%s\n", msg);
   /* if (*Message_Buffer) do_dialog(Message_Buffer); */
   if (msg == NULL) *Message_Buffer = 0; else strncpy(Message_Buffer, msg, 255);
   Message_Buffer[255] = 0;
}

/* read from minibuffer using prompt providing a default, and stuffing
   the minibuffer with init if necessary */
/* I should make recursive mini buffers */
char *read_from_minibuffer(char *prompt, char *deflt, char *what, int *n)
{
   char buf[256];
   jmp_buf_struct *mini_jmp_save, mini_jmp_buf;
   unsigned char *ps;
   unsigned char *p, ch;
   
   static Window_Type *current_window;
   /* may get changed if user leaves minibuffer and returns from other
      window which means that the new window becomes target of minibuffer action */
   Window_Type *w;
   char *ret;
   int len, save_point;

   if (!IS_MINIBUFFER) current_window = JWindow;
   if (select_minibuffer()) return(NULL);   /* we should be on a new line of mini buffer */

   ps = Mini_Info.prompt;
   p = (unsigned char *) prompt;
   len = 0;
   
   if (p != NULL) 
     {
	while (0 != (ch = *p++))
	  {
	     *ps++ = ch;
	     len++;
	  }
	*ps++ =  ' ';
	len++;
     }

   if ((deflt != NULL) && (*deflt != 0))
     {
	sprintf(buf,"(default: %s) ", deflt);
	p = (unsigned char *) buf;
	
	while (0 != (ch = *p++))
	  {
	     *ps++ = ch;
	     len++;
	  }
     }
   *ps = 0;

   /* Use calculate column to find effective length */
   
   Mini_Info.prompt_len = len;
   p = CLine->data; save_point = Point;
   CLine->data = Mini_Info.prompt;  Point = len;
   Mini_Info.effective_prompt_len = 0;
   Mini_Info.effective_prompt_len = calculate_column() - 1;
   CLine->data = p;  Point = save_point;

   touch_window();
   if (what != NULL) insert_string(what);
   update((Line *) NULL, 0, 0);
   
   mini_jmp_save = Jump_Buffer_Ptr;
   Jump_Buffer_Ptr = &mini_jmp_buf;
   
   if (setjmp(mini_jmp_buf.b) != 0)
     {
	SLang_restart(1);   /* just in case */
	update((Line *) NULL, 0, 0);
     }

   while(!Exit_From_MiniBuffer)
     {
	do_jed();
	/* if (setjmp(Jump_Buffer) != 0)
	  {
	     Exit_From_MiniBuffer = 1;
	  }  */

	if (KeyBoard_Quit && IS_MINIBUFFER) break;
	update((Line *) NULL, 0, 0);
     }
   
   if (Exit_From_MiniBuffer && !Executing_Keyboard_Macro 
       && (Repeat_Factor == NULL) && !JWindow->trashed && !input_pending(&Number_Zero))
     {
	goto_rc(Screen_Height, 1);
     }

   Jump_Buffer_Ptr = mini_jmp_save;

   Exit_From_MiniBuffer = 0;
   MiniBuffer_Active = 0;
   restore_macro_state();
   
   if (!KeyBoard_Quit)
     {
	if (CLine->len == 0)
	  {
	     if (deflt != NULL) insert_string(deflt);
	  }
	bob();
	push_mark();
	eob();
	ret = make_buffer_substring(n);
     }
   else ret = NULL;
   
   /* we should be in minibuffer so delete marks and spots */
   while (CBuf->spots) pop_spot();
   while (CBuf->marks) pop_mark(&Number_Zero);
   erase_buffer();

   /* Remove MiniWindow from the ring */
   w = JWindow;
   while (w->next != JWindow) w = w->next;
   other_window();
   w->next = JWindow;
   
   /* Note that by this time, current_window might be history. */
   while((JWindow != current_window) && (JWindow != w)) other_window();
   JWindow->trashed = 1;

   /* delete_buffer(MiniBuffer); */
   MiniBuffer = NULL;
   return(ret);
}

void exit_error(char *str, int severity)
{
   reset_display();
   reset_tty(); 
   fprintf(stderr,"\007\rJED: Fatal Error: %s!\n", str);
   if (CBuf != NULL) 
     {
	fprintf(stderr, "CBuf: %p, CLine: %p, Point %d\n", CBuf, CLine, Point);
	if (CLine != NULL) fprintf(stderr, "CLine: data: %p, len = %d, next: %p, prev %p\n",
				   CLine->data, CLine->len, CLine->next, CLine->prev);
	fprintf(stderr, "Max_LineNum: %d, LineNum: %d\n", Max_LineNum, LineNum);
	if (JWindow != NULL) fprintf(stderr, "JWindow: %p, top: %d, rows: %d, buffer: %p\n",
				     JWindow, JWindow->top, JWindow->rows, JWindow->buffer);
	auto_save_all();
     }
   if (severity)
     {
#ifdef unix
   fprintf(stderr, "Dumping Core.");
   abort ();
#endif
     }
   exit(-1);
}

static int Macro_Illegal_Now = 0;
static int check_macro_legality(void)
{
   if (Macro_Illegal_Now) 
     {
	msg_error("Illegal context for Macro definition.");
	return (0);
     }
   return (1);
}


int begin_keyboard_macro()
{
   if (!check_macro_legality()) return (0);
   
   Macro_Buffer_Ptr = Macro_Buffer;
   message("Defining Macro.");
   Defining_Keyboard_Macro = 1;
   set_macro_state();
   return(1);
}

int end_keyboard_macro()
{

    if (Defining_Keyboard_Macro) message("Macro Defined.");
    else
      {
	  if (!Executing_Keyboard_Macro) msg_error("Not defining Macro!");
	  Executing_Keyboard_Macro = 0;
	  return(1);
      }

   /* Macro_Ptr_Max = Macro_Buffer_Ptr; */
   Defining_Keyboard_Macro = 0;
   set_macro_state();
   return(1);
}

int execute_keyboard_macro()
{
    int repeat = 0, *repeat_ptr;
    char ch;

   if (!check_macro_legality()) return (0);

    if (Defining_Keyboard_Macro)
      {
	  msg_error("Can't execute a macro while defining one.");
	  return(0);
      }

   Executing_Keyboard_Macro = 1;
   set_macro_state();
   Macro_Buffer_Ptr = Macro_Buffer;

   /* save the repeat context */
   repeat_ptr = Repeat_Factor;
   if (repeat_ptr != NULL) repeat = *repeat_ptr;

   JWindow->trashed = 1;
   Macro_Illegal_Now = 1;
   while ((Macro_Buffer_Ptr < Macro_Ptr_Max) 
	  && Executing_Keyboard_Macro) /* since we might enter minibuffer 
					  and stop macro before returning 
					  here */
     {
	Repeat_Factor = NULL;
	ch = *Macro_Buffer_Ptr++;
	do_key(ch);
	if (KeyBoard_Quit || (*Error_Buffer)) break;
     }
   Macro_Illegal_Now = 0;

   /* restore context */
   Repeat_Factor = repeat_ptr;
   if (repeat_ptr != NULL) *repeat_ptr = repeat;
   Executing_Keyboard_Macro = 0;
   set_macro_state();
   return(1);
}
/*
   before this can be of any real use, I need strings without the NULL 
   chars! */
/*      
int get_macro()
{
   if ((Defining_Keyboard_Macro) || (Executing_Keyboard_Macro))
     {
	msg_error("Macro Buffer locked!");
	return(0);
     }
   if (Macro_Ptr_Max == Macro_Buffer) return (0);
}
*/

void get_last_macro ()
{
   register char *m, *s, ch;
   char buf[2 * MACRO_SIZE + 1];
   
   if (Defining_Keyboard_Macro) 
     {
	msg_error("Complete Macro first!");
	return;
     }
   
   m = Macro_Buffer;
   if (m == Macro_Ptr_Max)
     {
	msg_error("Macro not defined.");
	return;
     }
   
   s = buf;
   while (m < Macro_Ptr_Max)
     {
	ch = *m++;
	if ((ch < ' ') || (ch == 127))
	  {
	     *s++ = '^';
	     if (ch == 127) ch = '?'; else ch = '@' + ch;
	  }
	else if ((ch == '^') || (ch == '\\'))
	  {
	     *s++ = '\\';
	  }
	
	*s++ = ch;
     }
   *s = 0;
   SLang_push_string(buf);
}
