//
//  KMSnapshotWindowController.swift
//  PDF Reader Pro
//
//  Created by tangchao on 2023/12/12.
//

import Cocoa
 
@objc protocol KMSnapshotWindowControllerDelegate: NSObjectProtocol {
    @objc optional func snapshotControllerDidFinishSetup(_ controller: KMSnapshotWindowController)
    @objc optional func snapshotControllerWillClose(_ controller: KMSnapshotWindowController)
    @objc optional func snapshotControllerDidChange(_ controller: KMSnapshotWindowController)
    @objc optional func snapshotController(_ controller: KMSnapshotWindowController, miniaturizedRect isMiniaturize: Bool) -> NSRect
}

class KMSnapshotWindowController: KMBaseWindowController {
    
    @IBOutlet var pdfView: KMSnapshotPDFView!
    
    var hasWindow = false
    var forceOnTop = false
    
    weak var delegate: KMSnapshotWindowControllerDelegate?
    
    var string: String = ""
    
    var pageLabel: String = "" {
        didSet {
            self.synchronizeWindowTitleWithDocumentName()
        }
    }
    
    var animating = false
    var thumbnail: NSImage?
    var windowImage: NSImage?
    
    var EM_DASH_CHARACTER = 0x2014
 
    private let SMALL_DELAY =  0.1
    private let RESIZE_TIME_FACTOR = 0.6
    
    private let PAGE_KEY            = "page"
    private let RECT_KEY            = "rect"
    private let SCALEFACTOR_KEY     = "scaleFactor"
    private let AUTOFITS_KEY        = "autoFits"
    private let WINDOWFRAME_KEY     = "windowFrame"
    private let HASWINDOW_KEY       = "hasWindow"
    private let PAGELABEL_KEY       = "pageLabel"
    private let PAGEANDWINDOW_KEY   = "pageAndWindow"

    private let SKSnapshotWindowFrameAutosaveName = "SKSnapshotWindow"
    private let SKSnapshotViewChangedNotification = "SKSnapshotViewChangedNotification"
    
    private let SKSnapshotPageCellLabelKey = "label"
    private let SKSnapshotPageCellHasWindowKey = "hasWindow"
    
    deinit {
        KMPrint("KMSnapshotWindowController deinit.")
        
        NotificationCenter.default.removeObserver(self)
    }
    
    override var windowNibName: NSNib.Name? {
        return "SnapshotWindow"
    }

    override func windowDidLoad() {
        super.windowDidLoad()
    
        if NSWindow.instancesRespond(to: NSSelectorFromString("setTabbingMode:")) {
            self.window?.tabbingMode = .disallowed
        }
        if NSWindow.instancesRespond(to: NSSelectorFromString("toggleFullScreen:")) {
            // [[self window] setCollectionBehavior:[[self window] collectionBehavior] | NSWindowCollectionBehaviorFullScreenAuxiliary];
            self.window?.collectionBehavior.insert(.fullScreenAuxiliary)
        }
        if let data = self.window?.responds(to: NSSelectorFromString("contentLayoutRect")), data {
//            [[self window] setStyleMask:[[self window] styleMask] | NSFullSizeContentViewWindowMask];
            self.window?.styleMask.insert(.fullSizeContentView)
            self.pdfView.frame = self.window?.contentLayoutRect ?? NSMakeRect(0, 0, 400, 400)
        }
        self._updateWindowLevel()
//        [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeys:[NSArray arrayWithObjects:SKSnapshotsOnTopKey, SKShouldAntiAliasKey, SKGreekingThresholdKey, SKBackgroundColorKey, SKPageBackgroundColorKey, nil] context:&SKSnaphotWindowDefaultsObservationContext];
        // the window is initialially exposed. The windowDidExpose notification is useless, it has nothing to do with showing the window
        self.hasWindow = true
    }
    
    override func initNotification() {
        super.initNotification()
        
        NotificationCenter.default.addObserver(self, selector: #selector(preferenceInfoDidChange), name: KMPreferenceManager.didChangeNotification, object: nil)
    }
    
    override func windowTitle(forDocumentDisplayName displayName: String) -> String {
        return String(format: "%@ %C %@", displayName, EM_DASH_CHARACTER, String(format: KMLocalizedString("Page %@"), self.pageLabel))
    }
    
    func setNeedsDisplay(in rect: NSRect, of page: CPDFPage?) {
        var aRect = self.pdfView.convert(rect, from: page)
        let scale = self.pdfView.scaleFactor
        let maxX = ceil(NSMaxX(aRect) + scale)
        let maxY = ceil(NSMaxY(aRect) + scale)
        let minX = floor(NSMinX(aRect) - scale)
        let minY = floor(NSMinY(aRect) - scale)

        aRect = NSIntersectionRect(self.pdfView.bounds, NSMakeRect(minX, minY, maxX - minX, maxY - minY))
        if (NSIsEmptyRect(aRect) == false) {
            //            [pdfView setNeedsDisplayInRect:aRect];
            self.pdfView.setNeedsDisplayIn(aRect, of: page)
        }
    }
    
    func setNeedsDisplay(for annotation: CPDFAnnotation?, on page: CPDFPage?) {
        if let anno = annotation {
            self.setNeedsDisplay(in: anno.displayRect(), of: page)
        }
    }
    
    func redisplay() {
//        [pdfView requiresDisplay];
        self.pdfView.needsDisplay = true
    }

    @objc func handlePageChangedNotification(_ notification: NSNotification?) {
//         [self setPageLabel:[[pdfView currentPage] displayLabel]];
        let label = "\(self.pdfView.currentPage().pageIndex()+1)"
        self.pageLabel = label
        self.handlePDFViewFrameChangedNotification(nil)
     }
    
    @objc func handleDocumentDidUnlockNotification(_ notification: NSNotification) {
        let label = "\(self.pdfView.currentPage().pageIndex()+1)"
        self.pageLabel = label
        self.handlePDFViewFrameChangedNotification(nil)
    }
    
    @objc func handlePDFViewFrameChangedNotification(_ notification: NSNotification?) {
        if let _ = self.delegate?.snapshotControllerDidChange?(self) {
            let note = NSNotification(name: NSNotification.Name(rawValue: SKSnapshotViewChangedNotification), object: self)
            NotificationQueue.default.enqueue(note as Notification, postingStyle: .whenIdle, coalesceMask: .onName, forModes: nil)
        }
    }
    
    @objc func handleViewChangedNotification(_ notification: NSNotification) {
        self.updateString()
        self.delegate?.snapshotControllerDidChange?(self)
    }
    
    @objc func handleDidAddRemoveAnnotationNotification(_ notification: NSNotification) {
        guard let annotation = notification.userInfo?["annotation"] as? CPDFAnnotation else {
            return
        }
        guard let page = notification.userInfo?["page"] as? CPDFPage else {
            return
        }
        if let data = page.document?.isEqual(to: self.pdfView.document), data && self.isPageVisible(page) {
            self.setNeedsDisplay(for: annotation, on: page)
        }
    }
    
    @objc func notifiyDidFinishSetup() {
        self.delegate?.snapshotControllerDidFinishSetup?(self)
    }
/*

     - (void)handleDidMoveAnnotationNotification:(NSNotification *)notification {
         PDFAnnotation *annotation = [[notification userInfo] objectForKey:SKPDFViewAnnotationKey];
         PDFPage *oldPage = [[notification userInfo] objectForKey:SKPDFViewOldPageKey];
         PDFPage *newPage = [[notification userInfo] objectForKey:SKPDFViewNewPageKey];
         if ([[newPage document] isEqual:[pdfView document]]) {
             if ([self isPageVisible:oldPage])
                 [self setNeedsDisplayForAnnotation:annotation onPage:oldPage];
             if ([self isPageVisible:newPage])
                 [self setNeedsDisplayForAnnotation:annotation onPage:newPage];
         }
     }

     #pragma mark Acessors

     - (NSRect)bounds {
         NSView *clipView = [[[pdfView documentView] enclosingScrollView] contentView];
         return [pdfView convertRect:[pdfView convertRect:[clipView bounds] fromView:clipView] toPage:[pdfView currentPage]];
     }
*/
    
    func setPdfDocument(_ pdfDocument: CPDFDocument, setup: NSDictionary?) {
        let number = (setup?.object(forKey: PAGE_KEY) as? NSNumber)?.intValue ?? 0
        let rect = NSRectFromString((setup?.object(forKey: RECT_KEY) as? String) ?? "")
        let scaleFactor = (setup?.object(forKey: SCALEFACTOR_KEY) as? NSNumber)?.doubleValue ?? 0
        let autoFits = (setup?.object(forKey: AUTOFITS_KEY) as? NSNumber)?.boolValue ?? false
        
        self.setPdfDocument(pdfDocument, goToPageNumber: number, rect: rect, scaleFactor: scaleFactor, autoFits: autoFits)
        
        self.hasWindow = (setup?.object(forKey: HASWINDOW_KEY) as? NSNumber)?.boolValue ?? false
        let frame = NSRectFromString(setup?.object(forKey: WINDOWFRAME_KEY) as? String ?? "")
        if frame.isEmpty == false {
            self.window?.setFrame(frame, display: false)
        }
    }
    
    func isPageVisible(_ page: CPDFPage?) -> Bool {
        guard let doc = page?.document else {
            return false
        }
        let result = doc.isEqual(to: self.pdfView.document)
        if result == false {
            return result
        }
        return NSLocationInRange(Int(page!.pageIndex()), self.pdfView.displayedPageIndexRange())
    }
    func pageIndex() -> UInt {
        return self.pdfView.currentPage().pageIndex()
    }
    
    func pageAndWindow() -> NSDictionary {
        return [SKSnapshotPageCellLabelKey : self.pageLabel, SKSnapshotPageCellHasWindowKey : NSNumber(booleanLiteral: self.hasWindow)]
    }

    override func setForceOnTop(_ flag: Bool) {
        super.setForceOnTop(flag)
        
        self.forceOnTop = flag
        if let data = self.window?.isVisible, data {
            self._updateWindowLevel()
        }
    }
    
    // MARK: - Actions

    @IBAction func doGoToNextPage(_ sender: AnyObject?) {
        self.pdfView.goToNextPage(sender)
    }

    @IBAction func doGoToPreviousPage(_ sender: AnyObject?) {
        self.pdfView.goToPreviousPage(sender)
    }

    @IBAction func doGoToFirstPage(_ sender: AnyObject?) {
        self.pdfView.goToFirstPage(sender)
    }

    @IBAction func doGoToLastPage(_ sender: AnyObject?) {
        self.pdfView.goToLastPage(sender)
    }

    @IBAction func doGoBack(_ sender: AnyObject?) {
        self.pdfView.km_goBack(sender)
    }

    @IBAction func doGoForward(_ sender: AnyObject?) {
        self.pdfView.km_goForward(sender)
    }

    @IBAction func doZoomIn(_ sender: AnyObject?) {
        self.pdfView.zoomIn(sender)
    }

    @IBAction func doZoomOut(_ sender: AnyObject?) {
        self.pdfView.zoomOut(sender)
    }

//    @IBAction func doZoomToPhysicalSize(_ sender: AnyObject?) {
//        self.pdfv
//    }
//    - (IBAction):(id)sender {
//        [pdfView setPhysicalScaleFactor:1.0];
//    }

    @IBAction func doZoomToActualSize(_ sender: AnyObject?) {
        self.pdfView.scaleFactor = 1.0
    }

    @IBAction func toggleAutoScale(_ sender: AnyObject?) {
        self.pdfView.autoFits = !self.pdfView.autoFits
    }

    // MARK: - Thumbnails

    func thumbnailWithSize(_ size: CGFloat) -> NSImage? {
        let clipView = self.pdfView.documentView().contentView
        var bounds = self.pdfView.convert(clipView.bounds, from: clipView)
        if (bounds.equalTo(.zero) || bounds.isNull) {
             return nil
         }
        let imageRep = self.pdfView.bitmapImageRepForCachingDisplay(in: bounds)
        var transform: NSAffineTransform?
         var thumbnailSize = bounds.size
        var shadowBlurRadius: CGFloat = 0.0
        var shadowOffset: CGFloat = 0.0
        var image: NSImage?
        if let data = imageRep {
            self.pdfView.cacheDisplay(in: bounds, to: data)
        }
         
         bounds.origin = NSZeroPoint
         
         if (size > 0.0) {
             shadowBlurRadius = round(size / 32.0)
             shadowOffset = -ceil(shadowBlurRadius * 0.75)
             if (NSHeight(bounds) > NSWidth(bounds)) {
                 thumbnailSize = NSMakeSize(round((size - 2.0 * shadowBlurRadius) * NSWidth(bounds) / NSHeight(bounds) + 2.0 * shadowBlurRadius), size);
             } else {
                 thumbnailSize = NSMakeSize(size, round((size - 2.0 * shadowBlurRadius) * NSHeight(bounds) / NSWidth(bounds) + 2.0 * shadowBlurRadius));
             }
             transform = NSAffineTransform()
             transform?.translateX(by: shadowBlurRadius, yBy: shadowBlurRadius - shadowOffset)
             transform?.scaleX(by: (thumbnailSize.width - 2.0 * shadowBlurRadius) / NSWidth(bounds), yBy: (thumbnailSize.height - 2.0 * shadowBlurRadius) / NSHeight(bounds))
         }
         
        if (thumbnailSize.equalTo(.zero)) {
             return nil
         }
         image = NSImage(size: thumbnailSize)
         
        if (image!.size.equalTo(.zero)) {
             return nil
         }
        image?.lockFocus()
        NSGraphicsContext.current?.imageInterpolation = .high
        transform?.concat()
        NSGraphicsContext.saveGraphicsState()
//         [[PDFView defaultPageBackgroundColor] set];
        if (shadowBlurRadius > 0.0) {
            NSShadow.setShadowWith(.init(calibratedWhite: 0, alpha: 0.5), blurRadius: shadowBlurRadius, offset: NSMakeSize(shadowOffset, shadowOffset))
        }
        __NSRectFill(bounds)
        NSGraphicsContext.current?.imageInterpolation = .default
        NSGraphicsContext.restoreGraphicsState()
        imageRep?.draw(in: bounds)
        image?.unlockFocus()
         
         return image
     }

    func thumbnailAttachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 0)
    }
    
    func thumbnail512Attachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 512)
    }
    
    func thumbnail256Attachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 256)
    }
    
    func thumbnail128Attachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 128)
    }
    
    func thumbnail64Attachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 64)
    }
    
    func thumbnail32Attachment() -> NSAttributedString? {
        return self._thumbnailAttachment(size: 32)
    }
    
    // MARK: - Miniaturize / Deminiaturize

    func miniaturizedRectForDockingRect(_ dockRect: NSRect) -> NSRect {
        let clipView = self.pdfView.documentView().contentView
        let sourceRect = clipView.convert(clipView.bounds, to: nil)
        var targetRect: NSRect = .zero
        let windowSize = self.window?.frame.size ?? .zero
        let thumbSize = self.thumbnail?.size ?? .zero
        var thumbRatio: CGFloat = 0
        if thumbSize.width > 0 {
            thumbRatio = thumbSize.height / thumbSize.width
        }
        var dockRatio: CGFloat = 0
        if NSWidth(dockRect) > 0 {
            dockRatio = NSHeight(dockRect) / NSWidth(dockRect)
        }
        var scaleFactor: CGFloat = 0
         var shadowRadius = round(fmax(thumbSize.width, thumbSize.height) / 32.0)
         var shadowOffset = ceil(0.75 * shadowRadius)
         
         if (thumbRatio > dockRatio) {
             targetRect = NSInsetRect(dockRect, 0.5 * NSWidth(dockRect) * (1.0 - dockRatio / thumbRatio), 0.0)
             scaleFactor = NSHeight(targetRect) / thumbSize.height
         } else {
             targetRect = NSInsetRect(dockRect, 0.0, 0.5 * NSHeight(dockRect) * (1.0 - thumbRatio / dockRatio))
             scaleFactor = NSWidth(targetRect) / thumbSize.width
         }
         shadowRadius *= scaleFactor
         shadowOffset *= scaleFactor
         targetRect = NSOffsetRect(NSInsetRect(targetRect, shadowRadius, shadowRadius), 0.0, shadowOffset)
        if thumbRatio > dockRatio {
            if NSHeight(sourceRect) != 0 {
                scaleFactor = NSHeight(targetRect) / NSHeight(sourceRect)
            }
        } else {
            if NSWidth(sourceRect) != 0 {
                scaleFactor = NSWidth(targetRect) / NSWidth(sourceRect)
            }
        }
         
         return NSMakeRect(NSMinX(targetRect) - scaleFactor * NSMinX(sourceRect), NSMinY(targetRect) - scaleFactor * NSMinY(sourceRect), scaleFactor * windowSize.width, scaleFactor * windowSize.height);
     }

    func miniaturizeWindowFromRect(_ startRect: NSRect, toRect endRect: NSRect) {
        if (self.windowImage == nil) {
            self.windowImage = (self.window as? KMSnapshotWindow)?.windowImage
        }
         
        let miniaturizeWindow = KMAnimatedBorderlessWindow(contentRect: startRect)
        miniaturizeWindow.level = .floating
        miniaturizeWindow.backgroundImage = self.windowImage
        miniaturizeWindow.orderFront(nil)
        self.animating = true
         
        NSAnimationContext.runAnimationGroup { context in
            context.duration = RESIZE_TIME_FACTOR * miniaturizeWindow.animationResizeTime(endRect)
            miniaturizeWindow.animator().setFrame(endRect, display: true)
        } completionHandler: {
            if self.hasWindow {
                if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data {
                    self.window?.animationBehavior = .none
                }
                self.window?.orderFront(nil)
                self._updateWindowLevel()
                if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data {
                    self.window?.animationBehavior = .default
                }
            }
            miniaturizeWindow.orderOut(nil)
            self.animating = false
        }
     }
    
    func miniaturize() {
        if (self.animating) {
            return
        }
        if let dockRect = self.delegate?.snapshotController?(self, miniaturizedRect: true) {
            let startRect = self.window?.frame ?? .zero
            let endRect = self.miniaturizedRectForDockingRect(dockRect)
             
            self.miniaturizeWindowFromRect(startRect, toRect: endRect)
             
            if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data {
                self.window?.animationBehavior = .none
            }
         }
        self.window?.orderOut(nil)
        
        if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data {
            self.window?.animationBehavior = .default
        }
        self.hasWindow = false
     }
    
    func deminiaturize() {
        if (self.animating) {
            return
        }
        if let dockRect = self.delegate?.snapshotController?(self, miniaturizedRect: false) {
            let endRect = self.window?.frame ?? .zero
            let startRect = self.miniaturizedRectForDockingRect(dockRect)
            
            self.miniaturizeWindowFromRect(startRect, toRect: endRect)
        } else {
            self.showWindow(self)
        }
        self.hasWindow = true
    }
/*
     #pragma mark KVO

     - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
         if (context == &SKSnaphotWindowDefaultsObservationContext) {
             NSString *key = [keyPath substringFromIndex:7];
             if ([key isEqualToString:SKSnapshotsOnTopKey]) {
                 if ([[self window] isVisible])
                     [self updateWindowLevel];
             } else if ([key isEqualToString:SKShouldAntiAliasKey]) {
                 [pdfView setShouldAntiAlias:[[NSUserDefaults standardUserDefaults] boolForKey:SKShouldAntiAliasKey]];
                 [pdfView applyDefaultInterpolationQuality];
             } else if ([key isEqualToString:SKGreekingThresholdKey]) {
                 [pdfView setGreekingThreshold:[[NSUserDefaults standardUserDefaults] floatForKey:SKGreekingThresholdKey]];
             } else if ([key isEqualToString:SKBackgroundColorKey]) {
                 [pdfView setBackgroundColor:[[NSUserDefaults standardUserDefaults] colorForKey:SKBackgroundColorKey]];
             } else if ([key isEqualToString:SKPageBackgroundColorKey]) {
                 [pdfView applyDefaultPageBackgroundColor];
             }
         } else {
             [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
         }
     }
     
     */
    
    func updateString() {
        var mutableString = ""
        let clipView = self.pdfView.documentView().contentView
        let rect = clipView.convert(clipView.visibleRect, to: self.pdfView)
        
        for page in self.pdfView.displayedPages() {
            guard let _page = page as? CPDFPage else {
                continue
            }
            let frame = self.pdfView.convert(rect, to: _page)
            let sel = _page.selection(for: frame)
            if let data = sel?.hasCharacters(), data {
                if mutableString.isEmpty == false {
                    mutableString.append("\n")
                }
                mutableString.append(sel?.string() ?? "")
            }
        }
        self.string = mutableString
    }
    
    @objc func goToRect(_ rectValue: NSValue) {
        self.pdfView.go(to: rectValue.rectValue, on: self.pdfView.currentPage())
        self.pdfView.resetHistory()
        
        self.updateString()

        self.window?.makeFirstResponder(self.pdfView)
        self.handlePageChangedNotification(nil)

        NotificationCenter.default.addObserver(self, selector: #selector(handlePageChangedNotification), name: NSNotification.Name.CPDFViewPageChanged, object: self.pdfView)
        NotificationCenter.default.addObserver(self, selector: #selector(handleDocumentDidUnlockNotification), name: NSNotification.Name.CPDFDocumentDidUnlock, object: self.pdfView.document)

        let clipView = self.pdfView.documentView().contentView
        NotificationCenter.default.addObserver(self, selector: #selector(handlePDFViewFrameChangedNotification), name: NSView.frameDidChangeNotification, object: clipView)
        NotificationCenter.default.addObserver(self, selector: #selector(handlePDFViewFrameChangedNotification), name: NSView.boundsDidChangeNotification, object: clipView)
        NotificationCenter.default.addObserver(self, selector: #selector(handleViewChangedNotification), name: NSNotification.Name(SKSnapshotViewChangedNotification), object: self)
        NotificationCenter.default.addObserver(self, selector: #selector(handleDidAddRemoveAnnotationNotification), name: NSNotification.Name.CPDFListViewDidAddAnnotation, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(handleDidAddRemoveAnnotationNotification), name: NSNotification.Name.CPDFListViewDidRemoveAnnotation, object: nil)
//        NotificationCenter.default.addObserver(self, selector: #selector(handleDidMoveAnnotationNotification), name: SKPDFViewDidMoveAnnotationNotification, object: nil)
        self.perform(#selector(notifiyDidFinishSetup), with: nil, afterDelay: SMALL_DELAY)

        if self.hasWindow {
            self.showWindow(nil)
        }
    }
    
    func setPdfDocument(_ pdfDocument: CPDFDocument, goToPageNumber pageNum: Int, rect: NSRect, scaleFactor factor: CGFloat, autoFits: Bool) {
        let window = self.window
        
        self.pdfView.document = pdfDocument
        self.pdfView.scaleFactor = factor
        self.pdfView.autoScales = false
        self.pdfView.displaysPageBreaks = false
        self.pdfView.displayBox = .cropBox
        self.pdfView.setShouldAntiAlias(UserDefaults.standard.bool(forKey: KMShouldAntiAliasKey))
        self.pdfView.setGreekingThreshold(UserDefaults.standard.float(forKey: KMGreekingThresholdKey).cgFloat)
        self.pdfView.backgroundColor = UserDefaults.standard.color(forKey: KMBackgroundColorKey)
//        [pdfView applyDefaultPageBackgroundColor];
        self.pdfView.applyDefaultInterpolationQuality()
        
//        [self setWindowFrameAutosaveNameOrCascade:SKSnapshotWindowFrameAutosaveName];
        self.window?.setFrameUsingName("KMSnapshotWindow")
        self.shouldCascadeWindows = false
        
        if let controlView = self.pdfView.scalePopUpButton {
            var controlFrame: NSRect = .zero
            var frame: NSRect = .zero
            NSDivideRect(self.pdfView.frame, &controlFrame, &frame, NSHeight(controlView.frame), .minY)
            controlFrame.size.width = NSWidth(controlView.frame)
            controlView.frame = controlFrame
            controlView.autoresizingMask = [.maxXMargin, .maxYMargin]
            window?.contentView?.addSubview(controlView)
            self.pdfView.frame = frame
            window?.backgroundColor = NSColor(calibratedWhite: 0.97, alpha: 1)
            
            let page = pdfDocument.page(at: UInt(pageNum))
            frame = self.pdfView.convert(rect, from: page)
            var view: NSView?
            frame = self.pdfView.convert(frame, to: view)
            frame.size.height += NSHeight(controlFrame)
            
//                    frame = [NSWindow frameRectForContentRect:frame styleMask:[window styleMask] & ~NSWindowStyleMaskFullSizeContentView];
            var styleMask = window?.styleMask
            styleMask?.remove(.fullSizeContentView)

            frame = NSWindow.frameRect(forContentRect: frame, styleMask: styleMask!)
            frame.origin.x = NSMinX(window!.frame)
            frame.origin.y = NSMaxY(window!.frame) - NSHeight(frame)
            self.window?.setFrame(frame, display: false, animate: false)
        }
        
        self.pdfView.go(toPageIndex: pageNum, animated: false)
        
        if (autoFits) {
            self.pdfView.autoFits = autoFits
        }
        self.pdfView.autoScales = true
        
        // Delayed to allow PDFView to finish its bookkeeping
        // fixes bug of apparently ignoring the point but getting the page right.
        self.perform(#selector(goToRect), with: NSValue(rect: rect), afterDelay: SMALL_DELAY)
    }

    // MARK: - Noti Actions
    
    @objc func preferenceInfoDidChange(sender: Notification) {
        let info : [AnyHashable : Any] = sender.userInfo ?? [:]
 
    }
    
}

// MARK: - Private Methods

extension KMSnapshotWindowController {
    private func _updateWindowLevel() {
        let onTop = self.forceOnTop || UserDefaults.standard.bool(forKey: KMSnapshotsOnTopKey)
        self.window?.level = onTop ? .floating : .normal
        self.window?.hidesOnDeactivate = onTop
    }
    
    private func _thumbnailAttachment(size: CGFloat) -> NSAttributedString? {
        guard let imageData = self.thumbnailWithSize(size)?.tiffRepresentation else {
            return nil
        }
        
        let wrapper = FileWrapper(regularFileWithContents: imageData)
        let filename = String(format: "snapshot_page_%lu.tiff", self.pageIndex() + 1)
        wrapper.filename = filename
        wrapper.preferredFilename = filename
        let attachment = NSTextAttachment(fileWrapper: wrapper)
            
        return NSAttributedString(attachment: attachment)
    }
}

extension KMSnapshotWindowController: NSWindowDelegate {
    
    func windowWillClose(_ notification: Notification) {
//        @try { [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeys:[NSArray arrayWithObjects:SKSnapshotsOnTopKey, SKShouldAntiAliasKey, SKGreekingThresholdKey, SKBackgroundColorKey, SKPageBackgroundColorKey, nil]]; }
//        @catch (id e) {}
        self.delegate?.snapshotControllerWillClose?(self)
            
        self.delegate = nil
        // Yosemite and El Capitan have a retain cycle when we leave the PDFView with a document
//        if (RUNNING_AFTER(10_9) && RUNNING_BEFORE(10_12))
        self.pdfView.document = nil
    }
    
    func windowDidMiniaturize(_ notification: Notification) {
        self.window?.orderOut(nil)
        self.hasWindow = false
    }
    
    func windowDidDeminiaturize(_ notification: Notification) {
        self._updateWindowLevel()
        self.hasWindow = true
    }
}

extension KMSnapshotWindowController: NSMenuItemValidation {
    func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
        let action = menuItem.action
        if (action == #selector(doGoToNextPage)) {
            return self.pdfView.canGoToNextPage()
        } else if (action == #selector(doGoToPreviousPage)) {
            return self.pdfView.canGoToPreviousPage()
        } else if (action == #selector(doGoToFirstPage)) {
            return self.pdfView.canGoToFirstPage()
        } else if (action == #selector(doGoToLastPage)) {
            return self.pdfView.canGoToLastPage()
        } else if (action == #selector(doGoBack)) {
            return self.pdfView.km_canGoBack()
        } else if (action == #selector(doGoForward)) {
            return self.pdfView.km_canGoForward()
        } else if (action == #selector(doZoomIn)) {
            return self.pdfView.canZoomIn
        } else if (action == #selector(doZoomOut)) {
            return self.pdfView.canZoomOut
        } else if (action == #selector(doZoomToActualSize)) {
            return abs(pdfView.scaleFactor - 1.0 ) > 0.01
        }
//        else if (action == #selector(doZoomToPhysicalSize)) {
//            return fabs(pdfView.physicalScaleFactor - 1.0 ) > 0.01
//        }
        else if (action == #selector(toggleAutoScale)) {
            menuItem.state = self.pdfView.autoFits ? .on : .off
            return true
        }
        return true
    }
}