import AppKit
import Carbon
import ServiceManagement
import OSLog

fileprivate enum ViewConstants {
    static let spacing2: CGFloat = 2
    static let spacing5: CGFloat = 2
    static let spacing10: CGFloat = 10
    static let spacing20: CGFloat = 20
    static let spacing40: CGFloat = 40
}

class SettingsViewController: NSViewController, NSTextFieldDelegate,
    KeyDetectorButtonDelegate, NSTableViewDataSource, NSTableViewDelegate,
    MyTableCellViewDelegate
{
    fileprivate static let logger = Logger(
        subsystem: Bundle.main.bundleIdentifier!,
        category: String(describing: SettingsViewController.self)
    )

    private var recording = false

    // NOTE: This is the default shortcut. If you were to change it, don't
    //       forget to change other places in this file and delegate, too.
    private var keyCode = Int(kVK_Space)
    private var modifiers = Int(optionKey)

    // NOTE:  PERF: This is very slow to initialize because it creates a
    //             a new process. This also cannot be done on a separate
    //             thread. This sucks because the program now takes
    //             considerably longer to launch.
    private let dirPicker: NSOpenPanel = {
        let panel = NSOpenPanel()
        panel.message = "Select a directory to search applications in . . ."
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.allowsMultipleSelection = false
        return panel
    }()

    private var shortcutsLabel: NSTextField = {
        let textField = NSTextField(labelWithString: "Shortcut")
        textField.font = NSFont.systemFont(
            ofSize: NSFontDescriptor.preferredFontDescriptor(
                forTextStyle: .title2).pointSize, weight: .bold)
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    private var ctrlButton: NSButton = {
        let button = NSButton()
            button.title = "⌃"
            button.action = #selector(handleModifiers)
            button.setButtonType(.pushOnPushOff)
            button.sizeToFit()
            button.bezelStyle = .rounded
            button.translatesAutoresizingMaskIntoConstraints = false
            return button
    }()

    private var cmdButton: NSButton = {
        let button = NSButton()
        button.title = "⌘"
        button.action = #selector(handleModifiers)
        button.setButtonType(.pushOnPushOff)
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private var optButton: NSButton = {
        let button = NSButton()
        button.title = "⌥"
        button.action = #selector(handleModifiers)
        button.setButtonType(.pushOnPushOff)
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private var shiftButton: NSButton = {
        let button = NSButton()
        button.title = "⇧"
        button.action = #selector(handleModifiers)
        button.setButtonType(.pushOnPushOff)
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private var plusLabel: NSTextField = {
        let textField = NSTextField()
        textField.stringValue = "+"
        textField.isEditable = false
        textField.isBezeled = false
        textField.drawsBackground = false
        textField.alignment = .center
        textField.font = NSFont.systemFont(
            ofSize: NSFontDescriptor.preferredFontDescriptor(
                forTextStyle: .body).pointSize, weight: .bold)
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    private var recordButton: KeyDetectorButton = {
        let button = KeyDetectorButton()
        button.title = "Record"
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private var pathsLabel: NSTextField = {
        let textField =
            NSTextField(labelWithString: "Application Directories")
        textField.font = NSFont.systemFont(
            ofSize: NSFontDescriptor.preferredFontDescriptor(
                forTextStyle: .title2).pointSize, weight: .bold)
        textField.translatesAutoresizingMaskIntoConstraints = false
        return textField
    }()

    private var tableScrollView: NSScrollView = {
        let scroll = NSScrollView()
        scroll.drawsBackground = false
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()

    private var pathsTableView: NSTableView = {
        let table = NSTableView()

        table.backgroundColor = .clear

        table.doubleAction = #selector(editItem)

        table.headerView = nil
        table.allowsMultipleSelection = true
        table.allowsColumnReordering = false
        table.allowsColumnResizing = false
        table.allowsColumnSelection = false
        table.addTableColumn(NSTableColumn(
            identifier: NSUserInterfaceItemIdentifier("Paths")))

        //rowHeight cgfloat must see doc

        table.translatesAutoresizingMaskIntoConstraints = false
        return table
    }()

    private var pathsControl: NSSegmentedControl = {
        let control = NSSegmentedControl()
        control.segmentCount = 2
        control.segmentStyle = .roundRect

        control.setImage(
            NSImage(systemSymbolName: "plus",
            accessibilityDescription: nil), forSegment: 0)
        control.setImage(
            NSImage(systemSymbolName: "minus",
            accessibilityDescription: nil), forSegment: 1)

        control.setToolTip("Add Path", forSegment: 0)
        control.setToolTip("Remove Path", forSegment: 1)
        control.trackingMode = .momentary

        control.translatesAutoresizingMaskIntoConstraints = false
        return control
    }()

    private var launchAtLoginButton: NSButton = {
        let button = NSButton()
        button.title = "Launch at login - OFF"
        button.action = #selector(launchAtLogin)
        button.setButtonType(.toggle)
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.isBordered = false
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private var resetAllButton: NSButton = {
        let button = NSButton()
        button.title = "Reset"
        button.action = #selector(reset)
        button.setButtonType(.momentaryLight)
        button.sizeToFit()
        button.bezelStyle = .rounded
        button.isBordered = false
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()

    private func addSubviews() {
        view.addSubview(shortcutsLabel)
        view.addSubview(ctrlButton)
        view.addSubview(cmdButton)
        view.addSubview(optButton)
        view.addSubview(shiftButton)
        view.addSubview(plusLabel)
        view.addSubview(recordButton)

        view.addSubview(pathsLabel)
        view.addSubview(tableScrollView)
        view.addSubview(pathsControl)

        view.addSubview(launchAtLoginButton)
        view.addSubview(resetAllButton)
    }

    private func setConstraints() {
        NSLayoutConstraint.activate([
            shortcutsLabel.topAnchor.constraint(
                equalTo: view.topAnchor,
                constant: ViewConstants.spacing10),
            shortcutsLabel.leadingAnchor.constraint(
                equalTo: view.leadingAnchor,
                constant: ViewConstants.spacing10),

            ctrlButton.topAnchor.constraint(
                equalTo: shortcutsLabel.bottomAnchor,
                constant: ViewConstants.spacing10),
            ctrlButton.leadingAnchor.constraint(
                equalTo: shortcutsLabel.leadingAnchor),

            cmdButton.centerYAnchor.constraint(
                equalTo: ctrlButton.centerYAnchor),
            cmdButton.leadingAnchor.constraint(
                equalTo: ctrlButton.trailingAnchor,
                constant: ViewConstants.spacing5),

            optButton.centerYAnchor.constraint(
                equalTo: ctrlButton.centerYAnchor),
            optButton.leadingAnchor.constraint(
                equalTo: cmdButton.trailingAnchor,
                constant: ViewConstants.spacing5),

            shiftButton.centerYAnchor.constraint(
                equalTo: ctrlButton.centerYAnchor),
            shiftButton.leadingAnchor.constraint(
                equalTo: optButton.trailingAnchor,
                constant: ViewConstants.spacing5),

            plusLabel.centerYAnchor.constraint(
                equalTo: ctrlButton.centerYAnchor),
            plusLabel.leadingAnchor.constraint(
                equalTo: shiftButton.trailingAnchor,
                constant: ViewConstants.spacing5),

            recordButton.widthAnchor.constraint(equalToConstant: 40),
            recordButton.centerYAnchor.constraint(
                equalTo: ctrlButton.centerYAnchor),
            recordButton.leadingAnchor.constraint(
                equalTo: plusLabel.trailingAnchor,
                constant: ViewConstants.spacing5),

            pathsLabel.topAnchor.constraint(
                equalTo: ctrlButton.bottomAnchor,
                constant: ViewConstants.spacing20),
            pathsLabel.leadingAnchor.constraint(
                equalTo: shortcutsLabel.leadingAnchor),

            tableScrollView.widthAnchor.constraint(equalToConstant: 350),
            tableScrollView.heightAnchor.constraint(equalToConstant: 100),
            tableScrollView.topAnchor.constraint(
                equalTo: pathsLabel.bottomAnchor),
            tableScrollView.leadingAnchor.constraint(
                equalTo: view.leadingAnchor),
            tableScrollView.trailingAnchor.constraint(
                equalTo: view.trailingAnchor),

            pathsControl.topAnchor.constraint(
                equalTo: tableScrollView.bottomAnchor,
                constant: ViewConstants.spacing10),
            pathsControl.leadingAnchor.constraint(
                equalTo: view.leadingAnchor,
                constant: ViewConstants.spacing10),

            launchAtLoginButton.topAnchor.constraint(
                equalTo: pathsControl.bottomAnchor,
                constant: ViewConstants.spacing10),
            launchAtLoginButton.trailingAnchor.constraint(
                equalTo: resetAllButton.leadingAnchor,
                constant: -ViewConstants.spacing10),

            resetAllButton.centerYAnchor.constraint(
                equalTo: launchAtLoginButton.centerYAnchor),
            resetAllButton.trailingAnchor.constraint(
                equalTo: view.trailingAnchor,
                constant: -ViewConstants.spacing10),
            resetAllButton.bottomAnchor.constraint(
                equalTo: view.bottomAnchor,
                constant: -ViewConstants.spacing10),
        ])
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        tableScrollView.documentView = pathsTableView

        cmdButton.target = self
        optButton.target = self
        ctrlButton.target = self
        shiftButton.target = self
        recordButton.delegate = self
        launchAtLoginButton.target = self
        resetAllButton.target = self

        recordButton.defaultKey = kVK_Space

        recordButton.target = self

        pathsTableView.dataSource = self
        pathsTableView.delegate = self

        pathsTableView.delegate = self

        pathsControl.target = self
        pathsControl.action = #selector(affectPaths(_:))

        addSubviews()
        setConstraints()
    }

    override func viewWillAppear() {
        super.viewWillAppear()

        // PERF: Maybe we shouldn't fetch it on every appearance?
        //       Only do it in AppDelegate?
        if let code =
            UserDefaults.standard.object(forKey: "keyCode") as? Int
        {
            keyCode = code
        }
        if let mods =
            UserDefaults.standard.object(forKey: "keyModifiers") as? Int
        {
            modifiers = mods
        }

        syncModifierButtons()
        launchAtLoginStatus()
    }

    override func viewDidAppear() {
        super.viewDidAppear()

        self.view.window?.center()
    }

    override func viewWillDisappear() {
        super.viewWillDisappear()

        HotKeyManager.shared.registerHotKey(key: keyCode,
            modifiers: modifiers)

        UserDefaults.standard.set(keyCode, forKey: "keyCode")
        UserDefaults.standard.set(modifiers, forKey: "keyModifiers")

        PathManager.shared.savePaths()
        PathManager.shared.rebuildIndex()
    }

    override func loadView() {
        self.view = NSView()
    }

    @objc
    private func handleModifiers() {
        // NOTE: Revert to default modifier if none of the modifier
        //       buttons are on.
        if cmdButton.state != .on, optButton.state != .on,
           ctrlButton.state != .on, shiftButton.state != .on
        {
            optButton.state = .on
        }

        detectModifers()
    }

    @objc
    private func launchAtLogin() {
        delegate.toggleLaunchAtLogin()
        launchAtLoginStatus()
    }

    private func launchAtLoginStatus() {
        if delegate.willLaunchAtLogin() {
            launchAtLoginButton.title = "Launch at login - ON"
            launchAtLoginButton.state = .on
        } else {
            launchAtLoginButton.title = "Launch at login - OFF"
            launchAtLoginButton.state = .off
        }
    }

    @objc
    private func reset() {
        keyCode = Int(kVK_Space)
        modifiers = Int(optionKey)
        HotKeyManager.shared.registerHotKey(key: keyCode, modifiers: modifiers)
        UserDefaults.standard.set(keyCode, forKey: "keyCode")
        UserDefaults.standard.set(modifiers, forKey: "keyModifiers")
        syncModifierButtons()

        PathManager.shared.reset()
        pathsTableView.reloadData()
    }

    private func detectModifers() {
        var mods = 0

        if cmdButton.state == .on {
            mods |= cmdKey
        }
        if optButton.state == .on {
            mods |= optionKey
        }
        if ctrlButton.state == .on {
            mods |= controlKey
        }
        if shiftButton.state == .on {
            mods |= shiftKey
        }

        if mods == 0 {
            mods |= optionKey
        } else {
            modifiers = mods
        }
    }

    private func syncModifierButtons() {
        ctrlButton.state = .off
        cmdButton.state = .off
        optButton.state = .off
        shiftButton.state = .off

        if modifiers & controlKey != 0 {
            ctrlButton.state = .on
        }
        if modifiers & cmdKey != 0 {
            cmdButton.state = .on
        }
        if modifiers & optionKey != 0 {
            optButton.state = .on
        }
        if modifiers & shiftKey != 0 {
            shiftButton.state = .on
        }

        if let character = keyName(virtualKeyCode: UInt16(keyCode)) {
            recordButton.title = character
        }
    }

    @objc
    private func affectPaths(_ sender: NSSegmentedControl) {
        // PERF: All of this could be written better.
        let selectedSegment = sender.selectedSegment
        switch selectedSegment {
        case 0:
            PathManager.shared.addPath("")
            pathsTableView.reloadData()

            let row = PathManager.shared.userPaths.count-1
            pathsTableView.selectRowIndexes(IndexSet(integer: row),
                byExtendingSelection: false)
            pathsTableView.scrollRowToVisible(row)
            (pathsTableView.view(atColumn: 0, row: row,
                makeIfNecessary: false) as? MyTableCellView)?
                    .startEditing()
            break
        case 1:
            var toRemove: [String] = []
            for row in pathsTableView.selectedRowIndexes {
                toRemove.append(PathManager.shared.userPaths[row])
            }
            PathManager.shared.userPaths.removeAll(
                where: { toRemove.contains($0) })
            pathsTableView.reloadData()
            break
        default:
            break
        }
    }

    @objc
    private func editItem(_ sender: NSTableView) {
        pathsTableView.deselectAll(nil)
        pathsTableView.selectRowIndexes(
            IndexSet(integer: pathsTableView.clickedRow),
            byExtendingSelection: false)

        if let cell = pathsTableView.view(atColumn: 0,
            row: pathsTableView.clickedRow,
            makeIfNecessary: false) as? MyTableCellView
        {
            cell.startEditing()
        }
    }

    func titleFieldFinishedEditing(tag: Int, text: String) {
        PathManager.shared.userPaths[tag] = text
        if PathManager.shared.userPaths[tag].isEmpty {
            PathManager.shared.userPaths.remove(at: tag)
            pathsTableView.reloadData()
        }
        pathsTableView.deselectAll(nil)
    }

    func titleFieldTextChanged(tag: Int, text: String) {
    }

    func keyWasSet(to keyCode: Int) {
        self.keyCode = Int(keyCode)
    }

    func selectionButtonClicked(tag: Int) {
        NSRunningApplication.current.activate(options: .activateAllWindows)
        delegate.window.level = .normal

        if dirPicker.runModal() == .OK {
            if let url = dirPicker.url {
                PathManager.shared.userPaths[tag] = url.path
                pathsTableView.reloadData()
            }
        }

        delegate.window.level = .statusBar
        delegate.window.makeKeyAndOrderFront(nil)
        if let controller =
            delegate.window.contentViewController as?  SearchViewController
        {
            controller.openSettings()
        }
    }

    func numberOfRows(in tableView: NSTableView) -> Int {
        return PathManager.shared.userPaths.count
    }

    func tableView(_ tableView: NSTableView,
        viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
    {
        let rect = NSRect(x: 0, y: 0,
            width: tableColumn!.width, height: 20)
        let cell = MyTableCellView(frame: rect)
        cell.titleField.stringValue = PathManager.shared.userPaths[row]
        cell.delegate = self
        cell.id = row

        return cell
    }

    func tableViewSelectionDidChange(_ notification: Notification) {
/*
        let selectedRow = tableView.selectedRow
        if selectedRow >= 0 {
            print("Selected: \(items[selectedRow])")
        }
*/
    }
}
