import AppKit
import Carbon

let OSCtrl  = NSEvent.ModifierFlags.control.rawValue
let OSCmd   = NSEvent.ModifierFlags.command.rawValue
let OSOpt   = NSEvent.ModifierFlags.option.rawValue
let OSShift = NSEvent.ModifierFlags.shift.rawValue
let OSMods  = UInt(OSCtrl | OSCmd | OSOpt | OSShift)

func modsContains(keys: UInt, in modifiers: UInt) -> Bool {
    return (modifiers & keys) == keys && ((modifiers ^ keys) & OSMods) == 0
}

func modsContainsNone(in modifiers: UInt) -> Bool {
    return (modifiers & OSMods) == 0
}

enum ViewConstants {
    static let spacing2:  CGFloat = 2
    static let spacing5:  CGFloat = 5
    static let spacing10: CGFloat = 10
    static let spacing15: CGFloat = 15
    static let spacing20: CGFloat = 20
    static let spacing25: CGFloat = 25
    static let spacing30: CGFloat = 30
    static let spacing35: CGFloat = 35
    static let spacing40: CGFloat = 40
}

#if(DEBUG)
struct EndpointConstants {
    static var versionURL  = "http://localhost:8081/version"
    static var appURL      = "http://localhost:8081/download"
    static var appURLZip   = "http://localhost:8081/static/app/CmdBar.zip"
    static var changelog   = "http://localhost:8081/changelog"
    static var validateURL = "http://localhost:8081/validate"
}
#else
struct EndpointConstants {
    static var versionURL  = "http://cmdbar.app/version"
    static var appURL      = "http://cmdbar.app/download"
    static var appURLZip   = "http://cmdbar.app/static/app/CmdBar.zip"
    static var changelog   = "http://cmdbar.app/changelog"
    static var validateURL = "https://cmdbar.app/validate"
}
#endif

enum Resulting<Success, Failure> {
    case success
    case failure
}

// Parse time string into seconds.
func intervalToSeconds(from timeString: String) -> Int {
    guard let value = Int(timeString.dropLast()) else { return 0 }
    let unit = timeString.suffix(1)
    switch unit {
    case "s":
        return value
    case "m":
        return value * 60
    case "h":
        return value * 60 * 60
    case "d":
        return value * 60 * 60 * 24
    default:
        return 0
    }
}

// Regex check if string is in the right format.
// Regular expression pattern to match "1s", "5m", "1h", "1d"
func isValidTimeFormat(_ timeString: String) -> Bool {
    let pattern = #"^\d+[smhd]$"#
    return NSPredicate(format: "SELF MATCHES %@", pattern)
        .evaluate(with: timeString)
}

// Turn seconds into a time string.
func stringifySeconds(_ seconds: Int) -> String {
    if seconds >= 60 && seconds < 3600 {
        return "\(String(format: "%.1f", Double(seconds) / 60.0))m"
    } else if seconds >= 3600 && seconds < 86400 {
        return "\(String(format: "%.1f", Double(seconds) / 3600.0))h"
    } else if seconds >= 86400 {
        return "\(String(format: "%.1f", Double(seconds) / 86400.0))h"
    }
    return "\(seconds)s"
}

// Execution errors.
private enum ExecError: Error {
    case doesntExist
    case noPermission
    case failed
    case error(err: String)
}

// Executes file and parses its output.
func execute(atPath path: URL) -> (title: String, body: String) {
    let stringPath = path.path()

    do {
        let output = try executeFile(path: path)
        if !output.isEmpty {
            let result = output.components(separatedBy: "\n")
            if result.count >= 1 {
                return (result.first!,
                        result.dropFirst().joined(separator: "\n"))
            }
        }
    } catch ExecError.failed {
        return ("", "execution failed: \(stringPath)")
    } catch ExecError.doesntExist {
        return ("", "doesn't exist: \(stringPath)")
    } catch ExecError.noPermission {
        return ("", "permission denied: \(stringPath)")
    } catch ExecError.error(let error) {
        return ("", "\(error)")
    } catch {
        return ("", "\(error)")
    }

    return ("", "")
}

// Executes a file.
private func executeFile(path: URL) throws -> String {
    if !FileManager.default.fileExists(atPath: path.path())
        { throw ExecError.doesntExist }
    if !FileManager.default.isExecutableFile(atPath: path.path())
        { throw ExecError.noPermission }

    let process = Process()
    process.executableURL = path

    let outputPipe = Pipe()
    let errorPipe = Pipe()
    process.standardOutput = outputPipe
    process.standardError = errorPipe

    do {
        try process.run()
    } catch {
        throw ExecError.failed
    }
    process.waitUntilExit()

    let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
    let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()

    let output = String(data: outputData, encoding: .utf8)
    let error = String(data: errorData, encoding: .utf8)

    if process.terminationStatus != 0 {
        if let err = error {
            throw ExecError.error(err: err)
        }
    }

    if let out = output, !out.isEmpty {
        var result = ""
        if out.hasSuffix("\n") {
            result = out.trimmingCharacters(in: .newlines)
        }
        return result
    }
    return ""
}

func getColors(_ light: NSColor, _ dark: NSColor,
               for appearance: NSAppearance) -> NSColor
{
    switch appearance.name {
    case .aqua, .vibrantLight, .accessibilityHighContrastAqua,
        .accessibilityHighContrastVibrantLight:
        return light
    case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua,
        .accessibilityHighContrastVibrantDark:
        return dark
    default:
        return NSColor.white
    }
}

// ex: systemImage("sunrise.circle.fill", .title2, .large,
//                 .init(paletteColors: [.white, .systemOrange]))
func systemImage(_ name: String, _ size: NSFont.TextStyle,
                 _ scale: NSImage.SymbolScale,
                _ configuration: NSImage.SymbolConfiguration) -> NSImage?
{
    return NSImage(systemSymbolName: name, accessibilityDescription: nil)?
        .withSymbolConfiguration(
            NSImage.SymbolConfiguration(
                textStyle: size, scale: scale).applying(configuration
            )
        )
}

func openLink(_ url: String) {
    guard let url = URL(string: url) else { return }
    NSWorkspace.shared.open(url)
}

fileprivate extension Notification.Name {
    static let beginMenuTracking =
        Notification.Name("com.apple.HIToolbox.beginMenuTrackingNotification")
    static let endMenuTracking =
        Notification.Name("com.apple.HIToolbox.endMenuTrackingNotification")
}

func persistMenuBar(_ state: Bool) {
    if state {
        DistributedNotificationCenter.default()
            .post(name: .beginMenuTracking, object: nil)
    } else {
        DistributedNotificationCenter.default()
            .post(name: .endMenuTracking, object: nil)
    }
}
