/*
 * Copyright 2016-2020 The NATS Authors
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import events = require('events');
import nats = require('nats');
import * as tls from 'tls';

export const version: string;



/**
 * Connect to a nats-server and return the client.
 * Argument can be a url, or an object with a 'url'
 * property and additional options.
 */
export function connect(clusterID: string, clientID: string, opts?: StanOptions): Stan;

export interface StanOptions extends ClientOpts {
	url?: string,
    connectTimeout?: number,
    ackTimeout?: number,
	discoverPrefix?: string,
    maxPubAcksInflight?: number,
    stanEncoding?: string,
    stanMaxPingOut?: number,
    stanPingInterval?: number,
	nc?: nats.Client
}

// these are standard node-nats options, some are omitted
// like json, noEcho, preserveBuffers because they don't
// make sense in nats-streaming. Encoding is here, however
// it's value must always be set to binary as the client
// exchanges protobuf messages
export interface ClientOpts {
    encoding?: BufferEncoding,
    maxPingOut?: number,
    maxReconnectAttempts?: number,
    name?: string,
    nkey?: string,
    noRandomize?: boolean,
    nonceSigner?: Function,
    pass?: string,
    pedantic?: boolean,
    pingInterval?: number,
    reconnect?: boolean,
    reconnectTimeWait?: number,
    servers?: Array<string>,
    tls?: boolean | tls.TlsOptions,
    token?: string,
    tokenHandler?: Function,
    url?: string,
    useOldRequestStyle?: boolean,
    user?: string,
    userCreds?: string,
    userJWT?: string | Function,
    verbose?: boolean,
    waitOnFirstConnect?: boolean,
    yieldTime?: number
}


declare class Message {
    /**
     * Returns the subject associated with this Message
     */
    getSubject():string;

    /**
     * Returns the sequence number of the message in the stream.
     */
    getSequence():number;

    /**
     * Returns a Buffer with the raw message payload
     */
    getRawData():Buffer;

    /**
     * Returns the data associated with the message payload. If the stanEncoding is not
     * set to 'binary', a string is returned.
     */
    getData():String|Buffer;

    /**
     * Returns the raw timestamp set on the message. This number is not a valid time in JavaScript.
     */
    getTimestampRaw():number;

    /**
     * Returns a Date object representing the timestamp of the message. This is an approximation of the timestamp.
     */
    getTimestamp():Date;

    /**
     * Returns a boolean indicating if the message is being redelivered
     */
    isRedelivered():boolean;

    /**
     * Returns an optional IEEE CRC32 checksum
     */
    getCrc32():number;

    /**
     * Acks the message, note this method shouldn't be called unless
     * the manualAcks option was set on the subscription.
     */
    ack(): void;
}


declare class Subscription extends events.EventEmitter {
    /**
     * Returns true if the subscription has been closed or unsubscribed from.
     */
    isClosed():boolean;

    /**
     * Unregisters the subscription from the streaming server.
     */
    unsubscribe(): void;

    /**
     * Close removes the subscriber from the server, but unlike the Subscription#unsubscribe(),
     * the durable interest is not removed. If the client has connected to a server
     * for which this feature is not available, Subscription#Close() will emit a
     * Subscription#error(NO_SERVER_SUPPORT) error. Note that this affects durable clients only.
     * If called on a non-durable subscriber, this is equivalent to Subscription#close()
     */
    close(): void;
}

/**
 * Callback informs the client that the message was processed by the server
 * @param err - undefined if there is no error processing the message
 * @param guid - the guid correlating the message with the callback invocation.
 */
interface AckHandlerCallback { (err: Error | undefined, guid: string): void; }



declare class Stan extends events.EventEmitter {

	/**
	 * Close the connection to the server.
	 */
	close(): void;

    /**
     * Publishes a message to the streaming server with the specified subject and data.
     * @param subject
     * @param data
     * @param callback
     * @returns guid generated for the published message
     */
    publish(subject: string, data?: Uint8Array|string|Buffer, callback?:AckHandlerCallback): string;

    /**
     * Subscribes to a given subject as an optional member of a queue group.
     * @param subject
     * @param qGroup
     * @param opts
     */
    subscribe(subject: string, opts?: SubscriptionOptions): Subscription
    subscribe(subject: string, qGroup: string, opts?: SubscriptionOptions): Subscription

    /**
     * Returns a SubscriptionOptions initialized to defaults
     */
    subscriptionOptions(): SubscriptionOptions;

}

declare enum StartPosition {
    NEW_ONLY = 0,
    LAST_RECEIVED,
    TIME_DELTA_START,
    SEQUENCE_START,
    FIRST
}

declare interface SubscriptionOptions  {
    durableName?: string;
    maxInFlight?: number;
    ackWait?: number;
    startPosition: StartPosition;
    startSequence?: number;
    startTime?: number;
    manualAcks?: boolean;

    /**
     * Sets the maximun number of unacknowledged messages that the streaming server will allow
     * before it sends a message.
     * @param n
     */
    setMaxInFlight(n: number):SubscriptionOptions;

    /**
     * Sets the number of milliseconds before a message is considered unacknowledged by
     * the streaming server.
     */
    setAckWait(millis: number): SubscriptionOptions;

    /**
     * Configures the subscription start mode.
     * Typically you would invoke this message with StartPostion#FIRST, StartPosition#NEW_ONLY or
     * StartPosition#LAST_RECEIVED. For all other uses (SubscriptionOptions#setStartSequence,
     * SubscriptionOptions#setStartTime, SubscriptionOptions#setStartAtTimeDelta, or
     * SubscriptionOptions#setStartWithLastReceived), the  method will configure
     * the startup value and position.
     *
     * @param startPosition
     */
    setStartAt(startPosition: StartPosition): SubscriptionOptions;

    /**
     * Configures the subscription to start with the message having the specified
     * sequence number.
     *
     * @param sequence
     */
    setStartAtSequence(sequence: number): SubscriptionOptions;

    /**
     * Configures the subscription to start with messages sent at the specified date.
     * @param date
     */
    setStartTime(date: Date): SubscriptionOptions;

    /**
     * Configures the subscription to replay  messages sent milliseconds ago.
     * @param millis - the number of milliseconds ago to use as the start time
     */
    setStartAtTimeDelta(millis: number):SubscriptionOptions;

    /**
     * Configures the subscription to replay with the last received message.
     */
    setStartWithLastReceived():SubscriptionOptions;

    /**
     * Configures the subscription to replay from first available message.
     */
    setDeliverAllAvailable():SubscriptionOptions;

    /**
     * Configures the subscription to require manual acknowledgement of messages
     * using Message#acknowledge.
     * @param tf - true if manual acknowlegement is required.
     */
    setManualAckMode(tf: boolean): SubscriptionOptions;

    /**
     * Sets a durable subscription name that the client can specify for the subscription.
     * This enables the subscriber to close the connection without canceling the subscription and
     * resume the subscription with same durable name. Note the server will resume the
     * subscription with messages
     * that have not been acknowledged.
     *
     * @param durableName
     */
    setDurableName(durableName: string): SubscriptionOptions;
}
