import * as path from "path";

import { AuthType } from "../enums/AuthType";
import { ConnectionType } from "../enums/ConnectionType";
import { NodeTrustedMailcore2NativeBinding } from "../types/NodeTrustedMailcore2NativeBinding";
import { IMAPFolderNativeBinding, IMAPFolderStatusNativeBinding, IMAPMessageNativeBinding, IMAPSessionNativeBinding, IMAPSyncResultNativeBinding } from '../types';
import { IMAPFolder } from "./IMAPFolder";
import { IMAPFolderStatus } from "./IMAPFolderStatus";
import { IMAPMessage } from "./IMAPMessage";
import { Data } from "../basetypes/Data";
import { IMAPSyncResult } from "./IMAPSyncResult";
import { MessageFlag } from "../enums/MessageFlag";
import { IMAPStoreFlagsRequestKind } from "../enums/IMAPStoreFlagsRequestKind";

// tslint:disable-next-line: no-var-requires
let bindings: NodeTrustedMailcore2NativeBinding;
try {
    bindings = require(path.join(__dirname, "../../lib/binding/trusted_mailcore2.node"));
} catch(e) {
    bindings = require(path.join(__dirname, "../../lib/binding/trusted_mailcore2_no_crypto.node"));
}

// tslint:disable-next-line
const { IMAPSession: _IMAPSession } = bindings;

/**
 * IMAPSession Class
 *
 * @public
 */
class IMAPSession extends bindings.IMAPSession {
    protected handle: IMAPSessionNativeBinding;

    constructor() {
        super()

        const handle = new _IMAPSession();
        this.handle = handle;
    }

    /**
     * Set hostname
     *
     * @memberof IMAPSession
     */
    set hostname(hostname: string) {
        this.handle.hostname = hostname;
    }

    /**
     * Get hostname
     *
     * @type {string}
     * @memberof IMAPSession
     */
    get hostname(): string {
        return this.handle.hostname;
    }

    /**
     * Set port
     *
     * @memberof IMAPSession
     */
    set port(port: number) {
        this.handle.port = port;
    }

    /**
     * Get port
     *
     * @type {number}
     * @memberof IMAPSession
     */
    get port(): number {
        return this.handle.port;
    }

    /**
     * Set string
     *
     * @memberof IMAPSession
     */
    set username(username: string) {
        this.handle.username = username;
    }

    /**
     * Get string
     *
     * @type {string}
     * @memberof IMAPSession
     */
    get username(): string {
        return this.handle.username;
    }

    /**
     * Set password
     *
     * @memberof IMAPSession
     */
    set password(password: string) {
        this.handle.password = password;
    }

    /**
     * Get password
     *
     * @type {string}
     * @memberof IMAPSession
     */
    get password(): string {
        return this.handle.password;
    }

    /**
     * Set checkCertificateEnabled
     *
     * @memberof IMAPSession
     */
    set checkCertificateEnabled(isEnabled: boolean) {
        this.handle.checkCertificateEnabled = isEnabled;
    }

    /**
     * Get checkCertificateEnabled
     *
     * @type {boolean}
     * @memberof IMAPSession
     */
    get checkCertificateEnabled(): boolean {
        return this.handle.checkCertificateEnabled;
    }

    /**
     * Set OAuth2Token
     *
     * @memberof IMAPSession
     */
    set OAuth2Token(OAuth2Token: string) {
        this.handle.OAuth2Token = OAuth2Token;
    }

    /**
     * Get OAuth2Token
     *
     * @type {string}
     * @memberof IMAPSession
     */
    get OAuth2Token(): string {
        return this.handle.OAuth2Token;
    }

    /**
     * Set authType
     *
     * @memberof IMAPSession
     */
    set authType(authType: AuthType) {
        this.handle.authType = authType;
    }

    /**
     * Get authType
     *
     * @type {AuthType}
     * @memberof IMAPSession
     */
    get authType(): AuthType {
        return this.handle.authType;
    }

    /**
     * Set connectionType
     *
     * @memberof IMAPSession
     */
    set connectionType(connectionType: ConnectionType) {
        this.handle.connectionType = connectionType;
    }

    /**
     * Get connectionType
     *
     * @type {ConnectionType}
     * @memberof IMAPSession
     */
    get connectionType(): ConnectionType {
        return this.handle.connectionType;
    }

    /**
     * Set strictGost flag (to force using GOST for TLS)
     *
     * @memberof IMAPSession
     */
    set strictGost(value: Boolean) {
        this.handle.strictGost = value;
    }

    /**
     * Get strictGost flag (to force using GOST for TLS)
     *
     * @type {Boolean}
     * @memberof IMAPSession
     */
    get strictGost(): Boolean {
        return this.handle.strictGost;
    }

    /**
     * Validate exist account
     *
     * @param {string} from address
     * @returns {number} 0 - success, other - failure
     *
     * @memberof IMAPSession
     */
    public async checkAccount(from: string): Promise<number> {
        return this.handle.checkAccount(from);
    }

    /**
     * Disconnect connection
     *
     * @memberof IMAPSession
     */
    public disconnect(): void {
        this.handle.disconnect();
    }

    /**
     * Disconnect connection asynchronously
     *
     * @memberof IMAPSession
     */
    public disconnectAsync(): Promise<void> {
        return new Promise<void>((resolve, reject) => {
            this.handle.disconnectAsync((error: string | Error | undefined | null) => {
                if (error) {
                    reject(error);
                    return;
                }

                resolve();
            });
        });
    }

    /**
     * Validate connection status
     *
     * @returns {boolean}
     *
     * @memberof IMAPSession
     */
    public isDisconnected(): boolean {
        return this.handle.isDisconnected();
    }

    /**
     * Login if needed to session
     *
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public loginIfNeeded(): number {
        return this.handle.loginIfNeeded();
    }

    /**
     * Asynchronously login if needed to session
     *
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public loginIfNeededAsync(): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.loginIfNeededAsync((error: string | Error | undefined | null, result: number) => {
                if (error) {
                    reject(error);
                }

                resolve(result);
            });
        });
    }

    /**
     * Connect if needed to session
     *
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public connectIfNeeded(): number {
        return this.handle.connectIfNeeded();
    }

    /**
     * Asynchronously connect if needed to session
     *
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public connectIfNeededAsync(): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.connectIfNeededAsync((error: string | Error | undefined | null, result: number) => {
                if (error) {
                    reject(error);
                }

                resolve(result);
            });
        });
    }

    /**
     * Create IMAP folder
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public createFolder(folder: string): number {
        return this.handle.createFolder(folder);
    }

     /**
     * Create IMAP folder
     *
     * @param {string} folder
     * @param {string} flag
     * @return {*}  {number}
     * @memberof IMAPSession
     */
      public createSpecialFolder(folder: string, flag: number): number {
        return this.handle.createSpecialFolder(folder, flag);
    }
    
    /**
     * Delete IMAP folder
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public deleteFolder(folder: string): number {
        return this.handle.deleteFolder(folder);
    }

    /**
     * Rename folder
     *
     * @param {string} folder
     * @param {string} folderOther
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public renameFolder(folder: string, folderOther: string): number {
        return this.handle.renameFolder(folder, folderOther);
    }

    /**
     * Subscribe to folder
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public subscribeFolder(folder: string): number {
        return this.handle.subscribeFolder(folder);
    }

    /**
     * Unsubscribe to folder
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public unsubscribeFolder(folder: string): number {
        return this.handle.unsubscribeFolder(folder);
    }

    /**
     * Fetch all subscribed folders
     *
     * @return {*}  {IMAPFolder[]}
     * @memberof IMAPSession
     */
    public fetchSubscribedFolders(): IMAPFolder[] {
        const folders: IMAPFolderNativeBinding[] = this.handle.fetchSubscribedFolders();
        const result: IMAPFolder[] = [];
        folders?.forEach((item: IMAPFolderNativeBinding) => {

            const value = new IMAPFolder();
            value.handle = item;
            result.push(value);
        })
        return result;
    }

    /**
     * Fetch all folders
     *
     * @return {*}  {IMAPFolder[]}
     * @memberof IMAPSession
     */
    public fetchAllFolders(): IMAPFolder[] {
        const folders: IMAPFolderNativeBinding[] = this.handle.fetchAllFolders();
        const result: IMAPFolder[] = [];
        folders.forEach((item: IMAPFolderNativeBinding) => {

            const value = new IMAPFolder();
            value.handle = item;
            result.push(value);
        })
        return result;
    }

    /**
     * Fetch all folders asynchronously
     *
     * @return {*}  {IMAPFolder[]}
     * @memberof IMAPSession
     */
    public fetchAllFoldersAsync(): Promise<IMAPFolder[]> {
        return new Promise<IMAPFolder[]>((resolve, reject) => {
            this.handle.fetchAllFoldersAsync((
                error: string | Error | undefined | null,
                folders: IMAPFolderNativeBinding[]
            ) => {
                if (error) {
                    return reject(error);
                }

                const result: IMAPFolder[] = [];
                folders.forEach((item: IMAPFolderNativeBinding) => {
                    const value = new IMAPFolder();
                    value.handle = item;
                    result.push(value);
                });
                resolve(result);
            });
        });
    }

    /**
     * Get folder status
     *
     * @param {string} folder Folder to fetch status
     * @return {IMAPFolderStatus} Folder status object
     * @memberof IMAPSession
     */
    public folderStatus(folder: string): IMAPFolderStatus {
        const status: IMAPFolderStatusNativeBinding = this.handle.folderStatus(folder);
        const result: IMAPFolderStatus = new IMAPFolderStatus();
        result.handle = status;

        return result;
    }

    /**
     * Get folder status asynchronously
     *
     * @param {string} folder Folder to fetch status
     * @return {IMAPFolderStatus} Folder status object
     * @memberof IMAPSession
     */
    public folderStatusAsync(folder: string): Promise<IMAPFolderStatus> {
        return new Promise<IMAPFolderStatus>((resolve, reject) => {
            this.handle.folderStatusAsync(folder,
                (error: string | Error | undefined | null, status: IMAPFolderStatusNativeBinding) => {
                    if (error) {
                        return reject(error);
                    }

                    const result: IMAPFolderStatus = new IMAPFolderStatus();
                    result.handle = status;
                    resolve(result);
                });
        });
    }

    /**
     * Move messages
     *
     * @param {string} folder
     * @param {number} uidSet
     * @param {string} destFolder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public moveMessages(folder: string, uidSet: number, destFolder: string): number {
        return this.handle.moveMessages(folder, uidSet, destFolder);
    }

    /**
     * Move messages asynchronously
     *
     * @param {string} folder
     * @param {number} uidSet
     * @param {string} destFolder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public moveMessagesAsync(folder: string, uidSet: number, destFolder: string): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.moveMessagesAsync(
                folder,
                uidSet,
                destFolder,
                (error: string | Error | undefined | null, result: number) => {
                    if (error) {
                        reject(error);
                    }

                    resolve(result);
                }
            );
        });
    }

    /**
     * Fetch messages by UID
     *
     * @param {string} folder foder to fetch messages from
     * @param {number} rangeStart Start of UIDs range, 0 by default
     * @param {number} rangeEnd End of UIDs range, "infinity" by default
     * @return {IMAPMessage[]} Array of fetched messages info
     * @memberof IMAPSession
     */
    public fetchMessagesByUID(folder: string, rangeStart?: number, rangeEnd?: number): IMAPMessage[] {
        const messages: IMAPMessageNativeBinding[] = this.handle.fetchMessagesByUID(folder, rangeStart, rangeEnd);
        const result: IMAPMessage[] = [];
        messages.forEach((item: IMAPMessageNativeBinding) => {
            const value = new IMAPMessage();
            value.handle = item;
            result.push(value);
        })
        return result;
    }

    /**
     * Fetch messages by UID asynchronously
     *
     * @param {string} folder foder to fetch messages from
     * @param {number} rangeStart Start of UIDs range, 0 by default
     * @param {number} rangeEnd End of UIDs range, "infinity" by default
     * @return {Promise<IMAPMessage[]>} Promise to return array of fetched messages info
     * @memberof IMAPSession
     */
    public fetchMessagesByUIDAsync(folder: string, rangeStart: number, rangeEnd: number): Promise<IMAPMessage[]> {
        return new Promise<IMAPMessage[]>((resolve, reject) => {
            this.handle.fetchMessagesByUIDAsync(folder, rangeStart, rangeEnd, (error: string | Error | undefined | null, messages: IMAPMessageNativeBinding[]) => {
                if (error) {
                    reject(error);
                    return;
                }

                const result: IMAPMessage[] = [];
                messages.forEach((item: IMAPMessageNativeBinding) => {

                    const value = new IMAPMessage();
                    value.handle = item;
                    result.push(value);
                })

                resolve(result);
            });
        });
    }

    /**
     * Fetch messsage by uid
     *
     * @param {string} folder
     * @param {number} uid
     * @return {Data} Data object with message data
     * @memberof IMAPSession
     */
    public fetchMessageByUID(folder: string, uid: number): Data {
        const hdata = this.handle.fetchMessageByUID(folder, uid);
        const data = new Data();
        data.handle = hdata;

        return data;
    }

    /**
     * Fetch messsage by uid asynchonously
     *
     * @param {string} folder
     * @param {number} uid
     * @return {Promise<Data>} Promise with Data object with message data
     * @memberof IMAPSession
     */
    public fetchMessageByUIDAsync(folder: string, uid: number): Promise<Data> {
        return new Promise<Data>((resolve, reject) => {
            try {
                this.handle.fetchMessageByUIDAsync(
                    folder,
                    uid,
                    (error: string | Error | undefined | null, hdata) => {
                        if (error || !hdata.length()) {
                            reject(error);
                            return;
                        }

                        const data = new Data();
                        if (hdata) {
                            data.handle = hdata;
                        }
                        resolve(data);
                    }
                );
            } catch (e) {
                reject(e);
            }
        });
    }

    /**
     * Fetch messsage by number
     *
     * @param {string} folder
     * @param {number} messageNumber
     * @return {Data}
     * @memberof IMAPSession
     */
    public fetchMessageByNumber(folder: string, messageNumber: number): Data {
        const hdata = this.handle.fetchMessageByNumber(folder, messageNumber);

        const data = new Data();
        data.handle = hdata;

        return data;
    }

    /**
     * Get isQResyncEnabled
     *
     * @type {boolean}
     * @memberof IMAPSession
     */
    get isQResyncEnabled(): boolean {
        return this.handle.isQResyncEnabled;
    }

    /**
     * Quick messsages synchronization by uid (with QRESYNC/CONDSTORE)
     *
     * @param {string} folder
     * @param {number} rangeStart? Start of UID range. Default value is 1
     * @param {number} rangeEnd? End of UID range. Default value is "int max" (to sync all messages)
     * @return {IMAPSyncResult | undefined}
     * @memberof IMAPSession
     */
    public syncMessagesByUID(
        folder: string,
        modseq: number,
        rangeStart?: number,
        rangeEnd?: number
    ): IMAPSyncResult | undefined {
        const syncResultNative: IMAPSyncResultNativeBinding | undefined = this.handle.syncMessagesByUID(
            folder,
            modseq,
            rangeStart,
            rangeEnd
        );

        if (!syncResultNative) {
            return undefined;
        }

        const syncResult = new IMAPSyncResult();
        syncResult.handle = syncResultNative;
        return syncResult;
    }

    /**
     * Asynchronous quick messsages synchronization by uid (with QRESYNC/CONDSTORE)
     *
     * @param {string} folder
     * @param {number} rangeStart? Start of UID range. Default value is 1
     * @param {number} rangeEnd? End of UID range. Default value is "int max" (to sync all messages)
     * @return {IMAPSyncResult | undefined}
     * @memberof IMAPSession
     */
    public syncMessagesByUIDAsync(
        folder: string,
        modseq: number,
    ): Promise<IMAPSyncResult | undefined> {
        return new Promise<IMAPSyncResult | undefined>((resolve, reject) => {
            this.handle.syncMessagesByUIDAsync(
                folder,
                modseq,
                (
                    error: string | Error | undefined | null,
                    syncResultNative: IMAPSyncResultNativeBinding | undefined
                ) => {
                    if (error) {
                        reject(error);
                    }

                    if (!syncResultNative) {
                        return resolve(undefined);
                    }

                    const syncResult = new IMAPSyncResult();
                    syncResult.handle = syncResultNative;
                    return resolve(syncResult);
                }
            );
        });
    }

    /**
     * Store flags by number
     *
     * @param {string} folder
     * @param {number} uidSet
     * @param {IMAPStoreFlagsRequestKind} requestKind
     * @param {MessageFlag} flags
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public storeFlagsByUID(folder: string, uidSet: number, requestKind: IMAPStoreFlagsRequestKind, flags: MessageFlag): number {
        return this.handle.storeFlagsByUID(folder, uidSet, requestKind, flags);
    }

    /**
     * Store flags by number
     *
     * @param {string} folder
     * @param {number} uidSet
     * @param {IMAPStoreFlagsRequestKind} requestKind
     * @param {MessageFlag} flags
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public storeFlagsByUIDAsync(
        folder: string,
        uidSet: number,
        requestKind: IMAPStoreFlagsRequestKind,
        flags: MessageFlag
    ): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.storeFlagsByUIDAsync(
                folder,
                uidSet,
                requestKind,
                flags,
                (error: string | Error | undefined | null, result: number) => {
                    if (error) {
                        reject(error);
                    }

                    resolve(result);
                });
        });
    }

    /**
     * Store flags by number
     *
     * @param {string} folder
     * @param {number} mnuber
     * @param {IMAPStoreFlagsRequestKind} requestKind
     * @param {MessageFlag} flags
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public storeFlagsByNumber(folder: string, mnuber: number, requestKind: IMAPStoreFlagsRequestKind, flags: MessageFlag): number {
        return this.handle.storeFlagsByNumber(folder, mnuber, requestKind, flags);
    }

    /**
     * Append message
     *
     * @param {string} folder
     * @param {Data} mdata
     * @param {MessageFlag} flags
     * @return {*}  {number} Created UID
     * @memberof IMAPSession
     */
    public appendMessage(folder: string, mdata: Data, flags: MessageFlag): number {
        return this.handle.appendMessage(folder, mdata.handle, flags);
    }

    /**
     * Append message asynchronously
     *
     * @param {string} folder
     * @param {Data} mdata
     * @param {MessageFlag} flags
     * @return {*}  {Promise<number>} Created UID
     * @memberof IMAPSession
     */
    public appendMessageAsync(folder: string, mdata: Data, flags: MessageFlag): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.appendMessageAsync(folder, mdata.handle, flags, (error: string | Error | undefined | null, result: number) => {
                if (error) {
                    reject(error);
                }

                resolve(result);
            });
        });
    }

    /**
     * Copy messages
     *
     * @param {string} folder
     * @param {string} destFolder
     * @param {number} locationStart
     * @param {number} [length]
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public copyMessages(folder: string, destFolder: string, locationStart: number, length?: number): number {
        return this.handle.copyMessages(folder, destFolder, locationStart, length);
    }

    /**
     * Expunge folder
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public expunge(folder: string): number {
        return this.handle.expunge(folder);
    }

    /**
     * Expunge folder asynchronously
     *
     * @param {string} folder
     * @return {*}  {number}
     * @memberof IMAPSession
     */
    public expungeAsync(folder: string): Promise<number> {
        return new Promise<number>((resolve, reject) => {
            this.handle.expungeAsync(folder, (error: string | Error | undefined | null, result: number) => {
                if (error) {
                    reject(error);
                }

                resolve(result);
            });
        });
    }

    /**
     * Get capabilityMove
     *
     * @type {string}
     * @memberof IMAPSession
     */
    get capabilityMove(): boolean {
        return this.handle.capabilityMove;
    }
    
    /**
     * Get capabilityCreateSpecialUse
     *
     * @type {string}
     * @memberof IMAPSession
     */
     get capabilityCreateSpecialUse(): boolean {
        return this.handle.capabilityCreateSpecialUse;
    }
    
}

export { IMAPSession }
