//
//  InAppBrowserWebViewController.swift
//  flutter_inappwebview
//
//  Created by Lorenzo on 17/09/18.
//

import FlutterMacOS
import AppKit
import WebKit
import Foundation

public class InAppBrowserWebViewController: NSViewController, InAppBrowserDelegate, Disposable {
    static var METHOD_CHANNEL_NAME_PREFIX = "com.pichillilorenzo/flutter_inappbrowser_";

    var progressBar: NSProgressIndicator!
    
    var window: InAppBrowserWindow?
    var id: String = ""
    var plugin: InAppWebViewFlutterPlugin?
    var windowId: Int64?
    var webView: InAppWebView?
    var channelDelegate: InAppBrowserChannelDelegate?
    var initialUrlRequest: URLRequest?
    var initialFile: String?
    var browserSettings: InAppBrowserSettings?
    var webViewSettings: InAppWebViewSettings?
    var initialData: String?
    var initialMimeType: String?
    var initialEncoding: String?
    var initialBaseUrl: String?
    var initialUserScripts: [[String: Any]] = []
    var isHidden = false

    public override func loadView() {
        guard let plugin = plugin else {
            return
        }
        
        let channel = FlutterMethodChannel(name: InAppBrowserWebViewController.METHOD_CHANNEL_NAME_PREFIX + id, binaryMessenger: plugin.registrar.messenger)
        channelDelegate = InAppBrowserChannelDelegate(channel: channel)
        
        var userScripts: [UserScript] = []
        for initialUserScript in initialUserScripts {
            userScripts.append(UserScript.fromMap(map: initialUserScript, windowId: windowId)!)
        }
        
        let preWebviewConfiguration = InAppWebView.preWKWebViewConfiguration(settings: webViewSettings)
        if let wId = windowId, let webViewTransport = plugin.inAppWebViewManager?.windowWebViews[wId] {
            webView = webViewTransport.webView
            webView!.initialUserScripts = userScripts
        } else {
            webView = InAppWebView(id: nil,
                                   plugin: nil,
                                   frame: .zero,
                                   configuration: preWebviewConfiguration,
                                   userScripts: userScripts)
        }
        
        guard let webView = webView else {
            return
        }
        
        webView.inAppBrowserDelegate = self
        webView.id = id
        webView.plugin = plugin
        webView.channelDelegate = WebViewChannelDelegate(webView: webView, channel: channel)
        
        let findInteractionController = FindInteractionController(
            plugin: plugin,
            id: id, webView: webView, settings: nil)
        webView.findInteractionController = findInteractionController
        findInteractionController.prepare()
        
        prepareWebView()
        webView.windowCreated = true
        
        progressBar = NSProgressIndicator()
        progressBar.style = .bar
        progressBar.isIndeterminate = false
        progressBar.startAnimation(self)
        
        view = NSView(frame: NSApplication.shared.mainWindow?.frame ?? .zero)
        view.addSubview(webView)
        view.addSubview(progressBar, positioned: .above, relativeTo: webView)
    }
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        
        webView?.translatesAutoresizingMaskIntoConstraints = false
        progressBar.translatesAutoresizingMaskIntoConstraints = false
        
        webView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0.0).isActive = true
        webView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0.0).isActive = true
        webView?.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
        webView?.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
        
        progressBar.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -6.0).isActive = true
        progressBar.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0.0).isActive = true
        progressBar.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0.0).isActive = true
        
        if let wId = windowId {
            channelDelegate?.onBrowserCreated()
            webView?.runWindowBeforeCreatedCallbacks()
        } else {
            if #available(macOS 10.13, *) {
                if let contentBlockers = webView?.settings?.contentBlockers, contentBlockers.count > 0 {
                    do {
                        let jsonData = try JSONSerialization.data(withJSONObject: contentBlockers, options: [])
                        let blockRules = String(data: jsonData, encoding: .utf8)
                        WKContentRuleListStore.default().compileContentRuleList(
                            forIdentifier: "ContentBlockingRules",
                            encodedContentRuleList: blockRules) { (contentRuleList, error) in

                                if let error = error {
                                    print(error.localizedDescription)
                                    return
                                }

                                let configuration = self.webView!.configuration
                                configuration.userContentController.add(contentRuleList!)

                                self.initLoad()
                        }
                        return
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
            
            initLoad()
        }
    }
    
    public override func viewDidAppear() {
        super.viewDidAppear()
        window = view.window as? InAppBrowserWindow
    }
    
    public func initLoad() {
        if let initialFile = initialFile {
            do {
                try webView?.loadFile(assetFilePath: initialFile)
            }
            catch let error as NSError {
                dump(error)
            }
        }
        else if let initialData = initialData {
            let baseUrl = URL(string: initialBaseUrl ?? "about:blank")!
            var allowingReadAccessToURL: URL? = nil
            if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, baseUrl.scheme == "file" {
                allowingReadAccessToURL = URL(string: allowingReadAccessTo)
                if allowingReadAccessToURL?.scheme != "file" {
                    allowingReadAccessToURL = nil
                }
            }
            webView?.loadData(data: initialData, mimeType: initialMimeType!, encoding: initialEncoding!, baseUrl: baseUrl, allowingReadAccessTo: allowingReadAccessToURL)
        }
        else if let initialUrlRequest = initialUrlRequest {
            var allowingReadAccessToURL: URL? = nil
            if let allowingReadAccessTo = webView?.settings?.allowingReadAccessTo, let url = initialUrlRequest.url, url.scheme == "file" {
                allowingReadAccessToURL = URL(string: allowingReadAccessTo)
                if allowingReadAccessToURL?.scheme != "file" {
                    allowingReadAccessToURL = nil
                }
            }
            webView?.loadUrl(urlRequest: initialUrlRequest, allowingReadAccessTo: allowingReadAccessToURL)
        }
        
        channelDelegate?.onBrowserCreated()
    }

    public func prepareWebView() {
        webView?.settings = webViewSettings
        webView?.prepare()

        if let browserSettings = browserSettings {
            if browserSettings.hideProgressBar {
                progressBar.isHidden = true
            }
        }
    }
    
    public func didChangeTitle(title: String?) {
        guard let title = title else {
            return
        }
        if let browserSettings = browserSettings,
           let toolbarTopFixedTitle = browserSettings.toolbarTopFixedTitle {
            window?.title = toolbarTopFixedTitle
        } else {
            window?.title = title
        }
        window?.update()
    }
    
    public func didStartNavigation(url: URL?) {
        window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
        window?.backButton?.isEnabled = webView?.canGoBack ?? false
        progressBar.doubleValue = 0.0
        progressBar.isHidden = false
        guard let url = url else {
            return
        }
        window?.searchBar?.stringValue = url.absoluteString
    }
    
    public func didUpdateVisitedHistory(url: URL?) {
        window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
        window?.backButton?.isEnabled = webView?.canGoBack ?? false
        guard let url = url else {
            return
        }
        window?.searchBar?.stringValue = url.absoluteString
    }
    
    public func didFinishNavigation(url: URL?) {
        window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
        window?.backButton?.isEnabled = webView?.canGoBack ?? false
        progressBar.doubleValue = 0.0
        progressBar.isHidden = true
        guard let url = url else {
            return
        }
        window?.searchBar?.stringValue = url.absoluteString
    }
    
    public func didFailNavigation(url: URL?, error: Error) {
        window?.forwardButton?.isEnabled = webView?.canGoForward ?? false
        window?.backButton?.isEnabled = webView?.canGoBack ?? false
        progressBar.doubleValue = 0.0
        progressBar.isHidden = true
    }
    
    public func didChangeProgress(progress: Double) {
        progressBar.isHidden = false
        progressBar.doubleValue = progress * 100
        if progress == 100.0 {
            progressBar.isHidden = true
        }
    }
    
    @objc public func reload() {
        webView?.reload()
        didUpdateVisitedHistory(url: webView?.url)
    }
    
    @objc public func goBack() {
        if let webView = webView, webView.canGoBack {
            webView.goBack()
        }
    }
    
    @objc public func goForward() {
        if let webView = webView, webView.canGoForward {
            webView.goForward()
        }
    }
    
    @objc public func goBackOrForward(steps: Int) {
        webView?.goBackOrForward(steps: steps)
    }
    
    @objc public func onMenuItemClicked(sender: Any?) {
        var identifier: String?
        if let sender = sender as? NSMenuItem {
            identifier = sender.identifier?.rawValue
        }
        if identifier == nil, let sender = sender as? NSButton {
            identifier = sender.identifier?.rawValue
        }
        if let identifier = identifier, let window = window {
            let menuItem = window.menuItems.first { item in
                return item.id == Int64(identifier)
            }
            if let menuItem = menuItem {
                channelDelegate?.onMenuItemClicked(menuItem: menuItem)
            }
        }
    }

    public func setSettings(newSettings: InAppBrowserSettings, newSettingsMap: [String: Any]) {
        window?.setSettings(newSettings: newSettings, newSettingsMap: newSettingsMap)
        
        let newInAppWebViewSettings = InAppWebViewSettings()
        let _ = newInAppWebViewSettings.parse(settings: newSettingsMap)
        webView?.setSettings(newSettings: newInAppWebViewSettings, newSettingsMap: newSettingsMap)
        
        if newSettingsMap["hideProgressBar"] != nil, browserSettings?.hideProgressBar != newSettings.hideProgressBar {
            progressBar.isHidden = newSettings.hideProgressBar
        }
        
        browserSettings = newSettings
        webViewSettings = newInAppWebViewSettings
    }
    
    public func getSettings() -> [String: Any?]? {
        let webViewSettingsMap = webView?.getSettings()
        if (self.browserSettings == nil || webViewSettingsMap == nil) {
            return nil
        }
        var settingsMap = self.browserSettings!.getRealSettings(obj: self)
        settingsMap.merge(webViewSettingsMap!, uniquingKeysWith: { (current, _) in current })
        return settingsMap
    }
    
    public func hide() {
        isHidden = true
        window?.hide()
    }
    
    public func show() {
        isHidden = false
        window?.show()
    }
    
    public func close() {
        window?.close()
    }
    
    public func dispose() {
        channelDelegate?.onExit()
        channelDelegate?.dispose()
        channelDelegate = nil
        webView?.dispose()
        webView?.removeFromSuperview()
        webView = nil
        window = nil
        plugin = nil
    }
    
    deinit {
        debugPrint("InAppBrowserWebViewController - dealloc")
        dispose()
    }
}
