/*
 * recenteditor.c
 *
 * A programmers editor
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This program is licensed under the terms of GNU GPL v2.1+
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h>
#include <signal.h>

#include "re_data.h"
#include "re_ui.h"
#include "ext/socklib.h"

#define LINEFORCESCROLL 1
#define COLFORCESCROLL 5

typedef struct re_t {
        redata_t *data;
        reui_t *ui;
        int flag_newfile;
        char filename[PATH_MAX];
        int x, y, w, h; // contents rect
        long cursorpos;
        int originline,origincol;
        int curline,curcol;
        int maxrow,maxcol;
        int contentsdirty;
} re_t;

volatile int flag_sigint;
volatile int flag_sigpipe;


static int setsignal(int num, void (*sighandler)(int));
static void sighandler_sigint(int num);
static void sighandler_sigpipe(int num);


re_t *re_init(void);
void re_free(re_t *re);

int re_setfilename(re_t *re, char *filename);
int re_processkey(re_t *re, SDL_Event *event);
int re_moveupdown(re_t *re, int totalinc);
int re_moveleftright(re_t *re, int totalinc);
int re_drawheader(re_t *re);
int re_drawcontents(re_t *re);


int
main(int argc, char *argv[])
{
        re_t *re;
        int do_exit;
        SDL_Event event;
        sselect *ssel;
        int flag_had_events;
        if(argc!=2 || strcmp(argv[argc-1],"--help")==0) {
                fprintf(stderr,"Syntax: %s filename\n",argv[0]);
                return(1);
        }
        if((re=re_init())==NULL) {
                fprintf(stderr,"ERROR: couldn't init structs.\n");
                return(2);
        }
        if((ssel=sselect_init())==NULL) {
                fprintf(stderr,"ERROR: couln't init internal data.\n");
                re_free(re),re=NULL;
                return(2);
        }
        if(re_setfilename(re,argv[argc-1])!=0) {
                fprintf(stderr,"ERROR: filename too long.\n");
                re_free(re),re=NULL;
                return(2);
        }
        if((redata_load(re->data,re->filename,NULL,NULL))!=0)
                re->flag_newfile=1;
        else
                re->flag_newfile=0;
#if 0
#warning TESTS
        {
                char buf[129];
                redata_hash(re->data,buf);
                fprintf(stderr,"%s %s\n",buf,re->filename);
        }
#endif
        reui_title(re->ui,re->filename);
        flag_sigint=flag_sigpipe=0;
        setsignal(SIGINT,sighandler_sigint);
        setsignal(SIGPIPE,sighandler_sigpipe);
        do_exit=0;
        if((ssel=sselect_init())==NULL) {
                fprintf(stderr,"ERROR: filename too long.\n");
                re_free(re),re=NULL;
                return(2);
        }
        reui_scr2renderer(re->ui,0,0,re->ui->w,re->ui->h);
        re->x=0,re->y=re->ui->fontheight,re->w=re->ui->w,re->h=re->ui->h-re->y;
        re->maxrow=re->h/re->ui->fontheight-1;
        re->maxcol=re->w/re->ui->fontwidth-1;
        re_drawheader(re);
        re_drawcontents(re);
        flag_had_events=0;
        while(do_exit==0 && flag_sigint==0) {
                if(re->contentsdirty)
                        re_drawcontents(re);
                if(re->ui->rendererdirty)
                        reui_present(re->ui);
                sselect_wait(ssel,(flag_had_events)?10:100);
                flag_had_events=(flag_had_events>0)?flag_had_events-1:0;
                SDL_PumpEvents();
                while(SDL_PeepEvents(&event,1,SDL_GETEVENT,SDL_FIRSTEVENT,SDL_LASTEVENT)>0) {
                        flag_had_events=10;
                        switch(event.type) {
                                case SDL_QUIT:
                                        do_exit=1;
                                        break;
                                case SDL_KEYDOWN:
                                        re_processkey(re,&event);
                                        break;
                                case SDL_WINDOWEVENT:
                                        if(event.window.event==SDL_WINDOWEVENT_SHOWN
                                          || event.window.event==SDL_WINDOWEVENT_EXPOSED) {
                                                re->ui->rendererdirty=1;
                                        }
                                        break;
                                default:
                                        break;
                        }
                }
        }
        sselect_free(ssel),ssel=NULL;
        re_free(re),re=NULL;
        return(0);
}

static int
setsignal(int num, void (*sighandler)(int))
{
        struct sigaction sa;
        sa.sa_handler=sighandler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        return(sigaction(num,&sa,0));
}

static void
sighandler_sigint(int num)
{
        flag_sigint=1;
}

static void
sighandler_sigpipe(int num)
{
        flag_sigpipe=1;
}


re_t *
re_init(void)
{
        re_t *re;
        if((re=malloc(sizeof(re_t)))==NULL)
                return(NULL); /* insuf. mem. */
        memset(re,0,sizeof(re_t));
        if((re->data=redata_init(NULL))==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* insuf. mem. */
        }
        if((re->ui=reui_init())==NULL) {
                re_free(re),re=NULL;
                return(NULL); /* video init error */
        }
        return(re);
}

void
re_free(re_t *re)
{
        if(re==NULL)
                return; /* all done */
        if(re->ui!=NULL)
                reui_free(re->ui),re->ui=NULL;
        if(re->data!=NULL)
                redata_free(re->data),re->data=NULL;
        free(re),re=NULL;
        return;
}

int
re_setfilename(re_t *re, char *filename)
{
        if(re==NULL || filename==NULL)
                return(-1);
        strncpy(re->filename,filename,sizeof(re->filename));
        re->filename[sizeof(re->filename)-1]='\0';
        if(strcmp(filename,re->filename)!=0)
                return(-1); /* filename too long */
        return(0);
}

int
re_processkey(re_t *re, SDL_Event *event)
{
        long newpos;
        char *ptr;
        int len;
        int has_nl;
        if(re==NULL || event==NULL || event->type!=SDL_KEYDOWN)
                return(-1); /* sanity check failed */
        if(event->key.keysym.sym==SDLK_DOWN || event->key.keysym.sym==SDLK_UP) {
                re_moveupdown(re,(event->key.keysym.sym==SDLK_UP)?-1:1);
        } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) {
                re_moveleftright(re,(event->key.keysym.sym==SDLK_LEFT)?-1:1);
        } else if(event->key.keysym.sym==SDLK_PAGEDOWN || event->key.keysym.sym==SDLK_PAGEUP) {
                re_moveupdown(re,(event->key.keysym.sym==SDLK_PAGEUP)?-(re->maxrow):re->maxrow);
        } else if(event->key.keysym.sym==SDLK_HOME || event->key.keysym.sym==SDLK_END) {
                if(redata_line_info(re->data,re->cursorpos,&newpos,&ptr,&len)==-1)
                        return(-1); /* couldn't get current line data */
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                re->cursorpos=(event->key.keysym.sym==SDLK_HOME)?newpos:newpos+len-has_nl;
                redata_pos2linecol(re->data,re->cursorpos,&(re->curline),&(re->curcol));
                if(re->curcol<re->origincol) {
                        re->origincol=re->curcol;
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
                if(re->curcol>(re->origincol+re->maxcol-COLFORCESCROLL)) {
                        re->origincol=re->curcol+COLFORCESCROLL-re->maxcol;
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
                re->contentsdirty=1;
                re_drawheader(re);
        } else if(event->key.keysym.sym==SDLK_a) {
                ;
        }
        return(-1);
}

int
re_moveupdown(re_t *re, int totalinc)
{
        long newpos,newpos2;
        char *ptr;
        int len;
        int has_nl;
        int maxcol;
        int inc,doneinc;

        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        inc=(totalinc<0)?-1:1;
        for(doneinc=0;doneinc!=totalinc;doneinc+=inc) {
                if(redata_line_info(re->data,re->cursorpos,&newpos,&ptr,&len)==-1)
                        return(-1); /* couldn't get current line data */
                if(inc==-1 && newpos==0)
                        return(0); /* going up but already at top; nothing to do */
                if(redata_line_info(re->data,(inc==1)?(newpos+len):(newpos-1),&newpos2,&ptr,&len)==-1)
                        return(-1); /* couldn't get next line data */
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                maxcol=redata_generic_utf8len(ptr,len)-has_nl;
                if(maxcol<re->curcol)
                        re->cursorpos=newpos2+len-has_nl;
                else
                        re->cursorpos=newpos2+(redata_generic_utf8col(ptr,len,re->curcol)-ptr);
                re->curline+=inc;
                if(re->curline<(re->originline+LINEFORCESCROLL)) {
                        re->originline-=LINEFORCESCROLL;
                        re->originline=(re->originline<0)?0:re->originline;
                }
                if(re->curline>(re->originline+re->maxrow-LINEFORCESCROLL))
                        re->originline+=LINEFORCESCROLL;
                re->contentsdirty=1;
        }
        re_drawheader(re);
        return(0);
}

int
re_moveleftright(re_t *re, int totalinc)
{
        long newpos;
        char *ptr,*ptr2;
        int len;
        int has_nl;
        int maxcol;
        int inc,doneinc;

        if(re==NULL)
                return(-1); /* sanity check failed */
        if(totalinc==0)
                return(0); /* nothing to do */
        inc=(totalinc<0)?-1:1;
        for(doneinc=0;doneinc!=totalinc;doneinc+=inc) {
                if(redata_line_info(re->data,re->cursorpos,&newpos,&ptr,&len)==-1)
                        return(-1); /* couldn't get current line data */
                if(inc==-1 && re->cursorpos==0)
                        return(-1); /* going left but already at leftmost char */
                maxcol=redata_generic_utf8len(ptr,len);
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                if(re->curcol<=maxcol)
                        ptr2=redata_generic_utf8col(ptr,len,re->curcol+inc);
                else
                        ptr2=NULL;
                if(ptr2!=NULL)
                        re->cursorpos=newpos+(ptr2-ptr);
                else
                        re->cursorpos=newpos+(len-has_nl); /* we're past the last col, set cursor to last pos in line */
                if((re->curcol+inc)>=0)
                        re->curcol+=inc;
                if(re->curcol<(re->origincol+COLFORCESCROLL)) {
                        re->origincol-=COLFORCESCROLL;
                        re->origincol=(re->origincol<0)?0:re->origincol;
                }
                if(re->curcol>(re->origincol+re->maxcol-COLFORCESCROLL))
                        re->origincol+=COLFORCESCROLL;
                re->contentsdirty=1;
        }
        re_drawheader(re);
        return(0);
}

int
re_drawheader(re_t *re)
{
        int line,col;
        if(re==NULL)
                return(-1);
        if(redata_pos2linecol(re->data,re->cursorpos,&line,&col)==-1)
                line=col=-1;
        reui_fill(re->ui,0,0,re->ui->w,re->ui->fontheight,"\x00\x00\xff\xff");
        reui_printf(re->ui,0,0,"\xff\xff\x00\xff","File: %s Line:%i (%i) Col:%i (%i) Pos:%li",re->filename,re->curline,line,re->curcol,col,re->cursorpos);
        return(0);
}

int
re_drawcontents(re_t *re)
{
        long pos,newpos;
        char *ptr,*visibleptr;
        int len;
        int y,row;
        char *curptr;
        int curptrlen;
        int has_nl;
        if(re==NULL)
                return(-1);
        reui_fill(re->ui,re->x,re->y,re->w,re->h,"\xdf\xdf\xdf\xff");
        row=re->curline-re->originline;
        pos=re->cursorpos;
        while(row>0) {
                if(redata_line_info(re->data,pos,&newpos,NULL,NULL)==-1)
                        return(-1);
                pos=(newpos>0)?newpos-1:0;
                row--;
        }
        /* highlight current line */
        reui_fill(re->ui,re->x,re->y+(re->curline-re->originline)*re->ui->fontheight,re->w,re->ui->fontheight+1,"\xef\xef\xef\xff");
        /* draw the lines */
        for(y=re->y;y<(re->y+re->h);y+=re->ui->fontheight,row++) {
                if(redata_line_info(re->data,pos,&newpos,&ptr,&len)==-1)
                        break; /* couldn't get line start pos */
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                visibleptr=redata_generic_utf8col((char *)ptr,len-has_nl,re->origincol);
                if(visibleptr!=NULL)
                        reui_write(re->ui,re->x,y,"\x00\x00\x00\xff",visibleptr,len-has_nl-(visibleptr-ptr));
                if(row==(re->curline-re->originline)) {
#warning DEBUG write of current char
                        reui_fill(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff");
#warning TODO: consider tabs
                        curptr=redata_generic_utf8col(ptr,len-has_nl,re->curcol);
                        curptrlen=(curptr==NULL)?0:(len-has_nl)-(curptr-ptr);
                        reui_write(re->ui,re->x+re->ui->fontwidth*(re->curcol-re->origincol),y,"\xff\xff\xff\xff",curptr,redata_generic_utf8charlen(ptr,curptrlen));
#warning TODO: if it is one of  '[','{','<','>','}',']', highlight the matching bracket/parens/anglebracket.
                }
                pos=newpos+len;
        }
        re->contentsdirty=0;
        re->ui->rendererdirty=1;
        return(0);
}