"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const Realm = require("realm");
const path = require("path");
const URI = require("urijs");
const rimraf = require("rimraf");
const util_1 = require("./shared/util");
function createRealmSchema(klass) {
    const myClass = function () { };
    myClass.prototype = Object.create(klass.prototype, {
        constructor: {
            configurable: true,
            enumerable: false,
            value: myClass,
            writable: true,
        },
    });
    Object.setPrototypeOf(myClass, klass);
    const schema = klass.schema;
    Object.defineProperty(myClass, "schema", {
        get() {
            return schema;
        },
    });
    return myClass;
}
exports.createRealmSchema = createRealmSchema;
class RealmPromise extends Promise {
    constructor(executor) {
        super(executor);
        this.openCount = 0;
    }
}
class RealmFactory {
    constructor(params) {
        this.syncedRealmPromises = {};
        this.discovery = params.discovery;
        this.adminToken = params.adminToken;
        this.dataPath = params.dataPath;
        this.logger = params.logger;
        this.realmDirectoryClient = params.realmDirectoryClient;
    }
    open(definition) {
        return __awaiter(this, void 0, void 0, function* () {
            let promise = this.syncedRealmPromises[definition.remotePath];
            if (!promise) {
                promise = new RealmPromise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                    let realm;
                    let syncLabel = definition.syncLabel;
                    if (!syncLabel) {
                        while (!syncLabel) {
                            try {
                                const shouldCreate = true;
                                const response = yield this.realmDirectoryClient.findByPath({
                                    realmPath: definition.remotePath,
                                    shouldCreate: shouldCreate
                                });
                                syncLabel = response.syncLabel;
                            }
                            catch (err) {
                                if (err.status !== 503) {
                                    return reject(err);
                                }
                            }
                        }
                        this.logger.debug(`Obtained sync label for realm at ${definition.remotePath}: ${syncLabel}`);
                    }
                    const labelTag = `label=${syncLabel}`;
                    const handle = yield this.discovery.waitForService("sync", ["role=master", labelTag]);
                    this.logger.debug(`Opening realm at ${definition.remotePath}: ${handle.address}:${handle.port}`);
                    const syncServiceWatcher = this.discovery.watchService("sync", ["role=master", labelTag]);
                    const uri = new URI().host(handle.address).port(handle.port).segment(definition.remotePath);
                    const config = {
                        schema: definition.schema,
                        path: definition.localPath ? path.join(this.dataPath, definition.localPath) : undefined,
                        sync: {
                            url: uri.scheme("realm").toString(),
                            user: Realm.Sync.User.adminUser(this.adminToken, uri.scheme("http").origin()),
                            error: (session, err) => {
                                const closeNotification = promise.openCount === 0 ? ' - Realm was already closed' : '';
                                const message = `Internal sync error (path: ${definition.remotePath})${closeNotification}. `
                                    + `Message: ${err.message}, category: ${err.category}, code: ${err.code}, name: ${err.name}.`;
                                if (err.isFatal) {
                                    this.logger.error(message);
                                    reject(err);
                                }
                                else if (promise.openCount > 0) {
                                    this.logger.warn(message);
                                }
                                else {
                                    this.logger.detail(message);
                                }
                            },
                        },
                    };
                    realm = yield Realm.open(config).progress((a, b) => {
                        this.logger.debug(`Open progress for realm at ${definition.remotePath}: ${a}/${b}`);
                    }).then((realm) => {
                        this.logger.detail(`Opened realm at ${definition.remotePath}`);
                        const oldClose = realm.close.bind(realm);
                        promise.localPath = realm.path;
                        promise.forceClose = (message) => {
                            oldClose();
                            this.logger.detail(message);
                            delete this.syncedRealmPromises[definition.remotePath];
                            syncServiceWatcher.removeListener("available", serviceAvailableListener);
                        };
                        realm.open = () => { promise.openCount++; };
                        realm.close = () => {
                            promise.openCount--;
                            if (promise.openCount === 0 && !realm.isClosed) {
                                promise.forceClose(`Closed realm at ${definition.remotePath}`);
                            }
                        };
                        return realm;
                    });
                    const serviceAvailableListener = (handle) => {
                        const uri = new URI().host(handle.address).port(handle.port).segment(definition.remotePath);
                        this.logger.detail(`Reconfiguring realm at ${definition.remotePath}: ${handle.address}:${handle.port}`);
                        const syncSession = realm.syncSession;
                        if (syncSession) {
                            syncSession._overrideServer(handle.address, handle.port);
                        }
                        else {
                            this.logger.error(`Reconfiguring realm at ${definition.remotePath} failed. Sync Session is not available.`);
                        }
                    };
                    syncServiceWatcher.addListener("available", serviceAvailableListener);
                    resolve(realm);
                }));
                this.syncedRealmPromises[definition.remotePath] = promise;
            }
            promise.openCount++;
            return promise;
        });
    }
    close() {
        return __awaiter(this, void 0, void 0, function* () {
            const openRealmPaths = Object.keys(this.syncedRealmPromises)
                .filter(path => {
                const realmPromise = this.syncedRealmPromises[path];
                return realmPromise.openCount > 0;
            });
            openRealmPaths.forEach(() => {
                const paths = `"${openRealmPaths.join('", "')}"`;
                this.logger.error(`RealmFactory was closed with open Realms: ${paths}, remember to close these Realms`);
            });
            this.syncedRealmPromises = {};
        });
    }
    forceCloseRealm(remotePath, message, deleteAfterClose = false) {
        return __awaiter(this, void 0, void 0, function* () {
            const promise = this.syncedRealmPromises[remotePath];
            if (promise) {
                yield promise;
                promise.forceClose(message);
                if (deleteAfterClose) {
                    yield util_1.delay(500);
                    const realmFolder = path.dirname(promise.localPath);
                    this.logger.detail(`Deleting RealmFactory file. Remote ${remotePath}. Local: ${realmFolder}`);
                    yield util_1.Promisify(rimraf.bind(rimraf), realmFolder);
                    yield util_1.delay(100);
                }
            }
        });
    }
}
exports.RealmFactory = RealmFactory;
//# sourceMappingURL=RealmFactory.js.map