/*
 * editor.c
 */

#include <memory.h>
#include <stdlib.h>

#include "config.h"
#include "player.h"
#include "fix.h"

/* externs */

extern struct command editor_list[];
extern char    *end_string();

/* interns */

void            editor_main();

/* print out some stats */

void            edit_stats(player * p, char *str)
{
   int             words = 0, lines = 0, blip = 0;
   char           *scan, *oldstack;
   oldstack = stack;
   for (scan = p->edit_info->buffer; *scan; scan++)
   {
      switch (*scan)
      {
    case ' ':
       if (blip)
          words++;
       blip = 0;
       break;
    case '\n':
       if (blip)
          words++;
       blip = 0;
       lines++;
       break;
    default:
       blip = 1;
      }
   }
   sprintf(oldstack, " Used %d bytes out of %d, in %d lines and %d words.\n",
           p->edit_info->size, p->edit_info->max_size, lines, words);
   stack = end_string(stack);
   tell_player(p, oldstack);
   stack = oldstack;
}

/* view the entire buffer */

void            edit_view(player * p, char *str)
{
   tell_player(p, p->edit_info->buffer);
}

/* insert a line into the buffer */

void            insert_line(player * p, char *str)
{
   ed_info        *e;
   int             len;
   char           *from, *to, *oldstack;
   e = p->edit_info;

   oldstack = stack;
   sprintf(stack, "%s\n", str);

   len = strlen(oldstack);
   if ((len + e->size) >= (e->max_size))
   {
      tell_player(p, " Line too long to fit into buffer.\n");
      return;
   }
   from = e->buffer + e->size;
   to = from + len;
   while (from != e->current)
      *(--to) = *(--from);
   *(--to) = *(--from);
   e->size += len;
   for (; len; len--)
      *(e->current)++ = *stack++;
   *stack++ = 0;
   tell_player(p, oldstack);
   stack = oldstack;
}


/* save and restore important flags that the editor changes */

void            save_flags(player * p)
{
   if (!p->edit_info)
      return;
   p->edit_info->flag_copy = p->flags;
   p->edit_info->sflag_copy = p->saved_flags;
   p->edit_info->input_copy = p->input_to_fn;
   p->flags &= ~PROMPT;
   if (p->saved_flags & QUIET_EDIT)
      p->saved_flags |= BLOCK_TELLS | BLOCK_SHOUT;
   p->input_to_fn = editor_main;
}

void            restore_flags(player * p)
{
   if (!p->edit_info)
      return;
   p->flags = p->edit_info->flag_copy;
   p->saved_flags = p->edit_info->sflag_copy;
   p->input_to_fn = p->edit_info->input_copy;
}


/* the main editor function */

void            editor_main(player * p, char *str)
{
   if (!p->edit_info)
   {
      log("error", "Editor called with no edit_info");
      return;
   }
   if (*str == '/')
   {
      restore_flags(p);
      match_commands(p, str + 1);
      save_flags(p);
      return;
   }
   if (*str == '.')
   {
      sub_command(p, str + 1, editor_list);
      if (p->edit_info)
    do_prompt(p, "+");
      return;
   }
   insert_line(p, str);
   do_prompt(p, "+");
}


/* fire editor up */

void            start_edit(player * p, int max_size, void *finish_func, void *quit_func,
                            char *current)
{
   ed_info        *e;
   int             old_length;
   if (p->edit_info)
      return;
   e = (ed_info *) MALLOC(sizeof(ed_info));
   memset(e, 0, sizeof(ed_info));
   p->edit_info = e;
   e->buffer = (char *) MALLOC(max_size);
   memset(e->buffer, 0, max_size);
   e->max_size = max_size;
   e->finish_func = finish_func;
   e->quit_func = quit_func;
   if (current)
   {
      old_length = strlen(current);
      memcpy(e->buffer, current, old_length);
      e->size = old_length + 1;
   } else
      e->size = 1;
   e->current = e->buffer + e->size - 1;
   p->flags |= DONT_CHECK_PIPE;
   save_flags(p);
   tell_player(p, " Entering editor ... Use /help editor for help.\n"
                  " /<command> for general commands, .<command> for editor "
                  "commands\n"
                  " Use '.end' to finish and keep edit\n");
   if (p->saved_flags & QUIET_EDIT)
      tell_player(p, " Blocking shouts and tells whilst in editor.\n");
   edit_view(p, 0);
   edit_stats(p, 0);
   do_prompt(p, "+");
}

/* tie up any loose ends */

void            finish_edit(player * p)
{
   if (!(p->edit_info))
      return;
   restore_flags(p);
   if (p->edit_info->buffer)
      FREE(p->edit_info->buffer);
   FREE(p->edit_info);
   p->edit_info = 0;
   p->flags &= ~DONT_CHECK_PIPE;
}

/* editor functions */

/* finish editing without changes */

void            edit_quit(player * p, char *str)
{
   void            (*fn) ();
   fn = p->edit_info->quit_func;
   (*fn) (p);
   finish_edit(p);
}

/* finish editing with changes */

void            edit_end(player * p, char *str)
{
   void            (*fn) ();
   fn = p->edit_info->finish_func;
   (*fn) (p);
   finish_edit(p);
}

/* clean the buffer completely */

void            edit_wipe(player * p, char *str)
{
   ed_info        *e;
   e = p->edit_info;
   memset(e->buffer, 0, e->size);
   e->size = 1;
   e->current = e->buffer;
   tell_player(p, " Edit buffer completely wiped ...\n");
}

/* view the current line */

void            edit_view_line(player * p, char *str)
{
   char           *scan, *oldstack;
   oldstack = stack;
   scan = p->edit_info->current;
   while (*scan && *scan != '\n')
      *stack++ = *scan++;
   *stack++ = '\n';
   *stack++ = 0;
   tell_player(p, oldstack);
   stack = oldstack;
}

/* move back up a line */

void            edit_back_line(player * p, char *str)
{
   char           *c;
   ed_info        *e;
   e = p->edit_info;
   c = e->current;
   if (c == e->buffer)
   {
      tell_player(p, " Can't go back any more, top of buffer.\n");
      return;
   }
/*   c -= 2;*/
	c--;
   while (c >= e->buffer && *c != '\n')
      c--;
   if (c == e->buffer)
      tell_player(p, " Reached top of buffer.\n");
   else
      c++;
   e->current = c;
   edit_view_line(p, 0);
}

/* move forward a line */

void            edit_forward_line(player * p, char *str)
{
   char           *c;
   ed_info        *e;
   e = p->edit_info;
   c = e->current;
   if (!*c)
   {
      tell_player(p, " Can't go forward, bottom of buffer.\n");
      return;
   }
   while (*c && *c != '\n')
      c++;
   if (*c)
      c++;
   e->current = c;
   if (!*c)
      tell_player(p, " Reached bottom of buffer.\n");
   else
      edit_view_line(p, 0);
}

/* print out available commands */

void            edit_view_commands(player * p, char *str)
{
   view_sub_commands(p, editor_list);
}

/* move to bottom of buffer */

void            edit_goto_top(player * p, char *str)
{
   p->edit_info->current = p->edit_info->buffer;
   tell_player(p, " Top of buffer.\n");
}

/* move to top of buffer */

void            edit_goto_bottom(player * p, char *str)
{
   p->edit_info->current = p->edit_info->buffer + p->edit_info->size - 1;
   tell_player(p, " Bottom of buffer.\n");
}

/* delete the current line */

void            edit_delete_line(player * p, char *str)
{
   ed_info        *e;
   char           *sl, *el;
   e = p->edit_info;
   sl = e->current;
   if (!*sl)
   {
      tell_player(p, " End of buffer, no line to delete.\n");
      return;
   }
   for (el = sl; (*el && *el != '\n'); el++)
      e->size--;
   if (*el)
   {
      el++;
      e->size--;
   }
   while (*el)
      *sl++ = *el++;
   while (sl != el)
      *sl++ = 0;
   tell_player(p, " Line deleted.\n");
}


/* go to a specific line */

void            edit_goto_line(player * p, char *str)
{
   char           *scan;
   int             line = 0;
   line = atoi(str);
   if (line < 1)
   {
      tell_player(p, " Argument is a number greater than zero.\n");
      return;
   }
   for (line--, scan = p->edit_info->buffer; (*scan && line); scan++)
      if (*scan == '\n')
    line--;
   if (!*scan)
   {
      tell_player(p, " Not that many lines.\n");
      return;
   }
   p->edit_info->current = scan;
   edit_view_line(p, 0);
}


/* toggle whether someone goes into quiet mode on edit */

void            toggle_quiet_edit(player * p, char *str)
{
   restore_flags(p);
   if (!strcasecmp("off", str))
      p->saved_flags &= ~QUIET_EDIT;
   else if (!strcasecmp("on", str))
      p->saved_flags |= QUIET_EDIT;
   else
      p->saved_flags ^= QUIET_EDIT;

   if (p->saved_flags & QUIET_EDIT)
      tell_player(p, " You will block tells and shouts upon editing.\n");
   else
      tell_player(p, " You won't block shouts and tells on editing.\n");
   save_flags(p);
}


/* the dreaded pager B-)  */

int             draw_page(player * p, char *text)
{
   int             end_line = 0, n;
   ed_info        *e;
   char           *oldstack;
   oldstack = stack;

   for (n = TERM_LINES + 1; n; n--, end_line++)
   {
      while (*text && *text != '\n')
         *stack++ = *text++;
      if (!*text)
         break;
      *stack++ = *text++;
   }
   *stack++ = 0;
   tell_player(p, oldstack);
   if (*text && p->edit_info)
   {
      e = p->edit_info;
      end_line += e->size;
      sprintf(oldstack, 
              "(Pager: lines %d to %d, from a total %d) Type n for next page: "
	      "\n",e->size, end_line, e->max_size);
      stack = end_string(oldstack);
      do_prompt(p, oldstack);
   }
   stack = oldstack;
   return *text;
}

void            quit_pager(player * p, ed_info * e)
{
   p->input_to_fn = e->input_copy;
   p->flags = e->flag_copy;
   if (e->buffer)
      FREE(e->buffer);
   FREE(e);
   p->edit_info = 0;
}

void            back_page(player * p, ed_info * e)
{
   char           *scan;
   int             n;
   scan = e->current;
   for (n = TERM_LINES + 1; n; n--)
   {
      while (scan != e->buffer && *scan != '\n')
    scan--;
      if (scan == e->buffer)
    break;
      e->size--;
      scan--;
   }
   e->current = scan;
}

void            forward_page(player * p, ed_info * e)
{
   char           *scan;
   int             n;
   scan = e->current;
   for (n = TERM_LINES + 1; n; n--)
   {
      while (*scan && *scan != '\n')
    scan++;
      if (!*scan)
    break;
      e->size++;
      scan++;
   }
   e->current = scan;
}

void            pager_fn(player * p, char *str)
{
   ed_info        *e;
   e = p->edit_info;
   switch (tolower(*str))
   {
      case 'b':
    back_page(p, e);
    break;
      case 'p':
    back_page(p, e);
    break;
      case 0:
    forward_page(p, e);
    break;
      case 'f':
    forward_page(p, e);
    break;
      case 'n':
    forward_page(p, e);
    break;
      case 't':
    e->current = e->buffer;
    e->size = 0;
    draw_page(p, e->current);
    break;
      case 'q':
    quit_pager(p, e);
    return;
   }
   if (!draw_page(p, e->current))
      quit_pager(p, e);
}


void            pager(player * p, char *text, int page)
{
   ed_info        *e;
   int             length = 0, lines = 0;
   char           *scan;

   if (p->saved_flags & NO_PAGER && !page)
   {
      tell_player(p, text);
      return;
   }
   if (p->edit_info)
   {
      tell_player(p, " Eeek, can't enter pager right now.\n");
      return;
   }
   for (scan = text; *scan; scan++, length++)
   {
      if (*scan == '\n')
      {
         lines++;
      }
      length++;
   }
   if (lines > (TERM_LINES + 1))
   {
      e = (ed_info *) MALLOC(sizeof(ed_info));
      memset(e, 0, sizeof(ed_info));
      p->edit_info = e;
      e->buffer = (char *) MALLOC(length);
      memcpy(e->buffer, text, length);
      e->current = e->buffer;
      e->max_size = lines;
      e->size = 0;
      e->input_copy = p->input_to_fn;
      e->flag_copy = p->flags;
      p->input_to_fn = pager_fn;
      p->flags &= ~PROMPT;
   }
   draw_page(p, text);
}