import * as net from 'net'
import { EventEmitter } from 'events'
import { VideohubCommand } from './Command'

const PING_INTERVAL: number = 5000

export enum ConnectionStatus {
    Idle = 'IDLE',
    Connecting = 'CONNECTING',
    Connected = 'CONNECTED'
}

interface AdapterConfigInterface {
    host: string
    port: number
}

// tslint:disable-next-line:no-console
const debug = console.log

export class VideohubConnection extends EventEmitter {
    public RECONNECT_TIMEOUT: number = 500

    private pingInterval: NodeJS.Timeout | null = null

    private socket: net.Socket | undefined = undefined

    private config: AdapterConfigInterface

    private command: string | null = null

    private stash: string[] = []

    private status: ConnectionStatus = ConnectionStatus.Idle

    constructor (host: string) {
        super()

        this.config = {
            host,
            port: 9990
        }
    }

    public getStatus (): ConnectionStatus {
        return this.status
    }

    public connect (): void {
        if (!this.socket) {
            this.createSocket()
        }

        if (this.socket) {
            this.socket.connect(this.config.port, this.config.host)
            this.status = ConnectionStatus.Connecting
        }
    }

    public send (command: VideohubCommand): void {
        if (this.socket !== undefined && command !== undefined) {
            try {
                this.socket.write(command.format())
            } catch (e) {
                debug(e)
            }
        }
    }

    private deleteSocket (): void {
        if (this.socket !== undefined) {
            this.socket.destroy()
            delete this.socket
        }

        if (this.pingInterval !== null) {
            clearInterval(this.pingInterval)
            this.pingInterval = null
        }
    }

    private createSocket (): void {
        let receivebuffer = ''

        this.deleteSocket()

        if (this.config.port === undefined) {
            this.config.port = 9990
        }

        if (this.config.host) {
            this.socket = new net.Socket()
            this.socket.setKeepAlive(true)
            this.socket.setNoDelay(true)

            this.socket.on('connect', () => {
                this.status = ConnectionStatus.Connected
                this.pingInterval = setInterval(() => {
                    this.send(new VideohubCommand('PING'))
                }, PING_INTERVAL)
            })

            this.socket.on('error', (err: Error) => {
                this.deleteSocket()
                this.status = ConnectionStatus.Idle
                this.emit('debug', err)
                setTimeout(() => this.connect(), this.RECONNECT_TIMEOUT)
            })

            this.socket.on('end', () => {
                this.deleteSocket()
                this.status = ConnectionStatus.Idle
                setTimeout(() => this.connect(), this.RECONNECT_TIMEOUT)
            })

            // separate buffered stream into lines with responses
            this.socket.on('data', (chunk) => {
                let i = 0
                let line = ''
                let offset = 0
                receivebuffer += chunk

                // tslint:disable-next-line:no-conditional-assignment
                while ((i = receivebuffer.indexOf('\n', offset)) !== -1) {
                    line = receivebuffer.substr(offset, i - offset)
                    offset = i + 1
                    if (this.socket !== undefined) {
                        this.socket.emit('receiveline', line.toString())
                    }
                }

                receivebuffer = receivebuffer.substr(offset)
            })

            this.socket.on('receiveline', (line) => {
                if (this.command === null && line.match(/:/)) {
                    this.command = line
                } else if (this.command === null && line.trim() === 'ACK') {
                    this.emit('ack')
                } else if (this.command === null && line.trim() === 'NAK') {
                    this.emit('nak')
                } else if (this.command !== null && line.length > 0) {
                    this.stash.push(line.trim())
                } else if (line.length === 0 && this.command !== null) {
                    const cmd = this.command.trim().split(/:/)[0]
                    this.emit('data', cmd, this.stash)

                    this.stash = []
                    this.command = null
                } else if (line.length > 0) {
                    debug('weird response from videohub', line, line.length)
                }
            })
        }
    }
}
