import { DeviceFactoryInterface, DeviceInterface, DeviceInterfaceEvents } from './DeviceTypes'
import { DeviceConfiguration, DeviceStatus, DeviceActionsInterface } from '../../common/DeviceTypes'
import { LogManager } from '../LogManager'
import { EventEmitter } from 'events'
import { io, Socket } from 'socket.io-client'
import { LogLevel } from '../../common/LogTypes'
import shortid = require('shortid')

const TALLYARBITER_TYPE = 'TALLYARBITER'

interface TallyArbiterInternalDevice {
	linkedBusses: string[]
    name: string
    enabled: boolean
    id: string
}

interface TallyArbiterDeviceState {
    busId: string
    deviceId: string
    sources: string[]
}

interface TallyArbiterBusOption {
    label: string
    type: string
	id: string
    color: string
    priority: number
}

export class TallyArbiterDeviceFactory implements DeviceFactoryInterface {
    private logger: LogManager

    constructor(logger: LogManager) {
        this.logger = logger
    }
    
    getType(): string { return TALLYARBITER_TYPE }
    
    create(config: DeviceConfiguration): DeviceInterface | undefined {
        if (!config) return

        if (!config.id) config.id = shortid.generate()

        const device = new TallyArbiterDevice(config, this.logger)
        return device
    }
}

export class TallyArbiterDevice extends EventEmitter implements DeviceInterface {

    private config: DeviceConfiguration
    private logger: LogManager

    private client: Socket | undefined
    private status: DeviceStatus
    
    private actionHandlers: {
        [actionId: string]: {
            [handlerId: string]: {
                params: any
                registered: boolean
                handler: (value: number) => void
            }
        }
    } = {}

    private tallyStates: {
        [actionId: string]: boolean
    } = {}

    public static OutputActions = {
        Tally: 'tally_arbiter'
    }

    private devices: TallyArbiterInternalDevice[] = []
    private deviceStates: TallyArbiterDeviceState[] = []
    private busOptions: TallyArbiterBusOption[] = []
    
    constructor (config: DeviceConfiguration, logger: LogManager) {
        super()

        this.config = config
        this.logger = logger

        this.status = DeviceStatus.Unknown

        this.initConnection()
    }

    private initConnection() {
        if (this.client) {
            this.client.removeAllListeners()
            delete this.client
        }

        if (this.config.enabled) {
            const port = this.config.params.port || 4455
            this.client = io(`http://${this.config.address}:${port}`)
            
            this.client.on('connect', () => {
                this.setStatus(DeviceStatus.Ok)
                this.logger.log('Connected to TallyArbiter', LogLevel.Info, { type: 'DEVICE', id: this.config.id })
                this.client?.emit('companion')
            })
            this.client.on('event', (data: any) => {
                console.log(data)
            })
            this.client.on('device_states', (deviceStates: TallyArbiterDeviceState[]) => { 
                for (const item of deviceStates) {
                    const existing = this.deviceStates.find(d => d.deviceId === item.deviceId && d.busId === item.busId)
                    if (existing) {
                        existing.sources = item.sources
                    } else {
                        this.deviceStates.push(item)
                    }
                }
                this.updateTallyStates()
            });
            this.client.on('devices', (devices: TallyArbiterInternalDevice[]) => {
                this.devices = devices
            });
            this.client.on('bus_options', (busOptions: TallyArbiterBusOption[]) => {
                this.busOptions = busOptions;
            });
        
            this.client.on('disconnect', () => {
                this.setStatus(DeviceStatus.Warning)
                this.logger.log('Disconnected from TallyArbiter', LogLevel.Warning, { type: 'DEVICE', id: this.config.id })})
        }
    }

    private setStatus(status: DeviceStatus) {
        if (this.status != status) {
            this.status = status
            this.emit(DeviceInterfaceEvents.StatusChanged, this.status)
        }
    }

    public getConfig(): DeviceConfiguration { return this.config }
    public updateConfig(config: DeviceConfiguration): void {
        this.config = config
        this.initConnection()
    }
    public connect(): void {
        if (this.client) this.client.connect()
    }
    public disconnect(): void {
        if (this.client) this.client.disconnect()
    }
    public getStatus(): DeviceStatus { return this.status }
    public getConnected(): boolean { return this.client ? this.client.connected : false }
    public getActions(): DeviceActionsInterface {
        return {
            in: [],
            out: [ TallyArbiterDevice.OutputActions.Tally ]
        }
    }

    public getParams() {
        return {
            "devices": this.devices,
            "deviceStates": this.deviceStates,
            "busOptions": this.busOptions
        }
    }

    public trigger(actionId: string, _edge: number, _params: any): void {
        this.logger.log(`Unknown TallyArbiter action: ${actionId}`, LogLevel.Error, { type: 'DEVICE', id: this.config.id })
    }

    private updateTallyStates(): void {
        if (this.actionHandlers[TallyArbiterDevice.OutputActions.Tally] === undefined) return

        for (const actionId in this.actionHandlers[TallyArbiterDevice.OutputActions.Tally]) {
            const actionObj = this.actionHandlers[TallyArbiterDevice.OutputActions.Tally][actionId]

            const deviceState = this.deviceStates.find(d => d.deviceId === actionObj.params.tallyArbiterDeviceId && d.busId == actionObj.params.busId)
            const tally = deviceState !== undefined && deviceState.sources.length > 0
            if (tally !== this.tallyStates[actionId]) {
                actionObj.handler(tally ? 1 : 0)
                this.tallyStates[actionId] = tally
            }
        }
    }
    public addHandler(id: string, action: string, params: any, handler: (value: number) => void): void {
        if (this.actionHandlers[action] === undefined) this.actionHandlers[action] = {}

        this.actionHandlers[action][id] = {
            params: params,
            registered: false,
            handler: handler
        }

        if (action === TallyArbiterDevice.OutputActions.Tally) {
            this.updateTallyStates()
        }
    }
    public removeHandler(handlerId: string): void {
        for (const actionId in this.actionHandlers) {
            if (this.actionHandlers[actionId][handlerId] !== undefined) {
                delete this.actionHandlers[actionId][handlerId]
            }
        }
    }
    public destroy(): void {
        if (this.client) {
            this.client.removeAllListeners()
            delete this.client
        }

        this.removeAllListeners()
    }
}