import { DeviceFactoryInterface, DeviceInterface, DeviceInterfaceEvents } from './DeviceTypes'
import { DeviceConfiguration, DeviceStatus, DeviceActionsInterface } from '../../common/DeviceTypes'
import { LogManager } from '../LogManager'
import { EventEmitter } from 'events'
import { Client } from 'node-osc'
import { LogLevel } from '../../common/LogTypes'
import shortid = require('shortid')

const COMPANION_TYPE = 'COMPANION'

export class CompanionDeviceFactory implements DeviceFactoryInterface {
    private logger: LogManager

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

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

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

export class CompanionDevice extends EventEmitter implements DeviceInterface {

    private config: DeviceConfiguration
    private logger: LogManager

    private client: Client | undefined
    private status: DeviceStatus
    
    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.close()
            delete this.client
        }

        if (this.config.enabled) {
            const port = this.config.params.port || 12321
            this.client = new Client(this.config.address, port)
            
            this.setStatus(DeviceStatus.Ok)
            this.logger.log('Connected to Companion', LogLevel.Info, { 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) {
            const port = this.config.params.port || 12321
            this.client = new Client(this.config.address, port)
            
            this.setStatus(DeviceStatus.Ok)
            this.logger.log('Connected to Companion', LogLevel.Info, { type: 'DEVICE', id: this.config.id })
        }
    }
    public disconnect(): void {
        if (this.client) {
            this.client.close()
            delete this.client
        }
    }
    public getStatus(): DeviceStatus { return this.status }
    public getConnected(): boolean { return true }
    public getActions(): DeviceActionsInterface {
        return {
            in: [ 'press' ],
            out: []
        }
    }
    public getParams() {
        return {}
    }
    public trigger(actionId: string, edge: number, params: any): void {
        if (this.client) {
            if (actionId === 'press') {
                const direction = edge !== 0
                try {
                    this.client.send(`/location/${params.page}/${params.row}/${params.column}/${direction ? 'down' : 'up'}`, 1, (err: any) => {
                        if (err) {
                            console.log(err)
                            this.logger.log(`Companion trigger error: ${err}`, LogLevel.Error, { type: 'DEVICE', id: this.config.id })

                            this.client?.close()
                            delete this.client
                            
                            const port = this.config.params.port || 12321
                            this.client = new Client(this.config.address, port)
                        } else {
                            if (direction)
                                this.logger.log(`Companion button down: ${params.page}/${params.row}/${params.column}`, LogLevel.Info, { type: 'DEVICE', id: this.config.id })
                            else
                                this.logger.log(`Companion button up: ${params.page}/${params.row}/${params.column}`, LogLevel.Info, { type: 'DEVICE', id: this.config.id })
                        }
                    })
                } catch (e) {
                    this.logger.log(`Companion trigger exception: ${e}`, LogLevel.Error, { type: 'DEVICE', id: this.config.id })
                }
            } else {
                this.logger.log(`Unknown companion action: ${actionId}`, LogLevel.Error, { type: 'DEVICE', id: this.config.id })
            }
        } else {
            this.logger.log(`Companion not connected.`, LogLevel.Error, { type: 'DEVICE', id: this.config.id })
        }
    }
    public addHandler(_handlerId: string, _action: string, _params: any, _handler: (value: number) => void): void {
        // nop
    }
    public removeHandler(_handlerId: string): void {
        // nop
    }
    public destroy(): void {
        if (this.client) {
            this.client.close()
            delete this.client
        }

        this.removeAllListeners()
    }
}