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

#include "tools/skottie_ios_app/SkiaContext.h"

#include "include/core/SkSurface.h"
#include "include/gpu/ganesh/GrDirectContext.h"
#include "include/gpu/ganesh/mtl/GrMtlBackendContext.h"
#include "include/gpu/ganesh/mtl/GrMtlDirectContext.h"
#include "tools/skottie_ios_app/SkMetalViewBridge.h"

#import <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#import <UIKit/UIKit.h>

// A UIView that uses a Metal-backed SkSurface to draw.
@interface SkiaMtkView : MTKView
    @property (strong) SkiaViewController* controller;

    // Override of the MTKView interface.  Uses Skia+Metal to draw.
    - (void)drawRect:(CGRect)rect;

    // Required initializer.
    - (instancetype)initWithFrame:(CGRect)frameRect
                    device:(id<MTLDevice>)device
                    queue:(id<MTLCommandQueue>)queue
                    grDevice:(GrDirectContext*)dContext;
@end

@implementation SkiaMtkView {
    id<MTLCommandQueue> fQueue;
    GrDirectContext*    fDContext;
}

- (instancetype)initWithFrame:(CGRect)frameRect
                device:(id<MTLDevice>)mtlDevice
                queue:(id<MTLCommandQueue>)queue
                grDevice:(GrDirectContext*)dContext {
    self = [super initWithFrame:frameRect device:mtlDevice];
    fQueue = queue;
    fDContext = dContext;
    SkMtkViewConfigForSkia(self);
    return self;
}

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    // TODO(halcanary): Use the rect and the InvalidationController to speed up rendering.
    SkiaViewController* viewController = [self controller];
    if (!viewController || ![[self currentDrawable] texture] || !fDContext) {
        return;
    }
    CGSize size = [self drawableSize];
    sk_sp<SkSurface> surface = SkMtkViewToSurface(self, fDContext);
    if (!surface) {
        NSLog(@"error: no sksurface");
        return;
    }
    [viewController draw:rect toCanvas:surface->getCanvas() atSize:size];
    fDContext->flushAndSubmit(surface.get());
    surface = nullptr;

    id<MTLCommandBuffer> commandBuffer = [fQueue commandBuffer];
    [commandBuffer presentDrawable:[self currentDrawable]];
    [commandBuffer commit];

    bool paused = [viewController isPaused];
    [self setEnableSetNeedsDisplay:paused];
    [self setPaused:paused];
}
@end

@interface SkiaMetalContext : SkiaContext
    @property (strong) id<MTLDevice> metalDevice;
    @property (strong) id<MTLCommandQueue> metalQueue;
    - (instancetype) init;
    - (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame;
    - (SkiaViewController*) getViewController:(UIView*)view;
@end

@implementation SkiaMetalContext {
    sk_sp<GrDirectContext> fDContext;
}

- (instancetype) init {
    self = [super init];
    [self setMetalDevice:MTLCreateSystemDefaultDevice()];
    if(![self metalDevice]) {
        NSLog(@"Metal is not supported on this device");
        return nil;
    }
    [self setMetalQueue:[[self metalDevice] newCommandQueue]];
    GrMtlBackendContext backendContext = {};
    backendContext.fDevice.reset((__bridge void*)[self metalDevice]);
    backendContext.fQueue.reset((__bridge void*)[self metalQueue]);
    fDContext = GrDirectContexts::MakeMetal(backendContext, GrContextOptions());

    if (!fDContext) {
        NSLog(@"GrDirectContexts::MakeMetal failed");
        return nil;
    }
    return self;
}

- (UIView*) makeViewWithController:(SkiaViewController*)vc withFrame:(CGRect)frame {
    SkiaMtkView* skiaView = [[SkiaMtkView alloc] initWithFrame:frame
                                                 device:[self metalDevice]
                                                 queue:[self metalQueue]
                                                 grDevice:fDContext.get()];
    [skiaView setPreferredFramesPerSecond:30];
    [skiaView setController:vc];
    return skiaView;
}

- (SkiaViewController*) getViewController:(UIView*)view {
    return [view isKindOfClass:[SkiaMtkView class]] ? [(SkiaMtkView*)view controller] : nil;
}
@end

SkiaContext* MakeSkiaMetalContext() { return [[SkiaMetalContext alloc] init]; }
