"use strict";
/**
 * Created by frank.zickert on 02.05.19.
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const aws_sdk_1 = __importDefault(require("aws-sdk"));
const graphql_tag_1 = __importDefault(require("graphql-tag"));
const universal_cookie_1 = __importDefault(require("universal-cookie"));
const typed_graphqlify_1 = require("typed-graphqlify");
const auth_middleware_1 = require("../authentication/auth-middleware");
/**
 * transforms a function into a Promise
 */
const promisify = foo => new Promise((resolve, reject) => {
    foo((error, result) => {
        if (error) {
            reject(error);
        }
        else {
            resolve(result);
        }
    });
});
const applyOfflineConfig = (offline) => {
    return offline ? {
        region: 'localhost',
        endpoint: 'http://localhost:8000'
    } : {};
};
exports.setEntry = (tableName, pkEntity, pkId, skEntity, skId, jsonData, isOffline) => {
    //console.log("setEntry: ", pkEntity, "|", pkId, "|", skEntity, "|", skId );
    return promisify(callback => new aws_sdk_1.default.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).update({
        TableName: tableName,
        /**
         * ALL KEYS MUST BE SPECIFIED HERE!
         */
        Key: {
            pk: `${pkEntity}|${pkId}`,
            sk: `${skEntity}|${skId}`
        },
        UpdateExpression: `SET jsonData = :jsonData`,
        ExpressionAttributeValues: {
            ':jsonData': `${JSON.stringify(jsonData)}`,
        }
    }, callback))
        .then(() => {
        //console.log("data stored!")
        var result = {};
        result[pkEntity] = pkId;
        result[skEntity] = skId;
        result["data"] = `${JSON.stringify(jsonData).replace(/"/g, "\\\"")}`;
        return result;
    }).catch(error => { console.log(error); });
};
/**
 * Get all entries to a entity|value pair in the key-field whose range have the specified rangeEntity
 *
 * @param key specify which field is the key: pk or sk
 * @param entity specifies the entity of the key-field
 * @param value specify the id of the key-field
 * @param rangeEntity specify the entity of the range
 * @returns {Promise<string>|any}
 */
exports.ddbListEntries = (tableName, key, entity, value, rangeEntity, isOffline) => {
    //console.log("ddbListEntries: ", tableName, key, entity, value, rangeEntity, isOffline);
    const q = {
        // use the table_name as specified in the serverless.yml
        TableName: tableName,
        IndexName: key === "sk" ? "reverse" : undefined,
        /**
         * ALL KEYS MUST HAVE KEY-CONDITION-EXPRESSIONS!
         */
        KeyConditionExpression: `${key} = :value and begins_with(${key === "pk" ? "sk" : "pk"}, :entity)`,
        ExpressionAttributeValues: {
            ":value": `${entity}|${value}`,
            ":entity": rangeEntity ? rangeEntity : "undefined"
        }
    };
    //console.log("query: ", q);
    return promisify(callback => new aws_sdk_1.default.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).query(q, callback))
        .then(result => {
        //console.log("ddb-result: ", result);
        return result["Items"];
        /*
         if (result.Items) {

         return result.Items.map(item => JSON.stringify(item));
         }

         return [];*/
        //return result.Items.map(item => JSON.stringify(item));
    }).catch(error => { console.log(error); });
};
exports.ddbGetEntry = (tableName, pkEntity, pkValue, skEntity, skValue, isOffline) => {
    //console.log("ddbGetEntry: ", `${pkEntity}|${pkValue}`, ` -- ${skEntity}|${skValue}`, " -- ", tableName);
    const q = {
        TableName: tableName,
        Key: {
            pk: `${pkEntity}|${pkValue}`,
            sk: `${skEntity}|${skValue}`
        }
    };
    //console.log("ddbGetEntry-query: ", q);
    return promisify(callback => new aws_sdk_1.default.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).get(q, callback))
        .then(result => {
        //console.log("ddbGetEntry result: ", result);
        return result["Item"] ? result["Item"] : result;
    }).catch(error => { console.log(error); });
};
exports.ddbScan = (tableName, key, entity, start_value, end_value, rangeEntity, isOffline) => {
    //console.log("scan: ", tableName, key, entity, start_value, end_value, rangeEntity, isOffline);
    const q = {
        // use the table_name as specified in the serverless.yml
        TableName: tableName,
        FilterExpression: `${key} between :sv and :ev and begins_with(${key === "pk" ? "sk" : "pk"}, :entity)`,
        ExpressionAttributeValues: {
            ":sv": `${entity}|${start_value}`,
            ":ev": `${entity}|${end_value}`,
            ":entity": rangeEntity ? rangeEntity : "undefined"
        }
    };
    const allQ = {
        // use the table_name as specified in the serverless.yml
        TableName: tableName,
        FilterExpression: `begins_with(${key}, :entity) and begins_with(${key === "pk" ? "sk" : "pk"}, :rangeentity)`,
        ExpressionAttributeValues: {
            ":entity": entity,
            ":rangeentity": rangeEntity ? rangeEntity : "undefined"
        }
    };
    //console.log("query: ", q);
    return promisify(callback => new aws_sdk_1.default.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).scan(start_value && end_value ? q : allQ, callback))
        .then(result => {
        //console.log("ddb-result: ", result);
        return result["Items"];
        /**
         * TODO


         return await client.select(Object.assign({
         query: query,
         context: context
         }, params)).then(result => {
         //console.log("select result: ", result)

         return result.data.Items.concat(typeof result.data.LastEvaluatedKey != "undefined" ?
         scan(
         client, {
         query: query,
         context: context,
         params: {
         ExclusiveStartKey: result.data.LastEvaluatedKey
         }
         }
         ): []);
        // continue scanning if we have more movies, because
        // scan can retrieve a maximum of 1MB of data
        if (typeof data.LastEvaluatedKey != "undefined") {
            console.log("Scanning for more...");
            params.ExclusiveStartKey = data.LastEvaluatedKey;
            docClient.scan(params, onScan);
        }

         return [];*/
        //return result.Items.map(item => JSON.stringify(item));
    }).catch(error => { console.log(error); });
};
exports.deleteEntry = (tableName, pkEntity, pkValue, skEntity, skValue, isOffline) => {
    //console.log("delete entry: ", pkEntity, pkValue, skEntity, skValue)
    //console.log("pk: ", `${pkEntity}|${pkValue}`);
    //console.log("sk: ", `${skEntity}|${skValue}`);
    return promisify(callback => new aws_sdk_1.default.DynamoDB.DocumentClient(applyOfflineConfig(isOffline)).delete({
        // use the table_name as specified in the serverless.yml
        TableName: tableName,
        Key: {
            pk: `${pkEntity}|${pkValue}`,
            sk: `${skEntity}|${skValue}`
        }
    }, callback))
        .then(result => {
        //console.log("result: ", result);
        return result["Item"] ? result["Item"] : result;
    }).catch(error => { console.log(error); });
};
/**
 * this function provides a executable graphql-query
 * TODO the fields must be taken from the data-layer, not requiring the user to provide them
 */
exports.setEntryMutation = (entryId, data, fields, context = {}) => {
    //console.log("setEntryMutation: ", entryId, data, fields);
    const mutationObj = {};
    mutationObj[`set_${entryId}`] = typed_graphqlify_1.params(Object.keys(data).reduce((result, key) => {
        result[key] = `"${data[key]}"`;
        return result;
    }, {}), Object.keys(fields).reduce((result, key) => {
        result[key] = typed_graphqlify_1.types.string;
        return result;
    }, {}));
    return {
        mutation: graphql_tag_1.default `${typed_graphqlify_1.mutation(mutationObj)}`,
        context: context
    };
};
exports.deleteEntryMutation = (entryId, data, fields, context = {}) => {
    //console.log("deleteEntryMutation: ", entryId, data);
    const mutationObj = {};
    mutationObj[`delete_${entryId}`] = typed_graphqlify_1.params(Object.keys(data).reduce((result, key) => {
        result[key] = `"${data[key]}"`;
        return result;
    }, {}), Object.keys(fields).reduce((result, key) => {
        result[key] = typed_graphqlify_1.types.string;
        return result;
    }, {}));
    return {
        mutation: graphql_tag_1.default `${typed_graphqlify_1.mutation(mutationObj)}`,
        context: context
    };
};
/**
 * this function provides a executable graphql-query
 */
exports.getEntryListQuery = (entryId, data, fields, context = {}) => {
    //console.log("getEntryListQuery: ", entryId, data, fields, context);
    if (data == undefined) {
        console.error("getEntryListQuery requires a data argument");
        return undefined;
    }
    if (Object.keys(data).length !== 1) {
        console.error("getEntryListQuery requires exact 1 field provided in the data argument");
        return undefined;
    }
    const queryKey = Object.keys(data)[0];
    const queryObj = {};
    queryObj[`list_${entryId}_${queryKey}`] = typed_graphqlify_1.params(Object.keys(data).filter(key => key === queryKey).reduce((result, key) => {
        result[key] = `"${data[key]}"`;
        return result;
    }, {}), Object.keys(fields).reduce((result, key) => {
        result[key] = typed_graphqlify_1.types.string;
        return result;
    }, {}));
    //console.log("listQuery string: ", query(queryObj));
    return {
        query: graphql_tag_1.default `${typed_graphqlify_1.query(queryObj)}`,
        context: context
    };
};
exports.getEntryQuery = (entryId, data, fields, context = {}) => {
    //console.log("getEntryQuery: ", entryId, data, fields, context);
    if (data == undefined) {
        console.error("getEntryQuery requires a data argument");
        return undefined;
    }
    if (Object.keys(data).length !== 2) {
        console.error("getEntryQuery requires exact 2 fields provided in the data argument");
        return undefined;
    }
    const queryObj = {};
    queryObj[`get_${entryId}`] = typed_graphqlify_1.params(Object.keys(data).reduce((result, key) => {
        result[key] = `"${data[key]}"`;
        return result;
    }, {}), Object.keys(fields).reduce((result, key) => {
        result[key] = typed_graphqlify_1.types.string;
        return result;
    }, {}));
    //console.log("listQuery string: ", query(queryObj));
    return {
        query: graphql_tag_1.default `${typed_graphqlify_1.query(queryObj)}`,
        context: context
    };
};
exports.updateEntryQuery = (entryId, callback, context = {}) => {
    return { entryId: entryId, callback: callback, context: context };
};
/**
 * this function provides a executable graphql-query: "scan_{entryId}"
 *
 */
exports.getEntryScanQuery = (entryId, data, fields, context = {}) => {
    //console.log("getEntryScanQuery: ", entryId, data, fields, context);
    if (data == undefined) {
        console.error("getEntryScanQuery requires a data argument, this may be empty");
        return undefined;
    }
    const queryObj = {};
    if (Object.keys(data).length > 0) {
        const queryKey = Object.keys(data)[0];
        queryObj[`scan_${entryId}_${queryKey}`] = typed_graphqlify_1.params(Object.keys(data).reduce((result, key) => {
            // when we have an array at the key-pos in data, then we want to get a range
            if (Array.isArray(data[key])) {
                if (data[key].length > 0 && data[key][0] !== undefined) {
                    result[`start_${key}`] = `"${data[key][0]}"`;
                }
                if (data[key].length > 1 && data[key][1] !== undefined) {
                    result[`end_${key}`] = `"${data[key][1]}"`;
                }
            }
            else {
                result[key] = `"${data[key]}"`;
            }
            return result;
        }, {}), Object.keys(fields).reduce((result, key) => {
            result[key] = typed_graphqlify_1.types.string;
            return result;
        }, {}));
        //console.log(gql`${query(queryObj)}`);
        return {
            query: graphql_tag_1.default `${typed_graphqlify_1.query(queryObj)}`,
            context: context
        };
    }
    else {
        queryObj[`scan_${entryId}`] = typed_graphqlify_1.params({ scanall: `"yes"` }, Object.keys(fields).reduce((result, key) => {
            result[key] = typed_graphqlify_1.types.string;
            return result;
        }, {}));
        //console.log(gql`${query(queryObj)}`);
        return {
            query: graphql_tag_1.default `${typed_graphqlify_1.query(queryObj)}`,
            context: context
        };
    }
    //console.log("scanQuery string: ", query(queryObj));
};
function select(client, { query, context = {} }) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!context["userId"]) {
            context["userId"] = new universal_cookie_1.default().get(auth_middleware_1.IC_USER_ID);
        }
        //console.log("select: ", query, context);
        return yield client.query({
            query: query,
            context: context
        }).then(result => {
            //console.log("select result: ", result)
            return result.data ? result.data : result;
        }).catch(error => {
            console.log(error);
        });
    });
}
exports.select = select;
;
/**
 * uses this: https://github.com/acro5piano/typed-graphqlify
 *
 * TODO generalize to other data-types than string
 *
 * @param client
 * @param entryId
 * @param data
 * @returns {any|Promise<T>|Promise<U>}
 */
function mutate(client, { mutation, context = {} }) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!context["userId"]) {
            context["userId"] = new universal_cookie_1.default().get(auth_middleware_1.IC_USER_ID);
        }
        //console.log("mutate: ", mutation, context);
        //console.log("mutation string: ", mutation(mutationObj));
        return yield client.mutate({
            mutation: mutation,
            context: context
        }).then(result => { }).catch(error => { console.log(error); });
    });
}
exports.mutate = mutate;
;
/**
 *
 * @param client
 * @param callback (oldData) => newData
 * @param context
 * @returns {Promise<any>}
 */
function update(client, { entryId, getEntryQuery, setEntryMutation }) {
    return __awaiter(this, void 0, void 0, function* () {
        console.log("update: ", entryId, getEntryQuery());
        const oldData = yield select(client, getEntryQuery());
        console.log("oldData: ", oldData);
        return yield mutate(client, setEntryMutation(oldData[`get_${entryId}`]));
    });
}
exports.update = update;
;
//# sourceMappingURL=datalayer-libs.js.map