import { Dialog, Form, PropertyFormatter, RedirectionUtil, TypeNames } from '../models';
import { DialogProxy } from '../proxy/DialogProxy';
import { CvLocale } from '../util/CvLocale';
import { Log } from '../util/Log';
import { ObjUtil } from '../util/ObjUtil';
import { SessionTimer } from "../util/SessionTimer";
import { DialogService } from './DialogService';
/**
 * Top-level entry point into the Catavolt API
 */
export class CatavoltApiImpl {
    /**
     * Construct an CatavoltApiImpl
     * This should not be called directly, instead use the 'singleton' method
     * @private
     */
    constructor(serverUrl, serverVersion) {
        this.DEFAULT_LOCALE = new CvLocale('en');
        this.dataLastChangedTime = new Date();
        this._locale = null;
        this._sessionTimer = null;
        this._onSessionExpiration = null;
        if (CatavoltApiImpl._singleton) {
            throw new Error('Singleton instance already created');
        }
        this._devicePropsStatic = {};
        this._devicePropsDynamic = {};
        this._sessionTimer = new SessionTimer(() => this.remainingSessionTime, CatavoltApiImpl.CHECK_SESSION_INTERVAL_MILLIS, () => {
            this.logout().then(() => {
                this._onSessionExpiration && this._onSessionExpiration();
            });
        });
        this.initDialogApi(serverUrl, serverVersion);
        CatavoltApiImpl._singleton = this;
    }
    /* ********************
            Statics
     *********************** */
    /**
     * Get the default session time
     * @returns {number}
     */
    static get defaultTTLInMillis() {
        return CatavoltApiImpl.ONE_HOUR_IN_MILLIS;
    }
    /**
     * Get the singleton instance of the CatavoltApiImpl
     * @returns {CatavoltApiImpl}
     */
    static get singleton() {
        if (!CatavoltApiImpl._singleton) {
            CatavoltApiImpl._singleton = new CatavoltApiImpl(CatavoltApiImpl.SERVER_URL, CatavoltApiImpl.SERVER_VERSION);
        }
        return CatavoltApiImpl._singleton;
    }
    /* *****************
        Public Ops
       ******************* */
    /**
     * Add or replace a dynamic device property (func)
     * @param propName
     * @param propFn
     */
    addDynamicDeviceProp(propName, propFn) {
        this._devicePropsDynamic[propName] = propFn;
    }
    /**
     * Add or replace a static device property
     *
     * @param propName
     * @param propValue
     */
    addStaticDeviceProp(propName, propValue) {
        this._devicePropsStatic[propName] = propValue;
    }
    /**
     *
     * @param {ClientListener} clientListener
     */
    addClientListener(clientListener) {
        this._dialogApi.addClientListener(clientListener, this.locale);
    }
    /**
     * Get the preferred locale
     * @returns {CvLocale}
     */
    get locale() {
        if (!this._locale) {
            const defaultLocale = this.session.tenantProperties.browserLocale;
            if (defaultLocale) {
                try {
                    const localeJson = JSON.parse(defaultLocale);
                    if (localeJson.language) {
                        this._locale = new CvLocale(localeJson.language, localeJson.country);
                    }
                }
                catch (err) {
                    this._locale = this.DEFAULT_LOCALE;
                }
            }
        }
        if (!this._locale) {
            this._locale = this.DEFAULT_LOCALE;
        }
        return this._locale;
    }
    set locale(locale) {
        this._locale = locale;
    }
    /*@TODO*/
    changePasswordAndLogin(tenantId, clientType, userId, existingPassword, newPassword) {
        return Promise.reject(new Error('Not Yet Implemented'));
    }
    /**
     * Get the number of millis that the client will remain active between calls
     * to the server.
     * @returns {number}
     */
    get clientTimeoutMillis() {
        const mins = this.session.tenantProperties.clientTimeoutMinutes;
        return mins ? Number(mins) * 60 * 1000 : CatavoltApiImpl.defaultTTLInMillis;
    }
    /**
     * Get the currency symbol override if defined from the server.
     * @returns {string}
     */
    get currencySymbol() {
        const currencySymbol = this.session.tenantProperties.currencySymbol;
        return currencySymbol ? currencySymbol : null;
    }
    /**
     * Get the device props
     * @returns {{[p: string]: string}}
     */
    get deviceProps() {
        const newProps = ObjUtil.addAllProps(this._devicePropsStatic, {});
        for (const attr in this._devicePropsDynamic) {
            if (this._devicePropsDynamic.hasOwnProperty(attr)) {
                newProps[attr] = this._devicePropsDynamic[attr]();
            }
        }
        return newProps;
    }
    /**
     * Get the DialogApi instance
     * @returns {DialogApi}
     */
    get dialogApi() {
        return this._dialogApi;
    }
    /**
     * Initialize a dialog service implementation for use by this CatavoltApiImpl
     *
     * @param serverVersion
     * @param serverUrl
     */
    initDialogApi(serverUrl, serverVersion = CatavoltApiImpl.SERVER_VERSION) {
        this._dialogApi = new DialogService(new DialogProxy(), serverUrl, serverVersion);
    }
    /**
     * Check for the availability of the given featureSet
     * @see FeatureSet
     * @param featureSet
     * @returns {boolean}
     */
    isFeatureSetAvailable(featureSet) {
        try {
            const currentVersion = AppVersion.getAppVersion(this.session.serverVersion);
            const featureMinimumVersion = FeatureVersionMap[featureSet];
            return featureMinimumVersion.isLessThanOrEqualTo(currentVersion);
        }
        catch (error) {
            Log.error('Failed to compare appVersions for feature ' + featureSet);
            Log.error(error);
            return false;
        }
    }
    /**
     * Checked logged in status
     * @returns {boolean}
     */
    get isLoggedIn() {
        return !!this._session;
    }
    /**
     * Check offline status
     *
     * @param tenantId
     */
    isAnyUserInBriefcaseMode(tenantId) {
        return this.dialogApi.isAnyUserInBriefcaseMode(tenantId);
    }
    /**
     * Check offline status
     *
     * @param userInfo
     * @returns {Promise<boolean>}
     */
    isUserInBriefcaseMode(userInfo) {
        return this.dialogApi.isUserInBriefcaseMode(userInfo);
    }
    /**
     * Log in and retrieve the Session
     *
     * @param tenantId
     * @param clientType
     * @param userId
     * @param password
     *
     * @returns {Promise<Session | Redirection>}
     */
    login(tenantId, clientType, userId, password) {
        if (this.isLoggedIn) {
            return this.logout().then(result => this.processLogin(tenantId, clientType, userId, password));
        }
        else {
            return this.processLogin(tenantId, clientType, userId, password);
        }
    }
    loginWithToken(tenantId, clientType, permissionToken, proofKey) {
        if (this.isLoggedIn) {
            return this.logout().then(result => this.processLogin(tenantId, clientType, null, null, permissionToken, proofKey));
        }
        else {
            return this.processLogin(tenantId, clientType, null, null, permissionToken, proofKey);
        }
    }
    /**
     * Logout and destroy the session
     * @returns {{sessionId:string}}
     */
    logout() {
        if (this.isLoggedIn) {
            const sessionId = this.session.id;
            return this.dialogApi
                .deleteSession(this.session.tenantId, this.session.id)
                .then(result => {
                this._session = null;
                this._sessionTimer.stopSessionTimer();
                return result;
            })
                .catch(error => {
                Log.error(`Error logging out ${error}`);
                this._session = null;
                this._sessionTimer.stopSessionTimer();
                return { sessionId };
            });
        }
        else {
            return Promise.resolve({ sessionId: null });
        }
    }
    openDialogWithId(dialogId) {
        return this.dialogApi.getDialog(this.session.tenantId, this.session.id, dialogId).then((dialog) => {
            dialog.initialize(this);
            if (dialog.view instanceof Form) {
                return dialog;
            }
            else {
                throw new Error(`Unexpected top-level dialog view type: ${dialog.view.type}`);
            }
        });
    }
    openDialog(redirection) {
        return this.openDialogWithId(redirection.dialogId);
    }
    /**
     * Not yet implemented
     * @param {string} url
     * @returns {Promise<StreamProducer>}
     */
    openStream(url) {
        // return this.dialogApi.streamUrl(null, null, url);
        throw Error('not yet implemented');
    }
    toDialogOrRedirection(resultPr) {
        return resultPr.then((actionResult) => {
            if (RedirectionUtil.isDialogRedirection(actionResult)) {
                return this.openDialog(actionResult);
            }
            else if (RedirectionUtil.isRedirection(actionResult)) {
                return Promise.resolve(actionResult);
            }
            else {
                // @TODO - this shouldn't be a null redirection - what should it be?
                return Promise.resolve(actionResult);
            }
        });
    }
    getRedirection(redirectionId) {
        return this.dialogApi.getRedirection(this.session.tenantId, this.session.id, redirectionId);
    }
    /**
     * Open a {@link WorkbenchAction}
     * @param workbenchAction
     * @returns {Promise<{actionId:string} | Redirection>}
     */
    performWorkbenchAction(workbenchAction) {
        return this.performWorkbenchActionForId(workbenchAction.workbenchId, workbenchAction.id);
    }
    /**
     * Open a {@link WorkbenchWorkbenchAction}
     * @param workbenchId
     * @param workbenchActionId
     * @returns {Promise<{actionId:string} | Redirection>}
     */
    performWorkbenchActionForId(workbenchId, workbenchActionId) {
        if (!this.isLoggedIn) {
            return Promise.reject(new Error('User is not logged in'));
        }
        return this.dialogApi.performWorkbenchAction(this.session.tenantId, this.session.id, workbenchId, workbenchActionId);
    }
    /**
     * Refresh the CatavoltApiImpl
     *
     * @returns {Promise<Session>}
     */
    refreshSession(tenantId, sessionId) {
        return this.dialogApi.getSession(tenantId, sessionId).then(session => {
            this._session = session;
            return session;
        });
    }
    /**
     * Time remaining before this session is expired by the server
     * @returns {number}
     */
    get remainingSessionTime() {
        return this.clientTimeoutMillis - (new Date().getTime() - this.dialogApi.lastServiceActivity.getTime());
    }
    /**
     *
     * @param {ClientListener} clientListener
     */
    removeClientListener(clientListener) {
        this._dialogApi.removeClientListener(clientListener);
    }
    /**
     * Get the Session
     * @returns {Session}
     */
    get session() {
        return this._session;
    }
    /**
     * Return whether or not the session has expired
     * @returns {boolean}
     */
    get sessionHasExpired() {
        return this.remainingSessionTime < 0;
    }
    /**
     * Function that will be notified when the session expires
     * @param {() => void} onSessionExpiration
     */
    set onSessionExpiration(onSessionExpiration) {
        this._onSessionExpiration = onSessionExpiration;
    }
    /**
     *
     * @param {string} contentId
     * @param {StreamConsumer} streamConsumer
     * @returns {Promise<LargeProperty>}
     */
    streamContent(contentId, streamConsumer) {
        return Dialog.loadLargeProperty((params) => {
            return this.dialogApi.getContent(this.session.tenantId, this.session.id, contentId, params);
        }, streamConsumer);
    }
    processLogin(tenantId, clientType, userId, password, permissionToken, proofKey) {
        const login = {
            userId,
            password,
            permissionToken,
            proofKey,
            clientType,
            deviceProperties: this.deviceProps,
            type: TypeNames.LoginTypeName
        };
        return this.dialogApi.createSession(tenantId, login).then((result) => {
            if (result.type === TypeNames.SessionTypeName) {
                this._session = result;
                this._sessionTimer.resetSessionTimer();
                return result;
            }
            else {
                return result;
            }
        });
    }
}
CatavoltApiImpl.ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
CatavoltApiImpl.CHECK_SESSION_INTERVAL_MILLIS = 30 * 1000;
// defaults
CatavoltApiImpl.SERVER_URL = 'https://dialog.hxgn-api.net';
CatavoltApiImpl.SERVER_VERSION = 'v0';
class AppVersion {
    constructor(major, minor, patch) {
        this.major = major;
        this.minor = minor;
        this.patch = patch;
    }
    static getAppVersion(versionString) {
        const [major, minor, patch] = versionString.split('.');
        return new AppVersion(Number(major || 0), Number(minor || 0), Number(patch || 0));
    }
    /**
     * Is 'this' version less than or equal to the supplied version?
     * @param anotherVersion - the version to compare to 'this' version
     * @returns {boolean}
     */
    isLessThanOrEqualTo(anotherVersion) {
        if (anotherVersion.major > this.major) {
            return true;
        }
        else if (anotherVersion.major === this.major) {
            if (anotherVersion.minor > this.minor) {
                return true;
            }
            else if (anotherVersion.minor === this.minor) {
                return anotherVersion.patch >= this.patch;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
}
const FeatureVersionMap = {
    View_Support: AppVersion.getAppVersion('1.3.447'),
    Unified_Search: AppVersion.getAppVersion('1.3.463')
};
export const Catavolt = CatavoltApiImpl.singleton;
export const propertyFormatter = PropertyFormatter.singleton(Catavolt);
