"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AtemDevice = exports.AtemDeviceFactory = void 0;
// import { GetModelSpec, ModelSpec } from './AtemModels'
const atem_connection_1 = require("atem-connection");
const events_1 = require("events");
// import { AtemInfo } from './AtemInfo'
const DeviceTypes_1 = require("./DeviceTypes");
const shortid = require("shortid");
const DeviceTypes_2 = require("../../common/DeviceTypes");
const LogTypes_1 = require("../../common/LogTypes");
const enums_1 = require("atem-connection/dist/enums");
const tally_1 = require("atem-connection/dist/lib/tally");
const commands_1 = require("atem-connection/dist/commands");
process.execArgv = [];
const ATEM_TYPE = 'ATEM';
class AtemDeviceFactory {
    constructor(logger) {
        this.logger = logger;
    }
    getType() { return ATEM_TYPE; }
    create(config) {
        if (!config)
            return;
        if (!config.id)
            config.id = shortid.generate();
        const device = new AtemDevice(config, this.logger);
        return device;
    }
}
exports.AtemDeviceFactory = AtemDeviceFactory;
class AtemDevice extends events_1.EventEmitter {
    static includesPath(paths, test) {
        if (!paths)
            return false;
        for (const path of paths) {
            if (path.startsWith(test))
                return true;
        }
        return false;
    }
    static isPath(paths, test) {
        if (!paths)
            return false;
        for (const path of paths) {
            if (!path.startsWith(test))
                return false;
        }
        return true;
    }
    constructor(config, logger) {
        super();
        this.connected = false;
        this.status = DeviceTypes_2.DeviceStatus.Unknown;
        this.actionHandlers = {};
        this.tallyStates = {};
        this.keyStates = {};
        this.auxStates = {};
        this.momentaryAuxStore = {};
        this.config = config;
        this.logger = logger;
        this.state = atem_connection_1.AtemStateUtil.Create();
        this.client = new atem_connection_1.Atem({ disableMultithreaded: true });
        this.client.on('connected', () => {
            this.state = this.client.state || atem_connection_1.AtemStateUtil.Create();
            // this.spec = GetModelSpec(this.state.info.model)
            this.connected = true;
            this.setStatus(DeviceTypes_2.DeviceStatus.Ok);
            this.emit('connected');
            this.logger.log(`ATEM connected successfully.`, LogTypes_1.LogLevel.Info, { type: 'DEVICE', id: this.config.id });
            this.updateAllStates();
            // setTimeout(() => {
            //     this.emit(DeviceInterfaceEvents.ParamsChanged)
            //     this.updateAllStates()
            // }, 1000)
        });
        this.client.on('disconnected', () => {
            this.connected = false;
            this.setStatus(DeviceTypes_2.DeviceStatus.Unknown);
            this.logger.log(`ATEM disconnected.`, LogTypes_1.LogLevel.Info, { type: 'DEVICE', id: this.config.id });
        });
        this.client.on('error', (message) => {
            this.connected = false;
            this.setStatus(DeviceTypes_2.DeviceStatus.Error);
            this.logger.log(`ATEM error: ${message}.`, LogTypes_1.LogLevel.Error, { type: 'DEVICE', id: this.config.id });
        });
        this.client.on('stateChanged', (state, paths) => {
            this.state = state;
            if (AtemDevice.isPath(paths, 'info.lastTime'))
                return;
            //console.log(state)
            this.setStatus(DeviceTypes_2.DeviceStatus.Ok);
            this.logger.log(`ATEM state: ${paths.join(', ')}.`, LogTypes_1.LogLevel.Debug, { type: 'DEVICE', id: this.config.id });
            if (AtemDevice.includesPath(paths, 'inputs') || AtemDevice.includesPath(paths, 'macro.macroProperties')) {
                this.emit(DeviceTypes_1.DeviceInterfaceEvents.ParamsChanged);
            }
            if (AtemDevice.includesPath(paths, 'video')) {
                this.updateTallyStates();
                this.updateKeyStates();
            }
            if (AtemDevice.includesPath(paths, 'video.auxilliaries')) {
                this.updateAuxStates();
            }
            this.emit('stateChanged', state, paths);
        });
        if (config.enabled) {
            this.connect();
        }
    }
    updateConfig(config) {
        if (config.enabled === true && !this.config.enabled) {
            this.connect();
        }
        else if (config.enabled === false && this.config.enabled) {
            this.disconnect();
        }
        else if (config.address !== this.config.address) {
            this.client.disconnect().then(() => this.connect(), (e) => this.logger.log(e, LogTypes_1.LogLevel.Error, { type: 'DEVICE', id: this.config.id }));
        }
        this.config = config;
    }
    getConfig() { return this.config; }
    getType() { return ATEM_TYPE; }
    getActions() {
        return {
            in: [AtemDevice.InputActions.Key, AtemDevice.InputActions.Macro, AtemDevice.InputActions.Aux],
            out: [AtemDevice.OutputActions.Tally, AtemDevice.OutputActions.Key, AtemDevice.OutputActions.Aux]
        };
    }
    getParams() {
        const inputs = {
            tally: [],
            aux: []
        };
        const inputKeys = Object.getOwnPropertyNames(this.state.inputs);
        for (const inputId of inputKeys) {
            const item = this.state.inputs[parseInt(inputId, 10)];
            if (item) {
                if (item.meAvailability > 0 && item.internalPortType !== enums_1.InternalPortType.MediaPlayerKey) {
                    inputs.tally.push(item);
                }
                if ((item.sourceAvailability & enums_1.SourceAvailability.Auxiliary) !== 0) {
                    inputs.aux.push(item);
                }
            }
        }
        const keys = [];
        let index = 1;
        for (const _key in this.state.video.downstreamKeyers) {
            keys.push({ id: 'dsk' + index, label: 'DSK ' + index });
            index += 1;
        }
        for (const meId in this.state.video.mixEffects) {
            const me = this.state.video.mixEffects[meId];
            if (me) {
                for (const keyId in me.upstreamKeyers) {
                    keys.push({ id: 'me' + meId + '_key' + keyId, label: 'ME ' + (meId + 1) + ' - Key ' + (keyId + 1) });
                }
            }
        }
        const aux = [];
        index = 1;
        for (const key in this.state.video.auxilliaries) {
            const input = this.state.inputs[8001 + parseInt(key, 10)];
            aux.push({ id: key, label: (input ? input.longName : ('Auxilary ' + index)) });
            index += 1;
        }
        const macros = [];
        if (this.state.macro.macroProperties) {
            for (const macroIndex in this.state.macro.macroProperties) {
                const macro = this.state.macro.macroProperties[macroIndex];
                if (macro) {
                    const label = (macroIndex + 1) + ': ' + (macro.name === '' ? `Macro ${macroIndex + 1}` : macro.name);
                    macros.push({ id: macroIndex, label });
                }
            }
        }
        const videohubActions = [];
        for (const item of aux) {
            videohubActions.push({ id: 'aux' + item.id, label: item.label });
        }
        return {
            inputs,
            keys,
            aux,
            macros,
            videohubActions
        };
    }
    connect() {
        this.client.connect(this.config.address);
    }
    disconnect() {
        this.client.disconnect();
    }
    destroy() {
        this.removeAllListeners();
        this.client.disconnect();
    }
    getStatus() { return this.status; }
    getConnected() { return this.connected; }
    trigger(actionId, edge, params) {
        switch (actionId) {
            case 'key':
                // only trigger keys on rising edge
                if (edge === 1) {
                    const keyId = params.keyId + '';
                    const keyAction = params.keyAction + '';
                    const currentState = this.getKeyerState(keyId);
                    if (currentState) {
                        if (keyAction === 'toggle') {
                            this.setKeyerState(keyId, { onAir: !currentState.onAir, inTransition: false });
                        }
                        else if (keyAction === 'auto') {
                            this.setKeyerState(keyId, { onAir: !currentState.onAir, inTransition: true });
                        }
                    }
                }
                break;
            case 'macro':
                // only trigger keys on rising edge
                if (edge === 1) {
                    const macroIndex = params.macroId;
                    const macro = this.state.macro.macroProperties[macroIndex];
                    if (macro && macro.isUsed && !this.state.macro.macroRecorder.isRecording) {
                        if (this.state.macro.macroPlayer.isRunning) {
                            if (this.state.macro.macroPlayer.macroIndex === macroIndex) {
                                this.client.macroStop();
                            }
                        }
                        else if (this.state.macro.macroPlayer.isWaiting) {
                            if (this.state.macro.macroPlayer.macroIndex === macroIndex) {
                                this.client.macroContinue();
                            }
                        }
                        else {
                            this.client.macroRun(macroIndex);
                        }
                    }
                }
                break;
            case 'aux':
                const auxBusId = params.auxId;
                const auxSrcId = params.auxSrc;
                const input = this.state.inputs[parseInt(auxSrcId, 10)];
                if (input && this.state.video.auxilliaries[auxBusId] !== undefined) {
                    if (params.auxAction === 'latch' && edge === 1) {
                        delete this.momentaryAuxStore[auxBusId];
                        this.client.setAuxSource(input.inputId, parseInt(auxBusId, 10));
                    }
                    else if (params.auxAction === 'momentary') {
                        if (edge === 1) {
                            this.momentaryAuxStore[auxBusId] = this.state.video.auxilliaries[auxBusId];
                            this.client.setAuxSource(input.inputId, parseInt(auxBusId, 10));
                        }
                        else {
                            const value = this.momentaryAuxStore[auxBusId];
                            if (value !== undefined) {
                                this.client.setAuxSource(value, parseInt(auxBusId, 10));
                                delete this.momentaryAuxStore[auxBusId];
                            }
                        }
                    }
                }
                break;
        }
    }
    getKeyerState(keyId) {
        if (!keyId)
            return;
        if (keyId.startsWith('dsk')) {
            const dskId = parseInt(keyId.substr(3), 10) - 1;
            const dsk = this.state.video.downstreamKeyers[dskId];
            if (dsk !== undefined) {
                return {
                    onAir: dsk.onAir,
                    inTransition: dsk.isAuto
                };
            }
        }
        else if (keyId.startsWith('me')) {
            const keyIdParts = keyId.split('_');
            if (keyIdParts.length === 2) {
                const meId = parseInt(keyIdParts[0].substr(2, 1), 10);
                const uskId = parseInt(keyIdParts[1].substr(3), 10);
                const me = this.state.video.mixEffects[meId];
                const usk = me?.upstreamKeyers[uskId];
                if (usk) {
                    return {
                        onAir: usk.onAir,
                        inTransition: (me?.transitionPosition.inTransition || false) && (me.transitionProperties.selection.includes((1 << (uskId + 1))))
                    };
                }
            }
        }
        return undefined;
    }
    setKeyerState(keyId, desiredState) {
        if (!keyId || !desiredState)
            return;
        if (keyId.startsWith('dsk')) {
            const dskId = parseInt(keyId.substr(3), 10) - 1;
            const dsk = this.state.video.downstreamKeyers[dskId];
            if (dsk) {
                if (dsk.onAir !== desiredState.onAir) {
                    if (desiredState.inTransition) {
                        this.client.autoDownstreamKey(dskId, desiredState.onAir);
                        console.log('DSK auto');
                    }
                    else {
                        this.client.setDownstreamKeyOnAir(desiredState.onAir, dskId);
                        console.log('DSK set');
                    }
                }
            }
        }
        else if (keyId.startsWith('me')) {
            const keyIdParts = keyId.split('_');
            if (keyIdParts.length === 2) {
                const meId = parseInt(keyIdParts[0].substr(2, 1), 10);
                const uskId = parseInt(keyIdParts[1].substr(3), 10);
                const me = this.state.video.mixEffects[meId];
                const usk = me?.upstreamKeyers[uskId];
                if (me && usk) {
                    if (usk.onAir !== desiredState.onAir) {
                        if (desiredState.inTransition) {
                            const keyInProperties = {
                                nextSelection: [(1 << (uskId + 1))],
                                nextStyle: me.transitionProperties.style
                            };
                            const keyOutProperties = {
                                nextSelection: me.transitionProperties.nextSelection,
                                nextStyle: me.transitionProperties.style
                            };
                            const cmd = new commands_1.TransitionPropertiesCommand(me.index);
                            cmd.updateProps(keyInProperties);
                            console.log('Send TransitionProperties / auto for ' + keyId);
                            this.client.sendCommand(cmd).then(() => {
                                console.log('Send auto trans / auto for ' + keyId);
                                this.client.autoTransition(me.index).then(() => {
                                    setTimeout(() => {
                                        console.log('Send restoring TransitionProperties / auto for ' + keyId);
                                        const cmd = new commands_1.TransitionPropertiesCommand(me.index);
                                        cmd.updateProps(keyOutProperties);
                                        this.client.sendCommand(cmd);
                                    }, 100);
                                }, err2 => this.logger.log(err2, LogTypes_1.LogLevel.Error, { type: 'DEVICE', id: this.config.id }));
                            }, err => this.logger.log(err, LogTypes_1.LogLevel.Error, { type: 'DEVICE', id: this.config.id }));
                        }
                        else {
                            this.client.setUpstreamKeyerOnAir(desiredState.onAir, me.index, uskId);
                        }
                    }
                }
            }
        }
    }
    addHandler(id, action, params, handler) {
        if (this.actionHandlers[action] === undefined)
            this.actionHandlers[action] = {};
        this.actionHandlers[action][id] = {
            params,
            handler
        };
        if (action === AtemDevice.OutputActions.Tally) {
            this.updateTallyStates();
        }
        else if (action === AtemDevice.OutputActions.Key) {
            this.updateKeyStates();
        }
        else if (action === AtemDevice.OutputActions.Aux) {
            this.updateAuxStates();
        }
    }
    getAuxInput(auxBusId) {
        return this.state.video.auxilliaries[parseInt(auxBusId, 10)];
    }
    removeHandler(handlerId) {
        for (const actionId in this.actionHandlers) {
            if (this.actionHandlers[actionId][handlerId] !== undefined) {
                delete this.actionHandlers[actionId][handlerId];
            }
        }
    }
    // public getModel (): string {
    //     if (this.spec !== undefined) return this.spec.label
    //     return ''
    // }
    setStatus(status) {
        if (this.status != status) {
            this.status = status;
            this.emit(DeviceTypes_1.DeviceInterfaceEvents.StatusChanged, this.status);
        }
    }
    // private nextTransitionIncludesKey (me: VideoState.MixEffect, key: number) {
    //     return (me.transitionProperties.nextSelection & (1 << (key + 1))) !== 0
    // }
    updateAllStates() {
        this.updateTallyStates();
        this.updateKeyStates();
        this.updateAuxStates();
    }
    updateTallyStates() {
        const activeInputIds = [[], [], [], []];
        for (const meId in this.state.video.mixEffects) {
            const me = this.state.video.mixEffects[meId];
            if (me) {
                activeInputIds[me.index] = (0, tally_1.listVisibleInputs)('program', this.state, me.index);
            }
            else {
                activeInputIds[parseInt(meId, 10)] = [];
            }
        }
        for (const actionId in this.actionHandlers[AtemDevice.OutputActions.Tally]) {
            const actionObj = this.actionHandlers[AtemDevice.OutputActions.Tally][actionId];
            const tally = actionObj.params.tallyMe1 && activeInputIds[0].includes(actionObj.params.tallyInput)
                || actionObj.params.tallyMe2 && activeInputIds[1].includes(actionObj.params.tallyInput)
                || actionObj.params.tallyMe3 && activeInputIds[2].includes(actionObj.params.tallyInput)
                || actionObj.params.tallyMe4 && activeInputIds[3].includes(actionObj.params.tallyInput);
            if (tally !== this.tallyStates[actionId]) {
                this.tallyStates[actionId] = tally;
                actionObj.handler(tally ? 1 : 0);
            }
        }
    }
    updateKeyStates() {
        for (const actionId in this.actionHandlers[AtemDevice.OutputActions.Key]) {
            const actionObj = this.actionHandlers[AtemDevice.OutputActions.Key][actionId];
            const keyId = (actionObj.params.keyTally + '');
            const setTally = (t) => {
                if (t !== this.keyStates[actionId]) {
                    this.keyStates[actionId] = t;
                    actionObj.handler(t ? 1 : 0);
                }
            };
            if (keyId.startsWith('dsk')) {
                const dskId = parseInt(keyId.substr(3), 10) - 1;
                if (this.state.video.downstreamKeyers[dskId]) {
                    setTally(this.state.video.downstreamKeyers[dskId]?.onAir || false);
                }
            }
            else if (keyId.startsWith('me')) {
                const keyIdParts = keyId.split('_');
                if (keyIdParts.length === 2) {
                    const meId = parseInt(keyIdParts[0].substr(2, 1), 10);
                    const uskId = parseInt(keyIdParts[1].substr(3), 10);
                    const me = this.state.video.mixEffects[meId];
                    if (me && me.upstreamKeyers[uskId]) {
                        setTally(me.upstreamKeyers[uskId]?.onAir || false);
                    }
                }
            }
        }
    }
    updateAuxStates() {
        for (const actionId in this.actionHandlers[AtemDevice.OutputActions.Aux]) {
            const actionObj = this.actionHandlers[AtemDevice.OutputActions.Aux][actionId];
            const auxBusId = actionObj.params.auxTallyId;
            const auxSrc = actionObj.params.auxTallySrc;
            if (this.state.video.auxilliaries[auxBusId] !== undefined) {
                const tally = this.state.video.auxilliaries[auxBusId] === auxSrc;
                if (tally !== this.auxStates[actionId]) {
                    this.auxStates[actionId] = tally;
                    actionObj.handler(tally ? 1 : 0);
                }
            }
        }
    }
}
exports.AtemDevice = AtemDevice;
AtemDevice.OutputActions = {
    Tally: 'tally',
    Key: 'key',
    Aux: 'aux'
};
AtemDevice.InputActions = {
    Key: 'key',
    Macro: 'macro',
    Aux: 'aux'
};
//# sourceMappingURL=AtemDevice.js.map