/*
 * Copyright (c) 2019 Clementine Computing LLC.
 *
 * This file is part of PopuFare.
 * 
 * PopuFare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * PopuFare 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with PopuFare.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <sys/termios.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/fb.h>

#include <stdio.h>
#include <stdlib.h>
#include <poll.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <regex.h>
#include <fcntl.h>

#include "../common/common_defs.h"

#include "fbutil.h"
#include "fbfonts.h"

extern fbfont smallfont;
extern fbfont medfont;
extern fbfont bigfont;

//  This include file contains the palette tables for storing the old palette to be later restored, as well as
//the palette table we use for our application.  It also has a byzantine and insane table where I have factored out
//all of the possible permutations of painting color X over color Y with or without the blend bit set in color X, so
//you can use the LOOKUP_BLEND(x, y) macro and it will return the resulting color.
#include "color_tables.h"
//-------------------------------

named_color color_names[] =
{
    {"white",       FBCOLOR_WHITE},
    {"light_gray",     FBCOLOR_GRAY},
    {"dark_gray",     FBCOLOR_DKGRAY},
    {"black",       FBCOLOR_BLACK},
    {"trans_white",     FBCOLOR_WHITE | FBCOLOR_TRANSLUCENT},
    {"trans_light_gray",   FBCOLOR_GRAY | FBCOLOR_TRANSLUCENT},
    {"trans_dark_gray",   FBCOLOR_DKGRAY | FBCOLOR_TRANSLUCENT},
    {"trans_black",     FBCOLOR_BLACK | FBCOLOR_TRANSLUCENT},

    {"dark_red",     FBCOLOR_DKRED},
    {"red",       FBCOLOR_RED},
    {"light_red",     FBCOLOR_LTRED},
    {"trans_dark_red",     FBCOLOR_DKRED | FBCOLOR_TRANSLUCENT},
    {"trans_red",     FBCOLOR_RED | FBCOLOR_TRANSLUCENT},
    {"trans_light_red",   FBCOLOR_LTRED | FBCOLOR_TRANSLUCENT},

    {"dark_green",     FBCOLOR_DKGREEN},
    {"green",       FBCOLOR_GREEN},
    {"light_green",     FBCOLOR_LTGREEN},
    {"trans_dark_green",   FBCOLOR_DKGREEN | FBCOLOR_TRANSLUCENT},
    {"trans_green",     FBCOLOR_GREEN | FBCOLOR_TRANSLUCENT},
    {"trans_light_green",   FBCOLOR_LTGREEN | FBCOLOR_TRANSLUCENT},

    {"dark_blue",     FBCOLOR_DKBLUE},
    {"blue",       FBCOLOR_BLUE},
    {"light_blue",     FBCOLOR_LTBLUE},
    {"trans_dark_blue",   FBCOLOR_DKBLUE | FBCOLOR_TRANSLUCENT},
    {"trans_blue",     FBCOLOR_BLUE | FBCOLOR_TRANSLUCENT},
    {"trans_light_blue",   FBCOLOR_LTBLUE | FBCOLOR_TRANSLUCENT},

    {"dark_yellow",     FBCOLOR_DKYELLOW},
    {"yellow",       FBCOLOR_YELLOW},
    {"light_yellow",     FBCOLOR_LTYELLOW},
    {"trans_dark_yellow",   FBCOLOR_DKYELLOW | FBCOLOR_TRANSLUCENT},
    {"trans_yellow",     FBCOLOR_YELLOW | FBCOLOR_TRANSLUCENT},
    {"trans_light_yellow",   FBCOLOR_LTYELLOW | FBCOLOR_TRANSLUCENT},

    {"dark_cyan",     FBCOLOR_DKCYAN},
    {"cyan",       FBCOLOR_CYAN},
    {"light_cyan",     FBCOLOR_LTCYAN},
    {"trans_dark_cyan",   FBCOLOR_DKCYAN | FBCOLOR_TRANSLUCENT},
    {"trans_cyan",     FBCOLOR_CYAN | FBCOLOR_TRANSLUCENT},
    {"trans_light_cyan",   FBCOLOR_LTCYAN | FBCOLOR_TRANSLUCENT},

    {"dark_magenta",     FBCOLOR_DKMAGENTA},
    {"magenta",     FBCOLOR_MAGENTA},
    {"light_magenta",     FBCOLOR_LTMAGENTA},
    {"trans_dark_magenta",   FBCOLOR_DKMAGENTA | FBCOLOR_TRANSLUCENT},
    {"trans_magenta",     FBCOLOR_MAGENTA | FBCOLOR_TRANSLUCENT},
    {"trans_light_magenta",   FBCOLOR_LTMAGENTA | FBCOLOR_TRANSLUCENT},

    {NULL, 0}  //THIS TERMINATOR IS NEEDED SO WE DON'T WALK OFF THE END OF THE LIST!
};


static inline unsigned int consume_hex_digits(char **s, int digits)
{
    unsigned int accum = 0;
    int i;
    char *trav;

    if(!s)
        return 0;

    trav = *s;

    if(!trav)
        return 0;

    for(i = 0; i < digits; i++)
    {
        if(!*trav)
            break;

        switch(*trav)
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
            case 8:
            case 9:
                accum |= (*trav) - '0';
                accum <<= 4;
                trav++;
                break;

            case 'A':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
                accum |= (*trav) - '7';
                accum <<= 4;
                trav++;
                break;

            case 'a':
            case 'b':
            case 'c':
            case 'd':
            case 'e':
            case 'f':
                accum |= (*trav) - 'W';
                accum <<= 4;
                trav++;
                break;

            default:
                *s = trav;
                return accum;
                break;
        }
    }

    *s = trav;
    return accum;
}

pixel string_to_color(char *str)
{
    char *trav;
    pixel color = FBCOLOR_BLACK;

    int i;
    int r = 0, g = 0, b = 0, a = 0xFF;

    if(str)
    {
        trav = str;

        if(*trav == '#')        //If we have a numeric color
        {
            trav++;

            i = 0;

            r = consume_hex_digits(&trav, 2);    //Get the first hex octet

            if(*trav)          //if there are more characters, we'll expect at least #rrggbb
            {
                g = consume_hex_digits(&trav, 2);  //covert green
                b = consume_hex_digits(&trav, 2);  //and blue

                if(*trav)        //if there are still more characters, it may be #rrggbbaa
                {
                    a = consume_hex_digits(&trav, 2);  //convert alpha
                }

                color = FROM_RGBA(r,g,b,a);    //make a framebuffer color from the supplied RGBA values
            }
            else          //otherwise, treat the single octet as a raw framebuffer color
            {
                color = r;  //if we only got one octet, use it as a raw color
            }
        }
        else  //if there was no # to indicate a numeric color, let's scan our list of named colors
        {
            i = 0;

            while(1)
            {
                if(color_names[i].name == NULL)    //if we've hit the end of the list
                {
                    break;  //give up
                }

                if(!strcasecmp(color_names[i].name, trav))  //if it matches
                {
                    color = color_names[i].color;    //snag the color and we're done
                    break;
                }
                else
                {
                    i++;          //otherwise, try the next one
                }
            }
        }
    }

    return color;  //report our results back
}

static unsigned int clip_x = 0;
static unsigned int clip_y = 0;
static unsigned int clip_xx = XRES;
static unsigned int clip_yy = YRES;

void reset_clip_rect()
{
    clip_x = 0;
    clip_y = 0;
    clip_xx = XRES;
    clip_yy = YRES;
}

#define SWAP(a,b)  {tmp = a; a = b; b = tmp;}

void set_clip_rect(unsigned x, unsigned y, unsigned xx, unsigned yy)
{
    int tmp;

    if(x > xx) SWAP(x, xx);
    if(y > yy) SWAP(y, yy);

    clip_x = (x <= XRES)?x:XRES;

    clip_xx = (xx <= XRES)?xx:XRES;

    clip_y = (y <= YRES)?y:YRES;

    clip_yy = (yy <= YRES)?yy:YRES;
}


void get_clip_rect(unsigned *x, unsigned *y, unsigned *xx, unsigned *yy)
{
    if(x) *x = clip_x;

    if(xx) *xx = clip_xx;

    if(y) *y = clip_y;

    if(yy) *y = clip_yy;
}


static inline int coord_to_index(unsigned x, unsigned y)
{
    if( (x < clip_x) || (x >= clip_xx) || (y < clip_y) || (y >= clip_yy) )
        return -1;

    return x + (y * XRES);
}

static pixel fgcolor;
static pixel bgcolor;
static unsigned int cursor_x;
static unsigned int cursor_y;

static int ts_calib_left = 0x07F;
static int ts_calib_right = 0x3BD;
static int ts_calib_top = 0x040;
static int ts_calib_bottom = 0x3AA;

static int fontnum;

void set_rawcolor(pixel color)
{
    fgcolor = color;
}

void set_bgrawcolor(pixel color)
{
    bgcolor = color;
}


static void load_ts_calib()
{
    FILE *f;
    int retval;
    int l,r,t,b;

    f = fopen(TOUCHSCREEN_CALIB_FILE, "rb");

    if(f)
    {
        retval = fscanf(f, "%d %d %d %d", &l, &r, &t, &b);

        if(retval == 4)
        {
            ts_calib_left = l;
            ts_calib_right = r;
            ts_calib_top = t;
            ts_calib_bottom = b;
        }

        fclose(f);
    }
}

static void save_ts_calib()
{
    FILE *f;

    f = fopen(TOUCHSCREEN_CALIB_FILE, "w");

    if(f)
    {
        fprintf(f, "%d %d %d %d\n", ts_calib_left, ts_calib_right, ts_calib_top, ts_calib_bottom);
        fclose(f);
    }
}


static enum
{
    CS_WAIT_RELEASE,
    CS_WAIT_PRESS_L,
    CS_WAIT_RELEASE_L,
    CS_WAIT_PRESS_R,
    CS_WAIT_RELEASE_R,
    CS_WAIT_PRESS_T,
    CS_WAIT_RELEASE_T,
    CS_WAIT_PRESS_B,
    CS_WAIT_RELEASE_B,
    CS_DONE,

} calib_state;

static unsigned long long calib_sum;
static unsigned long calib_count;

int begin_touchscreen_calibration()
{
    calib_state = CS_WAIT_RELEASE;
    calib_sum = 0;
    calib_count = 0;

    return 0;
}

#define CALIB_WIDGET_SIZE  (32)

int draw_touchscreen_calibration()
{
    set_rawcolor(FBCOLOR_WHITE);
    cls();

    switch(calib_state)
    {
        case CS_WAIT_RELEASE:
            set_rawcolor(FBCOLOR_BLACK);
            set_font(2);
            set_pos(100,100);
            print_string("Calibration", 0);
            break;

        case CS_WAIT_PRESS_L:
        case CS_WAIT_RELEASE_L:
            set_rawcolor(FBCOLOR_BLACK);

            set_pos(0,YRES / 2);
            line_to(CALIB_WIDGET_SIZE, YRES / 2);

            set_pos(CALIB_WIDGET_SIZE / 2, (YRES - CALIB_WIDGET_SIZE) / 2);
            line_to(CALIB_WIDGET_SIZE / 2, (YRES + CALIB_WIDGET_SIZE) / 2);
            break;

        case CS_WAIT_PRESS_R:
        case CS_WAIT_RELEASE_R:
            set_rawcolor(FBCOLOR_BLACK);

            set_pos(XRES - CALIB_WIDGET_SIZE, YRES / 2);
            line_to(XRES, YRES / 2);

            set_pos(XRES - (CALIB_WIDGET_SIZE / 2), (YRES - CALIB_WIDGET_SIZE) / 2);
            line_to(XRES - (CALIB_WIDGET_SIZE / 2), (YRES + CALIB_WIDGET_SIZE) / 2);
            break;

        case CS_WAIT_PRESS_T:
        case CS_WAIT_RELEASE_T:
            set_rawcolor(FBCOLOR_BLACK);

            set_pos(XRES / 2, 0);
            line_to(XRES / 2, CALIB_WIDGET_SIZE);

            set_pos((XRES - CALIB_WIDGET_SIZE) / 2, CALIB_WIDGET_SIZE / 2);
            line_to((XRES + CALIB_WIDGET_SIZE) / 2, CALIB_WIDGET_SIZE / 2);
            break;

        case CS_WAIT_PRESS_B:
        case CS_WAIT_RELEASE_B:
            set_rawcolor(FBCOLOR_BLACK);

            set_pos(XRES / 2, YRES - CALIB_WIDGET_SIZE);
            line_to(XRES / 2, YRES);

            set_pos((XRES - CALIB_WIDGET_SIZE) / 2, YRES - (CALIB_WIDGET_SIZE / 2));
            line_to((XRES + CALIB_WIDGET_SIZE) / 2, YRES - (CALIB_WIDGET_SIZE / 2));
            break;

        default:
            break;
    }

    present_framebuffer();

    return 0;
}


static int tmp_l, tmp_r, tmp_t, tmp_b;

int advance_touchscreen_calibration(int raw_x, int raw_y, int down)
{

    int adj;

    switch(calib_state)
    {

        case CS_WAIT_RELEASE:
            if(!down)
            {
                calib_state++;
            }
            break;

        case CS_WAIT_RELEASE_L:
        case CS_WAIT_RELEASE_R:
            if(down)
            {
                calib_sum += raw_x;
                calib_count++;
            }
            else
            {
                if(calib_count > 16)
                {
                    if(calib_state == CS_WAIT_RELEASE_L)
                    {
                        tmp_l = (int)(calib_sum / calib_count);
                    }
                    else
                    {
                        tmp_r = (int)(calib_sum / calib_count);
                    }

                    calib_sum = 0;
                    calib_count = 0;
                    calib_state++;
                }
            }
            break;

        case CS_WAIT_RELEASE_T:
        case CS_WAIT_RELEASE_B:
            if(down)
            {
                calib_sum += raw_y;
                calib_count++;
            }
            else
            {
                if(calib_count > 16)
                {
                    if(calib_state == CS_WAIT_RELEASE_T)
                    {
                        tmp_t = (int)(calib_sum / calib_count);
                    }
                    else
                    {
                        tmp_b = (int)(calib_sum / calib_count);
                    }

                    calib_sum = 0;
                    calib_count = 0;
                    calib_state++;
                }
            }
            break;

        case CS_WAIT_PRESS_L:
        case CS_WAIT_PRESS_R:
        case CS_WAIT_PRESS_T:
        case CS_WAIT_PRESS_B:
            if(down)
            {
                calib_state++;
            }
            break;


        default:
          break;
    }

    if(calib_state == CS_DONE)
    {
        adj = ( (tmp_r * CALIB_WIDGET_SIZE / 2) - (tmp_l * CALIB_WIDGET_SIZE / 2) ) / (XRES - CALIB_WIDGET_SIZE);

        tmp_l -= adj;
        tmp_r += adj;

        if(tmp_l < 0)
            tmp_l = 0;

        if(tmp_l >= tmp_r)
        {
            calib_state = CS_WAIT_RELEASE;
            return 0;
        }

        adj = ( (tmp_b * CALIB_WIDGET_SIZE / 2) - (tmp_t * CALIB_WIDGET_SIZE / 2) ) / (YRES - CALIB_WIDGET_SIZE);

        tmp_t -= adj;
        tmp_b += adj;

        if(tmp_t < 0)
            tmp_b = 0;

        if(tmp_t >= tmp_b)
        {
            calib_state = CS_WAIT_RELEASE;
            return 0;
        }

        ts_calib_left = tmp_l;
        ts_calib_right = tmp_r;
        ts_calib_top = tmp_t;
        ts_calib_bottom = tmp_b;

        save_ts_calib();
        return 1;
    }


    return 0;
}

int translate_ts_x(unsigned rawx)
{
    int accum = (rawx - ts_calib_left);

    if(accum < 0)
        accum = 0;

    accum *= (XRES - 1);

    accum /= (ts_calib_right - ts_calib_left);

    if(accum >= XRES)
    {
        return XRES - 1;
    }

    return accum;
}

int translate_ts_y(unsigned rawy)
{
    int accum = (rawy - ts_calib_top);

    if(accum < 0)
        accum = 0;

    accum *= (YRES - 1);

    accum /= (ts_calib_bottom - ts_calib_top);

    if(accum >= YRES)
    {
        return YRES - 1;
    }

    return accum;
}


static int fbfd = -1;

static pixel *fb = NULL;

#ifdef USE_REMOTE_UI
    static inline void dump_fb_shadow(pixel *src)
    {
        int fb_file;

        fb_file = creat(FB_SHADOW_TMP,  S_IRUSR | S_IWUSR);

        if(fb_file >= 0)
        {
            write(fb_file, src, sizeof(pixel) * PIXELS);

            close(fb_file);

            rename(FB_SHADOW_TMP, FB_SHADOW_FILE);
        }
    }
#else
    #define dump_fb_shadow(x)
#endif

//    Normally, we want to do all of our drawing operations in a piece of 'mere mortal' (a.k.a. Cacheable)
//system ram pulled off of the heap rather than doing that drawing (lots of little bitty read-modify-write
//operations) to the framebuffer memory which is generally, while mmap()'d into our process space like normal
//ram, marked by the kernel as a very special piece of non-cacheable, DMA-safe low memory, which as a result is
//slow to read/write to and from as A: the CPU can't cache it, and B: even if you have a smart memory controller
//and a multi-path bus, the DMA controller that scans the framebuffer spends most of its time reading this RAM
//(somewhere about 25% of our SOC's total bus bandwidth(!), so we really don't want to get in its way any more
//than we absolutely have to, so it's faster to write to some normal userland memory and then copy it all into
//the actual framebuffer in one shot when we're done drawing.  *sigh*, if we only had double buffering and a
//smarter memory controller.  On the other hand this runs on a glorified microcontroller, so we take what we
//can get...
#ifdef FRAMEBUFFER_SOFTWARE_BACKBUFFER
    static pixel fb_work[PIXELS] = {0};

    int present_framebuffer()
    {
        int i;
        int n;
        unsigned long *s, *d;

        if(!fb)
        {
            return -1;
        }

//        printf("Woof!\n");

        s = (unsigned long *)fb_work;  //grab our source pointer (the backbuffer) as a machine-word sized type
        d = (unsigned long *)fb;  //and our desination pointer (the framebuffer) " " " " "

        n = (PIXELS * sizeof(pixel)) / sizeof(unsigned long);  //figure out how many machine words that really is

        for(i=0; i < n; i++)    //Loop and copy...
        {
            *d++ = *s++;
        }

        dump_fb_shadow(fb_work);

        return 0;
    }
#else  //if we have selected to draw directly to the screen without using a software backbuffer

    static pixel *fb_work = NULL;  //allocate a backbuffer pointer anyway, to later fill with the actual framebuffer

    int present_framebuffer()    //this function becomes trivial at this point and will probably be optimized out
    {          //by the compiler, but for interface sanity, we define it anyway...
        dump_fb_shadow(fb_work);

        return 0;
    }
#endif


int open_framebuffer_nondestructive()
{
    pixel *foo;

    fb = NULL;

    fbfd = open(CONFIG_FRAMEBUFFER_PATH, O_RDWR);

    if(fbfd < 0)
    {
        fprintf(stderr, "Cannot open %s\n", CONFIG_FRAMEBUFFER_PATH);
        return -1;
    }

    #ifdef FRAMEBUFFER_RESTORE_PALETTE
        if (ioctl(fbfd, FBIOGETCMAP, &old_palette))
        {
            fprintf(stderr,"FBIOGETCMAP failure\n");
            close(fbfd);
            fbfd = -1;

            return -1;
        }
    #endif

    if (ioctl(fbfd, FBIOPUTCMAP, &our_palette))
    {
        fprintf(stderr, "FBIOPUTCMAP failed\n");

        close(fbfd);
        fbfd = -1;
        return -1;
    }

    foo = (pixel *)mmap(NULL, sizeof(pixel) * PIXELS, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);


    if( (!foo) || (foo == MAP_FAILED))
    {
        close(fbfd);
        fbfd = -1;
        fprintf(stderr, "Cannot MMAP framebuffer!\n");
        return -2;
    }

    fb = foo;

    #ifndef FRAMEBUFFER_SOFTWARE_BACKBUFFER
        //If we have no software backbuffer, assign the backbuffer pointer to point to the real framebuffer
        fb_work = fb;
    #else
        //If we're double buffering, copy the current framebuffer to our backbuffer so we can edit its current contents.
        memcpy(fb_work, fb, sizeof(pixel) * PIXELS);
    #endif

    //  We close the file handle in close_framebuffer(), we want to keep it around so we can
    //restore the palette to the state we found it in...

    set_bgcolor(255,255,255);
    set_color(0,0,0);
    set_pos(0,0);
    load_ts_calib();

    return 0;
}

int open_framebuffer()
{
    int retval;

    //First, open the framebuffer in nondestructive mode...
    retval = open_framebuffer_nondestructive();

    //If that fails, pass the failure up the call chain
    if(retval)
    {
        return retval;
    }

    //Otherwise, it's time to clear the screen!
    set_color(255,255,255);
    set_bgcolor(255,255,255);
    cls();
    set_color(0,0,0);
    set_pos(0,0);
    load_ts_calib();

    return 0;
}

//If we're trying to close up the framebuffer, we have two things to do:
void close_framebuffer()
{
    if(fb)  //Release our memory mapping
    {
        munmap(fb, sizeof(pixel) * PIXELS);
        fb = NULL;
    }

    if(fbfd >= 0)  //and restore the color palette back to how we found it...
    {
        #ifdef FRAMEBUFFER_RESTORE_PALETTE
            ioctl(fbfd, FBIOPUTCMAP, &old_palette);
        #endif

        close(fbfd);
        fbfd = -1;
    }
}

void set_color(int r, int g, int b)
{
    fgcolor = FROM_RGBA(r, g, b, 255);
}

void set_bgcolor(int r, int g, int b)
{
    bgcolor = FROM_RGBA(r, g, b, 255);
}

void set_color_t(int r, int g, int b, int a)
{
    fgcolor = FROM_RGBA(r, g, b, a);
}

void set_bgcolor_t(int r, int g, int b, int a)
{
    bgcolor = FROM_RGBA(r, g, b, a);
}

void set_pos(int x, int y)
{
    if(x < 0)
    {
        cursor_x = 0;
    }
    else if(x >= XRES)
    {
        cursor_x = XRES - 1;
    }
    else
    {
        cursor_x = x;
    }

    if(y < 0)
    {
        cursor_y = 0;
    }
    else if(y >= YRES)
    {
        cursor_y = YRES - 1;
    }
    else
    {
        cursor_y = y;
    }
}


//Plots a point in the current foreground color.
static inline void plot(int x,int y)
{
    int index = coord_to_index(x,y);

    if(index < 0)  return;

    fb_work[index] = LOOKUP_BLEND(fgcolor, fb_work[index]);
}

//Plots a point using the foreground color, but forcing the transparent bit on even if it is not
//normally on.  This is used for font antialiasing, and probably won't ever be used for much else.
static inline void plot_t(int x, int y)
{
    int index = coord_to_index(x,y);

    if(index < 0)  return;

    pixel tmpcolor = (fgcolor | ALPHA_BIT);
    fb_work[index] = LOOKUP_BLEND(tmpcolor, fb_work[index]);
}

//Plots a point in the current background color.
static inline void bgplot(int x,int y)
{
    int index = coord_to_index(x,y);

    if(index < 0)  return;

    fb_work[index] = LOOKUP_BLEND(bgcolor, fb_work[index]);
}

void line_to(int xdest, int ydest)
{

    int tmp;

    int x0 = cursor_x;
    int y0 = cursor_y;

    int x1, y1;

    int dx, dy, err, ys;
    int x, y;

    int steep;

#ifndef FRAMEBUFFER_SOFTWARE_BACKBUFFER
    if(!fb_work) { return; }
#endif

    if(xdest < 0)
    {
        xdest = 0;
    }
    else if(xdest >= XRES)
    {
        xdest = XRES - 1;
    }

    if(ydest < 0)
    {
        ydest = 0;
    }
    else if(ydest >= YRES)
    {
        ydest = YRES - 1;
    }

    x1 = xdest;
    y1 = ydest;

    steep = abs(y1 - y0) > abs(x1 - x0);

    if(steep)
    {
        SWAP(x0, y0);
        SWAP(x1, y1);
    }

    if(x0 > x1)
    {
        SWAP(x0, x1);
        SWAP(y0, y1);
    }

    dx = x1 - x0;
    err = dx / 2;

    dy = abs(y1 - y0);
    ys = (y0 < y1)?1:-1;

    y = y0;

    for(x = x0; x <= x1; x++)
    {
        if(steep)
        {
            plot(y,x);
        }
        else
        {
            plot(x,y);
        }

        err -= dy;

        if(err < 0)
        {
            y += ys;
            err += dx;
        }
    }

    cursor_x = xdest;
    cursor_y = ydest;
}

void draw_sprite(sprite *s)
{

    unsigned i, j;
    pixel *src;
    unsigned int tx, ty;
    pixel foo;
    int idx;

#ifndef FRAMEBUFFER_SOFTWARE_BACKBUFFER
    if(!fb_work) { return; }
#endif

    if(!s) { return; }

    src = s->data;

    for(j = 0; j < s->height; j++)
    {
        ty = cursor_y + j;

        if(ty >= YRES)
        {
            break;
        }

        tx = cursor_x;

        for(i = 0; i < s->width; i++)
        {
            foo = *src++;
            idx = coord_to_index(tx,ty);

            if(idx >= 0)
            {
                fb_work[idx] = LOOKUP_BLEND(foo, fb_work[idx]);
            }

            tx++;
        }
    }

}

void cls()
{
    int i, n;
    unsigned long pattern;
    unsigned long *tgt = (unsigned long *)fb_work;

#ifndef FRAMEBUFFER_SOFTWARE_BACKBUFFER
    if(!fb_work) { return; }
#endif

    n = sizeof(pattern) / sizeof(pixel);  //figure out how many pixels in a word

    pattern = 0;        //clear the word

    for(i=0; i < n; i++)      //for each one
    {
        pattern <<= (sizeof(pixel) * 8);    //shift the word over
        pattern |= (fgcolor & ~(pixel)ALPHA_BIT);  //and clear the alpha bit
    }

    n = PIXELS / n;        //figure out how many iterations we'll need

    for(i=0; i < n; i++)      //splat the pattern down
    {
        *tgt++ = pattern;
    }
}

void box(int x, int y)
{
    int tx = cursor_x;
    int ty = cursor_y;

    line_to(x, ty);
    line_to(x, y);
    line_to(tx, y);
    line_to(tx, ty);

    set_pos(x,y);
}


void box_fill(int x, int y)
{
    int i,j;

    int t,l,r,b;

#ifndef FRAMEBUFFER_SOFTWARE_BACKBUFFER
    if(!fb_work) { return; }
#endif

    if(x < 0)
    {
        x = 0;
    }
    else if(x >= XRES)
    {
        x = XRES - 1;
    }
    else
    {
        x = x;
    }

    if(y < 0)
    {
        y = 0;
    }
    else if(y >= YRES)
    {
        y = YRES - 1;
    }
    else
    {
        y = y;
    }

    if(y > cursor_y)
    {
        t = cursor_y;
        b = y;
    }
    else
    {
        b = cursor_y;
        t = y;
    }

    if(x > cursor_x)
    {
        l = cursor_x;
        r = x;
    }
    else
    {
        r = cursor_x;
        l = x;
    }

    for(j = t; j <= b; j++)
      for(i = l; i <= r; i++)
        plot(i,j);

    cursor_x = x;
    cursor_y = y;
}

//---------------------------------------
#define SELECT_FONT(fontnum, fnt)  \
    switch(fontnum)      \
    {          \
        case 0:        \
          fnt = &smallfont;    \
          break;      \
                                        \
        case 1:        \
          fnt = &medfont;    \
          break;      \
                                        \
        case 2:        \
          fnt = &bigfont;    \
          break;      \
                                        \
        default:      \
          fnt = &smallfont;    \
          break;      \
    }          \
//---------------------------------------


void font_cell_size(int *w, int *h)
{
    fbfont *fnt;

    SELECT_FONT(fontnum, fnt)

    if(w)
    {
        *w = fnt->cellwidth;
    }

    if(h)
    {
        *h = fnt->cellheight;
    }
}

void set_font(unsigned char font)
{
    fontnum = font;
}

//This function prints the specified string at the current cursor position.
//If the bkgr parameter is non-zero, background pixels (not occupied by the character itself)
//will be filled in.
void print_string(char *str, int bkgr)
{
    fbfont *fnt;
    char *c;

    int glyphidx;
    const char *glyphdata;

    int i,j;
    int tx, ty;

    if(str == NULL) return;

    SELECT_FONT(fontnum, fnt)  //Get the correct font data for the requested font number

    c = str;       //set our travelling pointer to the beginning of the string

    while(*c)      //while we have not hit a terminating '\0'
    {
        if( (*c < fnt->firstglyph) || (*c > fnt->lastglyph) )  //if we are being asked to draw a glyph we don't have
        {
            glyphidx = fnt->undefglyph - fnt->firstglyph;  //replace it with the font's designated 'undef' glyph
        }
        else              //otherwise, we have the glyph
        {
            glyphidx = *c - fnt->firstglyph;      //compute its index into the glyph table
        }

        //    Next, using that index, use it to compute a pointer offset into the raw font data block
        //(i.e. figure out how many bytes we have to index in before we get the first byte that represents
        //the drawing instructions for this glyph).
        glyphdata = fnt->data + (glyphidx * (fnt->cellwidth * fnt->cellheight));

        for(j = 0; j < fnt->cellheight; j++)  //For each scan line of the font
        {
            ty = cursor_y + j;      //ty indexes Y of the screen location we're drawing this scan line to

            if(ty >= YRES)      //if it's off the edge of the screen, save time and skip it
            {
              break;
            }

            for(i = 0; i < fnt->cellwidth; i++)  //for each pixel in this scan line of the glyph bitmap
            {
                tx = cursor_x + i;    //tx indexes X of the screen location we're drawing this
                                                //pixel of this scan line to

                if(tx >= XRES)      //if it's off the edge of the screen, ignore it and keep counting
                {
                    glyphdata++;    //note that we continue rather than break so we can keep incrementing
                    continue;      //the pointer to the glyph data so we can draw the left half of the next
                }        //scan line of a glyph that runs off the right edge of the screen


                switch(*(glyphdata++))    //see what character represents this pixel in the font map...
                {
                    case ' ':      //a ' ' (space) character is a background pixel.  Don't plot, unless
                        if(bkgr)    //in background mode, in which case, plot background color.
                        {
                            bgplot(tx,ty);
                        }
                        break;

                    case '.':      //a '.' (period) character is a blended foreground pixel.  If we're in
                        if(bkgr)    //background mode, we need to:  (this could be more efficeint, but for clarity
                        {      //I'm keeping it simple):

                            bgplot(tx,ty);  //plot the background first, and then
                            plot_t(tx,ty);  //just plot the translucent foreground pixel
                        }
                        else      //If we're not in background filling mode,
                        {
                            plot_t(tx,ty);  //plot the foreground pixel in translucent mode
                        }
                        break;

                    default:      //Any other character counts as a solid plot...
                        plot(tx,ty);
                        break;
                }
            }
        }

        cursor_x += fnt->cellwidth;    //Advance the cursor to the next cell

        if(cursor_x >= XRES)      //If we somehow have run off the end of the screen
        {
            cursor_x = XRES - 1;    //peg our cursor/turtle to the right edge
            break;        //and don't bother trying to draw the rest of the string
        }          //(as it will also be off the screen)

        c++;          //get the next character and do it all again...
    }

}


void beep(int fd, int hz, int milis)
{
    char buffer[64];
    int n;

    n = sprintf(buffer, "/B:%X,%X\r", hz, milis);

    write(fd,buffer,n);
}

void set_backlight(int fd, int on)
{
    char buffer[64];
    int n;

    n = sprintf(buffer, "/D:%c\r", on?'0':'1');

    write(fd,buffer,n);
}

void select_blend_mode(int mode)
{
    switch(mode)
    {
        case BLENDMODE_LIGHT:
            blend_table = light_blend_table;
            break;

        case BLENDMODE_DARK:
        default:
            blend_table = dark_blend_table;
            break;
    }
}

void set_alpha_level(unsigned int alpha, unsigned int mode)
{
    recompute_alpha_lookup(alpha, (mode == BLENDMODE_DARK)?dark_blend_table:light_blend_table);
}

/*
int main()
{
    int fd;

    char buffer[1024];

    struct pollfd fds[2];

    int retval;

    fd = open_rs232_device("/dev/ttyS1");

    if(fd < 0)
      return fd;

    fds[0].fd = fd;
    fds[0].events = POLLIN;

    open_framebuffer();

    set_color(0,0,255);
    set_pos(100,100);
    box_fill(200,200);

    set_color(0,255,0);
    box(300,300);

    set_pos(150,100);
    set_color(0,0,0);
    set_bgcolor(255,255,0);
    print_string("Hello ",0);
    print_string("World", 1);

    set_font(1);
    set_pos(150,240);
    print_string("Hello ",0);
    print_string("World", 1);

    set_pos(320,240);
    set_color(255,0,0);

    set_backlight(fd,0);
    sleep(1);
    set_backlight(fd,1);

    beep(fd,880,100);
    beep(fd,0,100);
    beep(fd,440,100);

    while(1)
    {

        retval = poll(fds, 1, 10000);

        if(retval < 1)
        {
            printf("Meep!\n");
            continue;
        }

        retval = read(fd, buffer, sizeof(buffer) - 1);

        if(retval > 0)
        {
            int x,y,z;

            buffer[retval] = '\0';

            strip_crlf(buffer);
            printf("Got: \"%s\"\n", buffer);

            if(sscanf(buffer, "/T:%x,%x,%x", &z, &x, &y) == 3)
            {
                if(z)
                {
                    x = translate_ts_x(x);
                    y = translate_ts_y(y);

                    line_to(x,y);
                }
            }
        }
    }


    return 0;
}

*/

