Skip to content
Snippets Groups Projects
SetActor.swift 4.23 KiB
Newer Older
Bruno Muniz's avatar
Bruno Muniz committed
import Foundation

// https://github.com/ekazaev/ChatLayout

enum ReactionTypes {
    case delayedUpdate
}

enum InterfaceActions {
    case changingKeyboardFrame
    case changingContentInsets
    case changingFrameSize
    case sendingMessage
    case scrollingToBottom
}

public final class SetActor<Option: SetAlgebra, ReactionType> {
    public enum Action {
        case onEmpty
        case onChange
        case onRemoval(_ option: Option)
        case onInsertion(_ option: Option)
    }

    public enum ExecutionType {
        case once
        case eternal
    }

    public final class Reaction {
        public let action: Action
        public let type: ReactionType
        public let actionBlock: () -> Void
        public let executionType: ExecutionType

        public init(
            type: ReactionType,
            action: Action,
            executionType: ExecutionType = .once,
            actionBlock: @escaping () -> Void
        ) {
            self.type = type
            self.action = action
            self.executionType = executionType
            self.actionBlock = actionBlock
        }
    }

    public var options: Option {
        didSet { optionsChanged(oldOptions: oldValue) }
    }

    public private(set) var reactions: [Reaction]

    public init(options: Option = [], reactions: [Reaction] = []) {
        self.options = options
        self.reactions = reactions
        optionsChanged(oldOptions: [])
    }

    public func add(reaction: Reaction) {
        reactions.append(reaction)
    }

    public func remove(reaction: Reaction) {
        reactions.removeAll(where: { $0 === reaction })
    }

    public func removeAllReactions(where shouldBeRemoved: (Reaction) throws -> Bool) throws {
        try reactions.removeAll(where: shouldBeRemoved)
    }

    public func removeAllReactions() {
        reactions.removeAll()
    }

    private func optionsChanged(oldOptions: Option) {
        let reactions = self.reactions
        let onChangeReactions = reactions.filter {
            guard case .onChange = $0.action else {
                return false
            }
            return true
        }

        onChangeReactions.forEach { reaction in
            reaction.actionBlock()
            if reaction.executionType == .once {
                self.reactions.removeAll(where: { $0 === reaction })
            }
        }

        if options.isEmpty {
            let onEmptyReactions = reactions.filter {
                guard case .onEmpty = $0.action else {
                    return false
                }
                return true
            }
            onEmptyReactions.forEach { reaction in
                reaction.actionBlock()
                if reaction.executionType == .once {
                    self.reactions.removeAll(where: { $0 === reaction })
                }
            }
        }

        let insertedOptions = options.subtracting(oldOptions)
        for option in [insertedOptions] {
            let onEmptyReactions = reactions.filter {
                guard case let .onInsertion(newOption) = $0.action,
                      newOption == option else {
                          return false
                      }
                return true
            }
            onEmptyReactions.forEach { reaction in
                reaction.actionBlock()
                if reaction.executionType == .once {
                    self.reactions.removeAll(where: { $0 === reaction })
                }
            }
        }

        let removedOptions = oldOptions.subtracting(options)
        for option in [removedOptions] {
            let onEmptyReactions = reactions.filter {
                guard case let .onRemoval(newOption) = $0.action,
                      newOption == option else {
                          return false
                      }
                return true
            }

            onEmptyReactions.forEach { reaction in
                reaction.actionBlock()
                if reaction.executionType == .once {
                    self.reactions.removeAll(where: { $0 === reaction })
                }
            }
        }
    }
}

extension SetActor where ReactionType: Equatable {
    public func removeAllReactions(_ type: ReactionType) {
        reactions.removeAll(where: { $0.type == type })
    }
}