/*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
// (C) PicoPeta Simputers Pvt. Ltd. 

#include <stdio.h>
#include <stdlib.h>
#include <tk.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "sketchbook_core.h"

#define stip_wid 3
#define stip_ht 3
static char stip_bits[] = {0x02, 0x05, 0x02};
Pixmap stip_map=None;

static sk_node_t *new_sk_node(unsigned short int x, unsigned short int y,
    unsigned short int width, unsigned int color,
    unsigned short int stipple);

static stroke_node_t *new_stroke_node(char dx, char dy);
static void free_sk_node(sk_node_t *skn);
static int do_pen_down(unsigned short int x, unsigned short int y,
        unsigned short int width, unsigned int color,
        unsigned short int stipple);
static int do_sk_add(signed char dx, signed char dy);
static void sk_draw_node(Tcl_Interp *interp, sk_node_t *skn);

int do_sk_clean(void);

// The global data structures
static sk_node_t *sk_head=NULL;
static sk_node_t *sk_tail=NULL;

Display *sdisp;
Window swin;
GC sgc;
Tk_Window scanv, simgwin;
Colormap scmap;
XColor scol;
short int sx, sy;
int lines_on_off=1;

void sk_init()
{
    sk_head = NULL;
    sk_tail = NULL;
}

static sk_node_t *new_sk_node(unsigned short int x, unsigned short int y,
    unsigned short int width, unsigned int color,
    unsigned short int stipple)
{
    sk_node_t *new_node;

    new_node = (sk_node_t *)malloc(sizeof(sk_node_t));
    if(!new_node) {
        fprintf(stderr,"%s(): Unable to allocate memory\n",__func__);
        return NULL;
    }
    new_node->x = x;
    new_node->y = y;
    new_node->width = width;
    new_node->color = color;
    new_node->stipple = stipple;
    new_node->next = NULL;
    new_node->prev = NULL;
    new_node->stroke_head = NULL;
    new_node->stroke_tail = NULL;
    return new_node;
}

static stroke_node_t *new_stroke_node(char dx, char dy)
{
    stroke_node_t *new_node;

    new_node = (stroke_node_t *)malloc(sizeof(stroke_node_t));
    if(!new_node) {
        fprintf(stderr,"%s(): Unable to allocate memory\n",__func__);
        return NULL;
    }
    new_node->no_of_pts = 0;
    new_node->dx_dy[new_node->no_of_pts++] = dx;
    new_node->dx_dy[new_node->no_of_pts++] = dy;
    new_node->next = NULL;
    return new_node;
}

static void free_sk_node(sk_node_t *skn) {
    // Free all nodes of the stroke buffer
    stroke_node_t *stn;
    if(!skn)
        return;
    else
        stn=skn->stroke_head;

    while(stn) {
        skn->stroke_head = skn->stroke_head->next;
        free(stn);
        stn=skn->stroke_head;
    }
    free(skn);
}

int sk_load(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    char *file_name, *cnvs_win;
    int fd;
    struct stat file_stat;
    off_t file_size;
    char *file_data=NULL, *tfile_data=NULL;
    char buffer[4096];
    int buff_size=4095;

    if(objc!=3) {
        Tcl_WrongNumArgs(interp,1,objv,"<filename> <canv_name>");
        return TCL_ERROR;
    }
    if(!(file_name=Tcl_GetStringFromObj(objv[1],NULL)))
            return TCL_ERROR;
    if(!(cnvs_win=Tcl_GetStringFromObj(objv[2],NULL)))
            return TCL_ERROR;

    simgwin = Tk_MainWindow(interp);
    scanv = Tk_NameToWindow(interp, cnvs_win, simgwin);
    sdisp = Tk_Display(scanv);
    if (!sdisp) { return TCL_ERROR; }
    swin = Tk_WindowId(scanv);
    scmap = DefaultColormap(sdisp, 0);
    if ((stip_map = XCreateBitmapFromData(sdisp, RootWindow(sdisp,
            DefaultScreen(sdisp)), stip_bits, stip_wid, stip_ht)) == 0) {
       perror("Can't create bitmap");
       return TCL_ERROR;
    }

    //fprintf(stderr,__func__"(): file_name = %s\n",file_name);
    do_sk_clean();
    if (lines_on_off) { do_sk_draw_lines(); }
    fd=open(file_name, O_RDONLY);
    if (fd < 0) { return TCL_ERROR; }

    if (fstat(fd, &file_stat)) { return TCL_ERROR; }
    file_size = file_stat.st_size;
    if (file_size < 12) { return TCL_OK; }
    file_data = (char *)malloc(sizeof(char)*file_size);
    if (!file_data) { return TCL_ERROR; }
    tfile_data = file_data;

    if (read(fd, file_data, file_size) < file_size) {
       free(tfile_data); return TCL_ERROR;
    }
    buffer[0] = 0;
    
    while(file_size > 0) {
        unsigned short int x,y,width,stipple;
        unsigned int color;
        signed char dx,dy;

        memcpy(&x, file_data, 2);
        memcpy(&y, &(file_data[2]), 2);
        memcpy(&width, &(file_data[4]), 2);
        memcpy(&color, &(file_data[6]), 4);
        memcpy(&stipple, &(file_data[10]), 2);

        file_data += 12; file_size -= 12;

        do_pen_down(x,y,width,color,stipple);

	memcpy(&dx, file_data++, 1);
	file_size--;

	while ((dx != END_CHAR) && (file_size > 0)) {
           memcpy(&dy, file_data++, 1);
           do_sk_add(dx, dy);

           memcpy(&dx, file_data++, 1);
           file_size -= 2;
        }
        XFlush(sdisp);
    }
    XFlush(sdisp);
    close(fd);
    free(tfile_data);
    return TCL_OK;
}

void store_sk_node(sk_node_t *skn, int fd)
{
    stroke_node_t *stn = skn->stroke_head;
    char end=END_CHAR;
    char temp[1024];
    int count=0;

    memcpy(temp, &skn->x, 2);
    memcpy(&(temp[2]), &skn->y, 2);
    memcpy(&(temp[4]), &skn->width, 2);
    memcpy(&(temp[6]), &skn->color, 4);
    memcpy(&(temp[10]), &skn->stipple, 2);
    write(fd, temp, 12);

    while(stn) {
        if ((count+stn->no_of_pts) > 1024) {
           count = 0; write(fd, temp, count);
        }
        memcpy(&(temp[count]), stn->dx_dy, stn->no_of_pts);
        count += stn->no_of_pts;
        stn = stn->next;
    }

    memcpy(&(temp[count++]), &end, 1);
    write(fd, temp, count);
}

int sk_store(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    char *file_name;
    int fd;
    sk_node_t *skn = sk_head;

    if(objc!=2) {
        Tcl_WrongNumArgs(interp,1,objv,"<filename>");
        return TCL_ERROR;
    }
    if(!(file_name=Tcl_GetStringFromObj(objv[1],NULL)))
            return TCL_ERROR;

    //fprintf(stderr,__func__"(): file_name = %s\n",file_name);

    fd=open(file_name,O_WRONLY|O_CREAT|O_TRUNC,0777);

    while(skn) {
        store_sk_node(skn, fd);
        skn = skn->next;
    }
    
    close(fd);
    return TCL_OK;
}

int sk_undo(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    //fprintf(stderr,__func__"()");
    if(sk_tail == sk_head) {
        if(sk_tail)
            free_sk_node(sk_tail);
        sk_head = sk_tail = NULL;
    } else {
        sk_node_t *skn;

        skn = sk_tail;
        sk_tail->prev->next = NULL;
        sk_tail = sk_tail->prev;

        free_sk_node(skn);
    }
    return TCL_OK;
}

int sk_pen_up(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    // NOP for now!!
    return TCL_OK;
}

// backend for sk_pen_down
int do_pen_down(unsigned short int x, unsigned short int y,
        unsigned short int width, unsigned int color,
        unsigned short int stipple)
{
    char clr[9];
    //fprintf(stderr,__func__"(),x=%d,y=%d,wid=%d,col=%d,stip=%d\n",x,y,width,color,stipple);
    if (sk_head == NULL) {
        sk_head = new_sk_node(x, y, width, color, stipple);
        sk_tail = sk_head;
    } else {
        sk_node_t *skn = new_sk_node(x, y, width, color, stipple);
        sk_tail->next = skn;
        skn->prev = sk_tail;
        sk_tail = skn;
    }

    sx = x; sy = y;
    sprintf(clr, "#%06x", color);
    XParseColor(sdisp, scmap, clr, &scol);
    XAllocColor(sdisp, scmap, &scol);
    sgc = XCreateGC(sdisp, swin, 0, 0);
    XSetForeground(sdisp, sgc, scol.pixel);
    XSetLineAttributes(sdisp, sgc, width, 0, 0, JoinBevel);

    if (stipple) {
       XSetStipple(sdisp, sgc, stip_map);
       XSetFillStyle(sdisp, sgc, FillStippled);
    } else {
       XSetFillStyle(sdisp, sgc, FillSolid);
    }

    return TCL_OK;
}

int sk_pen_down(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    int x,y,width,color,stipple;
    char *cnvs_win;

    if (objc!=7) {
        Tcl_WrongNumArgs(interp,1,objv,"<x> <y> <width> <color> <stipple> <canv_name>");
        return TCL_ERROR;
    }

    if(Tcl_GetIntFromObj(interp,objv[1],&x)!=TCL_OK)
        return TCL_ERROR;

    if(Tcl_GetIntFromObj(interp,objv[2],&y)!=TCL_OK)
        return TCL_ERROR;

    if(Tcl_GetIntFromObj(interp,objv[3],&width)!=TCL_OK)
        return TCL_ERROR;

    if(Tcl_GetIntFromObj(interp,objv[4],&color)!=TCL_OK)
        return TCL_ERROR;

    if(Tcl_GetIntFromObj(interp,objv[5],&stipple)!=TCL_OK)
        return TCL_ERROR;

    if (!(cnvs_win = Tcl_GetStringFromObj(objv[6], NULL)))
        return TCL_ERROR;

    simgwin = Tk_MainWindow(interp);
    scanv = Tk_NameToWindow(interp, cnvs_win, simgwin);
    sdisp = Tk_Display(scanv);
    if (!sdisp) { return TCL_ERROR; }
    swin = Tk_WindowId(scanv);
    scmap = DefaultColormap(sdisp, 0);

    //fprintf(stderr,__func__"(),x=%d,y=%d,wid=%d,col=%d,stip=%d\n",x,y,width,color,stipple);
    return do_pen_down( (unsigned short int)x,
                        (unsigned short int)y,
                        (unsigned short int)width,
                        (unsigned int)color,
                        (unsigned short int)stipple);
}

int do_sk_add(signed char dx, signed char dy)
{
    //fprintf(stderr,__func__"(), dx=%d,dy=%d\n",(int)dx,(int)dy);
    if(!sk_tail || !sk_head)
        return TCL_ERROR;
    if(sk_tail->stroke_head == NULL) {
        sk_tail->stroke_head = new_stroke_node(dx, dy);
        sk_tail->stroke_tail = sk_tail->stroke_head;
    } else {
        stroke_node_t *stn = sk_tail->stroke_tail;
        if (stn->no_of_pts < 200) {
           stn->dx_dy[stn->no_of_pts++] = dx;
           stn->dx_dy[stn->no_of_pts++] = dy;
        } else {
           stroke_node_t *stn = new_stroke_node(dx, dy);
           sk_tail->stroke_tail->next = stn;
           sk_tail->stroke_tail = stn;
        }
    }
    XDrawLine(sdisp, swin, sgc, sx, sy, sx+dx, sy+dy);
    sx += dx; sy += dy;
    return TCL_OK;
}

int sk_add(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    int dx,dy;

    if(objc!=3) {
        Tcl_WrongNumArgs(interp,1,objv,"<dx> <dy>");
        return TCL_ERROR;
    }

    if(Tcl_GetIntFromObj(interp,objv[1],&dx)!=TCL_OK)
        return TCL_ERROR;

    if(Tcl_GetIntFromObj(interp,objv[2],&dy)!=TCL_OK)
        return TCL_ERROR;

    //fprintf(stderr,__func__"() , dx=%d,dy=%d\n",dx,dy);
    return do_sk_add((signed char)dx,(signed char)dy);
}

// Complete cleanup
int do_sk_clean()
{
    sk_node_t *skn = sk_head;

    //fprintf(stderr,__func__"()\n");
    while(skn) {
        sk_head = sk_head->next;
        free_sk_node(skn);
        skn = sk_head;
    }
    return TCL_OK;
}

int sk_clean(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    char *cnvs_win;

    if (objc != 2) {
       Tcl_WrongNumArgs(interp, 1, objv, "<canv_name>");
       return TCL_ERROR;
    }
    if(!(cnvs_win = Tcl_GetStringFromObj(objv[1],NULL)))
       return TCL_ERROR;
    simgwin = Tk_MainWindow(interp);
    scanv = Tk_NameToWindow(interp, cnvs_win, simgwin);
    sdisp = Tk_Display(scanv);
    if (!sdisp) { return TCL_ERROR; }
    swin = Tk_WindowId(scanv);
    XClearWindow(sdisp, swin);

    return do_sk_clean();
}

void dump_sk_node(sk_node_t *skn)
{
    stroke_node_t *stn;

    if(!skn)
        return;
    else
        stn=skn->stroke_head;

    fprintf(stderr,"x=%d,y=%d,width=%d,color=%d,stipple=%d\n",
            skn->x,skn->y,skn->width,skn->color,skn->stipple);

    while(stn) {
        int i;
        for (i=0; i < stn->no_of_pts; ) {
           fprintf(stderr,"\tdx=%d,dy=%d\n",stn->dx_dy[i++],stn->dx_dy[i++]);
        }
        stn=stn->next;
    }
}

// Debug dump
int sk_dump(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    sk_node_t *skn = sk_head;

    //fprintf(stderr,__func__"()\n");
    while(skn) {
        dump_sk_node(skn);
        skn = skn->next;
    }
    return TCL_OK;
}

int do_sk_draw_lines()
{
    char clr[] = "#0000ff";
    int i, y1, y2;

    XParseColor(sdisp, scmap, clr, &scol);
    XAllocColor(sdisp, scmap, &scol);
    sgc = XCreateGC(sdisp, swin, 0, 0);
    XSetForeground(sdisp, sgc, scol.pixel);
    XSetLineAttributes(sdisp, sgc, 1, 0, 0, JoinBevel);

    for (i=0, y1=27, y2=27; i < 10; i++, y1 += 27, y2 += 27) {
        XDrawLine(sdisp, swin, sgc, 0, y1, 240, y2);
    }
}

int sk_draw_lines(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    char *cnvs_win;

    if (objc != 3) {
       Tcl_WrongNumArgs(interp, 1, objv, "<canv_name> <on_off>");
       return TCL_ERROR;
    }
    if(!(cnvs_win = Tcl_GetStringFromObj(objv[1],NULL)))
       return TCL_ERROR;
    if(Tcl_GetIntFromObj(interp,objv[2],&lines_on_off)!=TCL_OK)
        return TCL_ERROR;

    return TCL_OK;
}

static void sk_draw_node(Tcl_Interp *interp, sk_node_t *skn)
{
    stroke_node_t * stn;
    char clr[9];

    if (!skn) { return; } else { stn = skn->stroke_head; }
    sx = skn->x; sy = skn->y;
    sprintf(clr, "#%06x", skn->color);
    XParseColor(sdisp, scmap, clr, &scol);
    XAllocColor(sdisp, scmap, &scol);
    sgc = XCreateGC(sdisp, swin, 0, 0);
    XSetForeground(sdisp, sgc, scol.pixel);
    XSetLineAttributes(sdisp, sgc, skn->width, 0, 0, JoinBevel);

    if (skn->stipple) {
       XSetStipple(sdisp, sgc, stip_map);
       XSetFillStyle(sdisp, sgc, FillStippled);
    } else {
       XSetFillStyle(sdisp, sgc, FillSolid);
    }

    while (stn) {
       int i;
       for (i=0; i < stn->no_of_pts; i += 2) {
           XDrawLine(sdisp, swin, sgc, sx, sy, 
                         sx+stn->dx_dy[i], sy+stn->dx_dy[i+1]);
           sx += stn->dx_dy[i]; sy += stn->dx_dy[i+1];
       }
       stn = stn->next;
    }
}

int sk_draw(ClientData cd, Tcl_Interp *interp, int objc,
        Tcl_Obj *CONST objv[])
{
    char *cnvs_win;
    sk_node_t *skn=sk_head;

    if (objc != 2) {
       Tcl_WrongNumArgs(interp, 1, objv, "<canv_name>");
       return TCL_ERROR;
    }
    if (!(cnvs_win = Tcl_GetStringFromObj(objv[1], NULL)))
       return TCL_ERROR;
    simgwin = Tk_MainWindow(interp);
    scanv = Tk_NameToWindow(interp, cnvs_win, simgwin);
    sdisp = Tk_Display(scanv);
    if (!sdisp) { return TCL_ERROR; }
    swin = Tk_WindowId(scanv);
    XClearWindow(sdisp, swin);
    
    if (lines_on_off) { do_sk_draw_lines(); }
    while (skn) {
       sk_draw_node(interp, skn);
       skn = skn->next;
    }
    return TCL_OK;
}
