import Daemon from './Daemon';
import Journal from './Journal';
import Supervisor from './Supervisor';

const DEFAULT_CONFIG = {
    base: 2,
    supervisor_epsilon: 0.01,
    daemons: [
        { human: 2, robot: 2, epsilon: 0.01 },
        { human: 3, robot: 3, epsilon: 0.01 },
        { human: 4, robot: 4, epsilon: 0.01 },
        { human: 5, robot: 5, epsilon: 0.01 },
        { human: 6, robot: 6, epsilon: 0.01 },
    ],
};

export default class Predictor {
    /**
     * @type {Number}
     */
    base;

    /**
     * @type {Number}
     */
    score;

    /**
     * @type {Journal}
     */
    journal;

    /**
     * @type {Supervisor}
     */
    supervisor;

    /**
     * @param {Object} config
     */
    constructor(config = DEFAULT_CONFIG) {
        this.base = config.base;
        this.score = 0;
        this.journal = new Journal();
        const daemons = this._createDaemons(config.daemons);
        this.supervisor = new Supervisor(daemons, config.supervisor_epsilon);
    }

    /**
     * @param {Number|String} humanValue
     *
     * @returns {Number}
     */
    pass(humanValue) {
        const value = parseInt(humanValue, 10);
        if (value < 0 || value >= this.base) {
            throw new Error(`Passed value must be in [0, ${this.base})`);
        }
        const prediction = this.supervisor.predict(this.journal);
        this.score += prediction === value ? -1 : 1;
        this.supervisor.adjust(this.journal, value);
        this.journal.makeMove(value, prediction);
        return prediction;
    }

    /**
     * @param {Object} daemonConfigs
     *
     * @returns {Daemon[]}
     *
     * @private
     */
    _createDaemons(daemonConfigs) {
        return daemonConfigs.map(config => {
            return new Daemon(
                this.base,
                config.human,
                config.robot,
                config.epsilon || 0.01
            );
        });
    }

    /**
     * @returns {Number}
     */
    stepCount() {
        return this.journal.length;
    }
}