Source: sim/command/Commander.js

/** @module */

import CommandStore from './CommandStore.js'
import ComMessenger from './ComMessenger.js'
import ConfigKeeper from './ConfigKeeper.js'
import History from './History.js'

/**
 * Both the command pattern and the memento pattern are used here.
 * Commands are performed to make changes to the mementos.
 *
 * Memento Pattern:
 *  Caretaker: ConfigKeeper class. It keeps config as a list of mementos.
 *  Originator: sender returned by CommandStore.addSender, used by menuItem, Candidate, Voter, etc
 *  Mementos: Config is a list of mementos. It's not a strict pattern.
 *  Also, the way we are storing commands is like storing mementos of a commands (not strictly).
 *
 * Command Pattern:
 * * Client: a CandidateCommander, VoterCommander, MenuItem, etc
 * * Command: Here, a command is a memento of a way to make a command.
 *     CommandStore stores lists of ways to make commands.
 *     mostly with a name and an action.
 *     Also, History stores a list of commands.
 *  Sender: A sender is returned by CommandStore.addSender.
 *  Receiver: a property of a Candidate, Voter, MenuItem, etc.
 *  For example, Candidate.setXY calls CandidateCommander.setXYSenderForList.go(id, value),
 *    which then calls Candidate.setXYAction to set Candidate.x and Candidate.y
 *
 * This isn't exactly either pattern.
 * Instead of commands with execute functions,
 * we use CommandStore, a class that stores lists of ways to make commands, given parameters.
 * Instead of mementos, the lists of ways to make commands act as mementos.
 *
 * Initially, the three component classes here were part of one larger class,
 * but hopefully it is easier to read code that is shorter and sticks to one context.
 * This class is similar to the mediator in the mediator pattern.
 *
 * References:
 * * [Command Pattern on refactoring.guru](https://refactoring.guru/design-patterns/command)
 * * [Memento Pattern on refactoring.guru](https://refactoring.guru/design-patterns/memento)
 * * [Mediator Pattern on refactoring.guru](https://refactoring.guru/design-patterns/mediator)
 * @constructor
 */
export default function Commander(optComMessenger) {
    const self = this

    const commandStore = new CommandStore(self)
    const configKeeper = new ConfigKeeper(self)
    const history = new History(self)
    const comMessenger = optComMessenger || new ComMessenger()
    comMessenger.addCommander(self)

    /**
     * A menu item or other object can add an action that it wants to execute with a value.
     * The action can be performed by calling commander.do(command),
     * where command has a name and value.
     * @param {Object} args - argument catchall
     * @param {String} args.name - The name of the action.
     * @param {Function} args.action - The action function itself, which is called with a value.
     */

    // make a sender with a command and a way to do the command
    self.addSender = (args) => {
        configKeeper.addSender(args)
        const sender = commandStore.addSender(args)
        return sender
    }

    /** Say we want to change a value for the nth candidate.
     * And say the nth candidate doesn't exist.
     * Then we have to use an action which acts on the list of candidates,
     * rather than the candidate itself.
     */

    /**
     * Make a sender with a command and a way to do the command.
     * This type of sender deals with lists.
     * @param {Object} args
     * @returns {Object} - methods command(id,value) and go(id,value)
     */
    self.addSenderForList = (args) => {
        const sender = commandStore.addSenderForList(args)
        sender.getCurrentValue = configKeeper.addSenderForList(args)
        return sender
    }

    /**
     * Actually execute the action.
     * @param {Object} command -
     */
    self.execute = (command) => {
        configKeeper.execute(command)
        commandStore.execute(command)
    }

    // ConfigKeeper functions

    self.makeUndo = configKeeper.makeUndo
    self.passLoadConfig = configKeeper.passLoadConfig
    self.passLoadCommands = configKeeper.passLoadCommands
    self.getConfig = configKeeper.getConfig

    self.loadConfig = (newConfig) => {
        // the broadcast will call passLoadConfig
        comMessenger.broadcastLoadConfig(newConfig, self)
    }

    self.loadCommands = (commands) => {
        // the broadcast will call passLoadComands
        comMessenger.broadCastLoadCommands(commands, self)
    }

    // History functions

    /**
     * Put a commandMessenger in the middle,
     * between the command being asked for and the command being done.
     * @param {Object} command - a command to do. See passDo.
     */
    self.do = (command) => {
        // the broadcast will call passDo
        comMessenger.broadcastDo(command, self)
    }

    self.doCommands = (commands) => {
        // the broadcast will call passDoCommands
        comMessenger.broadcastDoCommands(commands, self)
    }

    self.passDo = history.passDo
    self.passDoCommands = history.passDoCommands
    self.undo = history.undo
    self.redo = history.redo
    self.clearHistory = history.clearHistory
}