/*{{{  #includes*/
#ifdef CONFIG_H
#   include "config.h"
#endif

#include <sys/types.h>
#include <ctype.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>

#include <local/bool.h>

#define GETTK_C
#define I_SET_C
#define I_DISPLAY_C
#define I_FIELDEDIT_C
#define I_FINDS_C
#define I_FOLDING_C
#define I_GETMSG_C
#define I_KEYBOARD_C
#define I_KEYTAB_C
#define I_MAIN_C
#define I_MESSAGES_C
#define I_PROMPT_C
#define I_SCREEN_C
#define I_SIGNALS_C

#include "origami.h"
/*}}}  */

/*{{{  vars*/
/*{{{  union stackent*/
typedef union {
  TOKEN *tp;
  int   v;
} stackent;
/*}}}  */

private stackent *macro_stack;
private TOKEN *macro_pos;
private bool defining_macro=FALSE;
private bool prompting=FALSE;
private bool do_macro=TRUE,
             do_arg=TRUE;
private int argument=0;
private int macro_count;

public TOKEN empty_macro[1]={M_END_MACRO};
public macro_entry *fix_macros=0;
public int fix_m_count=0;
public bool macro_tag;
public int executing_macro=0;
public int scr_off=0;
public int m_s_depth=M_DEPTH;
public int *macro_int=0,auto_macro=0,ab_macro=0,p_in_m=0,p_out_m=0,ocl_arg;
#ifdef MOUSY
  public int mouse_x=0,mouse_y=0,
             mouse_go_y=0,
             mouse_outside=0,
             mouse_b=0;
#endif

private TOKEN *string_macro=empty_macro;
/*}}}  */

/*{{{  ori_abort             set tags for signal interrupt*/
public void ori_abort(int sig)
{ static int handle=FALSE;

  if (handle++) return;
  signal(SIGINT, SIG_IGN);
  if (defining_macro) { defining_macro=FALSE;title_op(RESET_DM); }
  do_arg=do_macro=FALSE;
  aborted=interrupt_restore=TRUE;
  signal(SIGINT, ori_abort);
  handle=0;
}
/*}}}  */
/*{{{  stop_macros           stop macro-execution at correct point*/
public void stop_macros(void)
{
  do_macro=TRUE;
  executing_macro=0;
  macro_pos=fix_macros[0].string;
  if (!argument) disable_abort();
}
/*}}}  */
/*{{{  push_macro            push a macro-call on the stack*/
public bool push_macro(TOKEN *to)
{
  /*{{{  stack-handling*/
  if (!macro_stack) {
    macro_stack=(stackent*)malloc(m_s_depth*sizeof(stackent));
    if (!macro_stack)
     { message(M_NO_MEMORY,TRUE);return(TRUE); }
  } else if (executing_macro==m_s_depth) {
    stop_macros();
    message(get_msg(M_STACK_OVER),TRUE);
    return(TRUE);
  }
  /*}}}  */
  macro_stack[executing_macro++].tp = macro_pos;
  if (!argument && executing_macro==1) enable_abort();
  macro_pos=to;
  return(FALSE);
}
/*}}}  */
/*{{{  get_arg               get a int-Argument from macro-buffer*/
public TOKEN get_arg(void)
{ static TOKEN crashed[]={0,0,M_END_MACRO};

  if (!executing_macro) { push_macro(crashed);ori_abort(0); }
  return(*macro_pos++);

}
/*}}}  */
/*{{{  call_number_macro     call macro with given number*/
public void call_number_macro(int mno)
{
  if (mno<=0 || mno>(fix_m_count+1)) return;
  if (fix_macros[mno].string != empty_macro)
    push_macro(fix_macros[mno].string);
}
/*}}}  */
/*{{{  def_mac_handle        maybe store the given token in the kbdmacro*/
private TOKEN def_mac_handle(TOKEN ch)
{ if (defining_macro && ch!=O_DEF_MACRO)
   { fix_macros[0].string[macro_count++]=ch;
     /*{{{  macro-buffer full*/
     if (macro_count==MAX_MACRO_LENGTH)
      { message(get_msg(M_KEY_BUFF_FULL),TRUE);
        restore_cursor_and_sleep(10);
        fix_macros[0].string[--macro_count]=M_END_MACRO;
        ori_abort(0);
      }
     /*}}}  */
   }
  return(ch);
}
/*}}}  */
/*{{{  prompt_string         generate the correct string from macro*/
public void prompt_string(unsigned char *s)
{
  TOKEN read;

  while ((read=get_arg())!=M_END_MACRO)
     if (read==M_INT_STRING)
      { strcpy((char*)s,i_to_a(macro_int[get_arg()]));s += strlen((char*)s); }
     else if (read<0)
      { strcpy((char*)s,
               (char*)get_msg(-read,empty_text,
                                    empty_text,
                                    empty_text,
                                    empty_text,
                                    empty_text,
                                    empty_text));
        s += strlen((char*)s);
      }
     else
      { *s++ = (unsigned char) read; }
  *s = '\0';
}
/*}}}  */
/*{{{  single_key            get a token*/
/*{{{  forward declaration for internal_hide_key*/
TOKEN internal_hide_key(bool);
/*}}}  */

private TOKEN single_key(bool hide)
{
  static TOKEN rep_token=O_NOP;
  static int set_argument= -1;
  TOKEN ch=O_NOP;

  do {
    scan_abort_key();
    /*{{{  get next token*/
    if (executing_macro) {
      /*{{{  read a macro-token*/
      if (!do_macro || ((ch = *macro_pos++)==O_BREAK)) {
        if (argument) aborted=FALSE; else aborted=TRUE;
        stop_macros();
        if (ch==O_BREAK) do_arg=FALSE; else ch=O_BREAK;
      }
      /*}}}  */
    } else {
      /*{{{  get token from keyboard*/
      if (argument)
       /*{{{  handle repeating*/
       if (do_arg) {
         ch=rep_token;
         if (!(--argument))
           disable_abort();
       } else {
         disable_abort();
         ch=O_BREAK;
         argument=0;
         set_argument= -1;
         aborted=do_arg=do_macro=TRUE;
       }
       /*}}}  */
      else if (set_argument>=0)
       /*{{{  end repeating*/
       { set_argument= -1;
         disable_abort();
         return(O_PROMPT);
       }
       /*}}}  */
      else {
        /*{{{  read from keyboard*/
        do_macro=TRUE;
        def_mac_handle(ch = get_key());
        /*}}}  */
      }
      /*}}}  */
    }
    /*}}}  */
    /*{{{  M_ASCII*/
    if (ch==M_ASCII) ch=macro_int[get_arg()];
    /*}}}  */
    /*{{{  check correct char*/
    if (ch!=O_NOP)
     { chartest;
       if (ch==O_NOP)
        { if (knb_mac) call_number_macro(knb_mac);
          if (echo) message(get_msg(M_NOT_BOUND), TRUE);
          return(O_PROMPT);
        }
     }
    /*}}}  */
    if (ch<O_EXE_MACRO && ch>0) {
      /*{{{  normal commands*/
      switch (ch) {
        /*{{{  M_SCREEN_OFF*/
        case M_SCREEN_OFF:
          if (!(scr_off++)) screen_off();
          continue;
        /*}}}  */
        /*{{{  M_SCREEN_ON*/
        case M_SCREEN_ON:
          if (!(--scr_off)) screen_on();
          continue;
        /*}}}  */
        /*{{{  M_LASTMES*/
        case M_LASTMES:
         { if (macro_tag=((msgtyp)get_arg()==last_message))
              last_message=M_BASE_FORMAT;
           continue;
         }
        /*}}}  */
        /*{{{  M_STORE_LINE_NO*/
        case M_STORE_LINE_NO: {
          macro_int[get_arg()]=current?line_no(current)-1:0;
          continue;
        }
        /*}}}  */
        /*{{{  M_END_MACRO*/
        case M_END_MACRO: {
          macro_pos=macro_stack[--executing_macro].tp;
          if (!executing_macro && !argument) disable_abort();
          if (!executing_macro && !hide) return(O_PROMPT);
          continue;
        }
        /*}}}  */
        /*{{{  M_SET_COPY*/
        case M_SET_COPY:
         { int to=get_arg();
           int from=get_arg();

           set_copy(to,from);
           continue;
         }
        /*}}}  */
        /*{{{  M_SHOW_CURSOR*/
        case M_SHOW_CURSOR: {
          restore_cursor_and_sleep(macro_int[get_arg()]);
          continue;
        }
        /*}}}  */
        /*{{{  M_EDITING*/
        case M_EDITING: {
          macro_tag=prompting;
          continue;
        }
        /*}}}  */
        /*{{{  M_CALL*/
        case M_CALL: {
          int offset=get_arg();

          if (push_macro(macro_pos+offset))
           { ch=O_BREAK;
             break;
           }
          continue;
        }
        /*}}}  */
        /*{{{  M_CHANGED*/
        case M_CHANGED: {
          macro_tag=file_changed;
          continue;
        }
        /*}}}  */
        /*{{{  M_TEST_OVER*/
        case M_TEST_OVER: {
          macro_tag=overwrite;
          continue;
        }
        /*}}}  */
        /*{{{  M_TEST_AUTO*/
        case M_TEST_AUTO: {
          macro_tag=auto_save;
          continue;
        }
        /*}}}  */
        /*{{{  M_TEST_VIEW*/
        case M_TEST_VIEW: {
          macro_tag=read_only;
          continue;
        }
        /*}}}  */
        /*{{{  M_TEST_ECHO*/
        case M_TEST_ECHO: {
          macro_tag=echo;
          continue;
        }
        /*}}}  */
        /*{{{  O_UNCHANGE*/
        case O_UNCHANGE: {
          file_changed=FALSE;
          title_op(RESET_CF);
          no_message();
          break;
        }
        /*}}}  */
        /*{{{  M_JMP*/
        case M_JMP: {
          int diff;

          diff=get_arg();
          macro_pos+=diff;
          continue;
        }
        /*}}}  */
        /*{{{  M_JMP_FALSE*/
        case M_JMP_FALSE: {
          int diff;

          diff=get_arg();
          if (!macro_tag) macro_pos+=diff;
          continue;
        }
        /*}}}  */
        /*{{{  M_JMP_TRUE*/
        case M_JMP_TRUE: {
          int diff;

          diff=get_arg();
          if (macro_tag) macro_pos+=diff;
          continue;
        }
        /*}}}  */
        /*{{{  M_NOT*/
        case M_NOT: {
          macro_tag=!macro_tag;
          continue;
        }
        /*}}}  */
        /*{{{  M_LANGUAGE*/
        case M_LANGUAGE: {
          macro_tag=(l_tags[dialect]==get_arg());
          continue;
        }
        /*}}}  */
        /*{{{  M_TEXTLINE*/
        case M_TEXTLINE: {
          macro_tag=current?(current->foldline==NOT_FOLD):FALSE;
          continue;
        }
        /*}}}  */
        /*{{{  M_BEGIN_FOLD_COMMENT*/
        case M_BEGIN_FOLD_COMMENT: {
          macro_tag = current?(current->foldline==START_OPEN_FOLD):FALSE;
          continue;
        }
        /*}}}  */
        /*{{{  M_CLOSED_FOLD*/
        case M_CLOSED_FOLD: {
          macro_tag = current?(current->foldline==START_FOLD):FALSE;
          continue;
        }
        /*}}}  */
        /*{{{  M_END_FOLD_COMMENT*/
        case M_END_FOLD_COMMENT: {
          macro_tag=current?(current->foldline==END_FOLD):FALSE;
          continue;
        }
        /*}}}  */
        /*{{{  M_FILED_FOLD*/
        case M_FILED_FOLD: {
          macro_tag=current?(current->foldline==START_FILED):FALSE;
          continue;
        }
        /*}}}  */
        /*{{{  M_SET_COUNTER*/
        case M_SET_COUNTER: {
          int x=get_arg();

          macro_int[x]=get_arg();
          continue;
        }
        /*}}}  */
        /*{{{  M_ENTERED*/
        case M_ENTERED: {
          int x=get_arg();

          macro_int[x]=enter_depth;
          continue;
        }
        /*}}}  */
        /*{{{  M_S_ENTERED*/
        case M_S_ENTERED: {
          int x=get_arg();

          macro_int[x]=enter_depth_spaces;
          continue;
        }
        /*}}}  */
        /*{{{  M_INT_STRING*/
        case M_INT_STRING: {
          char st[32];
          char *s=st;
          TOKEN *t;

          strcpy(st,i_to_a(macro_int[get_arg()]));
          if (string_macro!=empty_macro) free(string_macro);
          t=string_macro=(TOKEN*)malloc(sizeof(TOKEN)*(1+strlen(st)));
          if (!string_macro)
           { string_macro=empty_macro;
             ori_abort(0);
             continue;
           }
          while (*s) *t++ = (TOKEN) *s++;
          *t = M_END_MACRO;
          push_macro(string_macro);
          continue;
        }
        /*}}}  */
        /*{{{  M_READ_REPEAT*/
        case M_READ_REPEAT: {
          macro_int[get_arg()]=set_argument;
          set_argument= -1;
          argument=0;
          do_arg=TRUE;
          continue;
        }
        /*}}}  */
        /*{{{  M_NULL_COUNTER*/
        case M_NULL_COUNTER: {
          macro_tag=(macro_int[get_arg()]==0);
          continue;
        }
        /*}}}  */
        /*{{{  M_MENU*/
        case M_MENU: {
          int *x=macro_int+get_arg();
          unsigned char prompt[LINELEN+1];

          prompt_string(prompt);
          *x=menu_item(prompt,*x,TRUE);
          no_message();
          continue;
        }
        /*}}}  */
        /*{{{  M_PROMPTs*/
        case M_PROMPT_C:
        case M_PROMPT: {
          int counter=get_arg();
          unsigned char string[LINELEN+1];
          unsigned char *s=string;
          unsigned char *d;
          TOKEN x=O_NOP;

          title_op(UPDTITLE);
          prompt_string(string);
          if (counter!=-2)
           /*{{{  show and wait for input*/
           { while (*s++);
             d = --s;
             /*{{{  read the string direct from keyboard*/
             disable_abort();
             enable_echo(FALSE);
             /* if aborted is true before get_key is called, then ... :-( */
             message(string,FALSE);
             *s=0;
             while (!aborted && (x=get_key())!=O_RETURN)
              { if (ch==M_PROMPT_C) break;
                if (   x<O_NOP
                    && (   isdigit((char) x)
                        || isalpha((char) x)
                        || ((char) x =='-')
                       )
                   )
                   *s++ = (char) x;
                else if ((x==O_DELETE) && s!=d)
                   --s;
                *s='\0';
                message(string,FALSE);
              }
             enable_echo(TRUE);
             enable_abort();
             /*}}}  */
             if (aborted) stop_macros();
             if (counter>=0)
               if (ch==M_PROMPT_C)
                 macro_int[counter]=x;
               else {
                 *d=toupper(*d);
                 if (*d==YES)
                   macro_int[counter]=1;
                 else if (*d==NO)
                   macro_int[counter]=0;
                 else
                   macro_int[counter]=atoi((char *)d);
               }
           }
           /*}}}  */
          else
             message(string,FALSE);
          continue;
        }
        /*}}}  */
        /*{{{  M_PUSH_INT*/
        case M_PUSH_INT: {
          if (executing_macro==m_s_depth) {
            stop_macros();
            message(get_msg(M_STACK_OVER),TRUE);
            ch=O_BREAK;
            break;
          } else {
            macro_stack[executing_macro++].v=macro_int[get_arg()];
            continue;
          }
        }
        /*}}}  */
        /*{{{  M_POP_INT*/
        case M_POP_INT: {
          macro_int[get_arg()]=macro_stack[--executing_macro].v;
          continue;
        }
        /*}}}  */
        /*{{{  M_POSITIV_COUNTER*/
        case M_POSITIV_COUNTER: {
          macro_tag=(macro_int[get_arg()]>0);
          continue;
        }
        /*}}}  */
        /*{{{  M_ADD_COUNTER*/
        case M_ADD_COUNTER: {
          int x=get_arg();

          macro_int[x]+=get_arg();
          continue;
        }
        /*}}}  */
        /*{{{  M_INV_COUNTER*/
        case M_INV_COUNTER: {
          int x=get_arg();

          macro_int[x] = -macro_int[x];
          continue;
        }
        /*}}}  */
        /*{{{  M_(RE)SET_USER_MODE*/
        case M_RESET_USER_MODE:
        case M_SET_USER_MODE: {
          int i=get_arg();

          if (um_active[i]!=(ch==M_SET_USER_MODE))
           { um_active[i]=(ch==M_SET_USER_MODE);
             title_op(CHGTITLE);
           }
          no_message();
          continue;
        }
        /*}}}  */
        /*{{{  M_SUM_COUNTER*/
        case M_SUM_COUNTER: {
          int x=get_arg();

          macro_int[x] += macro_int[get_arg()];
          continue;
        }
        /*}}}  */
        /*{{{  M_MULT*/
        case M_MULT: {
          int x=get_arg();

          macro_int[x] *= macro_int[get_arg()];
          continue;
        }
        /*}}}  */
        /*{{{  M_MOD*/
        case M_MOD: {
          int x=get_arg();
          int y=macro_int[get_arg()];

          if (y) macro_int[x] %= y; else ori_abort(0);
          continue;
        }
        /*}}}  */
        /*{{{  M_DIV*/
        case M_DIV: {
          int x=get_arg();
          int y=macro_int[get_arg()];

          if (y) macro_int[x] /= y; else ori_abort(0);
          continue;
        }
        /*}}}  */
        /*{{{  O_NUM_ON*/
        case O_NUM_ON:
          num_on();
          no_message();
          break;
        /*}}}  */
        /*{{{  O_APL_ON*/
        case O_APL_ON:
          apl_on();
          no_message();
          continue;
        /*}}}  */
        /*{{{  O_BELL*/
        case O_BELL:
          bell();
          no_message();
          break;
        /*}}}  */
        /*{{{  O_BELL_VISIBLE*/
        case O_BELL_VISIBLE:
          bell_visible();
          no_message();
          break;
        /*}}}  */
        /*{{{  M_SW_KBD*/
        case M_SW_KBD: {
          kbd_change(get_arg());
          continue;
        }
        /*}}}  */
        /*{{{  O_DEF_MACRO*/
        case O_DEF_MACRO: {
          if (!hide) {
            defining_macro = !defining_macro;
            if (defining_macro)
               macro_count=0;
            else
               fix_macros[0].string[macro_count]=M_END_MACRO;
            no_message();
            title_op(defining_macro ? SET_DM : RESET_DM);
          }
          break;
        }
        /*}}}  */
        /*{{{  O_DEF_FIX*/
        case O_DEF_FIX: {
          if (!hide) {
            unsigned char temp[LINELEN+1];
            int x;

            ch=O_BREAK;
            /*{{{  testing error-cases*/
            if (defining_macro)
             { message(get_msg(M_FIX_EXE),TRUE);
               restore_cursor_and_sleep(10);
               ori_abort(0);
               break;
             }
            /*}}}  */
            readprompt(temp,get_msg(M_WHICH_FIX),20);
            if (aborted) break;
            x=atoi((char *)temp);
            /*{{{  testing error-cases*/
            if (x<1 || x>fix_m_count) {
              message(get_msg(M_ERR_MACROPLACE),TRUE);
              restore_cursor_and_sleep(10);
              break;
            }
            if (!fix_macros[x].defineable) {
              message(get_msg(M_NO_DEFINE),TRUE);
              restore_cursor_and_sleep(10);
              break;
            }
            /*}}}  */
            if (fix_macros[x].string!=empty_macro) free(fix_macros[x].string);
            fix_macros[x].string=(TOKEN*)malloc((macro_count+1)*sizeof(TOKEN));
            if (fix_macros[x].string==(TOKEN*)0) {
              fix_macros[x].string= empty_macro;
              message(M_NO_MEMORY,TRUE);
              restore_cursor_and_sleep(10);
              break;
            }
            no_message();
            memcpy(fix_macros[x].string,fix_macros[0].string,
                   (macro_count+1)*sizeof(TOKEN));
          }
          break;
        }
        /*}}}  */
        /*{{{  O_NOP*/
        case O_NOP: {
          if (echo) no_message();
          continue;
        }
        /*}}}  */
        /*{{{  handle O_REP_DEF*/
        case O_REP_0:
        case O_REP_1:
        case O_REP_2:
        case O_REP_3:
        case O_REP_4:
        case O_REP_5:
        case O_REP_6:
        case O_REP_7:
        case O_REP_8:
        case O_REP_9: {
          if (set_argument<0)
           /*{{{  do it*/
           { unsigned char prom[LINELEN+1];
             unsigned char *s;

             if (defining_macro || executing_macro) { ori_abort(0);continue; }
             /*{{{  prepare internal data*/
             do_arg=TRUE;
             strcpy((char *)prom,(char *)get_msg(M_ARG));
             s=prom+strlen((char *)prom);
             set_argument=ch-O_REP_0;
             *s++ = '0'+set_argument;
             *s='\0';
             /*}}}  */
             /*{{{  get integer and the following token*/
             message(prom,FALSE);
             ch = internal_hide_key(TRUE);
             while (ch<O_NOP && isdigit(ch))
              { *s++ = ch;
                *s='\0';
                set_argument=set_argument*10+ch-'0';
                message(prom,FALSE);
                ch = internal_hide_key(TRUE);
              }
             /*}}}  */
             if (ch==O_BREAK || aborted)
              /*{{{  ready*/
              { set_argument= -1;
                argument=0;
                break;
              }
              /*}}}  */
             else
              /*{{{  start the repeating*/
              { argument=set_argument;
                rep_token=ch;
                if (!argument) argument=1;
                enable_abort();
                continue;
              }
              /*}}}  */
           }
           /*}}}  */
          else
             continue;
        }
        /*}}}  */
        /*{{{  default: return the value*/
        default: return(ch);
        /*}}}  */
      }
      /*}}}  */
    } else if (ch<=(O_EXE_MACRO+fix_m_count) && ch>=O_EXE_MACRO) {
      /*{{{  execute macros*/
      int number=ch-O_EXE_MACRO;

      switch (ch) {
        /*{{{  prehandle for exe_mac without break!*/
        case O_EXE_MACRO: {
          if (defining_macro) {
            message(get_msg(M_EXE_EXE),TRUE);
            restore_cursor_and_sleep(10);
            ori_abort(0);
            continue;
          }
          if (macro_count==0) *fix_macros[0].string=M_END_MACRO;
        }
        /*}}}  */
        /*{{{  default*/
        default: {
          if (push_macro(fix_macros[number].string)||hide)
           { ch=O_BREAK;
             break;
           }
          continue;
        }
        /*}}}  */
      }
      /*}}}  */
    } else
       exit_origami(r_ocl_err,get_msg(M_INT_OCL));
    ch=O_PROMPT;
    break;
  } while (!hide);
  return ch;
}
/*}}}  */
/*{{{  internal_hide_key     get a token direct from keyboard, store it to mac*/
public TOKEN internal_hide_key(bool direct)
{ TOKEN x,x2;
  bool  oldprompt=prompting;
  int   old_exe_mac;

  enable_echo(FALSE);
  prompting=TRUE;
  /*{{{  maybe call prompt-in-macro*/
  if (p_in_m) {
    old_exe_mac=executing_macro;
    call_number_macro(p_in_m);
    do
      x2=single_key(TRUE);
    while (invalid_prompt_key(x2) || old_exe_mac<executing_macro);
  }
  /*}}}  */
  /*{{{  get input-character*/
  if (direct)
     def_mac_handle(x=get_key());
  else
     do
      { x=single_key(FALSE); }
     while (x==O_PROMPT || invalid_prompt_key(x));
  /*}}}  */
  /*{{{  maybe call prompt-out-macro*/
  if (p_out_m) {
    old_exe_mac=executing_macro;
    call_number_macro(p_out_m);
    do
      x2=single_key(TRUE);
    while (invalid_prompt_key(x2) || old_exe_mac<executing_macro);
  }
  /*}}}  */
  prompting=oldprompt;
  enable_echo(TRUE);

  return(x);
}
/*}}}  */
/*{{{  hide_key              get a token. mode is in-prompt*/
public TOKEN hide_key(void)
{
  return(internal_hide_key(FALSE));
}
/*}}}  */
/*{{{  edit_key              get a token and set *keypad, mode is editing*/
public TOKEN edit_key(bool *keypad)
{
  TOKEN ch;

  ch=single_key(FALSE);
  *keypad=(ch>=O_NOP);
  return ch;
}
/*}}}  */
