/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#import "SkSampleUIView.h"

//#define SKGL_CONFIG         kEAGLColorFormatRGB565
#define SKGL_CONFIG         kEAGLColorFormatRGBA8

#define FORCE_REDRAW

#include "SkCanvas.h"
#include "SkCGUtils.h"
#include "SkSurface.h"
#include "SampleApp.h"

#if SK_SUPPORT_GPU
//#define USE_GL_1
#define USE_GL_2

#include "gl/GrGLInterface.h"
#include "GrContext.h"
#include "SkGpuDevice.h"
#endif

class SkiOSDeviceManager : public SampleWindow::DeviceManager {
public:
    SkiOSDeviceManager(GLint layerFBO) {
#if SK_SUPPORT_GPU
        fCurContext = NULL;
        fCurIntf = NULL;
        fCurRenderTarget = NULL;
        fMSAASampleCount = 0;
        fLayerFBO = layerFBO;
#endif
        fBackend = SkOSWindow::kNone_BackEndType;
    }
    
    virtual ~SkiOSDeviceManager() {
#if SK_SUPPORT_GPU
        SkSafeUnref(fCurContext);
        SkSafeUnref(fCurIntf);
        SkSafeUnref(fCurRenderTarget);
#endif
    }
    
    void setUpBackend(SampleWindow* win, int msaaSampleCount) override {
        SkASSERT(SkOSWindow::kNone_BackEndType == fBackend);
        
        fBackend = SkOSWindow::kNone_BackEndType;
        
#if SK_SUPPORT_GPU
        switch (win->getDeviceType()) {
            // these two don't use GL
            case SampleWindow::kRaster_DeviceType:
            case SampleWindow::kPicture_DeviceType:
                break;
            // these guys use the native backend
            case SampleWindow::kGPU_DeviceType:
                fBackend = SkOSWindow::kNativeGL_BackEndType;
                break;
            default:
                SkASSERT(false);
                break;
        }
        SkOSWindow::AttachmentInfo info;
        bool result = win->attach(fBackend, msaaSampleCount, &info);
        if (!result) {
            SkDebugf("Failed to initialize GL");
            return;
        }
        fMSAASampleCount = msaaSampleCount;
        
        SkASSERT(NULL == fCurIntf);
        switch (win->getDeviceType()) {
            // these two don't use GL
            case SampleWindow::kRaster_DeviceType:
            case SampleWindow::kPicture_DeviceType:
                fCurIntf = NULL;
                break;
            case SampleWindow::kGPU_DeviceType:
                fCurIntf = GrGLCreateNativeInterface();
                break;
            default:
                SkASSERT(false);
                break;
        }
        
        SkASSERT(NULL == fCurContext);
        if (SkOSWindow::kNone_BackEndType != fBackend) {
            fCurContext = GrContext::Create(kOpenGL_GrBackend,
                                            (GrBackendContext) fCurIntf);
        }
        
        if ((NULL == fCurContext || NULL == fCurIntf) &&
            SkOSWindow::kNone_BackEndType != fBackend) {
            // We need some context and interface to see results if we're using a GL backend
            SkSafeUnref(fCurContext);
            SkSafeUnref(fCurIntf);
            SkDebugf("Failed to setup 3D");
            win->detach();
        }
#endif // SK_SUPPORT_GPU
        // call windowSizeChanged to create the render target
        this->windowSizeChanged(win);
    }
    
    void tearDownBackend(SampleWindow *win) override {
#if SK_SUPPORT_GPU
        SkSafeUnref(fCurContext);
        fCurContext = NULL;
        
        SkSafeUnref(fCurIntf);
        fCurIntf = NULL;
        
        SkSafeUnref(fCurRenderTarget);
        fCurRenderTarget = NULL;
#endif
        win->detach();
        fBackend = SampleWindow::kNone_BackEndType;
    }

    SkSurface* createSurface(SampleWindow::DeviceType dType, SampleWindow* win) override{
#if SK_SUPPORT_GPU
        if (SampleWindow::IsGpuDeviceType(dType) && fCurContext) {
            SkSurfaceProps props(win->getSurfaceProps());
            return SkSurface::NewRenderTargetDirect(fCurRenderTarget, &props);
        }
#endif
        return NULL;
    }

    virtual void publishCanvas(SampleWindow::DeviceType dType,
                               SkCanvas* canvas,
                               SampleWindow* win) override {
#if SK_SUPPORT_GPU
        if (NULL != fCurContext) {
            fCurContext->flush();
        }
#endif
        win->present();
    }
    
    void windowSizeChanged(SampleWindow* win) override {
#if SK_SUPPORT_GPU
        if (NULL != fCurContext) {
            SkOSWindow::AttachmentInfo info;

            win->attach(fBackend, fMSAASampleCount, &info);
            
            glBindFramebuffer(GL_FRAMEBUFFER, fLayerFBO);
            GrBackendRenderTargetDesc desc;
            desc.fWidth = SkScalarRoundToInt(win->width());
            desc.fHeight = SkScalarRoundToInt(win->height());
            desc.fConfig = kSkia8888_GrPixelConfig;
            desc.fRenderTargetHandle = fLayerFBO;
            desc.fSampleCnt = info.fSampleCount;
            desc.fStencilBits = info.fStencilBits;

            SkSafeUnref(fCurRenderTarget);
            fCurRenderTarget = fCurContext->textureProvider()->wrapBackendRenderTarget(desc);
        }
#endif
    }
    
    GrContext* getGrContext() override {
#if SK_SUPPORT_GPU
        return fCurContext;
#else
        return NULL;
#endif
    }
    
    GrRenderTarget* getGrRenderTarget() override {
#if SK_SUPPORT_GPU
        return fCurRenderTarget;
#else
        return NULL;
#endif
    }
    
    bool isUsingGL() const { return SkOSWindow::kNone_BackEndType != fBackend; }
    
private:
   
#if SK_SUPPORT_GPU
    GrContext*              fCurContext;
    const GrGLInterface*    fCurIntf;
    GrRenderTarget*         fCurRenderTarget;
    int                     fMSAASampleCount;
    GLint                   fLayerFBO;
#endif
    
    SkOSWindow::SkBackEndTypes fBackend;
    
    typedef SampleWindow::DeviceManager INHERITED;
};

////////////////////////////////////////////////////////////////////////////////
@implementation SkSampleUIView

@synthesize fTitle, fRasterLayer, fGLLayer;

#include "SkApplication.h"
#include "SkEvent.h"
#include "SkWindow.h"

struct FPSState {
    static const int FRAME_COUNT = 60;
    
    CFTimeInterval fNow0, fNow1;
    CFTimeInterval fTime0, fTime1, fTotalTime;
    int fFrameCounter;
    SkString str;
    FPSState() {
        fTime0 = fTime1 = fTotalTime = 0;
        fFrameCounter = 0;
    }
    
    void startDraw() {
        fNow0 = CACurrentMediaTime();
    }
    
    void endDraw() {
        fNow1 = CACurrentMediaTime();
    }
    
    void flush(SkOSWindow* hwnd) {
        CFTimeInterval now2 = CACurrentMediaTime();
        
        fTime0 += fNow1 - fNow0;
        fTime1 += now2 - fNow1;
        
        if (++fFrameCounter == FRAME_COUNT) {
            CFTimeInterval totalNow = CACurrentMediaTime();
            fTotalTime = totalNow - fTotalTime;
            
            //SkMSec ms0 = (int)(1000 * fTime0 / FRAME_COUNT);
            //SkMSec msTotal = (int)(1000 * fTotalTime / FRAME_COUNT);
            //str.printf(" ms: %d [%d], fps: %3.1f", msTotal, ms0,
            //           FRAME_COUNT / fTotalTime);
            str.printf(" fps:%3.1f", FRAME_COUNT / fTotalTime);
            hwnd->setTitle(NULL);
            fTotalTime = totalNow;
            fTime0 = fTime1 = 0;
            fFrameCounter = 0;
        }
    }
};

static FPSState gFPS;

#define FPS_StartDraw() gFPS.startDraw()
#define FPS_EndDraw()   gFPS.endDraw()
#define FPS_Flush(wind) gFPS.flush(wind)

///////////////////////////////////////////////////////////////////////////////

- (id)initWithDefaults {
    if (self = [super initWithDefaults]) {
        fRedrawRequestPending = false;
        fFPSState = new FPSState;
        
#ifdef USE_GL_1
        fGL.fContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
#else
        fGL.fContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
#endif
        
        if (!fGL.fContext || ![EAGLContext setCurrentContext:fGL.fContext])
        {
            [self release];
            return nil;
        }
        
        // Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer
        glGenFramebuffers(1, &fGL.fFramebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, fGL.fFramebuffer);
        
        glGenRenderbuffers(1, &fGL.fRenderbuffer);
        glGenRenderbuffers(1, &fGL.fStencilbuffer);
        
        glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fGL.fRenderbuffer);
        
        glBindRenderbuffer(GL_RENDERBUFFER, fGL.fStencilbuffer);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fGL.fStencilbuffer);
        
        self.fGLLayer = [CAEAGLLayer layer];
        fGLLayer.bounds = self.bounds;
        fGLLayer.anchorPoint = CGPointMake(0, 0);
        fGLLayer.opaque = TRUE;
        [self.layer addSublayer:fGLLayer];
        fGLLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                       [NSNumber numberWithBool:NO],
                                       kEAGLDrawablePropertyRetainedBacking,
                                       SKGL_CONFIG,
                                       kEAGLDrawablePropertyColorFormat,
                                       nil];
        
        self.fRasterLayer = [CALayer layer];
        fRasterLayer.anchorPoint = CGPointMake(0, 0);
        fRasterLayer.opaque = TRUE;
        [self.layer addSublayer:fRasterLayer];
        
        NSMutableDictionary *newActions = [[NSMutableDictionary alloc] initWithObjectsAndKeys:[NSNull null], @"onOrderIn",
                                           [NSNull null], @"onOrderOut",
                                           [NSNull null], @"sublayers",
                                           [NSNull null], @"contents",
                                           [NSNull null], @"bounds",
                                           nil];
        fGLLayer.actions = newActions;
        fRasterLayer.actions = newActions;
        [newActions release];
        
        // rebuild argc and argv from process info
        NSArray* arguments = [[NSProcessInfo processInfo] arguments];
        int argc = [arguments count];
        char** argv = new char*[argc];
        for (int i = 0; i < argc; ++i) {
            NSString* arg = [arguments objectAtIndex:i];
            int strlen = [arg lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
            argv[i] = new char[strlen+1];
            [arg getCString:argv[i] maxLength:strlen+1 encoding:NSUTF8StringEncoding];
        }
        
        fDevManager = new SkiOSDeviceManager(fGL.fFramebuffer);
        fWind = new SampleWindow(self, argc, argv, fDevManager);

        fWind->resize(self.frame.size.width, self.frame.size.height,
                      kN32_SkColorType);
        
        for (int i = 0; i < argc; ++i) {
            delete [] argv[i];
        }
        delete [] argv;
    }
    return self;
}

- (void)dealloc {
    delete fDevManager;
    delete fFPSState;
    self.fRasterLayer = nil;
    self.fGLLayer = nil;
    [fGL.fContext release];
    [super dealloc];
}

- (void)layoutSubviews {
    int W, H;
    
    // Allocate color buffer backing based on the current layer size
    glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer);
    [fGL.fContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:fGLLayer];
    
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &fGL.fWidth);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &fGL.fHeight);
    
    glBindRenderbuffer(GL_RENDERBUFFER, fGL.fStencilbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, fGL.fWidth, fGL.fHeight);
    
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
    }
    
    if (fDevManager->isUsingGL()) {
        W = fGL.fWidth;
        H = fGL.fHeight;
        CGRect rect = CGRectMake(0, 0, W, H);
        fGLLayer.bounds = rect;
    }
    else {
        CGRect rect = self.bounds;
        W = (int)CGRectGetWidth(rect);
        H = (int)CGRectGetHeight(rect);
        fRasterLayer.bounds = rect;
    }
    
    printf("---- layoutSubviews %d %d\n", W, H);
    fWind->resize(W, H);
    fWind->inval(NULL);
}

///////////////////////////////////////////////////////////////////////////////

- (void)drawWithCanvas:(SkCanvas*)canvas {
    fRedrawRequestPending = false;
    fFPSState->startDraw();
    fWind->draw(canvas);
    fFPSState->endDraw();
#ifdef FORCE_REDRAW
    fWind->inval(NULL);
#endif
    fFPSState->flush(fWind);
}

- (void)drawInGL {
    // This application only creates a single context which is already set current at this point.
    // This call is redundant, but needed if dealing with multiple contexts.
    [EAGLContext setCurrentContext:fGL.fContext];
    
    // This application only creates a single default framebuffer which is already bound at this point.
    // This call is redundant, but needed if dealing with multiple framebuffers.
    glBindFramebuffer(GL_FRAMEBUFFER, fGL.fFramebuffer);
    
    GLint scissorEnable;
    glGetIntegerv(GL_SCISSOR_TEST, &scissorEnable);
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0,0,0,0);
    glClear(GL_COLOR_BUFFER_BIT);
    if (scissorEnable) {
        glEnable(GL_SCISSOR_TEST);
    }
    glViewport(0, 0, fGL.fWidth, fGL.fHeight);
    
   
    SkAutoTUnref<SkSurface> surface(fWind->createSurface());
    SkCanvas* canvas = surface->getCanvas();

    // if we're not "retained", then we have to always redraw everything.
    // This call forces us to ignore the fDirtyRgn, and draw everywhere.
    // If we are "retained", we can skip this call (as the raster case does)
    fWind->forceInvalAll();

    [self drawWithCanvas:canvas];

    // This application only creates a single color renderbuffer which is already bound at this point.
    // This call is redundant, but needed if dealing with multiple renderbuffers.
    glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer);
    [fGL.fContext presentRenderbuffer:GL_RENDERBUFFER];
}

- (void)drawInRaster {
    SkAutoTUnref<SkSurface> surface(fWind->createSurface());
    SkCanvas* canvas = surface->getCanvas();
    [self drawWithCanvas:canvas];
    CGImageRef cgimage = SkCreateCGImageRef(fWind->getBitmap());
    fRasterLayer.contents = (id)cgimage;
    CGImageRelease(cgimage);
}

- (void)forceRedraw {
    if (fDevManager->isUsingGL())
        [self drawInGL];
    else 
        [self drawInRaster];
}

///////////////////////////////////////////////////////////////////////////////

- (void)setSkTitle:(const char *)title {
    NSString* text = [NSString stringWithUTF8String:title];
    if ([text length] > 0)
        self.fTitle = text;
    
    if (fTitleItem && fTitle) {
        fTitleItem.title = [NSString stringWithFormat:@"%@%@", fTitle, 
                            [NSString stringWithUTF8String:fFPSState->str.c_str()]];
    }
}

- (void)postInvalWithRect:(const SkIRect*)r {
    if (!fRedrawRequestPending) {
        fRedrawRequestPending = true;
        bool gl = fDevManager->isUsingGL();
        [CATransaction begin];
        [CATransaction setAnimationDuration:0];
        fRasterLayer.hidden = gl;
        fGLLayer.hidden = !gl;
        [CATransaction commit];
        if (gl) {
            [self performSelector:@selector(drawInGL) withObject:nil afterDelay:0];
        }
        else {
            [self performSelector:@selector(drawInRaster) withObject:nil afterDelay:0];
            [self setNeedsDisplay];
        }
    }
}

- (void)getAttachmentInfo:(SkOSWindow::AttachmentInfo*)info {
    glBindRenderbuffer(GL_RENDERBUFFER, fGL.fRenderbuffer);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER,
                                 GL_RENDERBUFFER_STENCIL_SIZE,
                                 &info->fStencilBits);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER,
                                 GL_RENDERBUFFER_SAMPLES_APPLE,
                                 &info->fSampleCount);
}

@end
