#include "dmlib.h"
#include "dmargs.h"
#include "dmeval.h"
#include "dmtext.h"
#include <math.h>

#define AUVAL_NAME           "AuVal"
#define AUVAL_VERSION        "0.6"
#define AUVAL_TMPBUF_SIZE    (4096)
#define AUVAL_HISTORY_FILE   "history.txt"
#define AUVAL_HISTORY_USER   "formulas.txt"
#define SDL_NCOLORS           256


enum
{
    REDRAW_VISUALIZER = 0x00000001,
    REDRAW_EDITOR     = 0x00000002,
    REDRAW_INFO       = 0x00000004,
    REDRAW_ALL        = 0xffffffff,
};


typedef struct
{
    DMEvalNode *expr;
    DMEvalContext *ctx;
    
    double varTime, varFreq, varKeyTime,
           varKeyTimeRev, varKeyFreq, varUnit;

    int avail, bufsize, pos, oldpos;
    Uint8 *buf;

    DMMutex *mutex;
} AUAudioData;


char *optFontFile = "font.ttf";

int optVFlags      = SDL_SWSURFACE | SDL_HWPALETTE,
    optScrWidth    = 640,
    optScrHeight   = 480,
    optScrDepth    = 32,
    optFontSize    = 20,
    optAudioFreq   = 44100,
    optBMPSize     = 32,
    optHistoryLen  = 64;

BOOL optClipping = TRUE,
     optScale = TRUE;


DMOptArg optList[] = {
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 3,   0, "fs",         "Fullscreen", OPT_NONE },
    { 5, 's', "size",       "Initial window size/resolution -s 640x480", OPT_ARGREQ },
    { 6, 'd', "depth",      "Color depth of mode/window in bits (8/15/16/32)", OPT_ARGREQ },
    { 7, 'f', "freq",       "Audio output frequency", OPT_ARGREQ },
    { 8, 'b', "bmpsize",    "Bitmap size", OPT_ARGREQ },

    {10, 'n', "noclip",     "Disable clipping by default", OPT_NONE },
    {11, 'r', "range",      "Use range [0, 255] instead of [0.0, 1.0]", OPT_NONE },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp()
{
    printf("%s v%s\n(C) Copyright 2011 ccr/TNSP\n", AUVAL_NAME, AUVAL_VERSION);
    dmArgsPrintHelp(stdout, optList, optListN);
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN) {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 2:
        dmVerbosity++;
        break;
    
    case 3:
        optVFlags |= SDL_FULLSCREEN;
        break;

    case 5:
        if (optArg)
        {
            int w, h;
            if (sscanf(optArg, "%dx%d", &w, &h) == 2)
            {
                if (w < 320 || h < 200 || w > 3200 || h > 3200)
                {
                    dmError("Invalid width or height: %d x %d\n", w, h);
                    return FALSE;
                }
                optScrWidth = w;
                optScrHeight = h;
            }
            else 
            {
                dmError("Invalid size argument '%s'.\n", optArg);
                return FALSE;
            }
        }
        else
        {
            dmError("Dimensions option %s requires an argument.\n", currArg);
        }
        break;

    case 6:
        optScrDepth = atoi(optArg);
        break;

    case 7:
        {
            int tmp = atoi(optArg);
            if (tmp < 4000 || tmp > 96000)
            {
                dmError("Invalid audio frequency '%s'.\n", optArg);
                return FALSE;
            }
            optAudioFreq = tmp;
        }
        break;

    case 8:
        {
            int tmp = atoi(optArg);
            if (tmp < 32 || tmp > 512)
            {
                dmError("Invalid bitmap size '%s'.\n", optArg);
                return FALSE;
            }
            optBMPSize = tmp;
        }
        break;

    case 10:
        optClipping = FALSE;
        break;
    
    case 11:
        optScale = FALSE;
        break;

    default:
        dmError("Unknown option '%s'.\n", currArg);
        return FALSE;
    }
    
    return TRUE;
}


typedef struct
{
    ssize_t pos, len, size;
    char *data;
    BOOL dirty;
} AUEditBuf;


int au_editbuf_write(AUEditBuf *buf, ssize_t pos, int ch)
{
    if (buf->len+1 >= buf->size) return -3;
    
    if (pos < 0)
        return -1;
    else if (pos >= buf->len) {
        buf->data[buf->len++] = ch;
    } else {
        buf->data[pos] = ch;
    }
    buf->dirty = TRUE;
    return 0;
}


int au_editbuf_insert(AUEditBuf *buf, ssize_t pos, int ch)
{
    if (buf->len+1 >= buf->size) return -3;
    
    if (pos < 0)
        return -1;
    else if (pos >= buf->len) {
        buf->data[buf->len] = ch;
    } else {
        memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1);
        buf->data[pos] = ch;
    }
    buf->len++;
    buf->dirty = TRUE;
    return 0;
}


int au_editbuf_delete(AUEditBuf *buf, ssize_t pos)
{
    if (pos < 0)
        return -1;
    else if (pos < buf->len) {
        memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos);
        buf->len--;
        buf->dirty = TRUE;
        return 0;
    } else
        return -2;
}


void au_editbuf_clear(AUEditBuf *buf)
{
    buf->len = 0;
    buf->pos = 0;
    buf->dirty = TRUE;
}


AUEditBuf * au_editbuf_new(ssize_t n)
{
    AUEditBuf *res = dmCalloc(1, sizeof(AUEditBuf));
    
    res->data = (char *) dmMalloc(n);
    res->size = n;
    res->dirty = TRUE;
    
    return res;
}

AUEditBuf * au_editbuf_new_str(ssize_t n, const char *str)
{
    AUEditBuf *res = au_editbuf_new(n);
    strncpy(res->data, str, res->size);
    res->data[res->size - 1] = 0;
    res->pos = res->len = strlen(res->data);
    return res;
}


void au_editbuf_free(AUEditBuf *buf)
{
    if (buf != NULL)
    {
        dmFree(buf->data);
        dmFree(buf);
    }
}


AUEditBuf * au_editbuf_copy(AUEditBuf *src)
{
    AUEditBuf *res;
    
    if (src == NULL) return NULL;
    
    if ((res = au_editbuf_new(src->size)) == NULL)
        return NULL;
    
    memcpy(res->data, src->data, src->size);
    res->pos = res->len = src->len;
    res->dirty = TRUE;
    
    return res;
}


char * au_editbuf_get_string(AUEditBuf *buf, ssize_t start, ssize_t end)
{
    char *str;
    ssize_t siz;
    
    if (buf == NULL)
        return NULL;

    if (start < 0 || end > buf->len || start >= buf->len)
        return NULL;

    if (end < 0) {
        siz = buf->len - start + 1;
    } else if (start <= end) {
        siz = end - start + 1;
    } else
        return NULL;

    if ((str = dmMalloc(siz + 1)) == NULL)
        return NULL;

    memcpy(str, buf->data + start, siz);
    str[siz] = 0;

    return str;
}


void au_editbuf_setpos(AUEditBuf *buf, ssize_t pos)
{
    if (pos < 0)
        buf->pos = 0;
    else if (pos >= buf->len)
        buf->pos = buf->len;
    else
        buf->pos = pos;
}


BOOL au_init_video(SDL_Surface **screen)
{
    *screen = SDL_SetVideoMode(optScrWidth, optScrHeight, optScrDepth, optVFlags | SDL_RESIZABLE);
    if (*screen == NULL)
    {
        dmError("Can't SDL_SetVideoMode(): %s\n", SDL_GetError());
        return FALSE;
    }

    return TRUE;
}


void au_draw_editbuf(SDL_Surface *screen, TTF_Font *font, SDL_Color col,
    int xc, int yc, int w, int h, AUEditBuf *buf, int curcol)
{
    ssize_t left, maxlen, pos;
    int strw, strh, y1 = yc + h;
    char *line = NULL, *ptr;

    if (buf == NULL)
        return;

    if (TTF_SizeText(font, "X", &strw, &strh) != 0)
        goto error;

    maxlen = w / strw;
    line = dmMalloc(maxlen + 2);
    pos = 0;
    left = buf->len;
    ptr = buf->data;

    do
    {
        ssize_t ppos = buf->pos - pos;
        if (ppos >= 0 && ppos < maxlen)
        {
            int x0 = xc + ppos * strw;
            dmFillRect(screen, x0, yc, x0 + strw, yc + strh, curcol);
        }
        else
        if ((buf->pos % maxlen) == 0)
        {
            dmFillRect(screen, xc, yc + strh, xc + strw, yc + strh + strh, curcol);
        }

        ssize_t len = left > maxlen ? maxlen : left;
        strncpy(line, ptr, len);
        line[len] = 0;

        dmDrawTTFTextConst(screen, font, col, xc, yc, line);

        left -= len;
        ptr += len;
        pos += len;

        yc += strh;
    }
    while (left > 0 && yc < y1);


error:
    dmFree(line);
}


void au_vis_wave(SDL_Surface *screen, int x0, int y0, int x1, int y1, AUAudioData *data)
{
    int x, offs;
    const int height = y1 - y0, center = y0 + (height / 2);
    const int pitch = screen->pitch, bpp = screen->format->BytesPerPixel;
    const int xend = x1 * bpp;
    const float scale = 128.0f / (float) height;

    dmMutexLock(data->mutex);

    Uint8 *dp = data->buf,
          *sp = screen->pixels;

    for (offs = 0, x = x0 * bpp; x < xend && offs < data->avail; x += bpp, offs++)
    {
        const int curr = center + ((dp[offs] - 128) * scale);
        sp[x + curr * pitch] = 255;
    }

    dmMutexUnlock(data->mutex);
}


void au_vis_image(SDL_Surface *screen, AUAudioData *data)
{
    int y;
    Uint8 *p = (Uint8 *) screen->pixels;

    dmMutexLock(data->mutex);

    Uint8 *dp = data->buf;

    for (y = 0; y < screen->h; y++)
    {
        int x;
        for (x = 0; x < screen->w; x++)
            *p++ = *dp++;

        p += screen->pitch - screen->w;
    }

    dmMutexUnlock(data->mutex);
}


int au_get_note_from_key(SDL_keysym *key, int octave)
{
    int note;

    switch (key->sym)
    {
        case SDLK_z: note = 1; break;
        case SDLK_s: note = 2; break;
        case SDLK_x: note = 3; break;
        case SDLK_d: note = 4; break;
        case SDLK_c: note = 5; break;
        case SDLK_v: note = 6; break;
        case SDLK_g: note = 7; break;
        case SDLK_b: note = 8; break;
        case SDLK_h: note = 9; break;
        case SDLK_n: note = 10; break;
        case SDLK_j: note = 11; break;
        case SDLK_m: note = 12; break;

        case SDLK_q: note = 13; break;
        case SDLK_2: note = 14; break;
        case SDLK_w: note = 15; break;
        case SDLK_3: note = 16; break;
        case SDLK_e: note = 17; break;
        case SDLK_r: note = 18; break;
        case SDLK_5: note = 19; break;
        case SDLK_t: note = 20; break;
        case SDLK_6: note = 21; break;
        case SDLK_y: note = 22; break;
        case SDLK_7: note = 23; break;
        case SDLK_u: note = 24; break;
        case SDLK_i: note = 25; break;
        case SDLK_9: note = 26; break;
        case SDLK_o: note = 27; break;
        case SDLK_0: note = 28; break;
        case SDLK_p: note = 29; break;

        default: return -1;
    }

    note += 12 * octave;
    return (note < 1) ? 1 : (note > 120 ? 120 : note);
}


void au_adjust_value(int *val, int min, int max, int delta)
{
    *val += delta;
    if (*val < min) *val = min;
    else if (*val > max) *val = max;
}


int au_read_history(const char *filename, AUEditBuf **history, int maxHistory, int *histMax)
{
    char tmpStr[1024];
    int i;

    FILE *f = fopen(filename, "r");
    if (f == NULL)
    {
        dmError("Could not open input file '%s'.\n", filename);
        return -1;
    }

    while (fgets(tmpStr, sizeof(tmpStr), f) != NULL)
    {
        /* Strip the string end from linefeeds etc. */
        for (i = 0; tmpStr[i] != 0 && i < 512; i++);
        while (--i >= 0 && (tmpStr[i] == '\n' || tmpStr[i] == '\r' || isspace(tmpStr[i])))
            tmpStr[i] = 0;
        
        /* Add to history only if it's not an empty line */
        if (tmpStr[0] != 0)
        {
            au_editbuf_free(history[maxHistory + 1]);
            history[maxHistory + 1] = NULL;
            memmove(&history[2], &history[1], maxHistory * sizeof(history[0]));

            history[1] = au_editbuf_new_str(AUVAL_TMPBUF_SIZE, tmpStr);

            if (*histMax < maxHistory)
                (*histMax)++;
        }
    }

    fclose(f);
    return 0;
}


int au_save_history(const char *filename, AUEditBuf **history, int histMax)
{
    int i;

    FILE *f = fopen(filename, "w");
    if (f == NULL)
    {
        dmError("Could not create output file '%s'.\n", filename);
        return -1;
    }

    for (i = histMax; i >= 0; i--)
    {
        AUEditBuf *buf = history[i];
        if (buf != NULL)
        {
            buf->data[buf->len] = 0;
            fprintf(f, "%s\n", buf->data);
        }
    }

    fclose(f);
    return 0;
}


/* SDL audio callback. We do the actual rendering here, by setting
 * Lua variables and calling Lua evaluation function to generate samples.
 *
 * Mutex locking is used to ensure that the variables are not improperly
 * accessed.
 */
void au_sdl_audio_fill(void *udata, Uint8 *buf, int len)
{
    AUAudioData *data = (AUAudioData *) udata;

    dmMutexLock(data->mutex);
    
    data->avail = 0;

    if (!data->ctx->err)
    {
        while (data->avail < len && data->avail < data->bufsize)
        {
            double value;

            data->varKeyTimeRev = 1.0f - data->varKeyTime;

            if (dmEvalTreeExecute(data->ctx, data->expr, &value) != 0)
                break;

            if (optScale)
            {
                if (optClipping)
                    value = ((value < -1.0f) ? -1.0f : ((value > 1.0f) ? 1.0f : value));
                
                value = 128 + (value * 126);
            }
            else
            {
                if (optClipping)
                    value = ((value < 0) ? 0 : ((value > 255) ? 255 : value));
            }

            data->buf[data->avail++] = value;

            data->varTime += data->varFreq;
            data->varUnit += data->varKeyFreq;

            data->varKeyTime += data->varKeyFreq;
            if (data->varKeyTime > 1.0f)
                data->varKeyTime = 1.0f;
        }
        
        if (data->avail >= len && !data->ctx->err)
        {
            memcpy(buf, data->buf, len);
            data->pos += len;
        }
        else
            memset(buf, 0, len);
    }
        
    dmMutexUnlock(data->mutex);
}


int main(int argc, char *argv[])
{
    AUEditBuf *editBuf = au_editbuf_new(AUVAL_TMPBUF_SIZE);
    AUEditBuf **histBuf = NULL;
    int histPos = 0, histMax = 0, viewMode;
    SDL_Surface *screen = NULL, *bmp = NULL;
    TTF_Font *font = NULL;
    SDL_Color fontcol={255,255,255, 0};
    SDL_Event event;
    SDL_AudioSpec fmt;
    AUAudioData audata;
    int needRedraw;
    BOOL initSDL = FALSE, initTTF = FALSE, exitFlag,
         insertMode, audioPlaying = FALSE, jazzMode;

    memset(&audata, 0, sizeof(audata));

    /* Parse arguments */
    if (!dmArgsProcess(argc, argv, optList, optListN,
        argHandleOpt, NULL, FALSE))
        exit(1);


    /* Allocate edit history buffers */
    histBuf = dmCalloc(sizeof(histBuf[0]), optHistoryLen + 2);
    if (histBuf == NULL)
    {
        dmError("Could not allocate memory for edit history buffer!\n");
        goto error_exit;
    }


    /* Read in history file, if any exists */
    au_read_history(AUVAL_HISTORY_FILE, histBuf, optHistoryLen, &histMax);


    /* Initialize evaluator ctx */
    audata.ctx = dmEvalContextNew();
    audata.expr = NULL;
    
    dmEvalContextAddVar(audata.ctx, "t", &audata.varTime);
    dmEvalContextAddVar(audata.ctx, "k", &audata.varKeyTime);
    dmEvalContextAddVar(audata.ctx, "q", &audata.varKeyTimeRev);
    dmEvalContextAddVar(audata.ctx, "f", &audata.varUnit);


    /* Initialize SDL */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO) != 0)
    {
        dmError("Could not initialize SDL: %s\n", SDL_GetError());
        goto error_exit;
    }
    initSDL = TRUE;

    if (TTF_Init() < 0)
    {
        dmError("Could not initialize FreeType/TTF: %s\n", SDL_GetError());
        goto error_exit;
    }
    initTTF = TRUE;

    font = TTF_OpenFont(optFontFile, optFontSize);
    if (font == NULL)
    {
        dmError("Could not load TTF font '%s' (%d): %s\n",
            optFontFile, optFontSize, SDL_GetError());
        goto error_exit;
    }
    TTF_SetFontStyle(font, TTF_STYLE_NORMAL);
    
    if (!au_init_video(&screen))
        goto error_exit;

    SDL_WM_SetCaption(AUVAL_NAME " v" AUVAL_VERSION, AUVAL_NAME);

    SDL_EnableUNICODE(1);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);


    /* Initialize audio */
    fmt.freq = optAudioFreq;
    fmt.format = AUDIO_U8;
    fmt.channels = 1;
    fmt.samples = 512;
    fmt.callback = au_sdl_audio_fill;
    fmt.userdata = &audata;

    if (SDL_OpenAudio(&fmt, NULL) < 0)
    {
        dmError("Could not initialize SDL audio.\n");
        goto error_exit;
    }

    audata.bufsize = optAudioFreq;
    audata.buf = dmMalloc(audata.bufsize * sizeof(*audata.buf));
    audata.mutex = dmCreateMutex();
    audata.varTime = 0;
    audata.varKeyFreq = 1.0f / (double) optAudioFreq;

    SDL_PauseAudio(0);
    SDL_PauseAudio(1);
    

    /* Create visualizer bitmap surface */
    bmp = SDL_CreateRGBSurface(SDL_SWSURFACE, optBMPSize, optBMPSize, 8, 0, 0, 0, 0);
    SDL_Color pal[SDL_NCOLORS];
    int n;
    for (n = 0; n < SDL_NCOLORS; n++)
    {
        pal[n].r = n;
        pal[n].g = n;
        pal[n].b = n;
    }
    SDL_SetColors(bmp, pal, 0, SDL_NCOLORS);

    /* Misc inits */
    au_editbuf_clear(editBuf);
    needRedraw = REDRAW_ALL;
    exitFlag = FALSE;
    insertMode = TRUE;
    jazzMode = FALSE;
    viewMode = 0;

    /* Enter mainloop */
    while (!exitFlag)
    {
        /* Handle events */
        while (SDL_PollEvent(&event))
        switch (event.type)
        {
            case SDL_KEYDOWN:
                {
                /* Get key event modifiers into handy booleans */
                BOOL modCtrl  = event.key.keysym.mod & KMOD_CTRL,
                     modShift = event.key.keysym.mod & KMOD_SHIFT;

                switch (event.key.keysym.sym)
                {
                    case SDLK_F1:
                        audioPlaying = !audioPlaying;
                        SDL_PauseAudio(!audioPlaying);
                        needRedraw |= REDRAW_ALL;
                        break;

                    case SDLK_F2:
                        audata.varTime = 0;
                        audata.varKeyTime = 0;
                        needRedraw |= REDRAW_ALL;
                        break;

                    case SDLK_F4:
                        optClipping = !optClipping;
                        needRedraw |= REDRAW_VISUALIZER;
                        break;

                    case SDLK_F5:
                        /* Toggle audio scaling / range between [0.0, 1.0] and [0, 255] */
                        optScale = !optScale;
                        needRedraw |= REDRAW_VISUALIZER;
                        break;

                    
                    case SDLK_F6:
                        viewMode = (viewMode + 1) % 2;
                        needRedraw |= REDRAW_VISUALIZER;
                        break;

                    case SDLK_F8:
                        /* Toggle keyboard jazz mode */
                        jazzMode = !jazzMode;
                        needRedraw |= REDRAW_VISUALIZER;
                        break;

                    case SDLK_F9:
                        /* Save history to file */
                        au_save_history(AUVAL_HISTORY_USER, histBuf, histMax);
                        break;

                    case SDLK_ESCAPE:
                        exitFlag = TRUE;
                        break;
                    
                    case SDLK_RETURN:
                        /* Add to history buffer */
                        if (!jazzMode && editBuf->len > 0)
                        {
                            if (histMax > 0)
                            {
                                au_editbuf_free(histBuf[optHistoryLen+1]);
                                histBuf[optHistoryLen+1] = NULL;
                                memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0]));
                            }
                            
                            histPos = 0;
                            histBuf[1] = au_editbuf_copy(editBuf);
                            if (histMax < optHistoryLen) histMax++;
                            
                            au_editbuf_insert(editBuf, editBuf->len, 0);

                            /* You could do something here with the data ... */
                            //result = handleUserInput(conn, editBuf->data, editBuf->len);
                            
                            au_editbuf_clear(editBuf);
                        }
                        break;

                    case SDLK_UP: /* Backwards in input history */
                        if (jazzMode)
                            break;

                        if (histPos == 0)
                        {
                            au_editbuf_free(histBuf[0]);
                            histBuf[0] = au_editbuf_copy(editBuf);
                        }
                        if (histPos < histMax)
                        {
                            histPos++;
                            au_editbuf_free(editBuf);
                            editBuf = au_editbuf_copy(histBuf[histPos]);
                        }
                        break;
                        
                    case SDLK_DOWN: /* Forwards in input history */
                        if (jazzMode)
                            break;

                        if (histPos > 0)
                        {
                            histPos--;
                            au_editbuf_free(editBuf);
                            editBuf = au_editbuf_copy(histBuf[histPos]);
                        }
                        break;
                        
                    case SDLK_LEFT:
                        if (jazzMode)
                            break;

                        /* ctrl+left arrow = Skip words left */
                        if (modCtrl)
                        {
                            while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1]))
                                editBuf->pos--;
                            while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1]))
                                editBuf->pos--;
                        }
                        else
                        {
                            au_editbuf_setpos(editBuf, editBuf->pos - 1);
                        }
                        break;
                    
                    case SDLK_RIGHT:
                        if (jazzMode)
                            break;

                        /* ctrl+right arrow = Skip words right */
                        if (modCtrl)
                        {
                            while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos]))
                                editBuf->pos++;
                            while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos]))
                                editBuf->pos++;
                        }
                        else
                        {
                            au_editbuf_setpos(editBuf, editBuf->pos + 1);
                        }
                        break;

                    case SDLK_HOME:
                        if (jazzMode)
                            break;

                        au_editbuf_setpos(editBuf, 0);
                        break;

                    case SDLK_END:
                        if (jazzMode)
                            break;

                        au_editbuf_setpos(editBuf, editBuf->len);
                        break;
                    
                    case SDLK_BACKSPACE:
                        if (jazzMode)
                            break;

                        au_editbuf_delete(editBuf, editBuf->pos - 1);
                        au_editbuf_setpos(editBuf, editBuf->pos - 1);
                        break;
                    
                    case SDLK_DELETE: /* Delete character */
                        if (jazzMode)
                            break;

                        au_editbuf_delete(editBuf, editBuf->pos);
                        break;
                        
                    case SDLK_INSERT: /* Ins = Toggle insert / overwrite mode */
                        if (jazzMode)
                            break;

                        insertMode = !insertMode;
                        break;
                    
                    default:
                        if (jazzMode)
                        {
                            int key = event.key.keysym.unicode;
                            /* In keyboard jazz-mode, space stops playing */
                            if (key == SDLK_SPACE)
                            {
                                dmMutexLock(audata.mutex);
                                audata.varFreq = 1.0f;
                                audata.pos = 0;
                                audata.varTime = 0;
                                audata.varKeyTime = 0;
                                audata.varUnit = 0;
                                dmMutexUnlock(audata.mutex);

                                audioPlaying = FALSE;
                                SDL_PauseAudio(!audioPlaying);
                                break;
                            }
                            else
                            {
                                /* Calculate note based on key */
                                int period, note = au_get_note_from_key(&event.key.keysym, 4);
                                if (note > 0)
                                {
                                    period = 7680 - (note * 64) - (/* finetune */ 128 / 2);
                                    if (period < 1) period = 1;
                                
                                    dmMutexLock(audata.mutex);
                                    audata.varFreq = 8363.0f * pow(2.0f, (4608.0f - (double) period) / 768.0f) / optAudioFreq ;
                                    audata.pos = 0;
                                    audata.varTime = 0;
                                    audata.varKeyTime = 0;
                                    audata.varUnit = 0;
                                    dmMutexUnlock(audata.mutex);
                                
                                    audioPlaying = TRUE;
                                    SDL_PauseAudio(!audioPlaying);
                                }
                            }

                            needRedraw |= REDRAW_VISUALIZER;
                        }
                        else
                        {
                            int key = event.key.keysym.unicode;
                            if (isprint(key))
                            {
                                if (insertMode)
                                    au_editbuf_insert(editBuf, editBuf->pos, key);
                                else
                                    au_editbuf_write(editBuf, editBuf->pos, key);
                                au_editbuf_setpos(editBuf, editBuf->pos + 1);
                            }
                        }
                        break;
                }
                }
                needRedraw |= REDRAW_EDITOR | REDRAW_INFO;
                break;
            
            case SDL_VIDEORESIZE:
                /* Window resized, reinit video etc */
                optScrWidth = event.resize.w;
                optScrHeight = event.resize.h;

                if (!au_init_video(&screen))
                    goto error_exit;

                needRedraw = REDRAW_ALL;
                break;
            
            case SDL_VIDEOEXPOSE:
                /* Window exposed, redraw everything just to be sure */
                needRedraw = REDRAW_ALL;
                break;

            case SDL_QUIT:
                goto error_exit;
        }


        /* If formula has changed, update evaluation variables and load the
         * expression into Lua. Check for errors. The actual expression
         * evaluation is done in the audio rendering callback.
         */
        if (!jazzMode && editBuf->len > 0 && editBuf->dirty)
        {
            editBuf->dirty = FALSE;
            editBuf->data[editBuf->len] = 0;
            
            dmMutexLock(audata.mutex);
            
            dmEvalTreeFree(audata.expr);
            audata.expr = NULL;
            audata.ctx->err = dmEvalParseExpr(audata.ctx, editBuf->data, &audata.expr);
            dmEvalPrintOpTree(stdout, audata.ctx, audata.expr);
            printf("--\n");

            audata.pos        = 0;
            audata.varTime    = 0;
            audata.varFreq    = 1.0f;
            audata.varKeyTime = 0;
            audata.varUnit    = 0;

            dmMutexUnlock(audata.mutex);
        }
        
        /* Check if visualizer needs redrawing
         */
        if (audata.pos != audata.oldpos)
        {
            audata.oldpos = audata.pos;
            needRedraw |= REDRAW_VISUALIZER;
        }

        /* Redraw screen, if needed */
        if (needRedraw)
        {
            if (SDL_MUSTLOCK(screen) != 0 && SDL_LockSurface(screen) != 0)
            {
                dmError("Can't lock surface");
                goto error_exit;
            }

            /* Clear the surface, draw copyright etc */
            int fh = TTF_FontHeight(font);
            int eh = screen->h - fh * 4;
            
            if (needRedraw & REDRAW_INFO)
            {
                dmFillBox3D(screen, 0, 0, screen->w - 1, fh + 5, dmMapRGB(screen, 50, 50, 150),
                    dmMapRGB(screen, 150,150,250), dmMapRGB(screen, 25,25,50));

                dmDrawTTFTextConst(screen, font, fontcol, 5, 2,
                    AUVAL_NAME " v" AUVAL_VERSION " (C) Copyright 2013 ccr/TNSP");


                dmFillBox3D(screen, 0, eh - 25 - fh, screen->w - 1, eh - 16, dmMapRGB(screen, 50, 50, 150),
                    dmMapRGB(screen, 150,150,250), dmMapRGB(screen, 25,25,50));

                dmDrawTTFText(screen, font, fontcol, 5, eh - fh - 20, "%s | [%-8s] | %s",
                    optClipping ? "CLIPPED" : "WRAPPED",
                    optScale ? "0.0-1.0" : "0 - 255",
                    jazzMode ? "jazz" : "edit");
            }

            /* Draw the visualizer, based on the current mode*/
            if (needRedraw & REDRAW_VISUALIZER)
            {
                char *vms;
                dmFillRect(screen, 0, fh + 6, screen->w - 1, eh - fh - 27, dmMapRGB(screen, 15, 15, 15));

                switch (viewMode)
                {
                    case 0:
                        au_vis_wave(screen, 5, fh * 2 + 15, screen->w - 5, eh - 20, &audata);
                        vms = "scope";
                        break;

                    case 1:
                        au_vis_image(bmp, &audata);
                        dmScaledBlitSurfaceAny(bmp, 5, fh * 2 + 15, screen->w - 10, eh - fh * 2 - 30, screen, DMD_NONE);
                        vms = "bitmap";
                        break;
                    
                    default:
                        vms = "?";
                }

                dmDrawTTFText(screen, font, fontcol, 5, fh + 10, "%s | %s | f=%dHz | t=%-8.1f",
                    audioPlaying ? "PLAYING" : "STOPPED",
                    vms, optAudioFreq, audata.varTime
                    );
            }

            /* Draw the function editor box */                
            if (needRedraw & REDRAW_EDITOR)
            {
                dmMutexLock(audata.mutex);

                dmFillBox3D(screen, 0, eh - 15, screen->w - 1, screen->h - 1,
                    audata.ctx->err ? dmMapRGB(screen, 255, 0, 0) : dmMapRGB(screen, 0, 128, 64),
                    dmMapRGB(screen, 200, 255, 200), 100);

                au_draw_editbuf(screen, font, fontcol, 5, eh - 10, screen->w - 10, eh - 15, editBuf,
                    dmMapRGB(screen, 0, 0, 150));
                
                if (audata.ctx->err && audata.ctx->errStr != NULL)
                    dmDrawTTFTextConst(screen, font, fontcol, 5, screen->h - fh, audata.ctx->errStr);

                dmMutexUnlock(audata.mutex);
            }

            if (SDL_MUSTLOCK(screen) != 0)
                SDL_UnlockSurface(screen);

            SDL_Flip(screen);
            needRedraw = 0;
        }
        
        SDL_Delay(50);
    }

    /* Save history */
    au_save_history(AUVAL_HISTORY_FILE, histBuf, histMax);

error_exit:
    if (font)
        TTF_CloseFont(font);

    if (audioPlaying)
        SDL_PauseAudio(1);

    dmDestroyMutex(audata.mutex);

    if (initSDL)
        SDL_Quit();

    if (initTTF)
        TTF_Quit();

    return 0;
}
