/*
 * 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"

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 lastcol,lastrow;
        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_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,newpos2;
        char *ptr,*ptr2;
        int len;
        int has_nl;
        int oldcol;
        int linecols;
        int inc;
        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) {
                if(redata_line_info(re->data,re->cursorpos,&newpos,&ptr,&len)==-1)
                        return(-1); /* couldn't get current line data */
                if(event->key.keysym.sym==SDLK_UP && newpos==0)
                        return(-1); /* going up but already at top */
                oldcol=reui_utf8len(re->ui,ptr,re->cursorpos-newpos);
                if(redata_line_info(re->data,(event->key.keysym.sym==SDLK_DOWN)?(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);
                ptr2=reui_utf8col(re->ui,ptr,len-has_nl,oldcol);
                if(ptr2!=NULL)
                        re->cursorpos=newpos2+(ptr2-ptr);
                else
                        re->cursorpos=newpos2+len-has_nl;
                if(event->key.keysym.sym==SDLK_DOWN && re->lastrow<re->maxrow)
                        re->lastrow++;
                else if(event->key.keysym.sym==SDLK_UP && re->lastrow>0)
                        re->lastrow--;
                re_drawheader(re);
                re->contentsdirty=1;
        } else if(event->key.keysym.sym==SDLK_LEFT || event->key.keysym.sym==SDLK_RIGHT) {
                if(redata_line_info(re->data,re->cursorpos,&newpos,&ptr,&len)==-1)
                        return(-1); /* couldn't get current line data */
                if(event->key.keysym.sym==SDLK_LEFT && re->cursorpos==0)
                        return(-1); /* going left but already at leftmost char */
                linecols=reui_utf8len(re->ui,ptr,len);
                oldcol=reui_utf8len(re->ui,ptr,re->cursorpos-newpos);
                inc=(event->key.keysym.sym==SDLK_LEFT)?-1:1;
                has_nl=((len>0 && ptr[len-1]=='\n')?1:0);
                if(re->lastcol<=linecols) {
                        ptr2=reui_utf8col(re->ui,ptr,len,re->lastcol+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->lastcol+inc)>=0)
                        re->lastcol+=inc;
                re_drawheader(re);
                re->contentsdirty=1;
        }
        return(-1);
}


int
re_drawheader(re_t *re)
{
        if(re==NULL)
                return(-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","Fichero: %s Col:%i Row:%i Pos:%li",re->filename,re->lastcol,re->lastrow,re->cursorpos);
        return(0);
}

int
re_drawcontents(re_t *re)
{
        long pos,newpos;
        char *ptr;
        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->lastrow;
        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->lastrow)*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);
                reui_write(re->ui,re->x,y,"\x00\x00\x00\xff",(char *)ptr,len-has_nl);
                if(row==re->lastrow) {
     #warning DEBUG write of current char
                        reui_fill(re->ui,re->x+re->ui->fontwidth*re->lastcol,y,re->ui->fontwidth,re->ui->fontheight+1,"\x00\x00\x00\xff");
#warning TODO: consider tabs
                        curptr=reui_utf8col(re->ui,ptr,len-has_nl,re->lastcol);
                        curptrlen=(curptr==NULL)?0:(len-has_nl)-(curptr-ptr);
                        reui_write(re->ui,re->x+re->ui->fontwidth*re->lastcol,y,"\xff\xff\xff\xff",curptr,reui_utf8charlen(re->ui,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);
}