"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseWhere = exports.parseMeasurement = exports.Measurement = exports.Expression = void 0;
const grammar_1 = require("./grammar");
function regexHasFlags(re) {
    if (typeof re.flags !== "undefined") {
        return re.flags.length > 0;
    }
    return !re.toString().endsWith("/");
}
/**
 * Expression is used to build filtering expressions, like those used in WHERE
 * clauses. It can be used for fluent and safe building of queries using
 * untrusted input.
 *
 * @example
 * e => e
 *   .field('host').equals.value('ares.peet.io')
 *   .or
 *   .field('host').matches(/example\.com$/)
 *   .or
 *   .expr(e => e
 *     .field('country').equals.value('US')
 *     .and
 *     .field('state').equals.value('WA'));
 *
 * // Generates:
 * // "host" = 'ares.peet.io' OR "host" ~= /example\.com$/ OR \
 * //   ("county" = 'US' AND "state" = 'WA')
 */
class Expression {
    constructor() {
        this._query = [];
    }
    /**
     * Inserts a tag reference into the expression; the name will be
     * automatically escaped.
     * @param name
     * @return
     */
    tag(name) {
        this.field(name);
        return this;
    }
    /**
     * Inserts a field reference into the expression; the name will be
     * automatically escaped.
     * @param name
     * @return
     */
    field(name) {
        this._query.push(grammar_1.escape.quoted(name));
        return this;
    }
    /**
     * Inserts a subexpression; invokes the function with a new expression
     * that can be chained on.
     * @param fn
     * @return
     * @example
     * e.field('a').equals.value('b')
     *   .or.expr(e =>
     *     e.field('b').equals.value('b')
     *     .and.field('a').equals.value('c'))
     *   .toString()
     * // "a" = 'b' OR ("b" = 'b' AND "a" = 'c')
     */
    exp(fn) {
        this._query.push("(" + fn(new Expression()).toString() + ")");
        return this;
    }
    /**
     * Value chains on a value to the expression.
     *
     *  - Numbers will be inserted verbatim
     *  - Strings will be escaped and inserted
     *  - Booleans will be inserted correctly
     *  - Dates will be formatted and inserted correctly, including INanoDates.
     *  - Regular expressions will be inserted correctly, however an error will
     *    be thrown if they contain flags, as regex flags do not work in Influx
     *  - Otherwise we'll try to call `.toString()` on the value, throwing
     *    if we cannot do so.
     *
     * @param value
     * @return
     */
    value(value) {
        switch (typeof value) {
            case "number":
                this._query.push(value.toString());
                return this;
            case "string":
                this._query.push(grammar_1.escape.stringLit(value));
                return this;
            case "boolean":
                this._query.push(value ? "TRUE" : "FALSE");
                return this;
            default:
                if (value instanceof Date) {
                    this._query.push(grammar_1.formatDate(value));
                    return this;
                }
                if (value instanceof RegExp) {
                    if (regexHasFlags(value)) {
                        throw new Error("Attempted to query using a regex with flags, " +
                            "but Influx doesn't support flags in queries.");
                    }
                    this._query.push("/" + value.source + "/");
                    return this;
                }
                if (value && typeof value.toString === "function") {
                    this._query.push(value.toString());
                    return this;
                }
                throw new Error("node-influx doesn't know how to encode the provided value into a " +
                    "query. If you think this is a bug, open an issue here: https://git.io/influx-err");
        }
    }
    /**
     * Chains on an AND clause to the expression.
     */
    get and() {
        this._query.push("AND");
        return this;
    }
    /**
     * Chains on an OR clause to the expression.
     */
    get or() {
        this._query.push("OR");
        return this;
    }
    /**
     * Chains on a `+` operator to the expression.
     */
    get plus() {
        this._query.push("+");
        return this;
    }
    /**
     * Chains on a `*` operator to the expression.
     */
    get times() {
        this._query.push("*");
        return this;
    }
    /**
     * Chains on a `-` operator to the expression.
     */
    get minus() {
        this._query.push("-");
        return this;
    }
    /**
     * Chains on a `/` operator to the expression.
     */
    get div() {
        this._query.push("/");
        return this;
    }
    /**
     * Chains on a `=` conditional to the expression.
     */
    get equals() {
        this._query.push("=");
        return this;
    }
    /**
     * Chains on a `=~` conditional to the expression to match regexes.
     */
    get matches() {
        this._query.push("=~");
        return this;
    }
    /**
     * Chains on a `!`` conditional to the expression to match regexes.
     */
    get doesntMatch() {
        this._query.push("!~");
        return this;
    }
    /**
     * Chains on a `!=` conditional to the expression.
     */
    get notEqual() {
        this._query.push("!=");
        return this;
    }
    /**
     * Chains on a `>` conditional to the expression.
     */
    get gt() {
        this._query.push(">");
        return this;
    }
    /**
     * Chains on a `>=` conditional to the expression.
     */
    get gte() {
        this._query.push(">=");
        return this;
    }
    /**
     * Chains on a `<` conditional to the expression.
     */
    get lt() {
        this._query.push("<");
        return this;
    }
    /**
     * Chains on a `<=` conditional to the expression.
     */
    get lte() {
        this._query.push("<=");
        return this;
    }
    /**
     * Converts the expression into its InfluxQL representation.
     * @return
     */
    toString() {
        return this._query.join(" ");
    }
}
exports.Expression = Expression;
/**
 * Measurement creates a reference to a particular measurement. You can
 * reference it solely by its name, but you can also specify the retention
 * policy and database it lives under.
 *
 * @example
 * m.name('my_measurement') // "my_measurement"
 * m.name('my_measurement').policy('one_day') // "one_day"."my_measurement"
 * m.name('my_measurement').policy('one_day').db('mydb') // "mydb"."one_day"."my_measurement"
 */
class Measurement {
    constructor() {
        this._parts = [null, null, null];
    }
    /**
     * Sets the measurement name.
     * @param name
     * @return
     */
    name(name) {
        this._parts[2] = name;
        return this;
    }
    /**
     * Sets the retention policy name.
     * @param retentionPolicy
     * @return
     */
    policy(retentionPolicy) {
        this._parts[1] = retentionPolicy;
        return this;
    }
    /**
     * Sets the database name.
     * @param db
     * @return
     */
    db(db) {
        this._parts[0] = db;
        return this;
    }
    /**
     * Converts the measurement into its InfluxQL representation.
     * @return
     * @throws {Error} if a measurement name is not provided
     */
    toString() {
        if (!this._parts[2]) {
            throw new Error(`You must specify a measurement name to query! Got \`${this._parts[2]}\``);
        }
        return this._parts
            .filter((p) => Boolean(p))
            .map((p) => grammar_1.escape.quoted(p))
            .join(".");
    }
}
exports.Measurement = Measurement;
function parseMeasurement(q) {
    if (typeof q.measurement === "function") {
        return q.measurement(new Measurement()).toString();
    }
    return q.measurement;
}
exports.parseMeasurement = parseMeasurement;
function parseWhere(q) {
    if (typeof q.where === "function") {
        return q.where(new Expression()).toString();
    }
    return q.where;
}
exports.parseWhere = parseWhere;
