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

#define SP_EVENT_THREAD_START (SP_EVENT_USER)
#define SP_EVENT_THREAD_END (SP_EVENT_USER + 1)
#define SP_EVENT_THREAD_PROCESSING (SP_EVENT_USER + 2)

#if defined(MACOS9)
#define MAX_VALUE 1000000
#else
#define MAX_VALUE 100000000
#endif
#define CHECK_INTERVAL 1000

static spTopLevel toplevel;

static volatile spBool thread_exit_flag = SP_FALSE;
static spBool use_event_flag = SP_FALSE;
static void *thread = NULL;
static void *event = NULL;

spThreadReturn threadFunc(void *data)
{
    long k;
    int percentage, prev_percentage;
    spComponent component = (spComponent)data;

    spDebug(10, "threadFunc", "thread start\n");
    spThreadEnter();
    spPostUserEvent(component, SP_EVENT_THREAD_START, (void *)0);
    spDebug(10, "threadFunc", "spPostUserEvent of SP_EVENT_THREAD_START done\n");
    spThreadLeave();

    prev_percentage = -1;
    for (k = 1; k < MAX_VALUE; k++) {
        if (thread_exit_flag == SP_TRUE) {
            break;
        }
        
        percentage = (int)(100.0 * ((double)k / (double)MAX_VALUE));
                
        if (k % CHECK_INTERVAL == 0) {
            if (percentage > prev_percentage) {
#if 0
                spDebug(10, "threadFunc", "k = %ld, percentage = %d\n", k, percentage);
                fflush(stderr);
#endif
                
                if (use_event_flag == SP_TRUE) spResetEvent(event);
                spThreadEnter();
                spPostUserEvent(component, SP_EVENT_THREAD_PROCESSING, (void *)percentage);
                spDebug(10, "threadFunc", "spPostUserEvent of SP_EVENT_THREAD_PROCESSING (%d) done\n",
                        percentage);
                spThreadLeave();
                if (use_event_flag == SP_TRUE) spWaitEvent(event);
                
                prev_percentage = percentage;
            }
            spYieldThread();
        }
    }
    
    spDebug(10, "threadFunc", "loop end\n");
    
    spThreadEnter();
    spPostUserEvent(component, SP_EVENT_THREAD_END, (void *)100);
    spThreadLeave();
    spDebug(10, "threadFunc", "thread end\n");

    return SP_THREAD_RETURN_SUCCESS;
}

static void quitCB(spComponent component, void *data)
{
    /* quit program */
    if (spQuitPrompt(component) == SP_FALSE) {
        spDebug(10, "quitCB", "quit canceled\n");
        
        /* quit canceled */
        thread_exit_flag = SP_TRUE;

        if (use_event_flag == SP_TRUE) spSetEvent(event);
        spWaitThreadLoop(toplevel, thread);
        spDebug(10, "quitCB", "spWaitThreadLoop done\n");

        spDestroyThread(thread);
        
        /* create thread again */
        thread_exit_flag = SP_FALSE;
        thread = spCreateThread(0, SP_THREAD_PRIORITY_NORMAL, threadFunc, data);
    } else {
        spDebug(10, "quitCB", "exit program\n");
        
        thread_exit_flag = SP_TRUE;
        
        if (use_event_flag == SP_TRUE) spSetEvent(event);
        
        spDebug(10, "quitCB", "call spWaitThreadLoop\n");
        spWaitThreadLoop(toplevel, thread);
        
        spDebug(10, "quitCB", "call spDestroyThread\n");
        spDestroyThread(thread);
        spDestroyEvent(event);

        spQuit(0);
    }

    return;
}

static void checkUseEventCB(spComponent component, void *data)
{
    spGetToggleState(component, &use_event_flag);
    
    return;
}

spBool eventCB(spComponent component, spEventType event_type, void *data)
{
    static spBool show_dialog = SP_FALSE;
    static int prev_percentage = -1;
    char buf[SP_MAX_LINE];
    int percentage;

    percentage = (int)data;
    
    spDebug(10, "eventCB", "event_type = %ld\n", event_type);

    if (event_type == SP_EVENT_THREAD_START) {
        show_dialog = SP_TRUE;
        prev_percentage = -1;
        spSetTextString(component, "Thread started.");
    } else if (event_type == SP_EVENT_THREAD_END) {
        spSetTextString(component, "Thread finished.");
    } else if (event_type == SP_EVENT_THREAD_PROCESSING) {
        if (prev_percentage + 1 != percentage) {
            sprintf(buf, "Processing... (%d %% done), ** event skipped **", percentage);
        } else {
            sprintf(buf, "Processing... (%d %% done)", percentage);
        }
        spDebug(10, "eventCB", "show_dialog = %d, buf = %s\n", show_dialog, buf);
        spSetTextString(component, buf);
        prev_percentage = percentage;
        
        if (percentage >= 50 && show_dialog == SP_TRUE) {
            spDebug(10, "eventCB", "display message\n");
            show_dialog = SP_FALSE;
            spDisplayMessage(component, NULL, "%s\nThis message is displayed from an event callback function.", buf);
        }
        
        if (use_event_flag == SP_TRUE) spSetEvent(event);
    }
    
    spDebug(10, "eventCB", "done\n");
    
    return SP_TRUE;
}

static void textCB(spComponent component, void *data)
{
    char *string;

    if ((string = xspGetTextString(component)) != NULL) {
#if defined(DEBUG)
        printf("reason = %d: %s\n", spGetCallbackReason(component), string);
#endif
        xspFree(string);
    }
    
    return;
}

int spMain(int argc, char *argv[])
{
    spComponent frame;
    spComponent checkbox;
    spComponent button;
    spComponent text;

    use_event_flag = SP_FALSE;
    thread_exit_flag = SP_FALSE;

    /*spSetDebugLevel(10);*/
    
    spSetThreadLevel(SP_THREAD_LEVEL_LOW);
    
    /* initialize toolkit */
    toplevel = spInitialize(&argc, &argv,
                            SppThreadSafe, SP_TRUE,
                            NULL);
    
    /* create main window */
    frame = spCreateMainFrame("Thread Event",
                              SppCloseStyle, SP_CALLBACK_CLOSE,
                              NULL);

    /* create check box */
    checkbox = spCreateCheckBox(frame, "Use Event Object",
                                SppCallbackFunc, checkUseEventCB,
                                SppSet, use_event_flag,
                                NULL);
    
    /* create text field */
    text = spCreateTextField(frame, "text",
                             SppCallbackFunc, textCB,
                             /*SppEditable, SP_FALSE,*/
                             NULL);
    spAddEventCallback(text, (spEventCallbackFunc)eventCB);

    /* create push button */
    button = spCreatePushButton(frame, "Quit Program",
                                SppCallbackFunc, quitCB,
                                SppCallbackData, text,
                                SppInitialWidth, 400,
                                NULL);

    spAddCallback(frame, SP_CLOSE_CALLBACK, quitCB, text);
    
    /* popup window */
    spPopupWindow(frame);

    if (spIsPostEventSupported(toplevel) == SP_FALSE) {
        spDisplayError(frame, NULL, "Posting event is not supported on this environment.");
        spQuit(0);
    }

    event = spCreateEvent(SP_TRUE, SP_FALSE);

    thread = spCreateThread(0, SP_THREAD_PRIORITY_NORMAL, threadFunc, text);
    
    /* main loop */
    return spMainLoop(toplevel);
}
