#include <stdio.h>
#include <sp/spBaseLib.h>
#include <sp/spComponentLib.h>
#include <sp/spComponentMain.h>

#define INITIAL_CIRCLE_RADIUS 50
#define MAX_ZOOM_FACTOR /*10.0*/2.0
#define MIN_ZOOM_FACTOR /*0.1*/0.6
#define STRING_X_OFFSET 50

static int current_x = 0;
static int current_y = 0;
static int wheel_x = 0;
static int wheel_y = 0;
static int circle_radius = INITIAL_CIRCLE_RADIUS;

static void canvasCB(spComponent component, void *data)
{
    int width = 0, height = 0;
    int x, y;
    int len;
    spCallbackReason reason;
    char buf[SP_MAX_LINE];
    spGraphics graphics = (spGraphics)data;

    reason = spGetCallbackReason(component);
    spDebug(20, "canvasCB", "reason = %d\n", reason);
    
    spGetSize(component, &width, &height);
    spDebug(20, "canvasCB", "width = %d, height = %d\n", width, height);

    spSetGraphicsParams(graphics, SppForeground, "white", NULL);
    spFillRectangle(component, graphics, 0, 0, width, height);

    switch (reason) {
      case SP_CR_LBUTTON_PRESS:
      case SP_CR_LBUTTON_MOTION:
      case SP_CR_LBUTTON_RELEASE:
        spStrCopy(buf, sizeof(buf), "L Button");
        break;
      case SP_CR_MBUTTON_PRESS:
      case SP_CR_MBUTTON_MOTION:
      case SP_CR_MBUTTON_RELEASE:
        spStrCopy(buf, sizeof(buf), "M Button");
        break;
      case SP_CR_RBUTTON_PRESS:
      case SP_CR_RBUTTON_MOTION:
      case SP_CR_RBUTTON_RELEASE:
        spStrCopy(buf, sizeof(buf), "R Button");
        break;
      case SP_CR_KEY_PRESS:
        spStrCopy(buf, sizeof(buf), "Key Press: ");
        break;
      case SP_CR_KEY_RELEASE:
        spStrCopy(buf, sizeof(buf), "Key Release: ");
        break;
      default:
        buf[0] = NUL;
        break;
    }

    switch (reason) {
      case SP_CR_LBUTTON_PRESS:
      case SP_CR_MBUTTON_PRESS:
      case SP_CR_RBUTTON_PRESS:
        spStrCat(buf, sizeof(buf), " Press");
        break;
      case SP_CR_LBUTTON_MOTION:
      case SP_CR_MBUTTON_MOTION:
      case SP_CR_RBUTTON_MOTION:
        spStrCat(buf, sizeof(buf), " Motion");
        break;
      case SP_CR_LBUTTON_RELEASE:
      case SP_CR_MBUTTON_RELEASE:
      case SP_CR_RBUTTON_RELEASE:
        spStrCat(buf, sizeof(buf), " Release");
        break;
      case SP_CR_WHEEL:
        spStrCopy(buf, sizeof(buf), "Wheel");
        break;
      case SP_CR_ZOOM:
        spStrCopy(buf, sizeof(buf), "Zoom");
        break;
      case SP_CR_KEY_PRESS:
      case SP_CR_KEY_RELEASE:
        len = strlen(buf);
        spGetCallbackKeyString(component, buf + len, sizeof(buf) - len, NULL);
        break;
      default:
        break;
    }

    if (reason == SP_CR_WHEEL) {
        double delta_x, delta_y;
        if (spGetCallbackWheelValue(component, &delta_x, &delta_y) == SP_TRUE) {
            wheel_x += (int)spRound(delta_x);
            if (wheel_x >= width) {
                wheel_x -= width;
            } else if (wheel_x < 0) {
                wheel_x += width;
            }
            
            wheel_y -= (int)spRound(delta_y);
            if (wheel_y >= height) {
                wheel_y -= height;
            } else if (wheel_y < 0) {
                wheel_y += height;
            }
        }
    } else if (reason == SP_CR_ZOOM) {
        double h_factor, v_factor, factor;
        
        spGetParams(component,
                    SppHorizontalContentFactor, &h_factor,
                    SppVerticalContentFactor, &v_factor,
                    NULL);
        spDebug(20, "canvasCB", "h_factor = %f, v_factor = %f\n", h_factor, v_factor);
        
        factor = MAX(h_factor, v_factor);
        factor = MIN(factor, MAX_ZOOM_FACTOR);
        factor = MAX(factor, MIN_ZOOM_FACTOR);
        circle_radius = (int)spRound(INITIAL_CIRCLE_RADIUS * factor);
        spDebug(20, "canvasCB", "factor = %f, circle_radius = %d\n", factor, circle_radius);

        spSetParams(component,
                    SppHorizontalContentFactor, factor,
                    SppVerticalContentFactor, factor,
                    NULL);
    } else {
        if (spGetCallbackMousePosition(component, &x, &y) == SP_TRUE) {
            current_x = x;
            current_y = y;
            wheel_x = current_x;
            wheel_y = current_y;
        }
    }
    
    spSetGraphicsParams(graphics, SppForeground, "green", NULL);
    spFillArc(component, graphics, wheel_x - circle_radius, wheel_y - circle_radius,
              circle_radius * 2, circle_radius * 2, 0, 360);
    
    spSetGraphicsParams(graphics, SppForeground, "black", NULL);
    spDrawString(component, graphics, current_x + STRING_X_OFFSET, current_y, buf);
    
    spDebug(20, "canvasCB", "call spRefreshCanvas\n");
    spRefreshCanvas(component);
    
    spDebug(20, "canvasCB", "done\n");
    
    return;
}

int spMain(int argc, char *argv[])
{
    spTopLevel toplevel;
    spComponent frame;
    spComponent menu, menu_bar;
    spComponent canvas;
    spGraphics graphics;

    current_x = current_y = 0;
    wheel_x = wheel_y = 0;
    
    /* initialize toolkit */
    toplevel = spInitialize(&argc, &argv, NULL);
    
    /* create main window */
    frame = spCreateMainFrame("Canvas Mouse", NULL);

    /* create menu bar */
    menu_bar = spCreateMenuBar(frame, "menuBar", NULL);
    
    /* create menu */
    menu = spCreatePulldownMenu(menu_bar, "fileMenu",
                                SppTitle, "File",
                                NULL);
    
    /* add menu item */
    spAddMenuItem(menu, SP_QUIT_MENU_ITEM_NAME,
                  SppCallbackFunc, spQuitCB,
                  SppTitle, "Quit",
                  SppShortcut, "A-q",
                  NULL);

    graphics = spCreateGraphics("graphics1",
                                SppFontName, "-*-*-medium-*-normal--16-*-*-*-*-*-*-*",
                                NULL);
    
    /* create canvas */
    canvas = spCreateCanvas(frame, "canvas", 300, 300,
                            SppBorderOn, SP_TRUE,
                            SppAcceptWheelEvent, SP_TRUE,
                            SppFocusable, SP_TRUE,
                            SppCallbackFunc, canvasCB,
                            SppCallbackData, graphics,
                            NULL);
    spAddCallback(canvas, SP_BUTTON_MOTION_CALLBACK | SP_BUTTON_PRESS_CALLBACK | SP_BUTTON_RELEASE_CALLBACK
                  | SP_WHEEL_CALLBACK | SP_ZOOM_CALLBACK | SP_KEY_PRESS_CALLBACK | SP_KEY_RELEASE_CALLBACK,
                  canvasCB, graphics);

    /* popup window */
    spPopupWindow(frame);
    
    /* main loop */
    return spMainLoop(toplevel);
}
