import * as async from 'async';
import * as utils from '../../util/utils';
import { getLogger } from 'omelox-logger';
let logger = getLogger('omelox', __filename);
let transactionLogger = getLogger('transaction-log', __filename);
let transactionErrorLogger = getLogger('transaction-error-log', __filename);

export type TransactionCondictionFunction = (cb: (err ?: Error) => void) => void;
export type TransactionHandlerFunction = (cb: (err ?: Error) => void) => void;

export function transaction(name: string, conditions: { [key: string]: TransactionCondictionFunction }, handlers: { [key: string]: TransactionHandlerFunction }, retry?: number) {
    if (!retry) {
        retry = 1;
    }
    if (typeof name !== 'string') {
        logger.error('transaction name is error format, name: %s.', name);
        return;
    }
    if (typeof conditions !== 'object' || typeof handlers !== 'object') {
        logger.error('transaction conditions parameter is error format, conditions: %j, handlers: %j.', conditions, handlers);
        return;
    }

    let cmethods: TransactionCondictionFunction[] = [];
    let dmethods: TransactionHandlerFunction[] = [];
    let cnames: string[] = [];
    let dnames: string[] = [];
    for (let key in conditions) {
        if (typeof key !== 'string' || typeof conditions[key] !== 'function') {
            logger.error('transaction conditions parameter is error format, condition name: %s, condition function: %j.', key, conditions[key]);
            return;
        }
        cnames.push(key);
        cmethods.push(conditions[key]);
    }

    let i = 0;
    // execute conditions
    async.forEachSeries(cmethods, function (method, cb) {
        method(cb);
        transactionLogger.info('[%s]:[%s] condition is executed.', name, cnames[i]);
        i++;
    }, function (err: Error) {
        if (err) {
            process.nextTick(function () {
                transactionLogger.error('[%s]:[%s] condition is executed with err: %j.', name, cnames[--i], err.stack);
                let log = {
                    name: name,
                    method: cnames[i],
                    time: Date.now(),
                    type: 'condition',
                    description: err.stack
                };
                transactionErrorLogger.error(JSON.stringify(log));
            });
            return;
        } else {
            // execute handlers
            process.nextTick(function () {
                for (let key in handlers) {
                    if (typeof key !== 'string' || typeof handlers[key] !== 'function') {
                        logger.error('transcation handlers parameter is error format, handler name: %s, handler function: %j.', key, handlers[key]);
                        return;
                    }
                    dnames.push(key);
                    dmethods.push(handlers[key]);
                }

                let flag = true;
                let times = retry;

                // do retry if failed util retry times
                async.whilst(
                    function () {
                        return retry > 0 && flag;
                    },
                    function (callback) {
                        let j = 0;
                        retry--;
                        async.forEachSeries(dmethods, function (method, cb) {
                            method(cb);
                            transactionLogger.info('[%s]:[%s] handler is executed.', name, dnames[j]);
                            j++;
                        }, function (err: Error) {
                            if (err) {
                                process.nextTick(function () {
                                    transactionLogger.error('[%s]:[%s]:[%s] handler is executed with err: %j.', name, dnames[--j], times - retry, err.stack);
                                    let log = {
                                        name: name,
                                        method: dnames[j],
                                        retry: times - retry,
                                        time: Date.now(),
                                        type: 'handler',
                                        description: err.stack
                                    };
                                    transactionErrorLogger.error(JSON.stringify(log));
                                    utils.invokeCallback(callback);
                                });
                                return;
                            }
                            flag = false;
                            utils.invokeCallback(callback);
                            process.nextTick(function () {
                                transactionLogger.info('[%s] all conditions and handlers are executed successfully.', name);
                            });
                        });
                    },
                    function (err) {
                        if (err) {
                            logger.error('transaction process is executed with error: %j', err);
                        }
                        // callback will not pass error
                    }
                );
            });
        }
    });
}