SetProteinCommandHandler.kt

package de.pflugradts.passbird.application.commandhandling.handler.protein
import de.pflugradts.passbird.application.SecureInputUnavailableException
import de.pflugradts.passbird.application.UserInterfaceAdapterPort
import de.pflugradts.passbird.application.commandhandling.CommandExecutionTracker
import de.pflugradts.passbird.application.commandhandling.command.SetProteinCommand
import de.pflugradts.passbird.application.commandhandling.handler.TypedCommandHandler
import de.pflugradts.passbird.application.configuration.ReadableConfiguration
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import de.pflugradts.passbird.domain.model.slot.Slot
import de.pflugradts.passbird.domain.model.transfer.Input
import de.pflugradts.passbird.domain.model.transfer.Input.Companion.inputOf
import de.pflugradts.passbird.domain.model.transfer.Output
import de.pflugradts.passbird.domain.model.transfer.Output.Companion.outputOf
import de.pflugradts.passbird.domain.model.transfer.OutputFormatting.OPERATION_ABORTED
import de.pflugradts.passbird.domain.service.password.PasswordService
import de.pflugradts.passbird.domain.service.password.PasswordService.EggNotExistsAction.CREATE_ENTRY_NOT_EXISTS_EVENT
class SetProteinCommandHandler constructor(
    private val configuration: ReadableConfiguration,
    private val passwordService: PasswordService,
    private val userInterfaceAdapterPort: UserInterfaceAdapterPort,
    private val commandExecutionTracker: CommandExecutionTracker,
) : TypedCommandHandler<SetProteinCommand>(SetProteinCommand::class.java) {
    override fun handleCommand(command: SetProteinCommand) {
        val eggIdShell = command.argument
        val slot = command.slot
        if (!passwordService.eggExists(eggIdShell, CREATE_ENTRY_NOT_EXISTS_EVENT)) {
            commandExecutionTracker.markFailure()
            finish(command)
            return
        }
        if (passwordService.proteinExists(eggIdShell, slot) && !commandConfirmed(command)) {
            abort(command)
            return
        }
        val typeInput = receiveTypeInput(command) ?: run {
            abort(command)
            return
        }
        try {
            val structureInput = try {
                structureInputReceived(secureInputDetermined())
            } catch (_: SecureInputUnavailableException) {
                abort(command)
                return
            }
            try {
                if (structureInput.isEmpty) {
                    abort(command)
                    return
                }
                putProtein(eggIdShell, slot, typeInput, structureInput)
            } finally {
                structureInput.invalidate()
            }
        } finally {
            typeInput.invalidate()
        }
        finish(command)
    }
    private fun commandConfirmed(command: SetProteinCommand) = if (configuration.application.password.promptOnRemoval &&
        passwordService.eggExists(command.argument, PasswordService.EggNotExistsAction.DO_NOTHING)
    ) {
        val msg = "Existing Protein at Slot '${command.slot.index()}' of Egg '${command.argument.asString()}' " +
            "will be irrevocably overwritten.\nInput 'c' to confirm or anything else to abort.\nYour input: "
        userInterfaceAdapterPort.receiveConfirmation(Output.outputOf(Shell.shellOf(msg)))
    } else {
        true
    }
    private fun secureInputDetermined(): Boolean {
        val secureInput = configuration.domain.protein.secureProteinStructureInput
        if (configuration.domain.protein.promptForProteinStructureInputToggle) {
            val verb = if (secureInput) "Disable" else "Enable"
            if (userInterfaceAdapterPort.receiveYes(outputOf(shellOf("$verb secure input for next input? Y/n ")))) {
                return !secureInput
            }
        }
        return secureInput
    }
    private fun structureInputReceived(secureInput: Boolean) =
        with(outputOf(shellOf("Enter Protein Structure or just press enter to abort: "))) {
            when (secureInput) {
                true -> userInterfaceAdapterPort.receiveSecurely(this)
                false -> userInterfaceAdapterPort.receive(this)
            }
        }
    private fun receiveTypeInput(command: SetProteinCommand): Input? {
        val currentType = passwordService.viewProteinType(
            command.argument,
            command.slot,
        ).get()
        var selectedInput: Input? = null
        return try {
            val typeMsg = if (currentType.isEmpty) {
                "Enter Protein Type or just press enter to abort: "
            } else {
                "Enter new Protein Type to replace '${currentType.asString()}' or just press enter to keep it: "
            }
            selectedInput =
                userInterfaceAdapterPort.receive(outputOf(shellOf(typeMsg))).let { if (it.isEmpty) inputOf(currentType) else it }
            selectedInput.takeIf { it.isNotEmpty }
        } finally {
            if (selectedInput?.shell !== currentType) {
                currentType.scramble()
            }
        }
    }
    private fun putProtein(eggIdShell: Shell, slot: Slot, typeInput: Input, structureInput: Input) {
        val typeShell = typeInput.shell.copy()
        val structureShell = structureInput.shell.copy()
        try {
            if (passwordService.putProtein(
                    eggIdShell = eggIdShell,
                    slot = slot,
                    typeShell = typeShell,
                    structureShell = structureShell,
                ).failure
            ) {
                commandExecutionTracker.markFailure()
            }
        } finally {
            typeShell.scramble()
            structureShell.scramble()
        }
    }
    private fun abort(command: SetProteinCommand) {
        commandExecutionTracker.markAborted()
        userInterfaceAdapterPort.send(outputOf(shellOf("Operation aborted."), OPERATION_ABORTED))
        finish(command)
    }
    private fun finish(command: SetProteinCommand) {
        command.invalidateInput()
        userInterfaceAdapterPort.sendLineBreak()
    }
}