//
//  glk.m
//  CocoaGlk
//
//  Created by Andrew Hunter on Wed Jun 11 2003.
//  Copyright (c) 2003 Andrew Hunter. All rights reserved.
//

// The wrappers

// Oh, well then, everything's wrapped up in a neat little package
//     -- Homer Simpson

#import <Cocoa/Cocoa.h>

#import "glk.h"
#import "glkInternal.h"
#import "GlkSession.h"
#import "GlkStatus.h"

#import "GlkWriteBufferedStream.h"

#undef GLKLOG

// == Some convienience functions + macros ==
#define cStream ([ourStatus currentStream]->stream)

static GlkWindow* glkwin(winid_t w) {
    if (w == NULL ||
        w->win == nil) {
        return [ourSession rootWindow];
    }

    return w->win;
}

static GlkStream* glkstr(strid_t s) {
    if (s == NULL) return nil;
    return s->stream;
}

static GlkFileRef* glkfref(frefid_t f) {
    if (f == NULL) return nil;
    return f->ref;
}

// == The actual functions ==
void glk_exit(void) {
#ifdef GLKLOG
    NSLog(@"glk_exit()");
#endif
    
    [[[NSThread currentThread] threadDictionary] removeObjectForKey: GlkStatusKey];

    [ourSession exit];
}

void glk_set_interrupt_handler(void (*func)(void)) {
#ifdef GLKLOG
    NSLog(@"glk_set_interrupt_handler()");
#endif

    // Implement me
}

void glk_tick(void) {
#ifdef GLKLOG
    // NSLog(@"glk_tick()");
#endif

    // [ourSession threadPoolFree];
    // [ourSession tick];
}

glui32 glk_gestalt(glui32 sel, glui32 val) {
    GlkSession* theSession = ourSession;

#ifdef GLKLOG
    NSLog(@"glk_gestalt()");
#endif
    
    return [theSession gestaltForSel: sel
                             withVal: val];
}

glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr,
                       glui32 arrlen) {
    GlkSession* theSession = ourSession;

#ifdef GLKLOG
    NSLog(@"glk_gestalt_ext()");
#endif
    
    return [theSession gestaltForSel: sel
                             withVal: val];
}

unsigned char glk_char_to_lower(unsigned char ch) {
#ifdef GLKLOG
    //NSLog(@"glk_char_to_lower()");
#endif

    return tolower(ch);
}

unsigned char glk_char_to_upper(unsigned char ch) {
#ifdef GLKLOG
    //NSLog(@"glk_char_to_upper()");
#endif

    return toupper(ch);
}

winid_t glk_window_get_root(void) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_root()");
#endif

    return [ourStatus rootWindow];
}

winid_t glk_window_open(winid_t split, glui32 method, glui32 size,
                        glui32 wintype, glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_window_open()");
#endif

    GlkStatus* status = ourStatus;
    
    [ourSession threadPoolFree];

    if (split == NULL) {
        size = 100;
        method = winmethod_Proportional|winmethod_Above;
    }
    
    GlkWindow* newWin = [glkwin(split) openWithMethod: method
                                                 size: size
                                                 type: wintype
                                                 rock: rock];
	
	if( [newWin speechSynthesisOn] )
		[newWin toggleSpeechSynthesis: nil];
	
    winid_t newerWin  = [status newWindow: newWin];
 
    // Set the root window if necessary
    if (split == NULL) {
        [status setRootWindow: newerWin];
    }

    return newerWin;
}

void glk_window_close(winid_t cwin, stream_result_t *result) {
#ifdef GLKLOG
    NSLog(@"glk_window_close()");
#endif

    stream_result_t r = [glkwin(cwin) close];

    if (result) *result = r;

    [ourStatus removeWindow: cwin];
}

void glk_window_get_size(winid_t cwin, glui32 *widthptr,
                         glui32 *heightptr) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_size()");
#endif

    GlkSize sz = [glkwin(cwin) size];

    if (widthptr)  *widthptr  = sz.width;
    if (heightptr) *heightptr = sz.height;
}

void glk_window_set_arrangement(winid_t cwin, glui32 method,
                                glui32 size, winid_t keywin) {
#ifdef GLKLOG
    NSLog(@"glk_window_set_arrangement()");
#endif

    GlkArrangement arr;

    arr.method = method;
    arr.size   = size;
    arr.keyWin = glkwin(keywin);

    [glkwin(cwin) setArrangement: arr];
}

void glk_window_get_arrangement(winid_t cwin, glui32 *methodptr,
                                glui32 *sizeptr, winid_t *keywinptr) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_arrangement()");
#endif

    GlkArrangement arr = [glkwin(cwin) arrangement];

    if (methodptr) *methodptr = arr.method;
    if (sizeptr)   *sizeptr   = arr.size;
    if (keywinptr) *keywinptr = [ourStatus newWindow: arr.keyWin];
}

winid_t glk_window_iterate(winid_t cwin, glui32 *rockptr) {
#ifdef GLKLOG
    NSLog(@"glk_window_iterate()");
#endif

    GlkWindow* nextWin = [ourSession windowIterate: cwin?glkwin(cwin):nil
                                              rock: rockptr];

    if (nextWin == nil) {
        return NULL;
    }
    
    return [ourStatus newWindow: nextWin];
}

glui32 glk_window_get_rock(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_rock()");
#endif

    return [glkwin(cwin) rock];
}

glui32 glk_window_get_type(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_type()");
#endif

    return [glkwin(cwin) type];
}

winid_t glk_window_get_parent(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_parent()");
#endif

    return [ourStatus newWindow: [glkwin(cwin) parent]];
}

winid_t glk_window_get_sibling(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_sibling()");
#endif

    return [ourStatus newWindow: [glkwin(cwin) sibling]];
}

void glk_window_clear(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_clear()");
#endif

    [glkwin(cwin) clear];
}

void glk_window_move_cursor(winid_t cwin, glui32 xpos, glui32 ypos) {
#ifdef GLKLOG
    NSLog(@"glk_window_move_cursor()");
#endif

    [(GlkWriteBufferedStream*)cwin->stream->stream flushBuffers];

    [glkwin(cwin) moveCursorToPoint: GlkMakePoint(xpos, ypos)];
}

strid_t glk_window_get_stream(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_window_get_stream()");
#endif

    return cwin->stream;
}

void glk_window_set_echo_stream(winid_t cwin, strid_t str) {
#ifdef GLKLOG
    NSLog(@"glk_set_echo_stream()");
#endif

    [ourSession threadPoolFree];
    cwin->echostream = str;
    [glkwin(cwin) setEchoStream: glkstr(str)];
}

strid_t glk_window_get_echo_stream(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_get_echo_stream()");
#endif

    return cwin->echostream;
}

void glk_set_window(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_set_window()");
#endif

    [ourStatus setCurrentStream: cwin->stream];

    [[glkwin(cwin) view] focus];
    //[ourSession threadPoolFree];
    //[ourSession setGlkWindow: glkwin(cwin)];
}

strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode,
                             glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_stream_open_file()");
#endif

    [ourSession threadPoolFree];
    GlkStream* newStr = [ourSession openFile: glkfref(fileref)
                                    withMode: fmode
                                        rock: rock];

    return [ourStatus newStream: newStr];
}


// -----------------------------------------------------------------------------
//	glkunix_stream_open_pathname:
//		Function that can be used by glkunix_startup_code() to open a stream for
//		a file outside the user-permitted folders. Using it at other times is
//		unportable. This may be used to open game files the user wants to open
//		at startup.
//
//	REVISIONS:
//		2004-03-13	witness	Created.
// -----------------------------------------------------------------------------

strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, glui32 rock)
{
#ifdef GLKLOG
    NSLog(@"glkunix_stream_open_pathname()");
#endif
	
	// TODO: Should we enforce that this is only available during glkunix_startup_code()?
	
    GlkStream* newStr = [ourSession openFileWithForcedName: [NSString stringWithCString: pathname]
							usage: (textmode? fileusage_TextMode : fileusage_BinaryMode)
							withMode: filemode_Read rock: rock];

    return [ourStatus newStream: newStr];
	
}

strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode,
                               glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_stream_open_memory()");
#endif

    GlkStream* newStr = [ourSession openMemory: buf
                                        length: buflen
                                      withMode: fmode
                                          rock: rock];

    return [ourStatus newStream: newStr];
}

void glk_stream_close(strid_t str, stream_result_t *result) {
#ifdef GLKLOG
    NSLog(@"glk_stream_close()");
#endif

    [ourSession threadPoolFree];
    stream_result_t r = [glkstr(str) close];

    if (result) *result = r;

    [ourStatus removeStream: str];
}

strid_t glk_stream_iterate(strid_t str, glui32 *rockptr) {
#ifdef GLKLOG
    NSLog(@"glk_stream_iterate()");
#endif

    if (str == NULL) {
        return [ourStatus firstStream];
    } else {
        return str->next;
    }
}

glui32 glk_stream_get_rock(strid_t str) {
#ifdef GLKLOG
    NSLog(@"glk_stream_get_rock()");
#endif

    return [glkstr(str) rock];
}

void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode) {
#ifdef GLKLOG
    NSLog(@"glk_stream_set_position()");
#endif

    [glkstr(str) setPosition: pos
                    withMode: seekmode];
}

glui32 glk_stream_get_position(strid_t str) {
#ifdef GLKLOG
    NSLog(@"glk_stream_get_position()");
#endif

    return [glkstr(str) getPosition];
}

void glk_stream_set_current(strid_t str) {
#ifdef GLKLOG
    NSLog(@"glk_stream_set_current()");
#endif

    [ourStatus setCurrentStream: str];
}

strid_t glk_stream_get_current(void) {
#ifdef GLKLOG
    NSLog(@"glk_stream_get_current()");
#endif

    return [ourStatus currentStream];
}

void glk_put_char(unsigned char ch) {
#ifdef GLKLOG
    printf("%c", ch);
#endif
    [cStream putChar: ch];
}

void glk_put_char_stream(strid_t str, unsigned char ch) {
#ifdef GLKLOG
    printf("%c", ch);
#endif
    [glkstr(str) putChar: ch];
}

void glk_put_string(char *s) {
#ifdef GLKLOG
    printf("%s", s);
#endif

    [cStream putString: [NSString stringWithCString: s]];
}

void glk_put_string_stream(strid_t str, char *s) {
#ifdef GLKLOG
    printf("%s", s);
#endif

    // [ourSession threadPoolFree];
    [glkstr(str) putString: [NSString stringWithCString: s]];
}

void glk_put_buffer(char *buf, glui32 len) {
#ifdef GLKLOG
    NSLog(@"glk_put_buffer(): %@", [NSData dataWithBytes: buf
                                                  length: len]);
#endif

    // [ourSession threadPoolFree];
    [cStream putBuffer: [NSData dataWithBytes: buf
                                       length: len]];
}

void glk_put_buffer_stream(strid_t str, char *buf, glui32 len) {
#ifdef GLKLOG
    NSLog(@"glk_put_buffer_stream()");
#endif

    // [ourSession threadPoolFree];
    [glkstr(str) putBuffer: [NSData dataWithBytes: buf
                                           length: len]];
}

void glk_set_style(glui32 styl) {
#ifdef GLKLOG
    NSLog(@"glk_set_style()");
#endif

    [cStream setGlkStyle: styl];
}

void glk_set_style_stream(strid_t str, glui32 styl) {
#ifdef GLKLOG
    NSLog(@"glk_set_style_stream()");
#endif

    [glkstr(str) setGlkStyle: styl];
}

glsi32 glk_get_char_stream(strid_t str) {
#ifdef GLKLOG
    // NSLog(@"glk_get_char_stream()"); -- waay too many of these
#endif

    return [glkstr(str) getChar];
}

glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len) {
#ifdef GLKLOG
    NSLog(@"glk_get_line_stream()");
#endif

    [ourSession threadPoolFree];
    NSData* line = [glkstr(str) getLineInBuffer: len];

    if (line != nil) {
        memcpy(buf, [line bytes], [line length]);
        return [line length];
    }

    return 0;
}

glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len) {
#ifdef GLKLOG
    NSLog(@"glk_get_buffer_stream()");
#endif

    [ourSession threadPoolFree];
    NSData* line = [glkstr(str) getBuffer: len];

    if (line != nil) {
        memcpy(buf, [line bytes], [line length]);
        return [line length];
    }

    return 0;
}

void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint,
                       glsi32 val) {
#ifdef GLKLOG
    NSLog(@"glk_stylehint_set()");
#endif

    [ourSession setStyleHint: wintype
                       style: styl
                        hint: hint
                       value: val];
}

void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint) {
#ifdef GLKLOG
    NSLog(@"glk_stylehint_clear()");
#endif

    [ourSession clearStyleHint: wintype
                         style: styl
                          hint: hint];
}

glui32 glk_style_distinguish(winid_t cwin, glui32 styl1, glui32 styl2) {
#ifdef GLKLOG
    NSLog(@"glk_style_distinguish()");
#endif

    return [glkwin(cwin) distinguishStyle: styl1
                                fromStyle: styl2];
}

glui32 glk_style_measure(winid_t cwin, glui32 styl, glui32 hint,
                         glui32 *result) {
#ifdef GLKLOG
    NSLog(@"glk_style_measure()");
#endif

    return [glkwin(cwin) measureStyle: styl
                                hint: hint
                            resultIn: result];
}

frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_create_temp()");
#endif

    return [ourStatus newFileRef: [ourSession fileRefTemp: rock
                                                withUsage: usage]];
}

frefid_t glk_fileref_create_by_name(glui32 usage, char *name,
                                    glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_create_by_name()");
#endif

    [ourSession threadPoolFree];
    return [ourStatus newFileRef: [ourSession fileRefForName: name
                                                        rock: rock
                                                withUsage: usage]];
}

frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode,
                                      glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_create_by_prompt()");
#endif

    [ourSession threadPoolFree];
    return [ourStatus newFileRef: [ourSession fileRefByPromptingForMode: fmode
                                                                   rock: rock
                                                              withUsage: usage]];
}

frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref,
                                         glui32 rock) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_create_from_fileref()");
#endif

    [ourSession threadPoolFree];
    return [ourStatus newFileRef: [ourSession fileRefFromFileRef: glkfref(fref)
                                                            rock: rock
                                                       withUsage: usage]];
}

void glk_fileref_destroy(frefid_t fref) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_destroy()");
#endif

    [ourSession threadPoolFree];
    [ourStatus removeFileRef: fref];
}

frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_iterate()");
#endif

    if (fref == NULL) {
        return [ourStatus firstRef];
    } else {
        return fref->next;
    }
}

glui32 glk_fileref_get_rock(frefid_t fref) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_get_rock()");
#endif

    return [glkfref(fref) rock];
}

void glk_fileref_delete_file(frefid_t fref) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_delete_file()");
#endif

    [glkfref(fref) deleteFile];
}

glui32 glk_fileref_does_file_exist(frefid_t fref) {
#ifdef GLKLOG
    NSLog(@"glk_fileref_does_file_exist()");
#endif

    return [glkfref(fref) fileExists]==YES?1:0;
}

void glk_select(event_t *event) {
#ifdef GLKLOG
    NSLog(@"glk_select()");
#endif

    GlkEvent* theEvent = [ourSession select: event];

    if (event) {
        event->win = [ourStatus findIdForWindow: [theEvent win]];

        if ([theEvent type] == evtype_LineInput && event->win != NULL) {
            memcpy(event->win->outBuf,
                   [[theEvent data] bytes],
                   [[theEvent data] length]);
        }
    }
}

void glk_select_poll(event_t *event) {
#ifdef GLKLOG
    NSLog(@"glk_select_poll()");
#endif

    [ourSession threadPoolFree];
    GlkEvent* evt = [ourSession selectPoll];

    if (evt == nil) {
        event->type = evtype_None;
    } else {
        event->type = [evt type];
        event->win  = [ourStatus findIdForWindow: [evt win]];
        event->val1 = [evt val1];
        event->val2 = [evt val2];
    }
}

void glk_request_timer_events(glui32 millisecs) {
#ifdef GLKLOG
    NSLog(@"glk_request_timer_events()");
#endif

    BUG(@"glk_request_timer_events not implemented yet");
}

void glk_request_line_event(winid_t cwin, char *buf, glui32 maxlen,
                            glui32 initlen) {
#ifdef GLKLOG
    NSLog(@"glk_request_line_event()");
#endif

    [ourSession threadPoolFree];
    cwin->outBuf = buf;
    cwin->bufLen = maxlen;
    
    [glkwin(cwin) requestLineEvent: maxlen
                              init: [NSString stringWithCString: buf
                                                         length: initlen]];
}

void glk_request_char_event(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_request_char_event()");
#endif

    [glkwin(cwin) requestCharEvent];
}

void glk_request_mouse_event(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_request_mouse_event()");
#endif

    [glkwin(cwin) requestMouseEvent];
}

void glk_cancel_line_event(winid_t cwin, event_t *event) {
#ifdef GLKLOG
    NSLog(@"glk_cancel_line_event()");
#endif

    GlkEvent* theEvent = [glkwin(cwin) cancelLineEvent];

    if (event) {
        event->type = [theEvent type];
        event->win  = (winid_t)[theEvent win];
        event->val1 = [theEvent val1];
        event->val2 = [theEvent val2];
    }        
    
    if (event) {
        event->win = [ourStatus findIdForWindow: [theEvent win]];

        if ([theEvent type] == evtype_LineInput && event->win != NULL) {
            memcpy(event->win->outBuf,
                   [[theEvent data] bytes],
                   [[theEvent data] length]);
        }
    }
}

void glk_cancel_char_event(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_cancel_char_event()");
#endif

    [glkwin(cwin) cancelCharEvent];
}

void glk_cancel_mouse_event(winid_t cwin) {
#ifdef GLKLOG
    NSLog(@"glk_cancel_mouse_event()");
#endif

    [glkwin(cwin) cancelMouseEvent];
}

#ifdef GLK_MODULE_IMAGE

glui32 glk_image_draw(winid_t cwin, glui32 image,
                      glsi32 val1, glsi32 val2) {
    NSImage* img = [ourSession getImageResource: image];

    if (img) {
        [glkwin(cwin) drawImage: img
                           val1: val1
                           val2: val2];

        return 1;
    } else {
        return 0;
    }
}

glui32 glk_image_draw_scaled(winid_t cwin, glui32 image,
                             glsi32 val1, glsi32 val2,
                             glui32 width, glui32 height) {
    NSImage* img = [ourSession getImageResource: image];

    if (img) {
        [glkwin(cwin) drawImageScaled: img
                                 val1: val1
                                 val2: val2
                            withWidth: width
                               height: height];
    }
}

glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height) {
    NSImage* img = [ourSession getImageResource: image];

    if (img == nil) {
        return 0;
    }

    if (width) {
        *width = [img size].width;
    }
    if (height) {
        *height = [img size].height;
    }

    return 1;
}

void glk_window_flow_break(winid_t cwin) {
    [glkwin(cwin) breakFlow];
}

void glk_window_erase_rect(winid_t cwin,
                           glsi32 left, glsi32 top,
                           glui32 width, glui32 height) {
    [glkwin(cwin) fillRect: NSMakeRect(left, top, width, height)
                withColour: 0xffffffff];
}

void glk_window_fill_rect(winid_t cwin, glui32 color,
                          glsi32 left, glsi32 top,
                          glui32 width, glui32 height) {
    [glkwin(cwin) fillRect: NSMakeRect(left, top, width, height)
                withColour: color];
}

void glk_window_set_background_color(winid_t cwin, glui32 color)  {
    [glkwin(cwin) setBackground: color];
}

#endif /* GLK_MODULE_IMAGE */

#ifdef GLK_MODULE_SOUND

schanid_t glk_schannel_create(glui32 rock) {
    NSLog(@"Function glk_schannel_create not implemented");
}

void glk_schannel_destroy(schanid_t chan) {
    NSLog(@"Function glk_schannel_destroy not implemented");
}

schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr) {
    NSLog(@"Function glk_schannel_iterate not implemented");
    return NULL;
}

glui32 glk_schannel_get_rock(schanid_t chan) {
    NSLog(@"Function glk_schannel_get_rock not implemented");
}

glui32 glk_schannel_play(schanid_t chan, glui32 snd) {
    NSLog(@"Function glk_schannel_play not implemented");
}

glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats,
                                    glui32 notify) {
    NSLog(@"Function glk_schannel_play_ext not implemented");
}

void glk_schannel_stop(schanid_t chan) {
    NSLog(@"Function glk_schannel_stop not implemented");
}

void glk_schannel_set_volume(schanid_t chan, glui32 vol) {
    NSLog(@"Function glk_schannel_set_volume not implemented");
}

void glk_sound_load_hint(glui32 snd, glui32 flag) {
    NSLog(@"Function glk_sound_load_hint not implemented");
}

#endif /* GLK_MODULE_SOUND */

#ifdef GLK_MODULE_HYPERLINKS

void glk_set_hyperlink(glui32 linkval) {
    NSLog(@"Function glk_set_hyperlink not implemented");
}

void glk_set_hyperlink_stream(strid_t str, glui32 linkval) {
    NSLog(@"Function glk_hyperlink_stream not implemented");
}

void glk_request_hyperlink_event(winid_t win) {
    NSLog(@"Function glk_request_hyperlink_event not implemented");
}

void glk_cancel_hyperlink_event(winid_t win) {
    NSLog(@"Function glk_cancel_hyperlink_event not implemented");
}


#endif /* GLK_MODULE_HYPERLINKS */
