"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const fs = require("fs");
const activelogger_1 = require("@activeledger/activelogger");
const httpd_1 = require("./httpd");
const pouchdb_1 = require("./pouchdb");
(function () {
    const FAUXTON_PATH = __dirname + "/fauxton/";
    const DIR_PREFIX = "./" + process.argv[2] + "/";
    const PouchDb = pouchdb_1.PouchDB.defaults({ prefix: DIR_PREFIX });
    const PouchDBDocBuffer = Buffer.from("ÿdocument-storeÿ");
    let pDBCache = {};
    let leveldownCache = {};
    let levelupdown = (name) => {
        if (!leveldownCache[name]) {
            leveldownCache[name] = pouchdb_1.leveldown(name);
        }
        return leveldownCache[name];
    };
    let getPDB = (name) => {
        if (!pDBCache[name]) {
            pDBCache[name] = new PouchDb(name, {
                db: levelupdown
            });
        }
        return pDBCache[name];
    };
    let http = new httpd_1.ActiveHttpd();
    http.use("/", "GET", () => {
        return {
            activeledger: "Welcome to Activeledger data!",
            adapters: ["leveldb"]
        };
    });
    http.use("_session", "ALL", () => {
        return { ok: true, userCtx: { name: null, roles: ["_admin"] } };
    });
    http.use("_all_dbs", "ALL", async () => {
        const isDirectory = (source) => fs.lstatSync(DIR_PREFIX + source).isDirectory() &&
            source != process.argv[2] &&
            source != "pouch__all_dbs__" &&
            source != "_replicator" &&
            source.indexOf("-mrview-") === -1;
        return fs.readdirSync(DIR_PREFIX).filter(isDirectory);
    });
    http.use("*", "GET", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        let info = await db.info();
        info.data_size = 0;
        fs.readdirSync(DIR_PREFIX + incoming.url[0]).map((source) => {
            info.data_size += fs.statSync(DIR_PREFIX + incoming.url[0] + "/" + source).size;
        });
        return info;
    });
    http.use("*", "PUT", async (incoming) => {
        let db = getPDB(incoming.body.name);
        await db.info();
        return { ok: true };
    });
    let deleteDb = (dir) => {
        fs.readdirSync(dir).map((source) => {
            fs.unlinkSync(dir + "/" + source);
        });
        fs.rmdirSync(dir);
    };
    http.use("*", "DELETE", async (incoming) => {
        let dir = DIR_PREFIX + incoming.url[0];
        if (fs.existsSync(dir)) {
            if (pDBCache[incoming.url[0]]) {
                await pDBCache[incoming.url[0]].close();
                pDBCache[incoming.url[0]] = null;
            }
            deleteDb(dir);
        }
        return { ok: true };
    });
    http.use("_uuids", "GET", async (incoming) => {
        let uuidGenV4 = (a) => a
            ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16)
            : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuidGenV4);
        let uuids = [];
        for (let i = 0; i < incoming.query.count; i++) {
            uuids.push(uuidGenV4());
        }
        return { uuids };
    });
    http.use("/*/_index", "GET", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.getIndexes();
    });
    http.use("/*/_index", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.createIndex(incoming.body);
    });
    http.use("/*/_index/*/*/*", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.deleteIndex({
            ddoc: incoming.url[2],
            type: incoming.url[3],
            name: incoming.url[4]
        });
    });
    http.use("/*/_index/_bulk_delete", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        const indexes = (await db.getIndexes()).indexes;
        incoming.body.docids.forEach(async (docId) => {
            let index = indexes.find((index) => {
                return index.ddoc == docId;
            });
            if (index) {
                await db.deleteIndex({
                    ddoc: index.ddoc,
                    type: index.type,
                    name: index.name
                });
            }
        });
        return { ok: true };
    });
    let replicator = async (incoming) => {
        if (incoming.body && incoming.body.source && incoming.body.target) {
            let source = incoming.body.source.url.substr(incoming.body.source.url.lastIndexOf("/") + 1);
            let target = incoming.body.target.url.substr(incoming.body.target.url.lastIndexOf("/") + 1);
            activelogger_1.ActiveLogger.warn(`Replication : ${source} -> ${target}`);
            return await getPDB(source).replicate.to(getPDB(target));
        }
        else {
            return "";
        }
    };
    http.use("/_replicate", "POST", async (incoming) => {
        return await replicator(incoming);
    });
    http.use("/_replicator", "POST", async (incoming) => {
        return await replicator(incoming);
    });
    http.use("/smash", "GET", (incoming) => {
        return new Promise(async (resolve, reject) => {
            activelogger_1.ActiveLogger.warn(`Restoration : ${incoming.query.s} -> ${incoming.query.t}`);
            let bulkdocs = [];
            let source = getPDB(incoming.query.s);
            let target = getPDB(incoming.query.t);
            await target.info();
            source.replicate
                .to(target, {
                filter: (doc) => {
                    if (doc.$activeledger &&
                        doc.$activeledger.delete &&
                        doc.$activeledger.rewrite) {
                        bulkdocs.push(doc.$activeledger.rewrite);
                        return false;
                    }
                    else {
                        return true;
                    }
                }
            })
                .then(() => {
                if (bulkdocs.length) {
                    activelogger_1.ActiveLogger.warn(`Restoration : ${bulkdocs.length} streams being corrected`);
                    target
                        .bulkDocs(bulkdocs, { new_edits: false })
                        .then(async () => {
                        activelogger_1.ActiveLogger.warn(`Restoration : Datastore stopping...`);
                        await source.close();
                        pDBCache[incoming.query.s] = null;
                        await target.close();
                        pDBCache[incoming.query.t] = null;
                        deleteDb(DIR_PREFIX + incoming.query.s);
                        fs.renameSync(DIR_PREFIX + incoming.query.t, DIR_PREFIX + incoming.query.s);
                        activelogger_1.ActiveLogger.warn(`Restoration : Datastore starting...`);
                        resolve({ ok: true });
                    })
                        .catch((e) => reject(e));
                }
                else {
                    resolve({ ok: true });
                }
            })
                .catch((e) => reject(e));
        });
    });
    http.use("*/_all_docs", "GET", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.allDocs(prepareAllDocs(incoming.query));
    });
    http.use("*/_all_docs", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.allDocs(prepareAllDocs(Object.assign(incoming.query, incoming.body)));
    });
    let prepareAllDocs = (options) => {
        if (options.limit) {
            options.limit = parseInt(options.limit) || null;
        }
        if (options.startkey) {
            options.startkey = options.startkey.replace(/"/g, "");
        }
        if (options.endkey) {
            options.endkey = options.endkey.replace(/"/g, "");
            options.endkey = options.endkey.slice(0, -1) + "\u9999";
        }
        return options;
    };
    let genericGet = (dbLoc, path) => {
        return new Promise(async (resolve, reject) => {
            let db = getPDB(dbLoc);
            db.get(decodeURIComponent(path))
                .then((doc) => resolve(doc))
                .catch((e) => {
                reject(e);
            });
        });
    };
    let genericDelete = (dbLoc, docName) => {
        return new Promise(async (resolve, reject) => {
            await getPDB(dbLoc).info();
            let db = levelupdown(DIR_PREFIX + dbLoc);
            if (db.status === "open") {
                db.del(Buffer.concat([PouchDBDocBuffer, Buffer.from(docName)]), (error) => {
                    if (error) {
                        return reject(error);
                    }
                    return resolve({ success: "ok" });
                });
            }
            else {
                return reject("database not opened");
            }
        });
    };
    http.use("*/*", "DELETE", async (incoming) => {
        return await genericDelete(incoming.url[0], incoming.url[1]);
    });
    http.use("*/*", "GET", async (incoming) => {
        return await genericGet(incoming.url[0], incoming.url[1]);
    });
    http.use("*/_design/*", "GET", async (incoming) => {
        return await genericGet(incoming.url[0], `_design/${incoming.url[2]}`);
    });
    http.use("*/_local/*", "GET", async (incoming) => {
        return await genericGet(incoming.url[0], `_local/${incoming.url[2]}`);
    });
    http.use("*/**", "PUT", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        try {
            return await db.put(incoming.body);
        }
        catch (e) {
            return "";
        }
    });
    http.use("*", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        try {
            return await db.post(incoming.body);
        }
        catch (e) {
            return "";
        }
    });
    http.use("*/_changes", "GET", async (incoming, req, res) => {
        let db = getPDB(incoming.url[0]);
        if (incoming.query.limit < 1) {
            incoming.query.limit = 1;
        }
        if (incoming.query.since !== "now") {
            incoming.query.since = parseInt(incoming.query.since);
        }
        if (incoming.query.feed === "continuous" ||
            incoming.query.feed === "longpoll") {
            if (incoming.query.feed === "continuous") {
            }
            else {
                let hBInterval = setInterval(() => {
                    res.write("\n");
                }, incoming.query.heartbeat);
                let cleanUp = () => {
                    if (hBInterval) {
                        clearInterval(hBInterval);
                    }
                };
                res.setHeader("Content-type", httpd_1.ActiveHttpd.mimeType[".json"]);
                incoming.query.live = incoming.query.continuous = false;
                db.changes(incoming.query).then((complete) => {
                    if (complete.results.length) {
                        res.write(JSON.stringify(complete));
                        res.end();
                        cleanUp();
                    }
                    else {
                        res.write('{"results":[\n');
                        incoming.query.live = incoming.query.continuous = true;
                        let changes = db
                            .changes(incoming.query)
                            .on("change", (change) => {
                            res.write(JSON.stringify(change));
                            res.write('],\n"last_seq":' + change.seq + "}\n");
                            changes.cancel();
                        })
                            .on("error", (e) => {
                            req.connection.removeListener("close", cancelChanges);
                            res.end();
                            cleanUp();
                        })
                            .on("complete", () => {
                            req.connection.removeListener("close", cancelChanges);
                            res.end();
                            cleanUp();
                        });
                        let cancelChanges = () => {
                            changes.cancel();
                        };
                        req.connection.on("close", cancelChanges);
                    }
                });
            }
            return "handled";
        }
        else {
            return await db.changes(incoming.query);
        }
    });
    http.use("*/_bulk_docs", "POST", async (incoming) => {
        if (!Array.isArray(incoming.body)) {
            if (incoming.body.docs) {
                let opts = {
                    new_edits: incoming.body.new_edits ||
                        (incoming.body.options && incoming.body.options.new_edits)
                };
                if (typeof opts.new_edits === "undefined" ||
                    typeof opts.new_edits !== "boolean") {
                    opts.new_edits = true;
                }
                let db = getPDB(incoming.url[0]);
                return await db.bulkDocs(incoming.body.docs, opts);
            }
        }
    });
    http.use("*/_find", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.find(incoming.body);
    });
    http.use("*/_explain", "POST", async (incoming) => {
        let db = getPDB(incoming.url[0]);
        return await db.explain(incoming.body);
    });
    http.use("_utils/**", "ALL", (incoming, req, res) => {
        if (req.url === "/_utils") {
            res.writeHead(301, {
                Location: "/_utils/"
            });
            return;
        }
        let file = FAUXTON_PATH + "/index.html";
        if (req.url !== "/_utils/") {
            file = FAUXTON_PATH + req.url.replace("/_utils", "");
        }
        if (fs.existsSync(file)) {
            res.setHeader("Content-type", httpd_1.ActiveHttpd.mimeType[path.parse(file).ext] || "text/plain");
            return fs.readFileSync(file);
        }
    });
    http.listen(parseInt(process.argv[3]));
})();
//# sourceMappingURL=selfhost.js.map