#include <application.h>
#include <screen.h>
#include <window.h>
#import <AppKit/NSApplication.h>
#import <Foundation/NSGeometry.h>
#import <Foundation/NSBundle.h>
#import <ApplicationServices/ApplicationServices.h>
#import <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h> 
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <sys/time.h>


#if __BIG_ENDIAN__
#define CLIENT_IMAGE_FORMAT GL_UNSIGNED_INT_8_8_8_8_REV
#else
#define CLIENT_IMAGE_FORMAT GL_UNSIGNED_INT_8_8_8_8
#endif
#define BUFFER_WIDTH   512
#define BUFFER_HEIGHT  512
#define ROW_BYTES BUFFER_WIDTH * 4
unsigned char textureBuffer[ROW_BYTES * BUFFER_HEIGHT];


static void drawAnObject()
{
    glColor3f(1.0f, 0.85f, 0.35f);
    glBegin(GL_TRIANGLES);
    {
        glVertex3f(  0.0,  0.6, 0.0);
        glVertex3f( -0.2, -0.3, 0.0);
        glVertex3f(  0.2, -0.3 ,0.0);
    }
    glEnd();
}


@interface MyOpenGLView : NSOpenGLView
    -(void) paint;
@end


@implementation MyOpenGLView
    -(void) paint
    {
/*
        printf("paint\n");
        glClearColor(0, 0, 0, 0);
        glClear(GL_COLOR_BUFFER_BIT);
        //glDrawPixels(rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
        glDrawPixels(BUFFER_WIDTH, BUFFER_HEIGHT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, textureBuffer);
        //drawAnObject();
        glFlush();
*/
    }
    -(void) drawRect:(NSRect)bounds
    {
        printf("draw rect\n");
//        [self paint];
        [[self openGLContext] flushBuffer];
    }
    -(bool) acceptsFirstResponder { return YES; }
    -(bool) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication { return YES; }
    -(void) mouseDown:(NSEvent *)theEvent { printf("mouse down\n"); }
    -(void) mouseUp:(NSEvent *)theEvent { printf("mouse up\n"); }
    -(void) mouseDragged:(NSEvent *)theEvent { printf("mouse dragged\n"); }
    -(void) keyDown:(NSEvent *)theEvent { printf("key down\n"); }
    -(void) keyUp:(NSEvent *)theEvent { printf("key up\n"); }
@end


MyOpenGLView *myGlView;





#define USE_GL
//#define CAPTURE_WHOLE_DISPLAY // Warning, this is dangerous and might require a reboot to get control back        
//#define BUFFERED_DIRECT_DISPLAY





class MacScreen : public Screen
{
public:
    MacScreen(int _x, int _y, int wi, int ht) {
        if ( !app ) {
            printf("Please create an Application object first before creating a Screen\n");
            return;
        }

        x = _x;
        y = _y;
        _y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - y - ht;
        NSRect rect = { { x, _y }, { wi, ht } };
        window = [[NSWindow alloc]
            initWithContentRect : rect
            styleMask : 0x00F
            backing : 0//(NSBackingStoreType)NSBackingStoreBuffered
            defer : NO ];
        //[window setLevel:CGShieldingWindowLevel()];
        myGlView = [[MyOpenGLView alloc] init];
        [window setContentView:myGlView];
        [window display];
        [window makeKeyAndOrderFront:nil];
        printf("Window created\n");

/*
        [gc makeCurrentContext];
        NSOpenGLContext *gl_context = new NSOpenGLContext;
        CGLContextObj ctx;
        ctx = [ gl_context cglContext ];
        img = new Image(BUFFER_WIDTH, BUFFER_HEIGHT, textureBuffer, Format_RGBA_8888, BUFFER_WIDTH*4);
*/
#if defined(USE_GL)
        img = new Image(wi, ht, Format_RGBA_8888);
#elif defined(BUFFERED_DIRECT_DISPLAY)
        img = new Image(wi, ht, Format_RGBA_8888);
#elif defined(CAPTURE_WHOLE_DISPLAY) // Warning, this is dangerous and might require a reboot to get control back        
/*
        GDHandle deviceList = GetDeviceList(); 
        unsigned char *fb = (unsigned char *)(*(*deviceList)->gdPMap)->baseAddr; 
        img = new Image(wi, ht, fb, Format_RGBA_8888, (*(*deviceList)->gdPMap)->rowBytes);
*/
        CGDirectDisplayID display = kCGDirectMainDisplay;
        CGDisplayCaptureWithOptions(display, kCGCaptureNoFill);
        unsigned char *fb = (unsigned char *)CGDisplayAddressForPosition(display, x, y);
        img = new Image(wi, ht, fb, Format_RGBA_8888, CGDisplayBytesPerRow(display));
        //CGDisplayRelease(kCGDirectMainDisplay);
#else
        img = new Image(wi, ht, Format_RGBA_8888);
#endif
    }
    ~MacScreen() {
        printf("Destruct\n");
#if defined(USE_GL)
        // nothing
#elif defined(BUFFERED_DIRECT_DISPLAY)
        // nothing
#elif defined(CAPTURE_WHOLE_DISPLAY)
        CGDisplayRelease(kCGDirectMainDisplay);
#else
        // nothing
#endif
    }
    NSWindow *window;
    void flush() {
#if defined(USE_GL)
        /*
        [myGlView setNeedsDisplay:YES];
        [myGlView paint];
        */

        [[myGlView openGLContext] makeCurrentContext];
        [[myGlView openGLContext] update];
        [myGlView setNeedsDisplay:YES];

        glClear(GL_COLOR_BUFFER_BIT);


//        glClearColor(0, 0, 0, 0);
//        glClear(GL_COLOR_BUFFER_BIT);
        glDrawPixels(img->width(), img->height(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, img->bits());

        //NSGraphicsContext *gfx = [NSGraphicsContext graphicsContextWithWindow:window];
        //[gfx flushGraphics];
        //[window flushWindow];

        [[myGlView openGLContext] flushBuffer];

        glutSwapBuffers();

        //glFlush(); // Appears to cause delay, but might just be waiting for vertical retrace

        // Flush only if haven't flushed in the last 25ms
        static bool first = true;
        static struct timeval lastTime;
        struct timeval time;
        gettimeofday(&time, 0);
        if ( first ) {
            lastTime = time;
            first = false;
//            glFinish();
            //glFlush();
        } else {
            int diff = time.tv_sec - lastTime.tv_sec;
            diff *= 1000000;
            diff += time.tv_usec - lastTime.tv_usec;
            if ( diff > 20000 ) {
//                glFinish();
                //glFlush();
                lastTime = time;
            }
        }


#elif defined(BUFFERED_DIRECT_DISPLAY)
        CGDirectDisplayID display = kCGDirectMainDisplay;
        CGDisplayCaptureWithOptions(display, kCGCaptureNoFill);
        unsigned char *fb = (unsigned char *)CGDisplayAddressForPosition(display, 0, 0);
        Image disp(img->width(), img->height(), fb, Format_RGBA_8888, CGDisplayBytesPerRow(display));
        blit(&disp, 0, 0, img, 0, 0, img->width(), img->height());
        CGDisplayRelease(display); // Appears to cause repaint of everything
#elif defined(CAPTURE_WHOLE_DISPLAY)
        // nothing
#else
        NSGraphicsContext *gc;
        CGContextRef context;
        CGColorSpaceRef colorSpace;
        CGBitmapInfo bitmapInfo;
        CGContextRef bitmapContext;
        CGImageRef image;
        CGRect imageRect;
        gc = [NSGraphicsContext graphicsContextWithWindow:window];
        context = (CGContextRef)[gc graphicsPort];
        //context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
        colorSpace = CGColorSpaceCreateDeviceRGB();
        //bitmapInfo = kCGImageAlphaNoneSkipLast;
        bitmapInfo = kCGImageAlphaPremultipliedLast;
        bitmapContext = CGBitmapContextCreate(img->bits(), img->width(), img->height(), 8, img->width() * 4, colorSpace, bitmapInfo);
        image = CGBitmapContextCreateImage(bitmapContext);
        imageRect = CGRectMake(0, 0, img->width(), img->height());
        CGContextDrawImage(context, imageRect, image);
        [gc flushGraphics];
        CGContextSynchronize(context);
        CGContextFlush(context);
        CGContextRelease(context);
        CGContextRelease(bitmapContext);
        CGColorSpaceRelease(colorSpace);
        CFRelease(image);
        delete gc;
        //[[window contentView] setNeedsDisplay:YES];
        // [myGlView setNeedsDisplay:YES];
#endif
    }
    Image *frameBuffer() { return img; }
private:
    int x, y;
    Image *img;
};


Screen *getScreen()
{
    static Screen *s = 0;
    return s ? s : s = new MacScreen(0, 0, BUFFER_WIDTH, BUFFER_HEIGHT);
}


#ifndef MIN
#define MIN(a,b)    (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define MAX(a,b)    (((a)>(b))?(a):(b))
#endif


void blit(Image *dst, int dstX, int dstY, Image *src, int srcX, int srcY, int w, int h)
{
    bool flush = true; // XXX should check dst is the screen

    w = MIN(MIN(w, src->width()  - srcX), dst->width()  - dstX);
    h = MIN(MIN(h, src->height() - srcY), dst->height() - dstY);
    unsigned char *rowDst = dst->bits() + dstX*4 + dstY*dst->step();
    unsigned char *rowSrc = src->bits() + srcX*4 + srcY*src->step();
    unsigned int *dstPix;
    unsigned int *srcPix;
    int i, j = h;
    while (j--) {
        dstPix = (unsigned int *)rowDst;
        srcPix = (unsigned int *)rowSrc;

//        memcpy(dstPix, srcPix, w*4);

        size_t len = w;
        while ((size_t)dstPix & 63 && len) {
            *dstPix++ = *srcPix++;
            len--;
        }
        size_t align_count = ((size_t)dstPix & 63) >> 2;
        int switch_val = (len > align_count) ? align_count : len;
        switch (switch_val)
        {
            case 15: *dstPix++ = *srcPix++;
            case 14: *dstPix++ = *srcPix++;
            case 13: *dstPix++ = *srcPix++;
            case 12: *dstPix++ = *srcPix++;
            case 11: *dstPix++ = *srcPix++;
            case 10: *dstPix++ = *srcPix++;
            case  9: *dstPix++ = *srcPix++;
            case  8: *dstPix++ = *srcPix++;
            case  7: *dstPix++ = *srcPix++;
            case  6: *dstPix++ = *srcPix++;
            case  5: *dstPix++ = *srcPix++;
            case  4: *dstPix++ = *srcPix++;
            case  3: *dstPix++ = *srcPix++;
            case  2: *dstPix++ = *srcPix++;
            case  1: *dstPix++ = *srcPix++;
            case  0: break;
        }
        len -= switch_val;
        unsigned long long *dstPix64 = (unsigned long long *)dstPix;
        unsigned long long *srcPix64 = (unsigned long long *)srcPix;
        while (len >= 8) {
            dstPix64[0] = srcPix64[0];
            dstPix64[1] = srcPix64[1];
            dstPix64[2] = srcPix64[2];
            dstPix64[3] = srcPix64[3];
            //dstPix64[4] = srcPix64[4];
            //dstPix64[5] = srcPix64[5];
            //dstPix64[6] = srcPix64[6];
            //dstPix64[7] = srcPix64[7];
            dstPix64 += 4;
            srcPix64 += 4;
            len -= 8;
        }
        dstPix = (unsigned int *)dstPix64;
        srcPix = (unsigned int *)srcPix64;
        while (len) {
            *dstPix++ = *srcPix++;
            len--;
        }


/*
        int n = (len + 7) / 8;
        // Duff's device
        switch (len & 0x07)
        {
            case 0: do { *dstPix++ = *srcPix++;
            case 7:      *dstPix++ = *srcPix++;
            case 6:      *dstPix++ = *srcPix++;
            case 5:      *dstPix++ = *srcPix++;
            case 4:      *dstPix++ = *srcPix++;
            case 3:      *dstPix++ = *srcPix++;
            case 2:      *dstPix++ = *srcPix++;
            case 1:      *dstPix++ = *srcPix++;
                    } while (--n > 0);
        }
*/

/*
        int n = (w + 7) / 8;
        // Duff's device
        switch (w & 0x07)
        {
            case 0: do { *dstPix++ = *srcPix++;
            case 7:      *dstPix++ = *srcPix++;
            case 6:      *dstPix++ = *srcPix++;
            case 5:      *dstPix++ = *srcPix++;
            case 4:      *dstPix++ = *srcPix++;
            case 3:      *dstPix++ = *srcPix++;
            case 2:      *dstPix++ = *srcPix++;
            case 1:      *dstPix++ = *srcPix++;
                    } while (--n > 0);
        }
*/

        rowDst += dst->step();
        rowSrc += src->step();
    }

//    if ( flush )
//        getScreen()->flush();
}


