#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if unix /* but not WIN32 */
# include <alloca.h>
#else
# include "winjunk.h"
#endif

#include "FL/Fl.H"
#include "FL/Fl_Browser.H"
#include "FL/Fl_Input.H"
#include "Hist.H"

void HistInput::hist( Hist *h ) {
  hist_ = h;
  if(h) h->input(this);
}

int HistInput::handle( int ev ) {
  if(hist_ && hist_->handle_nav(ev))
    return 1;
  return Fl_Input::handle(ev);
}

void HistBrowser::hist( Hist *h ) {
  hist_ = h;
  if(h) h->browser(this);
}

int HistBrowser::handle( int ev ) {
  if(hist_ && hist_->handle_nav(ev))
    return 1;
  return Fl_Browser::handle(ev);
}

int Hist::handle( int ev ) {
  if(handle_nav(ev))
    return 1;
  return Fl_Group::handle(ev);
}

int HistBrowser::selscanup( int from ) {
  while(from < size() && !selected(from))
    from++;
  return from;
}

int HistBrowser::selscandown( int from ) {
  if(from > size()) from = size()-1;
  while(from > 0 && selected(from))
    from--;
  return from;
}

void HistBrowser::tighten_histrange() {
  int i;
  if(min <= 0) min = 1;
  if(max > size()) max = size();
  for(i = min; i <= max && !selected(i); i++)
	;
  min = i;
  for(i = max; i >= min && !selected(i); i--)
	;
  max = i;
}

static char msgprefix[] = "@C2@.# ";
#define MSGPREFIXSKIP (sizeof(msgprefix) - 3)	/* retain "# " */

int HistBrowser::is_cmd( int line ) {
  if(line <= 0 || line > size())
    return 0;
  return 0!=strncmp(text(line), msgprefix, sizeof(msgprefix)-1);
}

#define MAXHISTORY	500

void HistBrowser::addline( const char *str, int as_cmd ) {
  char *what = (char *)str;
  if(as_cmd) {
    what = (char *)alloca( strlen(str) + sizeof(msgprefix) + 1 );
    strcpy(what, msgprefix);
    strcpy(what+sizeof(msgprefix)-1, str);
  }
  if(size() >= MAXHISTORY) {
    for(int hyst = 0; hyst < 5; hyst++) {
	remove( 1 );
	min--;
	max--;
    }
  }
  add( what, (void *)as_cmd );
  middleline( size() ); // bottomline() won't do
}

int HistBrowser::find_cmd( int fromline, int incr )
{
  while(fromline > 0 && fromline <= size()) {
    if(is_cmd(fromline))
	return fromline;
    fromline += incr;
  }
  return 0;
}

int HistBrowser::handle_nav( int ev ) {
  int incr = 1;
  int line;
  HistInput *inp;
  if(hist_ == NULL || (inp = hist_->input()) == NULL)
    return 0;

  if(ev == FL_KEYBOARD || ev == FL_SHORTCUT) {
    switch(Fl::event_key()) {

    case FL_Up:
    case FL_Page_Up:
	incr = -1;		/* and fall into... */

    case FL_Down:
    case FL_Page_Down:
	tighten_histrange();
	line = selected(min) ? min : size()-incr;
	line = find_cmd( line+incr, incr );
	if(line) {
	    deselect(0);
	    select(line, 1);
	    middleline( line );
	    min = max = line;
	    inp->value( text(line) );
	    inp->position( inp->size(), inp->size() );
	}
	inp->take_focus();
	return 1;
    }
  }
  return 0;
}


void HistBrowser::picked_cb( HistBrowser *brow, void * ) {
  int i, which = brow->value();
  if(which <= 0) return;
  if(brow->min > which) brow->min = which;
  if(brow->max < which) brow->max = which;
  brow->tighten_histrange();

  if(!Fl::event_state(FL_BUTTON1|FL_BUTTON2|FL_BUTTON3)) {
    // All mouse-buttons released -- it's worth rebuilding our selection.
    int pos, len = 0;
    const char *s;
    for(i = brow->min; i <= brow->max; i++) {
	if(brow->selected(i)) {
	    s = brow->text(i);
	    if(!strncmp(s, msgprefix, sizeof(msgprefix)-1))
		s += MSGPREFIXSKIP;
	    len += strlen(s) + 1;
	}
    }
    if(len == 0) {
	Fl::selection_owner( NULL );
    } else {
	char *all = (char *)alloca( len + 1 );
	pos = 0;
	for(i = brow->min; i <= brow->max; i++) {
	    if(brow->selected(i)) {
		s = brow->text(i);
		if(!strncmp(s, msgprefix, sizeof(msgprefix)-1))
		    s += MSGPREFIXSKIP;
		len = strlen(s);
		memcpy(&all[pos], s, len);
		pos += len;
		all[pos++] = '\n';
	    }
	}
	Fl::selection( *brow, all, pos );
    }
  }
}