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

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

#define FINDS_C
#define I_BUFFLOOP_C
#define I_DISPLAY_C
#define I_FIELDEDIT_C
#define I_FOLDHELP_C
#define I_FOLDING_C
#define I_GETTK_C
#define I_GETMSG_C
#define I_KEYBOARD_C
#define I_LOOP_C
#define I_MAIN_C
#define I_MISC_C
#define I_MESSAGES_C
#define I_ORIEDT_C
#define I_PROMPT_C
#define I_SCREEN_C
#define I_STRING_C

#define I_VIRTUAL_C

#include "origami.h"
#include <lib/ori_add_lib.h>
#ifdef REGEXP
#  include <lib/ori_re_lib.h>
#endif
/*}}}  */

/*{{{  variables*/
public unsigned char item_to_look_for[LINELEN+1];
#ifdef REGEXP
   public boolean inverted_search;
#else
#  define inverted_search False
#endif
private unsigned char replace_item[LINELEN+1];
/*}}}  */

/*{{{  line_no*/
public int line_no(element const *q,element const * const from)
{
  int i;
  element const *p;
  boolean c_c_l;
  linetyp lt;

  if (!q || !from || !bd.f.real_head)
     return(0);
  c_c_l=    bd.f.count_comment_line
         && (bd.m.dialect.typ!=F_C_NONE && bd.m.dialect.typ!=F_C_TDS);
  i = 0;
  if ((p=from)==bd.f.real_head)
     p=move_on(p);
  for (lt=get_linetyp(*p);p!=q;)
   { if (lt&NOT_FOLD)
        i++;
     else if (lt&START_FILED)
        i += 3;
     else if (normal_att(&(p->x.fold),False) || bd.m.dialect.typ==F_C_TDS)
        i++;
     else
        i+=2;
     if (p==bd.f.real_tail)
        break;
     /*{{{  move_on hard coded. This is time-critical!*/
     if (lt&START_FOLD)
        p=p->x.fold.data;
     else
        p=p->next;
     /*}}}  */
     lt=get_linetyp(*p);
     if (c_c_l && lt!=NOT_FOLD) { i++;c_c_l=False; }
   }
  return i;
}
/*}}}  */
/*{{{  cur_line_no*/
public int cur_line_no(void)
{ int x;

  if (!(x=title_op(LINE_NO)))
     x=line_no(bd.f.current,bd.f.real_head);
  return(x);
}
/*}}}  */
/*{{{  close_and_open_folds*/
public void close_and_open_folds(element * const qq)
{
  element *q;
  element *old_q;

  q=qq;
  while (q != bd.f.head)
   { old_q = q;
     q = q->prec;
     if (test_linetyp(*q,START_FOLD) && q->x.fold.data==old_q)
        pre_open_fold(q);
     if (test_linetyp(*q,END_FOLD))
      { if (test_linetyp(*(q->x.fold.other_end),START_OPEN_FOLD))
         { q = q->x.fold.other_end;
           close_fold_at(q);
         }
      }
   }
  q = qq;
  while (q != bd.f.tail)
   { q = q->next;
     if (test_linetyp(*q,START_OPEN_FOLD))
        close_fold_at(q);
   }
}
/*}}}  */
/*{{{  ptr_find_item*/
private unsigned char *ptr_find_item
 ( boolean backup,
   boolean display,
   boolean do_enable_abort
   re_args(reg_types magic)
 )
 {
   int position;
   element *p, *q;
   unsigned char *ptr_found;
   /*{{{  USTRSTR*/
#   ifdef REGEXP
#     define USTRSTR(s,b,e) (magic?reg_ustrstr(s,b,e):ustrstr(s,item_to_look_for))
#   else
#     define USTRSTR(s,b,e) (ustrstr(s,item_to_look_for))
#   endif
   /*}}}  */
 
   if (!executing_macro && do_enable_abort)
      enable_abort();
   q=p=bd.f.current;
   ptr_found=0;
   copyin(line_buffer,p,False);
   position=scr2txt(bd.f.enter_spaces,bd.scr.cursor.w,line_buffer);
   if (backup)
    /*{{{  search reverse*/
    { while (p!=bd.f.head->next && !ptr_found && !aborted)
       { unsigned char *f;

         copyin(line_buffer,p,False);
         if (inverted_search && p==bd.f.current)
          /*{{{  ! doesn't match current line, set to it, toggled below*/
            f=line_buffer;
          /*}}}  */
         else
          /*{{{  set f to latest occurance*/
          { unsigned char *f_new;
            unsigned char *f_max;

            f_max=line_buffer+((p==bd.f.current)?position:ustrlen(line_buffer));
            if ((f=f_new=USTRSTR(line_buffer,True,True)))
               while
                (    (f_new=USTRSTR(f_new+1,False,True))
                  && f_new<f_max
                )
                  f=f_new;
            if (f==f_max)
               f=0;
          }
          /*}}}  */
         /*{{{  maybe !, so toggle result of search*/
         if (inverted_search)
            f=f?0:(line_buffer+p->indent);
         /*}}}  */
         if (f)
          { ptr_found=f;
            position=f-line_buffer;
          }
         else if (test_linetyp(*(p->prec),START_FOLD) && p->prec->next==p)
            p=p->prec->x.fold.other_end;
         else
            p=p->prec;
       }
    }
    /*}}}  */
   else
    /*{{{  search forward*/
    { while (p!=bd.f.tail && !ptr_found && !aborted)
       { unsigned char *f;

         copyin(line_buffer,p,False);
         /*{{{  check line*/
         if (p != bd.f.current)
          /*{{{  search in whole line*/
          { f=USTRSTR(line_buffer+p->indent,True,True);
            if (inverted_search)
               f=f?0:(line_buffer+p->indent);
          }
          /*}}}  */
         else if (inverted_search)
          /*{{{  ! matches never the current line*/
            f=0;
          /*}}}  */
         else if (position>=ustrlen(line_buffer))
          /*{{{  behind end of line doesn't match*/
            f=0;
          /*}}}  */
         else
          /*{{{  check inside line, from position on*/
            f=USTRSTR(line_buffer+position,False,True);
          /*}}}  */
         /*}}}  */
         if (f==0)
            p=move_on(p);
         else
          { ptr_found=f;
            position=f-line_buffer;
          }
       }
    }
    /*}}}  */
   if (!executing_macro && do_enable_abort)
      disable_abort();
   if (ptr_found)
      if (aborted)
         ptr_found=0;
      else
       { if (p != q)
          /*{{{  move and maybe open folds*/
          { int off;

            off=ustrlen(line_buffer);
            /*{{{  handle display/folds*/
            if (on_screen(p) && display)
               skip_to(p);
            else
             { bd.f.cur_line_typ=get_linetyp(*(bd.f.current=p));
               close_and_open_folds(p);
               if (display)
                  restore_element(bd.scr.txt_size.h/2);
             }
            /*}}}  */
            copyin(line_buffer, p, False);
            position+=ustrlen(line_buffer)-off;
          }
          /*}}}  */
         bd.scr.cursor.w=txt2scr(bd.f.enter_spaces,position,line_buffer);
         title_op(CHGXY);
       }
   return(ptr_found);
 
#  undef USTRSTR
 }
/*}}}  */
/*{{{  find_item*/
public boolean find_item
 ( boolean backup,
   boolean display,
   boolean do_enable_abort
   re_args(reg_types magic)
 )
 {
   return
    (ptr_find_item(backup,display,do_enable_abort re_args(magic))?True:False);
 }
/*}}}  */
/*{{{  pre_find_element*/
public element *pre_find_element(int new_line_no)
{
  int current_line_no;
  element *p;
  boolean c_c_l;

  for
   ( current_line_no=0,p=bd.f.head->next,
     c_c_l=     bd.f.count_comment_line
            && (bd.m.dialect.typ!=F_C_NONE && bd.m.dialect.typ!=F_C_TDS)
   ; current_line_no<new_line_no && p!=bd.f.tail
   ; p=move_on(p)
   )
     switch(get_linetyp(*p))
      { case START_FILED:
           current_line_no+=2;
        default:
           if (c_c_l && p!=bd.f.head->next)
            { current_line_no++;
              c_c_l=False;
            }
        case NOT_FOLD:
           current_line_no++;
           break;
      }

  return p;
}
/*}}}  */
/*{{{  find_element*/
public void find_element(int new_line_no, int close_line)
{
  element *p;

  pre_top_fold();
  p = pre_find_element(new_line_no);
  close_and_open_folds(p);
  bd.f.current = p;
  title_op(CHGXY);
  restore_element(close_line);
}
/*}}}  */
/*{{{  goto_line*/
public void goto_line(int no,element *p,boolean no_force)
{
  if (no_force && p && p==bd.f.current)
     return;

  pre_top_fold();
  if (!p)
   /*{{{  prompt for line number and get corresponding element*/
   {
#    define LINE_SIZE 16
     unsigned char command_str[LINE_SIZE+1];

     s_readprompt(command_str,get_msg(M_GOTO),LINE_SIZE,misc_history);
     if (command_str[0])
      { no=atoi((char *)command_str);
        p=pre_find_element(no);
      }
   }
   /*}}}  */
  else
   /*{{{  check, if p in buffer!*/
   { element *q;

     q=bd.f.real_head;
     q=move_on(q);
     while (p!=q)
      { if (q==bd.f.real_tail)
         { p=bd.f.tail;
           break;
         }
        q=move_on(q);
      }
   }
   /*}}}  */
  if (p)
   { bd.scr.cursor.w = 1;
     close_and_open_folds(p);
     bd.f.current = p;
     title_op(CHGXY);
     restore_element(bd.scr.txt_size.h/2);
   }
}
/*}}}  */
/*{{{  find_field*/
private void find_field(void)
{ int off;
  int txt_off;
  int x;
  int m1,m2;
  int y;

  if (bd.scr.cursor.w<1) return;
  copyin(line_buffer, bd.f.current, False);
  x_shift(&off,&txt_off,0,bd.scr.cursor.w,bd.f.enter_spaces,line_buffer);
  restore_shift(off);
  /*{{{  handle higlight borders*/
  switch (get_highlight(*bd.f.current))
   {
     /*{{{  c_f*/
     case c_f_highlight:
        m1=bd.scr.cursor.w;
        m2=LINELEN+3;
        break;
     /*}}}  */
     /*{{{  m_f*/
     case m_f_highlight:
        m1=bd.e.makefold_indent;
        m2=LINELEN+3;
        break;
     /*}}}  */
     /*{{{  f_m*/
     case f_m_highlight:
        m1=0;
        m2=bd.scr.cursor.w-1;
        break;
     /*}}}  */
     /*{{{  c_m*/
     case c_m_highlight:
        if (bd.e.makefold_indent<bd.scr.cursor.w)
         { m1=bd.e.makefold_indent;m2=bd.scr.cursor.w-1; }
        else
         { m2=bd.e.makefold_indent+1;m1=bd.scr.cursor.w-1;
           if (m1<m2)
              m1++;
         }
        break;
     /*}}}  */
     /*{{{  full*/
     case full_highlight:
        m1=0;
        m2=LINELEN+2;
        break;
     /*}}}  */
     /*{{{  no highlight:simply print*/
     case no_highlight:
     default:
        m1=LINELEN+2;
        m2=LINELEN+3;
        break;
     /*}}}  */
   }
  /*}}}  */
  move_cursor_to(y=bd.scr.cursor.h+bd.scr.off.h,1+bd.scr.off.w);
  x=prt_highlight_bin_text(m1-off,m2-off,bd.f.enter_spaces+off,bd.scr.txt_size.w-1,True,line_buffer+txt_off);
  if (x<bd.scr.txt_size.w) clrbuffline(bd.scr.cursor.h,x+1);
  ori_assert(bd.scr.cur_shift_w<=LINELEN,"shift-check");
  title_op(PRTTITLE);
  if (off && soln_str)
   { move_cursor_to(y,1+bd.scr.off.w);
     oputc(soln_str);
   }
  move_cursor_to(y,bd.scr.cursor.w-off+bd.scr.off.w);
}
/*}}}  */
/*{{{  query_replace*/
public void query_replace(boolean force)
{
  unsigned char ch='\0';
  boolean display=True;
  boolean ok=True;
  boolean done=False;

  ocl_var[var_ocl_arg].v= -1;
  get_search(False,True,False);
  if ((*item_to_look_for!='\0') && !aborted)
   { s_readprompt(replace_item,get_msg(M_REPLACE),LINELEN,replace_history);
     if (!aborted)
      { int ls;

        ocl_var[var_ocl_arg].v=0;
        ls=ustrlen(item_to_look_for);
        while
         (   !aborted
          && find_item(False,display,ch!=DO_REST re_args(reg_type))
         )
         { copyin(cur_dsp_line, bd.f.current, False);
           /*{{{  get next command, maybe prompt with menu*/
           if (ch!=DO_REST)
            { if (force)
                 ch=DO_REST;
              else
               { title_op(CHGXY);
                 msg_message(M_Q_R_PROMPT);
                 find_field();
                 ch=hide_key();
                 ch=toupper(ch);
                 if (ch==' ') ch=YES;
               }
              if (ch==DO_REST)
               { display=False;
                 if (!executing_macro)
                    enable_abort();
               }
            }
           /*}}}  */
           if ((ch==YES || ch==DO_AB || ch==DO_REST) && !aborted)
            /*{{{  do the replace*/
            { int rp_len;

              done=True;
              proc_to_edit_pos();
              if (bd.scr.cursor.w<bd.e.first_scr_e_pos)
               /*{{{  error on fold-marking pattern*/
               { ok=False;
                 msg_message(M_MARK_PATTERN);
                 break;
               }
               /*}}}  */
              /*{{{  replace and check*/
              if
               (0>(rp_len=proc_replace
                           ( ls,
                             replace_item,
                             cur_dsp_line,
                             scr2txt
                              ( bd.f.enter_spaces,
                                bd.scr.cursor.w,
                                cur_dsp_line
                              ),
                             LINELEN-bd.f.enter_spaces
                           )
                  )
               )
               { ok=False;
                 ocl_var[var_ocl_arg].v= -1;
                 break;
               }
              /*}}}  */
              proc_from_edit_pos();
              move_cursor(rp_len);
              copyout(cur_dsp_line, bd.f.current);
              ocl_var[var_ocl_arg].v++;
            }
            /*}}}  */
           else
              move_cursor(1);
           if (display)
              write_dsp_line(bd.f.current, bd.scr.cursor.h);
           if (ch==AB || ch==DO_AB)
              break;
         }
        if (!executing_macro && ch==DO_REST)
           disable_abort();
        if (!display)
           restore_element(bd.scr.txt_size.h/2);
        if (ok)
           no_message();
      }
   }
  if (!done) try_unchange();
}
/*}}}  */
/*{{{  do_replace*/
public void do_replace(void)
{
  query_replace(True);
  if (ocl_var[var_ocl_arg].v>=0)
     message(get_msg(F_SUBSTITUTIONS,ocl_var[var_ocl_arg].v));
}
/*}}}  */
/*{{{  its_search*/
public void its_search(boolean reverse)
{
  /*{{{  variables*/
  TOKEN c;
  boolean first;
  int hist_index=START_HISTORY_INDEX;
  /*{{{  positions*/
  win_data call;
  win_data stack[LINELEN+1];
  /*}}}  */
  static unsigned char prompt[]="I-Search: ";
  int i_lg;
  boolean found;
  boolean jmp_back=False;
#  ifdef REGEXP
     int re_err=0;
#  endif
  /*}}}  */

  /*{{{  screen off for highlight*/
  if (bd.m.select_mode!=no_selection)
     ocl_screen_off();
  /*}}}  */
  first=True;
new_init:
  found=True;
  /*{{{  init search, prompt*/
  i_lg=ustrlen(item_to_look_for);
  if (jmp_back)
   /*{{{  jump back*/
   { bd.scr.cursor.w=stack[i_lg].w;
     find_element(stack[i_lg].h,bd.scr.txt_size.h/2);
     jmp_back=False;
   }
   /*}}}  */
  else
#  ifdef REGEXP
       if (!re_err)
#  endif
      /*{{{  init position-stack*/
      { int k=0;

        stack[0].w=bd.scr.cursor.w;
        stack[0].h=cur_line_no();
        while (++k <= i_lg) stack[k]=stack[0];
        if (first) call=stack[0];
      }
      /*}}}  */
#  ifdef REGEXP
     if (reg_type)
        re_err=item_to_look_for[0]?get_search(True,False,True):0;
#  endif
  find_field();
  /*}}}  */
  /*{{{  handle commands*/
  do
   {
     /*{{{  store position in stack*/
     title_op(CHGXY);
     stack[i_lg].w=bd.scr.cursor.w;
     stack[i_lg].h=cur_line_no();
     /*}}}  */
     /*{{{  screen on for highlight*/
     if (bd.m.select_mode!=no_selection)
      { ocl_screen_on();
        upd_highlight(bd.f.real_head,bd.f.real_tail,True);
        restore(1);
      }
     /*}}}  */
     /*{{{  show prompt and position*/
     title_op(CHGXY);
     { enum dsp_size old_mode;

       old_mode=dsp.norm;
       dsp.norm=norm_dsp;
       moveclreol(message_line,1);
       move_cursor_to(message_line,1);
       prompt[0]=reverse?'R':'I';
       prt_bin_text
        ( 0,
          LINELEN,
          False,
#     ifdef REGEXP
          re_err
           ? get_msg(M_REG_EXP,REG_ERR_TAG(re_err))
           :
#     endif
              prompt
        );
      if (!first)
         prt_bin_text(0,LINELEN,False,item_to_look_for);
#     ifdef REGEXP
       if (!re_err)
#     endif
          if (!found)
             prt_bin_text(0,LINELEN,False,get_msg(M_FI_FAILED));
       dsp.norm=old_mode;
     }
     find_field();
     /*}}}  */
     /*{{{  get command*/
     do
       c=hide_key();
     while ((c==O_BREAK) && !aborted);
     /*}}}  */
     /*{{{  screen off for highlight or draw line again*/
     if (bd.m.select_mode!=no_selection)
        ocl_screen_off();
     else
        write_dsp_line(bd.f.current, bd.scr.cursor.h);
     /*}}}  */
     /*{{{  handle command*/
     switch (c) {
       /*{{{  O_DELETE*/
       case O_DELETE:
          first=False;
          /*{{{  shorten string*/
          if (i_lg)
             item_to_look_for[--i_lg]='\0';
          /*}}}  */
          /*{{{  jump back*/
          jmp_back=True;
          goto new_init;
          /*}}}  */
       /*}}}  */
       /*{{{  O_RETURN*/
       case O_RETURN:
          break;
       /*}}}  */
       /*{{{  O_FIND         O_RIGHT*/
       case O_RIGHT:
       case O_ITS_SEARCH:
       case O_FIND:
          first=False;
#         ifdef REGEXP
          if (re_err)
             break;
#         endif
          reverse=False;
          if (i_lg)
           /*{{{  look for next*/
           { move_cursor(1);
             if (!(found=find_item(reverse,True,True re_args(reg_type))))
                move_cursor(-1);
           }
           /*}}}  */
          else
             found=True;
          break;
       /*}}}  */
       /*{{{  O_FIND_REVERSE O_LEFT*/
       case O_ITS_REVERSE:
       case O_LEFT:
       case O_FIND_REVERSE:
          first=False;
#         ifdef REGEXP
          if (re_err)
             break;
#         endif
          reverse=True;
          if (i_lg)
           /*{{{  look for previous*/
             found=find_item(reverse,True,True re_args(reg_type));
           /*}}}  */
          else
             found=True;
          break;
       /*}}}  */
       /*{{{  O_DOWN O_UP*/
       case O_DOWN:
       case O_UP:
        { unsigned char *s;

          first=False;
          access_history
           ( hist_index+((c==O_UP)?HISTORY_ADD_OLD:HISTORY_ADD_NEW),
             search_history,True,&hist_index,&s
           );
          ustrcpy(item_to_look_for,s);
          goto new_init;
        }
       /*}}}  */
       /*{{{  ascii*/
       default:
         /*{{{  first: search-string= ascii*/
         if (first)
          { i_lg=0;
            item_to_look_for[0]='\0';
            first=False;
          }
         /*}}}  */
         /*{{{  ignore keys >=O_NOP*/
         if (c>=O_NOP)
            break;
         /*}}}  */
         /*{{{  append char to search-string*/
         item_to_look_for[i_lg++]=c;
         item_to_look_for[i_lg]='\0';
         /*}}}  */
#        ifdef REGEXP
            if (reg_type && (re_err=get_search(True,False,True)))
             { stack[i_lg]=stack[i_lg-1];
               goto new_init;
             }
#        endif
         if (reverse)
            move_cursor(1);
         found=find_item(reverse,True,True re_args(reg_type));
         if (reverse && !found)
            move_cursor(-1);
         break;
       /*}}}  */
     }
     /*}}}  */
   }
  while (c!=O_BREAK && !aborted && c!=O_RETURN);
  /*}}}  */
  /*{{{  screen on for highlight*/
  if (bd.m.select_mode!=no_selection)
   { ocl_screen_on();
     upd_highlight(bd.f.real_head,bd.f.real_tail,True);
     restore(1);
   }
  /*}}}  */
  /*{{{  <> O_RETURN -> jump to calling position*/
  if (c!=O_RETURN)
   { find_element(call.h,bd.scr.txt_size.h/2);
     bd.scr.cursor.w=call.w;
   }
  /*}}}  */
  if (item_to_look_for[0])
     add_history(search_history,item_to_look_for);
  message(empty_text);
}
/*}}}  */
