#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>

static int debug = 0;

typedef struct Text Text;

struct Text
{
       Control;
       int             border;
       int             topline;
       int             scroll;
       int             nvis;
       int             lastbut;
       CFont   *font;
       CImage  *image;
       CImage  *textcolor;
       CImage  *bordercolor;
       CImage  *selectcolor;
       CImage  *selectingcolor;
       Rune            **line;
       int             selectmode;     // Selsingle, Selmulti
       int             selectstyle;    // Seldown, Selup (use Selup only with Selsingle)
       uchar   *selected;
       int             nline;
       int             warp;
       int             align;
       int             sel;            // line nr of selection made by last button down
       int             but;            // last button down (still being hold)
       int             offsel; // we are on selection
};

enum
{
       Selsingle,
       Selmulti,
       Seldown,
       Selup,
};

enum{
       EAccumulate,
       EAdd,
       EAlign,
       EBorder,
       EBordercolor,
       EClear,
       EDelete,
       EFocus,
       EFont,
       EHide,
       EImage,
       ERect,
       EReplace,
       EReveal,
       EScroll,
       ESelect,
       ESelectcolor,
       ESelectingcolor,
       ESelectmode,
       ESelectstyle,
       EShow,
       ESize,
       ETextcolor,
       ETopline,
       EValue,
       EWarp,
};

static char *cmds[] = {
       [EAccumulate] = "accumulate",
       [EAdd] =                        "add",
       [EAlign] =                      "align",
       [EBorder] =             "border",
       [EBordercolor] =        "bordercolor",
       [EClear] =                      "clear",
       [EDelete] =             "delete",
       [EFocus] =              "focus",
       [EFont] =                       "font",
       [EHide] =                       "hide",
       [EImage] =              "image",
       [ERect] =                       "rect",
       [EReplace] =            "replace",
       [EReveal] =             "reveal",
       [EScroll] =                     "scroll",
       [ESelect] =             "select",
       [ESelectcolor] =        "selectcolor",
       [ESelectingcolor] =     "selectingcolor",
       [ESelectmode] = "selectmode",
       [ESelectstyle] =                "selectstyle",
       [EShow] =                       "show",
       [ESize] =                       "size",
       [ETextcolor] =          "textcolor",
       [ETopline] =            "topline",
       [EValue] =                      "value",
       [EWarp] =                       "warp",
       nil
};

static void     textshow(Text*);
static void     texttogglei(Text*, int);
static int      textline(Text*, Point);
static int      texttoggle(Text*, Point);

static void
textmouse(Control *c, Mouse *m)
{
       Text *t;
       int sel;

       t = (Text*)c;
       if (debug) fprint(2, "textmouse %s t->lastbut %d; m->buttons %d\n", t->name, t->lastbut, m->buttons);
       if (t->warp >= 0)
               return;
       if ((t->selectstyle == Selup) && (m->buttons&7)) {
               sel = textline(t, m->xy);
               if (t->sel >= 0) {
//                      if (debug) fprint(2, "textmouse Selup %q sel=%d t->sel=%d t->but=%d\n",
//                                              t->name, sel, t->sel, t->but);
                       t->offsel = (sel == t->sel) ? 0 : 1;
                       if ((sel == t->sel &&
                                   ((t->selected[t->sel] && !t->but) ||
                                    ((!t->selected[t->sel]) && t->but))) ||
                           (sel != t->sel &&
                                    ((t->selected[t->sel] && t->but) ||
                                        ((!t->selected[t->sel]) && (!t->but))))) {
                               texttogglei(t, t->sel);
                       }
               }
       }
       if(t->lastbut != (m->buttons&7)){
               if(m->buttons & 7){
                       sel = texttoggle(t, m->xy);
                       if(sel >= 0) {
                               if (t->selectstyle == Seldown) {
                                       chanprint(t->event, "%q: select %d %d",
                                               t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
                                       if (debug) fprint(2, "textmouse Seldown event %q: select %d %d\n",
                                               t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
                               } else {
                                       if (debug) fprint(2, "textmouse Selup no event yet %q: select %d %d\n",
                                               t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
                                       t->sel = sel;
                                       t->but =  t->selected[sel] ? (m->buttons & 7) : 0;
                               }
                       }
               } else if (t->selectstyle == Selup) {
                       sel = textline(t, m->xy);
                       t->offsel = 0;
                       if ((sel >= 0) && (sel == t->sel)) {
                               chanprint(t->event, "%q: select %d %d",
                                       t->name, sel, t->but);
                               if (debug) fprint(2, "textmouse Selup event %q: select %d %d\n",
                                       t->name, sel, t->but);
                       } else if (sel != t->sel) {
                               if  ((t->selected[t->sel] && t->but) ||
                                        ((!t->selected[t->sel]) && (!t->but))) {
                                       texttogglei(t, t->sel);
                               } else {
                                       textshow(t);
                               }
                               if (debug) fprint(2, "textmouse Selup cancel %q: select %d %d\n",
                                       t->name, sel, t->but);
                       }
                       t->sel = -1;
                       t->but = 0;
               }
               t->lastbut = m->buttons & 7;
       }
}

static void
textfree(Control *c)
{
       int i;
       Text *t;

       t = (Text*)c;
       _putctlfont(t->font);
       _putctlimage(t->image);
       _putctlimage(t->textcolor);
       _putctlimage(t->bordercolor);
       _putctlimage(t->selectcolor);
       _putctlimage(t->selectingcolor);
       for(i=0; i<t->nline; i++)
               free(t->line[i]);
       free(t->line);
       free(t->selected);
}

static void
textshow(Text *t)
{
       Rectangle r, tr;
       Point p;
       int i, ntext;
       Font *f;
       Rune *text;

       if (t->hidden)
               return;
       r = t->rect;
       f = t->font->font;
       draw(t->screen, r, t->image->image, nil, t->image->image->r.min);
       if(t->border > 0){
               border(t->screen, r, t->border, t->bordercolor->image, t->bordercolor->image->r.min);
               r = insetrect(r, t->border);
       }
       tr = r;
       t->nvis = Dy(r)/f->height;
       for(i=t->topline; i<t->nline && i<t->topline+t->nvis; i++){
               text = t->line[i];
               ntext = runestrlen(text);
               r.max.y = r.min.y+f->height;
               if(t->sel == i && t->offsel)
                       draw(t->screen, r, t->selectingcolor->image, nil, ZP);
               else if(t->selected[i])
                       draw(t->screen, r, t->selectcolor->image, nil, ZP);
               p = _ctlalignpoint(r,
                       runestringnwidth(f, text, ntext),
                       f->height, t->align);
               if(t->warp == i) {
                       Point p2;
                        p2.x = p.x + 0.5*runestringnwidth(f, text, ntext);
                        p2.y = p.y + 0.5*f->height;
                       moveto(t->controlset->mousectl, p2);
                       t->warp = -1;
               }
               _string(t->screen, p, t->textcolor->image,
                       ZP, f, nil, text, ntext, tr,
                       nil, ZP, SoverD);
               r.min.y += f->height;
       }
       flushimage(display, 1);
}

static void
textctl(Control *c, CParse *cp)
{
       int cmd, i, n;
       Rectangle r;
       Text *t;
       Rune *rp;

       t = (Text*)c;
       cmd = _ctllookup(cp->args[0], cmds, nelem(cmds));
       switch(cmd){
       default:
               ctlerror("%q: unrecognized message '%s'", t->name, cp->str);
               break;
       case EAlign:
               _ctlargcount(t, cp, 2);
               t->align = _ctlalignment(cp->args[1]);
               break;
       case EBorder:
               _ctlargcount(t, cp, 2);
               if(cp->iargs[1] < 0)
                       ctlerror("%q: bad border: %c", t->name, cp->str);
               t->border = cp->iargs[1];
               break;
       case EBordercolor:
               _ctlargcount(t, cp, 2);
               _setctlimage(t, &t->bordercolor, cp->args[1]);
               break;
       case EClear:
               _ctlargcount(t, cp, 1);
               for(i=0; i<t->nline; i++)
                       free(t->line[i]);
               free(t->line);
               free(t->selected);
               t->line = ctlmalloc(sizeof(Rune*));
               t->selected = ctlmalloc(1);
               t->nline = 0;
               textshow(t);
               break;
       case EDelete:
               _ctlargcount(t, cp, 2);
               i = cp->iargs[1];
               if(i<0 || i>=t->nline)
                       ctlerror("%q: line number out of range: %s", t->name, cp->str);
               free(t->line[i]);
               memmove(t->line+i, t->line+i+1, (t->nline-(i+1))*sizeof(Rune*));
               memmove(t->selected+i, t->selected+i+1, t->nline-(i+1));
               t->nline--;
               textshow(t);
               break;
       case EFocus:
               break;
       case EFont:
               _ctlargcount(t, cp, 2);
               _setctlfont(t, &t->font, cp->args[1]);
               break;
       case EHide:
               _ctlargcount(t, cp, 1);
               t->hidden = 1;
               break;
       case EImage:
               _ctlargcount(t, cp, 2);
               _setctlimage(t, &t->image, cp->args[1]);
               break;
       case ERect:
               _ctlargcount(t, cp, 5);
               r.min.x = cp->iargs[1];
               r.min.y = cp->iargs[2];
               r.max.x = cp->iargs[3];
               r.max.y = cp->iargs[4];
               if(Dx(r)<=0 || Dy(r)<=0)
                       ctlerror("%q: bad rectangle: %s", t->name, cp->str);
               t->rect = r;
               t->nvis = (Dy(r)-2*t->border)/t->font->font->height;
               break;
       case EReplace:
               _ctlargcount(t, cp, 3);
               i = cp->iargs[1];
               if(i<0 || i>=t->nline)
                       ctlerror("%q: line number out of range: %s", t->name, cp->str);
               free(t->line[i]);
               t->line[i] = _ctlrunestr(cp->args[2]);
               textshow(t);
               break;
       case EReveal:
               _ctlargcount(t, cp, 1);
               t->hidden = 0;
               textshow(t);
               break;
       case EScroll:
               _ctlargcount(t, cp, 2);
               t->scroll = cp->iargs[1];
               break;
       case ESelect:
               if(cp->nargs!=2 && cp->nargs!=3)
       badselect:
                       ctlerror("%q: bad select message: %s", t->name, cp->str);
               if(cp->nargs == 2){
                       if(strcmp(cp->args[1], "all") == 0){
                               memset(t->selected, 1, t->nline);
                               break;
                       }
                       if(strcmp(cp->args[1], "none") == 0){
                               memset(t->selected, 0, t->nline);
                               break;
                       }
                       if(cp->args[1][0]<'0' && '9'<cp->args[1][0])
                               goto badselect;
                       texttogglei(t, cp->iargs[1]);
                       break;
               }
               if(cp->iargs[1]<0 || cp->iargs[1]>=t->nline)
                       ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
               if(t->selected[cp->iargs[1]] != (cp->iargs[2]!=0))
                       texttogglei(t, cp->iargs[1]);
               break;
       case ESelectcolor:
               _ctlargcount(t, cp, 2);
               _setctlimage(t, &t->selectcolor, cp->args[1]);
               break;
       case ESelectmode:
               _ctlargcount(t, cp, 2);
               if(strcmp(cp->args[1], "single") == 0)
                       t->selectmode = Selsingle;
               else if(strncmp(cp->args[1], "multi", 5) == 0)
                       t->selectmode = Selmulti;
               break;
       case ESelectstyle:
               _ctlargcount(t, cp, 2);
                if(strcmp(cp->args[1], "down") == 0)
                       t->selectstyle = Seldown;
               else if(strcmp(cp->args[1], "up") == 0)
                       t->selectstyle = Selup;
               break;
       case EShow:
               _ctlargcount(t, cp, 1);
               textshow(t);
               break;
       case ESize:
               if (cp->nargs == 3)
                       r.max = Pt(10000, 10000);
               else{
                       _ctlargcount(t, cp, 5);
                       r.max.x = cp->iargs[3];
                       r.max.y = cp->iargs[4];
               }
               r.min.x = cp->iargs[1];
               r.min.y = cp->iargs[2];
               if(r.min.x<=0 || r.min.y<=0 || r.max.x<=0 || r.max.y<=0 || r.max.x < r.min.x || r.max.y < r.min.y)
                       ctlerror("%q: bad sizes: %s", t->name, cp->str);
               t->size.min = r.min;
               t->size.max = r.max;
               break;
       case ETextcolor:
               _ctlargcount(t, cp, 2);
               _setctlimage(t, &t->textcolor, cp->args[1]);
               break;
       case ETopline:
               _ctlargcount(t, cp, 2);
               i = cp->iargs[1];
               if(i < 0)
                       i = 0;
               if(i > t->nline)
                       i = t->nline;
               if(t->topline != i){
                       t->topline = i;
                       textshow(t);
               }
               break;
       case EValue:
               /* set contents to single line */
               /* free existing text and fall through to add */
               for(i=0; i<t->nline; i++){
                       free(t->line[i]);
                       t->line[i] = nil;
               }
               t->nline = 0;
               t->topline = 0;
               /* fall through */
       case EAccumulate:
       case EAdd:
               switch (cp->nargs) {
               default:
                       ctlerror("%q: wrong argument count in '%s'", t->name, cp->str);
               case 2:
                       n = t->nline;
                       break;
               case 3:
                       n = cp->iargs[1];
                       if(n<0 || n>t->nline)
                               ctlerror("%q: line number out of range: %s", t->name, cp->str);
                       break;
               }
               rp = _ctlrunestr(cp->args[cp->nargs-1]);
               t->line = ctlrealloc(t->line, (t->nline+1)*sizeof(Rune*));
               memmove(t->line+n+1, t->line+n, (t->nline-n)*sizeof(Rune*));
               t->line[n] = rp;
               t->selected = ctlrealloc(t->selected, t->nline+1);
               memmove(t->selected+n+1, t->selected+n, t->nline-n);
               t->selected[n] = (t->selectmode==Selmulti && cmd!=EAccumulate);
               t->nline++;
               if(t->scroll) {
                       if(n > t->topline + (t->nvis - 1)){
                               t->topline = n - (t->nvis - 1);
                               if(t->topline < 0)
                                       t->topline = 0;
                       }
                       if(n < t->topline)
                               t->topline = n;
               }
               if(cmd != EAccumulate)
                       if(t->scroll || t->nline<=t->topline+t->nvis)
                               textshow(t);
               break;
       case EWarp:
               _ctlargcount(t, cp, 2);
               i = cp->iargs[1];
               if(i <0 || i>=t->nline)
                       ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
               if(i < t->topline || i >=  t->topline+t->nvis){
                       t->topline = i;
               }
               t->warp = cp->iargs[1];
               textshow(t);
               t->warp = -1;
               break;
       }
}

static void
texttogglei(Text *t, int i)
{
       int prev;

       if(t->selectmode == Selsingle){
               /* clear the others */
               prev = t->selected[i];
               memset(t->selected, 0, t->nline);
               t->selected[i] = prev;
       }
       t->selected[i] ^= 1;
       textshow(t);
}

static int
textline(Text *t, Point p)
{
       Rectangle r;
       int i;

       r = t->rect;
       if(t->border > 0)
               r = insetrect(r, t->border);
       if(!ptinrect(p, r))
               return -1;
       i = (p.y-r.min.y)/t->font->font->height;
       i += t->topline;
       if(i >= t->nline)
               return -1;
       return i;
}

static int
texttoggle(Text *t, Point p)
{
       int i;

       i = textline(t, p);
       if (i >= 0)
               texttogglei(t, i);
       return i;
}

Control*
createtext(Controlset *cs, char *name)
{
       Text *t;

       t = (Text*)_createctl(cs, "text", sizeof(Text), name);
       t->line = ctlmalloc(sizeof(Rune*));
       t->selected = ctlmalloc(1);
       t->nline = 0;
       t->image = _getctlimage("white");
       t->textcolor = _getctlimage("black");
       t->bordercolor = _getctlimage("black");
       t->selectcolor = _getctlimage("yellow");
       t->selectingcolor = _getctlimage("paleyellow");
       t->font = _getctlfont("font");
       t->selectmode = Selsingle;
       t->selectstyle = Selup; // Seldown;
       t->lastbut = 0;
       t->mouse = textmouse;
       t->ctl = textctl;
       t->exit = textfree;
       t->warp = -1;
       t->sel = -1;
       t->offsel = 0;
       t->but = 0;
       return (Control *)t;
}