/**
 ** mscursor.c ---- the mouse cursor related stuff
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu]
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library 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.
 **
 **/

#include <stdarg.h>
#include "libgrx.h"
#include "ninput.h"
#include "clipping.h"

#define COMPATIBLE(c)   ((c)->work.gc_driver->mode == SDRV->rmode)

#define  MSCURSOR       (MOUINFO->cursor)
#define  CURSORMODE     (MOUINFO->cursmode)
#define  SPECIALMODE    (MOUINFO->cursmode != GR_M_CUR_NORMAL)
#define  BLOCKED        1
#define  ERASED         2

static void draw_special(void);
static void move_mouse(void);
static void draw_mouse(void);
static void erase_mouse(void);
static int block(GrContext *c, int x1, int y1, int x2, int y2);
static void unblock(int flags);

int GrMouseDetect(void)
{
    if (MOUINFO->msstatus == 0)
        return _GrMouseDetect();

    return (MOUINFO->msstatus > 0);
}

void _GrInitMouseCursor(void)
{
    if (MSCURSOR && !COMPATIBLE(MSCURSOR)) {
        if (MOUINFO->owncursor) {
            GrCursor *obsolete = MSCURSOR;
            MOUINFO->cursor    = NULL;
            MOUINFO->owncursor = FALSE;
            GrDestroyCursor(obsolete);
        }
        MOUINFO->cursor = NULL;
    }
    if (MSCURSOR == NULL) {
        GrMouseSetInternalCursor(GR_MCUR_TYPE_ARROW, GrAllocColor(255, 0, 0), GrBlack());
    }
    if (MSCURSOR && MSCURSOR->displayed) {
        GrEraseCursor(MSCURSOR);
    }
    MOUINFO->cursmode  = GR_M_CUR_NORMAL;
    MOUINFO->displayed = FALSE;
    MOUINFO->blockflag = 0;
    MOUINFO->docheck   = FALSE;
    MOUINFO->block     = block;
    MOUINFO->unblock   = unblock;
}

void GrMouseUpdateCursor(void)
{
    if (MOUINFO->displayed && !MOUINFO->blockflag) {
        move_mouse();
    }
}

void GrEventGenMmove(int when)
{
    MOUINFO->genmmove = when;
}

void GrMouseDisplayCursor(void)
{
    if (MOUINFO->msstatus != 2) return;
    if (MOUINFO->cursor == NULL) return;
    if (MOUINFO->displayed != FALSE) return;
    move_mouse();
    draw_mouse();
    MOUINFO->displayed = TRUE;
    MOUINFO->docheck = TRUE;
    MOUINFO->blockflag = 0;
}

void GrMouseEraseCursor(void)
{
    if (MOUINFO->msstatus != 2) return;
    if (MOUINFO->cursor == NULL) return;
    if (MOUINFO->displayed == FALSE) return;
    if (MOUINFO->blockflag != 0) return;
    MOUINFO->displayed = FALSE;
    MOUINFO->docheck = FALSE;
    erase_mouse();
}

void GrMouseSetCursor(GrCursor *C)
{
    if (!MOUINFO->displayed && C && (C != MSCURSOR) && COMPATIBLE(C)) {
        GrCursor *oldcursor = MSCURSOR;
        if (C->displayed) GrEraseCursor(C);
        MOUINFO->cursor = C;
        if (MOUINFO->owncursor) GrDestroyCursor(oldcursor);
        MOUINFO->owncursor   = FALSE;
    }
}

void GrMouseSetInternalCursor(int type, GrColor fg, GrColor bg)
{
    static char arrow12x16[] = {
    0,1,0,0,0,0,0,0,0,0,0,0,
    1,2,1,0,0,0,0,0,0,0,0,0,
    1,2,2,1,0,0,0,0,0,0,0,0,
    1,2,2,2,1,0,0,0,0,0,0,0,
    1,2,2,2,2,1,0,0,0,0,0,0,
    1,2,2,2,2,2,1,0,0,0,0,0,
    1,2,2,2,2,2,2,1,0,0,0,0,
    1,2,2,2,2,2,2,2,1,0,0,0,
    1,2,2,2,2,2,2,2,2,1,0,0,
    1,2,2,2,2,2,2,2,2,2,1,0,
    1,2,2,2,2,2,2,2,2,2,2,1,
    1,2,2,2,2,1,1,1,1,1,1,0,
    1,2,2,2,1,0,0,0,0,0,0,0,
    1,2,2,1,0,0,0,0,0,0,0,0,
    1,2,1,0,0,0,0,0,0,0,0,0,
    0,1,0,0,0,0,0,0,0,0,0,0,
    };
    static char cross13x13[] = {
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    1,1,1,1,1,1,2,1,1,1,1,1,1,0,
    1,2,2,2,2,2,1,2,2,2,2,2,1,0,
    1,1,1,1,1,1,2,1,1,1,1,1,1,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    0,0,0,0,0,1,2,1,0,0,0,0,0,0,
    };
    GrCursor *newc;
    GrColor cols[3];

    if (MOUINFO->displayed) return;
    cols[0] = 2;
    cols[1] = bg;
    cols[2] = fg;
    if (type == GR_MCUR_TYPE_CROSS )
        newc = GrBuildCursor(cross13x13, 14, 13, 13, 6, 6, cols);
    else
        newc = GrBuildCursor(arrow12x16, 12, 12, 16, 1, 1, cols);
    if (!newc) return;
    GrMouseSetCursor(newc);
    MOUINFO->owncursor = TRUE;
}

void GrMouseSetCursorMode(int mode,...)
{
    va_list ap;

    if (MOUINFO->displayed) return;
    va_start(ap, mode);
    switch (mode) {
    case GR_M_CUR_BOX:
        MOUINFO->x2 = va_arg(ap,int);
        MOUINFO->y2 = va_arg(ap,int);
    case GR_M_CUR_RUBBER:
    case GR_M_CUR_LINE:
        MOUINFO->cursmode = mode;
        MOUINFO->x1 = va_arg(ap,int);
        MOUINFO->y1 = va_arg(ap,int);
        MOUINFO->curscolor = GrXorModeColor(va_arg(ap, GrColor));
        break;
    default:
        MOUINFO->cursmode = GR_M_CUR_NORMAL;
        break;
    }
    va_end(ap);
}

static void draw_special(void)
{
    int xpos  = MSCURSOR->xcord;
    int ypos  = MSCURSOR->ycord;
    int check = MOUINFO->docheck;
    GrContext csave;

    MOUINFO->docheck = FALSE;
    GrSaveContext(&csave);
    GrSetContext(SCRN);
    switch (CURSORMODE) {
    case GR_M_CUR_RUBBER:
        GrBox(xpos, ypos, MOUINFO->x1, MOUINFO->y1, MOUINFO->curscolor);
        break;
    case GR_M_CUR_LINE:
        GrLine(xpos, ypos, MOUINFO->x1, MOUINFO->y1, MOUINFO->curscolor);
        break;
    case GR_M_CUR_BOX:
        GrBox((xpos+MOUINFO->x1), (ypos+MOUINFO->y1),
              (xpos+MOUINFO->x2), (ypos+MOUINFO->y2), MOUINFO->curscolor);
        break;
    }
    GrSetContext(&csave);
    MOUINFO->docheck = check;
}

static void move_mouse(void)
{
    if ((MOUINFO->xpos != MSCURSOR->xcord) ||
        (MOUINFO->ypos != MSCURSOR->ycord)) {
        int check  = MOUINFO->docheck;
        int dospec = SPECIALMODE && MSCURSOR->displayed;
        MOUINFO->docheck = FALSE;
        if (dospec) draw_special();
        GrMoveCursor(MSCURSOR, MOUINFO->xpos, MOUINFO->ypos);
        if (dospec) draw_special();
        MOUINFO->docheck = check;
    }
}

static void draw_mouse(void)
{
    int check = MOUINFO->docheck;
    MOUINFO->docheck = FALSE;
    GrDisplayCursor(MSCURSOR);
    if(SPECIALMODE) draw_special();
    MOUINFO->docheck = check;
}

static void erase_mouse(void)
{
    int check = MOUINFO->docheck;
    MOUINFO->docheck = FALSE;
    if(SPECIALMODE) draw_special();
    GrEraseCursor(MSCURSOR);
    MOUINFO->docheck = check;
}

static int block(GrContext *c, int x1, int y1, int x2, int y2)
{
    int mx1,my1,mx2,my2;
    int oldblock = MOUINFO->blockflag;

    if (!c) c = CURC;
    if (!MOUINFO->displayed) return(0);
    if (!MOUINFO->docheck) return(0);
    if (!c->gc_onscreen) return(0);
    if (oldblock & ERASED) return(0);
    MOUINFO->blockflag = BLOCKED;
    isort(x1, x2); x1 += c->gc_xoffset; x2 += c->gc_xoffset;
    isort(y1, y2); y1 += c->gc_yoffset; y2 += c->gc_yoffset;
    mx1 = MSCURSOR->xwpos;
    my1 = MSCURSOR->ywpos;
    mx2 = MSCURSOR->xwork + mx1 - 1;
    my2 = MSCURSOR->ywork + my1 - 1;
    if (SPECIALMODE) {
        int cx1,cy1,cx2,cy2;
        switch (CURSORMODE) {
        case GR_M_CUR_RUBBER:
        case GR_M_CUR_LINE:
            cx1 = MSCURSOR->xcord;
            cy1 = MSCURSOR->ycord;
            cx2 = MOUINFO->x1;
            cy2 = MOUINFO->y1;
            break;
        case GR_M_CUR_BOX:
            cx1 = MSCURSOR->xcord + MOUINFO->x1;
            cy1 = MSCURSOR->ycord + MOUINFO->y1;
            cx2 = MSCURSOR->xcord + MOUINFO->x2;
            cy2 = MSCURSOR->ycord + MOUINFO->y2;
            break;
        default:
           return 0;
        }
        isort(cx1, cx2); mx1 = imin(cx1, mx1); mx2 = imax(mx2, cx2);
        isort(cy1, cy2); my1 = imin(cy1, my1); my2 = imax(my2, cy2);
    }
    x1 = imax(x1, mx1); y1 = imax(y1, my1);
    x2 = imin(x2, mx2); y2 = imin(y2, my2);
    if ((x1 <= x2) && (y1 <= y2)) {
        MOUINFO->blockflag = oldblock | ERASED;
        MOUINFO->docheck = FALSE;
        erase_mouse();
        return (ERASED);
    }
    return ((oldblock & BLOCKED) ? 0 : BLOCKED);
}

static void unblock(int flags)
{
    if (!MOUINFO->displayed) return;
    if (flags & MOUINFO->blockflag & ERASED) {
        draw_mouse();
        MOUINFO->blockflag &= ~ERASED;
        MOUINFO->docheck = TRUE;
    }
    if (flags & MOUINFO->blockflag & BLOCKED) {
        MOUINFO->blockflag &= ~BLOCKED;
    }
}

