import * as http from 'http';
const EventSource = require('eventsource');

/** Specifies the location of a message broker server. */
export interface HTTPConfig {
    /** The host that the message brokers express application is running on. */
    host: string;

    /** The port of the host that the message brokers express application is running on. */
    port: number;

    /** The base URL of the message broker. */
    path: string;
}

export interface ErrorHandler {
    (err: Error): void;
}

// A message provide by the server using Server-Sent Events
interface ServerMessage {
    type: string;
    data: string;
    lastEventId: number;
    origin: string;
}

/** A message provided to a subscriber callback */
export interface ClientMessage {
    /** The topic name that the message was published on. */
    topicName: string;

    /** The message broker server allocated id for the message; monotonically increments per topic. */
    id: number;

    /** The data published by the publisher. */
    data: string;
}

export interface ClientCallback {
    (message: ClientMessage): void;
}

/**
 * Returns the client API to the message broker, providing publish and subscribe operations.
 * @param config Configuration specifying the server, port and base URL path of the message broker server.
 * @returns Returns a client providing publish and subscribe operations.
 */
export function client(config: HTTPConfig) {
    return {
        /**
         * Publishes a message to a message broker for other client to receive based on their subscriptions.
         * @param topicName The topic to publish on. This may be one or more URL segments.
         * @param data The data to publish on the topic
         * @param onError Optional error handler callback
         */
        publish: (topicName: string, data: string, onError: ErrorHandler | undefined = undefined): void => {
            var post = http.request({ hostname: config.host, port: config.port, path: `${config.path}/${topicName}`, method: 'POST', headers: { 'Content-Type': 'text/plain', 'Content-Length': Buffer.byteLength(data) } });

            // trap and propigate error messages as required
            post.on('error', (err: Error) => {
                if (onError) {
                    onError(err);
                }
            });

            // send message to the server
            post.write(data);
            post.end();
        },

        /**
         * Registers a subscription to messages on the given topic. The callback is called each time a message to published on the same topic.
         * @param topicName The topic to subscribe to. This may be one or more URL segments.
         * @param callback The function to call when data is publised on the topic; passing a message object.
         */
        subscribe: (topicName: string, callback: ClientCallback | undefined): void => {
            var eventSource = new EventSource(`http://${config.host}:${config.port}${config.path}/${topicName}`);
            var lastEventId = -1;

            // process messages from the server
            eventSource.onmessage = (event: ServerMessage): void => {
                // perform a duplicate message check
                if (event.lastEventId !== lastEventId) {
                    lastEventId = event.lastEventId;

                    // propigate message to the client
                    if (callback) {
                        callback({ topicName: topicName, id: event.lastEventId, data: event.data });
                    }
                }
            }
        }
    }
}