//
//  SignalData.swift
//  Adwaita
//
//  Created by david-swift on 31.07.24.
//

import CAdw

/// Data to pass to signal handlers.
public class SignalData {

    /// The closure.
    public var closure: ([Any?]) -> Any?
    /// Destroy the class.
    public var selfDestruction: (() -> Void)?

    /// The closure as a C handler.
    var handler: @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Void {
        { _, data in
            let data = unsafeBitCast(data, to: SignalData.self)
            _ = data.closure([])
        }
    }

    /// The closure as a C handler with three parameters.
    var threeParamsHandler: @convention(c) (
        UnsafeMutableRawPointer,
        UnsafeRawPointer?,
        UnsafeMutableRawPointer
    ) -> Void {
        { _, arg1, data in
            let data = unsafeBitCast(data, to: SignalData.self)
            _ = data.closure([arg1])
        }
    }

    /// The closure as a C handler with four parameters.
    var fourParamsHandler: @convention(c) (
        UnsafeMutableRawPointer,
        UnsafeRawPointer?,
        UnsafeRawPointer?,
        UnsafeMutableRawPointer
    ) -> Void {
        { _, arg1, arg2, data in
            let data = unsafeBitCast(data, to: SignalData.self)
            _ = data.closure([arg1, arg2])
        }
    }

    /// The closure as a C handler with five parameters.
    var fiveParamsHandler: @convention(c) (
        UnsafeMutableRawPointer,
        UnsafeRawPointer?,
        Double,
        Double,
        UnsafeMutableRawPointer
    ) -> Void {
        { _, arg1, arg2, arg3, data in
            let data = unsafeBitCast(data, to: SignalData.self)
            _ = data.closure([arg1, arg2, arg3])
        }
    }

    /// The closure as a C handler with a return value.
    var boolHandler: @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Bool {
        { _, data in
            let data = unsafeBitCast(data, to: SignalData.self)
            return data.closure([]) as? Bool ?? false
        }
    }

    /// Initialize the signal data.
    /// - Parameters:
    ///     - closure: The signal's closure.
    ///     - destroy: The self destruction.
    public convenience init(closure: @escaping () -> Any?, destroy: (() -> Void)? = nil) {
        self.init(closure: { _ in closure() }, destroy: destroy)
    }

    /// Initialize the signal data.
    /// - Parameters:
    ///     - closure: The signal's closure.
    ///     - destroy: The self destruction.
    public convenience init(closure: @escaping () -> Void, destroy: (() -> Void)? = nil) {
        self.init(closure: { _ in closure(); return nil }, destroy: destroy)
    }

    /// Initialize the signal data.
    /// - Parameters:
    ///     - closure: The signal's closure.
    ///     - destroy: The self destruction.
    public init(closure: @escaping ([Any]) -> Any?, destroy: (() -> Void)? = nil) {
        self.closure = closure
        self.selfDestruction = destroy
    }

    /// The return type.
    public enum ReturnType {

        /// Returns a boolean.
        case bool

    }

    /// Connect the signal data to a signal.
    /// - Parameters:
    ///     - pointer: The pointer to the object which holds the signal.
    ///     - signal: The signal's name.
    ///     - argCount: The number of arguments.
    ///     - return: The return type.
    public func connect(
        pointer: UnsafeMutableRawPointer?,
        signal: String,
        argCount: Int = 0,
        return: ReturnType? = nil
    ) {
        let callback: GCallback
        if let `return` {
            switch `return` {
            case .bool:
                callback = unsafeBitCast(boolHandler, to: GCallback.self)
            }
        } else if argCount >= 3 {
            callback = unsafeBitCast(fiveParamsHandler, to: GCallback.self)
        } else if argCount == 2 {
            callback = unsafeBitCast(fourParamsHandler, to: GCallback.self)
        } else if argCount == 1 {
            callback = unsafeBitCast(threeParamsHandler, to: GCallback.self)
        } else {
            callback = unsafeBitCast(handler, to: GCallback.self)
        }
        let destroy: GClosureNotify = { data, _ in
            guard let data else {
                return
            }
            let signalData: SignalData = Unmanaged.fromOpaque(data).takeUnretainedValue()
            signalData.selfDestruction?()
        }
        g_signal_connect_data(
            pointer,
            signal,
            callback,
            Unmanaged.passUnretained(self).toOpaque().cast(),
            destroy,
            G_CONNECT_AFTER
        )
    }

}
