#include "config.h"

#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif

#include <ctype.h>
#include <slang.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include "newt.h"
#include "newt_pr.h"

struct entry {
    int flags;
    char * buf;
    const char ** resultPtr;
    int bufAlloced;
    int bufUsed;		/* amount of the buffer that's been used */
    int cursorPosition; 	/* cursor *in the string* on on screen */
    int firstChar;		/* first character position being shown */
    newtEntryFilter filter;
    void * filterData;
};

static void entryDraw(newtComponent co);
static void entryDestroy(newtComponent co);
static struct eventResult entryEvent(newtComponent co,
			             struct event ev);

static struct eventResult entryHandleKey(newtComponent co, int key);

static struct componentOps entryOps = {
    entryDraw,
    entryEvent,
    entryDestroy,
    newtDefaultPlaceHandler,
    newtDefaultMappedHandler,
} ;

void newtEntrySet(newtComponent co, const char * value, int cursorAtEnd) {
    struct entry * en = co->data;

    if ((strlen(value) + 1) > (unsigned int)en->bufAlloced) {
	free(en->buf);
	en->bufAlloced = strlen(value) + 1;
	en->buf = malloc(en->bufAlloced);
	if (en->resultPtr) *en->resultPtr = en->buf;
    }
    memset(en->buf, 0, en->bufAlloced);		/* clear the buffer */
    strcpy(en->buf, value);
    en->bufUsed = strlen(value);
    en->firstChar = 0;
    if (cursorAtEnd)
	en->cursorPosition = en->bufUsed;
    else
	en->cursorPosition = 0;

    entryDraw(co);
} ;

newtComponent newtEntry(int left, int top, const char * initialValue, int width,
			const char ** resultPtr, int flags) {
    newtComponent co;
    struct entry * en;

    co = malloc(sizeof(*co));
    en = malloc(sizeof(struct entry));
    co->data = en;

    co->top = top;
    co->left = left;
    co->height = 1;
    co->width = width;
    co->isMapped = 0;
    co->callback = NULL;

    co->ops = &entryOps;

    en->flags = flags;
    en->cursorPosition = 0;
    en->firstChar = 0;
    en->bufUsed = 0;
    en->bufAlloced = width + 1;
    en->filter = NULL;

    if (!(en->flags & NEWT_FLAG_DISABLED))
	co->takesFocus = 1;
    else
	co->takesFocus = 0;

    if (initialValue && strlen(initialValue) > (unsigned int)width) {
	en->bufAlloced = strlen(initialValue) + 1;
    }
    en->buf = malloc(en->bufAlloced);
    en->resultPtr = resultPtr;
    if (en->resultPtr) *en->resultPtr = en->buf;

    memset(en->buf, 0, en->bufAlloced);
    if (initialValue) {
	strcpy(en->buf, initialValue);
	en->bufUsed = strlen(initialValue);
	en->cursorPosition = en->bufUsed;
    } else {
	*en->buf = '\0';
	en->bufUsed = 0;
	en->cursorPosition = 0;
    }

    return co;
}

static int visible_width(const char *str, int start, int end)
{
    int width = wstrlen(str + start, end-start);
    int len, w = 0;
    wchar_t wc;

    len = mbtowc(&wc, str+end, MB_CUR_MAX);
    if (len == 0)
       w = 1;
    else if (len > 0)
       w = wcwidth(wc);
    return width + w;
}

static void scroll(struct entry *en, int width)
{
    wchar_t wc;
    int len, w;
    int  newwidth = visible_width(en->buf, en->firstChar, en->cursorPosition);

    while (newwidth > width) {
        len = mbtowc(&wc, en->buf+en->firstChar, MB_CUR_MAX);
	if (!len) {
		en->firstChar++;
		break;
	}
	if (len < 0)
	   break;
	w = wcwidth(wc);
	if (w < 0)
	   break;
	en->firstChar += len;
	newwidth -= w;
    }
}

static void entryDraw(newtComponent co) {
    struct entry * en = co->data;
    int i;
    char * chptr;
    int len;
    char *tmpptr = NULL;

    if (!co->isMapped) return;

    if (en->flags & NEWT_FLAG_DISABLED)
	SLsmg_set_color(NEWT_COLORSET_DISENTRY);
    else
	SLsmg_set_color(NEWT_COLORSET_ENTRY);

    if (en->flags & NEWT_FLAG_HIDDEN) {
	newtGotorc(co->top, co->left);
	for (i = 0; i < co->width; i++)
	    SLsmg_write_char('_');
	newtGotorc(co->top, co->left);

	return;
    }

    newtGotorc(co->top, co->left);

    if (en->cursorPosition < en->firstChar) {
	/* scroll to the left */
	en->firstChar = en->cursorPosition;
    } else {
	/* scroll to the right */
	scroll(en, co->width);
    }

    chptr = en->buf + en->firstChar;

    if (en->flags & NEWT_FLAG_PASSWORD) {
	char *p;
	tmpptr = alloca(strlen(chptr)+2);
	strcpy(tmpptr, chptr);
	for (p = tmpptr; *p; p++)
	    *p = '*';
	chptr = tmpptr;
    }			

    len = wstrlen(chptr, -1);

    if (len <= co->width) {
	i = len;
	SLsmg_write_string(chptr);
	while (i < co->width) {
	    SLsmg_write_char('_');
	    i++;
	}
    } else {
	SLsmg_write_nstring(chptr, co->width);
    }

    if (en->flags & NEWT_FLAG_HIDDEN)
	newtGotorc(co->top, co->left);
    else
	newtGotorc(co->top, co->left + wstrlen(en->buf+en->firstChar, en->cursorPosition - en->firstChar));
}

void newtEntrySetFlags(newtComponent co, int flags, enum newtFlagsSense sense) {
    struct entry * en = co->data;
    int row, col;

    en->flags = newtSetFlags(en->flags, flags, sense);

    if (!(en->flags & NEWT_FLAG_DISABLED))
	co->takesFocus = 1;
    else
	co->takesFocus = 0;

    newtGetrc(&row, &col);
    entryDraw(co);
    newtGotorc(row, col);
}

static void entryDestroy(newtComponent co) {
    struct entry * en = co->data;

    free(en->buf);
    free(en);
    free(co);
}

static struct eventResult entryEvent(newtComponent co,
				     struct event ev) {
    struct entry * en = co->data;
    struct eventResult er;
    int ch;

    if (ev.when == EV_NORMAL) {
	switch (ev.event) {
	case EV_FOCUS:
	    newtCursorOn();
	    if (en->flags & NEWT_FLAG_HIDDEN)
		newtGotorc(co->top, co->left);
	    else
		newtGotorc(co->top, co->left +
			   wstrlen(en->buf + en->firstChar, en->cursorPosition - en->firstChar));
	    er.result = ER_SWALLOWED;
	    break;

	case EV_UNFOCUS:
	    newtCursorOff();
	    newtGotorc(0, 0);
	    er.result = ER_SWALLOWED;
	    if (co->callback)
		co->callback(co, co->callbackData);
	    break;

	case EV_KEYPRESS:
	    ch = ev.u.key;
	    if (en->filter)
		ch = en->filter(co, en->filterData, ch, en->cursorPosition);
	    if (ch) er = entryHandleKey(co, ch);
	    break;

	case EV_MOUSE:
	    if ((ev.u.mouse.type == MOUSE_BUTTON_DOWN) &&
		(en->flags ^ NEWT_FLAG_HIDDEN)) {
		if (strlen(en->buf) >= ev.u.mouse.x - co->left) {
		    en->cursorPosition = ev.u.mouse.x - co->left;
		    newtGotorc(co->top,
			       co->left +(en->cursorPosition - en->firstChar));
		} else {
		    en->cursorPosition = strlen(en->buf);
		    newtGotorc(co->top,
			       co->left +(en->cursorPosition - en->firstChar));
		}
	    }
	    break;
	}
    } else
	er.result = ER_IGNORED;

    return er;
}

static int previous_char(const char *buf, int pos)
{
    int len = 0;
    int off = 0;
    
    while (off < pos) {
       len = mblen(buf+off, MB_CUR_MAX);
       if (len <= 0)
	  return pos;
       off+=len;
    }
    return off-len;
}

static int next_char(const char *buf, int pos)
{
    int len = mblen(buf + pos, MB_CUR_MAX);
    if (len < 0)
       return pos;
    if (len == 0)
       return ++pos;
    return pos+len;
}

static struct eventResult entryHandleKey(newtComponent co, int key) {
    struct entry * en = co->data;
    struct eventResult er;
    char * chptr, * insPoint;

    er.result = ER_SWALLOWED;
    switch (key) {
      case '\r':				/* Return */
	if (en->flags & NEWT_FLAG_RETURNEXIT) {
	    er.result = ER_EXITFORM;
	} else {
	    er.result = ER_NEXTCOMP;
	}
	break;

      case '\001':				/* ^A */
      case NEWT_KEY_HOME:
	en->cursorPosition = 0;
	break;

      case '\005':				/* ^E */
      case NEWT_KEY_END:
	en->cursorPosition = en->bufUsed;
	break;

      case '\013':				/* ^K */
	en->bufUsed = en->cursorPosition;
	memset(en->buf + en->bufUsed, 0, en->bufAlloced - en->bufUsed);
	break;

      case '\025':				/* ^U */
	en->bufUsed -= en->cursorPosition;
	memmove(en->buf, en->buf + en->cursorPosition, en->bufUsed);
	en->cursorPosition = 0;
	memset(en->buf + en->bufUsed, 0, en->bufAlloced - en->bufUsed);
	break;

      case '\002':				/* ^B */
      case NEWT_KEY_LEFT:
	if (en->cursorPosition)
	    en->cursorPosition = previous_char(en->buf, en->cursorPosition);
	break;

      case '\004':
      case NEWT_KEY_DELETE:
	chptr = en->buf + en->cursorPosition;
	if (*chptr) {
	    int delta = next_char(en->buf, en->cursorPosition)-en->cursorPosition;
	    if (delta) {
	       chptr+=delta;
	       while (*chptr) {
	          *(chptr - delta) = *chptr;
		  chptr++;
	       }
	       memset(chptr - delta, 0, delta);
	       en->bufUsed-=delta;
	    }
	}
	break;

      case NEWT_KEY_BKSPC: {
	int prev = previous_char(en->buf, en->cursorPosition);
	if (en->cursorPosition != prev) {
	    /* if this isn't true, there's nothing to erase */
	    int delta = en->cursorPosition - prev;
	    chptr = en->buf + en->cursorPosition;
	    en->bufUsed-=delta;
	    en->cursorPosition-=delta;
	    while (*chptr) {
		*(chptr - delta) = *chptr;
		chptr++;
	    }
	    memset(chptr - delta, 0, delta);
	}
	}
	break;

      case '\006':				/* ^B */
      case NEWT_KEY_RIGHT:
	if (en->cursorPosition < en->bufUsed)
	    en->cursorPosition = next_char(en->buf, en->cursorPosition);
	break;

      default:
	if ((key >= 0x20 && key <= 0x7e) || (key >= 0x80 && key <= 0xff)) {
	    if (!(en->flags & NEWT_FLAG_SCROLL) && wstrlen(en->buf, -1) >= co->width) {
		/* FIXME this is broken */
		SLtt_beep();
		break;
	    }

	    if ((en->bufUsed + 1) == en->bufAlloced) {
		en->bufAlloced += 20;
		en->buf = realloc(en->buf, en->bufAlloced);
		if (en->resultPtr) *en->resultPtr = en->buf;
		memset(en->buf + en->bufUsed + 1, 0, 20);
	    }

	    if (en->cursorPosition == en->bufUsed) {
		en->bufUsed++;
	    } else {
		/* insert the new character */

		/* chptr is the last character in the string */
		chptr = (en->buf + en->bufUsed) - 1;
		if ((en->bufUsed + 1) == en->bufAlloced) {
		    /* this string fills the buffer, so clip it */
		    chptr--;
		} else
		    en->bufUsed++;

		insPoint = en->buf + en->cursorPosition;

		while (chptr >= insPoint) {
		    *(chptr + 1) = *chptr;
		    chptr--;
		}

	    }
            /* FIXME */
	    en->buf[en->cursorPosition++] = key;
	} else {
	    er.result = ER_IGNORED;
	}
    }

    entryDraw(co);

    return er;
}

char * newtEntryGetValue(newtComponent co) {
    struct entry * en = co->data;

    return en->buf;
}

void newtEntrySetFilter(newtComponent co, newtEntryFilter filter, void * data) {
    struct entry * en = co->data;
    en->filter = filter;
    en->filterData = data;
}
