/**
 * Reusable data formatting functions
 */
Ext.define('Ext.util.Format', {
    requires: [
        'Ext.DateExtras'
    ],

    singleton: true,

    /**
     * The global default date format.
     */
    defaultDateFormat: 'm/d/Y',

    escapeRe: /('|\\)/g,
    trimRe: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
    formatRe: /\{(\d+)\}/g,
    escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
    dashesRe: /-/g,
    iso8601TestRe: /\d\dT\d\d/,
    iso8601SplitRe: /[- :T\.Z\+]/,

    /**
     * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
     * @param {String} value The string to truncate.
     * @param {Number} length The maximum length to allow before truncating.
     * @param {Boolean} [word=false] True to try to find a common word break.
     * @return {String} The converted text.
     */
    ellipsis: function(value, len, word) {
        if (value && value.length > len) {
            if (word) {
                var vs = value.substr(0, len - 2),
                index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
                if (index != -1 && index >= (len - 15)) {
                    return vs.substr(0, index) + "...";
                }
            }
            return value.substr(0, len - 3) + "...";
        }
        return value;
    },

    /**
     * Escapes the passed string for use in a regular expression.
     * @param {String} str
     * @return {String}
     */
    escapeRegex: function(s) {
        return s.replace(Ext.util.Format.escapeRegexRe, "\\$1");
    },

    /**
     * Escapes the passed string for ' and \.
     * @param {String} string The string to escape.
     * @return {String} The escaped string.
     */
    escape: function(string) {
        return string.replace(Ext.util.Format.escapeRe, "\\$1");
    },

    /**
     * Utility function that allows you to easily switch a string between two alternating values.  The passed value
     * is compared to the current string, and if they are equal, the other value that was passed in is returned.  If
     * they are already different, the first value passed in is returned.
     *
     * __Note:__ This method returns the new value but does not change the current string.
     *
     *     // alternate sort directions
     *     sort = Ext.util.Format.toggle(sort, 'ASC', 'DESC');
     *
     *     // instead of conditional logic:
     *     sort = (sort === 'ASC' ? 'DESC' : 'ASC');
     *
     * @param {String} string The current string
     * @param {String} value The value to compare to the current string
     * @param {String} other The new value to use if the string already equals the first value passed in
     * @return {String} The new value
     */
    toggle: function(string, value, other) {
        return string == value ? other : value;
    },

    /**
     * Trims whitespace from either end of a string, leaving spaces within the string intact.  Example:
     *
     *     var s = '  foo bar  ';
     *     alert('-' + s + '-'); // alerts "-  foo bar  -"
     *     alert('-' + Ext.util.Format.trim(s) + '-'); // alerts "-foo bar-"
     *
     * @param {String} string The string to escape
     * @return {String} The trimmed string
     */
    trim: function(string) {
        return string.replace(Ext.util.Format.trimRe, "");
    },

    /**
     * Pads the left side of a string with a specified character.  This is especially useful
     * for normalizing number and date strings.  Example usage:
     *
     *     var s = Ext.util.Format.leftPad('123', 5, '0');
     *     // s now contains the string: '00123'
     *
     * @param {String} string The original string.
     * @param {Number} size The total length of the output string.
     * @param {String} [char=' '] (optional) The character with which to pad the original string.
     * @return {String} The padded string.
     */
    leftPad: function (val, size, ch) {
        var result = String(val);
        ch = ch || " ";
        while (result.length < size) {
            result = ch + result;
        }
        return result;
    },

    /**
     * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens.  Each
     * token must be unique, and must increment in the format {0}, {1}, etc.  Example usage:
     *
     *     var cls = 'my-class', text = 'Some text';
     *     var s = Ext.util.Format.format('
{1}
', cls, text);
     *     // s now contains the string: '
Some text
'
     *
     * @param {String} string The tokenized string to be formatted.
     * @param {String...} values The values to replace token {0}, {1}, etc.
     * @return {String} The formatted string.
     */
    format: function (format) {
        var args = Ext.toArray(arguments, 1);
        return format.replace(Ext.util.Format.formatRe, function(m, i) {
            return args[i];
        });
    },

    /**
     * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
     * @param {String} value The string to encode.
     * @return {String} The encoded text.
     */
    htmlEncode: function(value) {
        return ! value ? value: String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
    },

    /**
     * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
     * @param {String} value The string to decode.
     * @return {String} The decoded text.
     */
    htmlDecode: function(value) {
        return ! value ? value: String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
    },

    /**
     * Parse a value into a formatted date using the specified format pattern.
     * Note that this uses the native Javascript Date.parse() method and is therefore subject to its idiosyncrasies.
     * Most formats assume the local timezone unless specified. One notable exception is 'YYYY-MM-DD' (note the dashes)
     * which is typically interpreted in UTC and can cause date shifting.
     * @param {String/Date} value The value to format. Strings must conform to the format expected by the JavaScript
     * Date object's [parse() method](http://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse).
     * @param {String} [format='m/d/Y'] (optional) Any valid date format string.
     * @return {String} The formatted date string.
     */
    date: function(value, format) {
        var date = value;
        if (!value) {
            return "";
        }
        if (!Ext.isDate(value)) {
            date = new Date(Date.parse(value));
            if (isNaN(date)) {
                // Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
                if (this.iso8601TestRe.test(value)) {
                    // Fix for older android browsers to properly implement ISO 8601 formatted dates with timezone
                    if (Ext.os.is.Android && Ext.os.version.isLessThan("3.0")) {
                        /**
                         * This code is modified from the following source: <https://github.com/csnover/js-iso8601>
                         * © 2011 Colin Snover <http://zetafleet.com>
                         * Released under MIT license.
                         */
                        var potentialUndefinedKeys = [ 1, 4, 5, 6, 7, 10, 11 ];
                        var dateParsed, minutesOffset = 0;

                        // Capture Groups
                        // 1 YYYY (optional)
                        // 2 MM
                        // 3 DD
                        // 4 HH
                        // 5 mm (optional)
                        // 6 ss (optional)
                        // 7 msec (optional)
                        // 8 Z (optional)
                        // 9 ± (optional)
                        // 10 tzHH (optional)
                        // 11 tzmm (optional)
                        if ((dateParsed = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(value))) {

                            //Set any undefined values needed for Date to 0
                            for (var i = 0, k; (k = potentialUndefinedKeys[i]); ++i) {
                                dateParsed[k] = +dateParsed[k] || 0;
                            }

                            // Fix undefined month and decrement
                            dateParsed[2] = (+dateParsed[2] || 1) - 1;
                            //fix undefined days
                            dateParsed[3] = +dateParsed[3] || 1;

                            // Correct for timezone
                            if (dateParsed[8] !== 'Z' && dateParsed[9] !== undefined) {
                                minutesOffset = dateParsed[10] * 60 + dateParsed[11];

                                if (dateParsed[9] === '+') {
                                    minutesOffset = 0 - minutesOffset;
                                }
                            }

                            // Calculate valid date
                            date = new Date(Date.UTC(dateParsed[1], dateParsed[2], dateParsed[3], dateParsed[4], dateParsed[5] + minutesOffset, dateParsed[6], dateParsed[7]));
                        }
                    } else {
                        date = value.split(this.iso8601SplitRe);
                        date = new Date(date[0], date[1] - 1, date[2], date[3], date[4], date[5]);
                    }
                }
            }
            if (isNaN(date)) {
                // Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
                // get around that.
                date = new Date(Date.parse(value.replace(this.dashesRe, "/")));
                //<debug>
                if (isNaN(date)) {
                    Ext.Logger.error("Cannot parse the passed value " + value + " into a valid date");
                }
                //</debug>
            }
            value = date;
        }
        return Ext.Date.format(value, format || Ext.util.Format.defaultDateFormat);
    }
});


/**
 * Tab Panels are a great way to allow the user to switch between several pages that are all full screen. Each
 * Component in the Tab Panel gets its own Tab, which shows the Component when tapped on. Tabs can be positioned at
 * the top or the bottom of the Tab Panel, and can optionally accept title and icon configurations.
 *
 * Here's how we can set up a simple Tab Panel with tabs at the bottom. Use the controls at the top left of the example
 * to toggle between code mode and live preview mode (you can also edit the code and see your changes in the live
 * preview):
 *
 *     @example miniphone preview
 *     Ext.create('Ext.TabPanel', {
 *         fullscreen: true,
 *         tabBarPosition: 'bottom',
 *
 *         defaults: {
 *             styleHtmlContent: true
 *         },
 *
 *         items: [
 *             {
 *                 title: 'Home',
 *                 iconCls: 'home',
 *                 html: 'Home Screen'
 *             },
 *             {
 *                 title: 'Contact',
 *                 iconCls: 'user',
 *                 html: 'Contact Screen'
 *             }
 *         ]
 *     });
 * One tab was created for each of the {@link Ext.Panel panels} defined in the items array. Each tab automatically uses
 * the title and icon defined on the item configuration, and switches to that item when tapped on. We can also position
 * the tab bar at the top, which makes our Tab Panel look like this:
 *
 *     @example miniphone preview
 *     Ext.create('Ext.TabPanel', {
 *         fullscreen: true,
 *
 *         defaults: {
 *             styleHtmlContent: true
 *         },
 *
 *         items: [
 *             {
 *                 title: 'Home',
 *                 html: 'Home Screen'
 *             },
 *             {
 *                 title: 'Contact',
 *                 html: 'Contact Screen'
 *             }
 *         ]
 *     });
 *
 * For more information, see our [Tab Panel Guide](../../../components/tabpanel.html).
 */
Ext.define('Ext.tab.Panel', {
    extend: 'Ext.Container',
    xtype : 'tabpanel',
    alternateClassName: 'Ext.TabPanel',

    requires: ['Ext.tab.Bar'],

    config: {
        /**
         * @cfg {String} ui
         * Sets the UI of this component.
         * Available values are: `light` and `dark`.
         * @accessor
         */
        ui: 'dark',

        /**
         * @cfg {Object} tabBar
         * An Ext.tab.Bar configuration.
         * @accessor
         */
        tabBar: true,

        /**
         * @cfg {String} tabBarPosition
         * The docked position for the {@link #tabBar} instance.
         * Possible values are 'top' and 'bottom'.
         * @accessor
         */
        tabBarPosition: 'top',

        /**
         * @cfg layout
         * @inheritdoc
         */
        layout: {
            type: 'card',
            animation: {
                type: 'slide',
                direction: 'left'
            }
        },

        /**
         * @cfg cls
         * @inheritdoc
         */
        cls: Ext.baseCSSPrefix + 'tabpanel'

        /**
         * @cfg {Boolean/String/Object} scrollable
         * @accessor
         * @hide
         */

        /**
         * @cfg {Boolean/String/Object} scroll
         * @hide
         */
    },

    initialize: function() {
        this.callParent();

        this.on({
            order: 'before',
            activetabchange: 'doTabChange',
            delegate: '> tabbar',
            scope   : this
        });

        this.on({
            disabledchange: 'onItemDisabledChange',
            delegate: '> component',
            scope   : this
        });
    },

    platformConfig: [{
        theme: ['Blackberry', 'Blackberry103'],
        tabBarPosition: 'bottom'
    }],

    /**
     * Tab panels should not be scrollable. Instead, you should add scrollable to any item that
     * you want to scroll.
     * @private
     */
    applyScrollable: function() {
        return false;
    },

    /**
     * Updates the Ui for this component and the {@link #tabBar}.
     */
    updateUi: function(newUi, oldUi) {
        this.callParent(arguments);

        if (this.initialized) {
            this.getTabBar().setUi(newUi);
        }
    },

    /**
     * @private
     */
    doSetActiveItem: function(newActiveItem, oldActiveItem) {
        if (newActiveItem) {
            var items = this.getInnerItems(),
                oldIndex = items.indexOf(oldActiveItem),
                newIndex = items.indexOf(newActiveItem),
                reverse = oldIndex > newIndex,
                animation = this.getLayout().getAnimation(),
                tabBar = this.getTabBar(),
                oldTab = tabBar.parseActiveTab(oldIndex),
                newTab = tabBar.parseActiveTab(newIndex);

            if (animation && animation.setReverse) {
                animation.setReverse(reverse);
            }

            this.callParent(arguments);

            if (newIndex != -1) {
                this.forcedChange = true;
                tabBar.setActiveTab(newIndex);
                this.forcedChange = false;

                if (oldTab) {
                    oldTab.setActive(false);
                }

                if (newTab) {
                    newTab.setActive(true);
                }
            }
        }
    },

    /**
     * Updates this container with the new active item.
     * @param {Object} tabBar
     * @param {Object} newTab
     * @return {Boolean}
     */
    doTabChange: function(tabBar, newTab) {
        var oldActiveItem = this.getActiveItem(),
            newActiveItem;

        this.setActiveItem(tabBar.indexOf(newTab));
        newActiveItem = this.getActiveItem();
        return this.forcedChange || oldActiveItem !== newActiveItem;
    },

    /**
     * Creates a new {@link Ext.tab.Bar} instance using {@link Ext#factory}.
     * @param {Object} config
     * @return {Object}
     * @private
     */
    applyTabBar: function(config) {
        if (config === true) {
            config = {};
        }

        if (config) {
            Ext.applyIf(config, {
                ui: this.getUi(),
                docked: this.getTabBarPosition()
            });
        }

        return Ext.factory(config, Ext.tab.Bar, this.getTabBar());
    },

    /**
     * Adds the new {@link Ext.tab.Bar} instance into this container.
     * @private
     */
    updateTabBar: function(newTabBar) {
        if (newTabBar) {
            this.add(newTabBar);
            this.setTabBarPosition(newTabBar.getDocked());
        }
    },

    /**
     * Updates the docked position of the {@link #tabBar}.
     * @private
     */
    updateTabBarPosition: function(position) {
        var tabBar = this.getTabBar();
        if (tabBar) {
            tabBar.setDocked(position);
        }
    },

    onItemAdd: function(card) {
        var me = this;

        if (!card.isInnerItem()) {
            return me.callParent(arguments);
        }

        var tabBar = me.getTabBar(),
            initialConfig = card.getInitialConfig(),
            tabConfig = initialConfig.tab || {},
            tabTitle = (card.getTitle) ? card.getTitle() : initialConfig.title,
            tabIconCls = (card.getIconCls) ? card.getIconCls() : initialConfig.iconCls,
            tabHidden = (card.getHidden) ? card.getHidden() : initialConfig.hidden,
            tabDisabled = (card.getDisabled) ? card.getDisabled() : initialConfig.disabled,
            tabBadgeText = (card.getBadgeText) ? card.getBadgeText() : initialConfig.badgeText,
            innerItems = me.getInnerItems(),
            index = innerItems.indexOf(card),
            tabs = tabBar.getItems(),
            activeTab = tabBar.getActiveTab(),
            currentTabInstance = (tabs.length >= innerItems.length) && tabs.getAt(index),
            tabInstance;

        if (tabTitle && !tabConfig.title) {
            tabConfig.title = tabTitle;
        }

        if (tabIconCls && !tabConfig.iconCls) {
            tabConfig.iconCls = tabIconCls;
        }

        if (tabHidden && !tabConfig.hidden) {
            tabConfig.hidden = tabHidden;
        }

        if (tabDisabled && !tabConfig.disabled) {
            tabConfig.disabled = tabDisabled;
        }

        if (tabBadgeText && !tabConfig.badgeText) {
            tabConfig.badgeText = tabBadgeText;
        }

        //<debug warn>
        if (!currentTabInstance && !tabConfig.title && !tabConfig.iconCls) {
            if (!tabConfig.title && !tabConfig.iconCls) {
                Ext.Logger.error('Adding a card to a tab container without specifying any tab configuration');
            }
        }
        //</debug>

        tabInstance = Ext.factory(tabConfig, Ext.tab.Tab, currentTabInstance);

        if (!currentTabInstance) {
            tabBar.insert(index, tabInstance);
        }

        card.tab = tabInstance;

        me.callParent(arguments);

        if (!activeTab && activeTab !== 0) {
            tabBar.setActiveTab(tabBar.getActiveItem());
        }
    },

    /**
     * If an item gets enabled/disabled and it has an tab, we should also enable/disable that tab
     * @private
     */
    onItemDisabledChange: function(item, newDisabled) {
        if (item && item.tab) {
            item.tab.setDisabled(newDisabled);
        }
    },

    // @private
    onItemRemove: function(item, index) {
        this.getTabBar().remove(item.tab, this.getAutoDestroy());

        this.callParent(arguments);
    }
}, function() {
    //<deprecated product=touch since=2.0>
    /**
     * @cfg {Boolean} tabBarDock
     * @inheritdoc Ext.tab.Panel#tabBarPosition
     * @deprecated 2.0.0 Please use {@link #tabBarPosition} instead.
     */
    Ext.deprecateProperty(this, 'tabBarDock', 'tabBarPosition');
    //</deprecated>
});


/**
 * @class Ext.util.LineSegment
 *
 * Utility class that represents a line segment, constructed by two {@link Ext.util.Point}
 */
Ext.define('Ext.util.LineSegment', {
    requires: ['Ext.util.Point'],

    /**
     * Creates new LineSegment out of two points.
     * @param {Ext.util.Point} point1
     * @param {Ext.util.Point} point2
     */
    constructor: function(point1, point2) {
        var Point = Ext.util.Point;

        this.point1 = Point.from(point1);
        this.point2 = Point.from(point2);
    },

    /**
     * Returns the point where two lines intersect.
     * @param {Ext.util.LineSegment} lineSegment The line to intersect with.
     * @return {Ext.util.Point}
     */
    intersects: function(lineSegment) {
        var point1 = this.point1,
            point2 = this.point2,
            point3 = lineSegment.point1,
            point4 = lineSegment.point2,
            x1 = point1.x,
            x2 = point2.x,
            x3 = point3.x,
            x4 = point4.x,
            y1 = point1.y,
            y2 = point2.y,
            y3 = point3.y,
            y4 = point4.y,
            d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4),
            xi, yi;

        if (d == 0) {
            return null;
        }

        xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
        yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;

        if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)
            || xi < Math.min(x3, x4) || xi > Math.max(x3, x4)
            || yi < Math.min(y1, y2) || yi > Math.max(y1, y2)
            || yi < Math.min(y3, y4) || yi > Math.max(y3, y4)) {
            return null;
        }

        return new Ext.util.Point(xi, yi);
    },

    getLength: function() {
        return Math.abs(this.point1.getDistanceTo(this.point2));
    },

    getAngleToX: function() {
        var point1 = this.point1,
            point2 = this.point2,
            deltaY = point2.y - point1.y,
            deltaX = point2.x - point1.x;

        return Math.atan2(deltaY, deltaX);
    },

    getInBetweenPoint: function(distance) {
        var point1 = this.point1,
            angle = this.getAngleToX(),
            x = point1.x + Math.cos(angle) * distance,
            y = point1.y + Math.sin(angle) * distance;

        return new Ext.util.Point(x, y);
    },

    /**
     * Returns string representation of the line. Useful for debugging.
     * @return {String} For example `Point[12,8] Point[0,0]`
     */
    toString: function() {
        return this.point1.toString() + " " + this.point2.toString();
    }
});


'use strict';

// MODULES //

var RE = require( 'regex-function-name' ),
	isFunction = require( 'validate.io-function' ),
	isString = require( 'validate.io-string-primitive' );

// FUNCTION NAME //

/**
* FUNCTION: functionName( fcn )
*	Returns the name of a function.
*
* @param {Function} fcn - input function
* @returns {String} function name or null
*/
function functionName( fcn ) {
	var name;
	if ( !isFunction( fcn ) ) {
		throw new TypeError( 'invalid input argument. Must provide a function. Value: `' + fcn + '`.' );
	}
	if ( isString( fcn.name ) ) {
		name = fcn.name;
	} else {
		name = RE.exec( fcn.toString() )[ 1 ];
	}
	return (name === '') ? 'anonymous' : name;
} // end FUNCTION functionName()

// EXPORTS //

module.exports = functionName;


var program = require('commander');
var Random = require('./Random.js');
var random = new Random();
var env = require('../package.json');

/*
 * Class that interprets command line commands
 * using commander.
 */
var Command = function () {
    // Defines all the commands with commander.
    program
        .version(env.version)
        .description('Tool for generating random numbers from a seed')
        .option('-s, --seed <seed>', 'specify the seed', Math.random())
        .option('-d, --decimal', 'generates a random decimal number', false)
        .option('-r, --range <a>,<b>', 'generates a random integer in the range inclusive', this.range)
        .parse(process.argv);

    // Calls the method that will check the commands
    // the user has entered.
    this.switchFunction();
};

(function (status_, proto_) {

    /*
     * Method which switches the function string and
     * executes the chosen method from the Random
     * class.
     */
    proto_.switchFunction = function () {
        if (program.decimal) {
            console.log(random.decimal(program.seed));
        }
        else if (typeof program.range !== 'undefined') {
            console.log(random.range(program.seed, program.range[0], program.range[1]));
        }
        else {
            program.help();
        }
    };

    /*
     * Method which is used to separate arguments
     * into an array.
     */
    proto_.range = function (val) {
        return val.split(',').map(Number);
    };

}(Command, Command.prototype));

module.exports = Command;


/*
  Copyright Jesús Pérez <jesusprubio@gmail.com>

  This code may only be used under the MIT license found at
  https://opensource.org/licenses/MIT.
*/

// Different transports wrapper to allow to open a connection over
// UDP, TCP, TLS, WS or WSS in a transparent way.
// TODO: For now it only works as a client able to send (and receive) stuff!

'use strict';

// Private stuff

var dgram = require('dgram'),
    net = require('net'),
    tls = require('tls'),
    util = require('util'),
    EventEmitter = require('events').EventEmitter,
    WebSocketClient = require('websocket').client;

// Helpers

function addZeros(block) {
    if (block === '') {
        return '0';
    } else {
        return block;
    }
}

function normalize6(add6) {
    var normalizedAdd, splittedAdd, i;

    normalizedAdd = [];
    splittedAdd = add6.split(':');

    for (i = 0; i < splittedAdd.length; i += 1) {
        i = splittedAdd[i];
        normalizedAdd.push(addZeros(i));
    }

    return normalizedAdd.join(':');
}

// Public stuff

// Constructor

function SteroidsSocket(options) {
    var finalTarget;

    if (options.target && net.isIPv6(options.target)) {
        finalTarget = normalize6(options.target);
    } else {
        finalTarget = options.target;
    }

    this.target = finalTarget;
    this.port = options.port || 80;
    this.transport = options.transport || 'TCP';
    this.lport = options.lport || null;
    this.timeout = options.timeout || 8000;
    this.allowHalfOpen = options.allowHalfOpen || null;
    this.wsProto = options.wsProto || 'sip';
    this.wsPath = options.wsPath || null;

    // We init the socket in the send function to be able
    // to detect timeouts using UDP (no "received" or similar event)
}
// We're going to use the Node EventEmitter
util.inherits(SteroidsSocket, EventEmitter);

// Methods

SteroidsSocket.prototype.send = function (msg) {
    var self = this,
        // This var is to control of our timeout error
        received = false,
        wsError = false,
        protocols;

    // The libraries don't support any close function, so we need this
    // to emulate it.
    function timeoutCb() {
        if (!received) {
            self.emit('error', {
				type: 'socket: timeout',
				data: 'Connection problem: No response'
			});
        }
		// Websockets Node module doen't support any close function, we're using the client
		// https://github.com/Worlize/WebSocket-Node/blob/master/lib/WebSocketClient.js
		// So we need this var to "emulate" it and avoid returning multiple errors
        wsError = true;

        // We're closing the socket manually, so we need this to avoid errors
        self.close();
    }

    protocols = {
        'UDP': function () {
            if (net.isIPv6(self.target)) {
                self.metaSocket = dgram.createSocket('udp6');
            } else {
                self.metaSocket = dgram.createSocket('udp4');
            }

            self.metaSocket.on('error', function (err) {
                received = true; // to avoid the launch of our timeout error
                self.emit('error', {
                    type: 'socket',
                    data: err
                });
            });

            self.metaSocket.on('closed', function () {
                self.emit('closed');
            });

            self.metaSocket.on('message', function (msg, rinfo) {
                received = true;
                self.emit('message', {
                    type: 'received',
                    data: msg,
                    rinfo: rinfo
                });
            });

            // "connect" listener
            self.metaSocket.bind(self.lport, function () {
                var buff = new Buffer(msg);

                setTimeout(timeoutCb, self.timeout);
                self.metaSocket.send(
                    buff,
                    0,
                    buff.length,
                    self.port,
                    self.target
                );
            });
        },
        'TCP': function (isSecure) {
            function listenCb() {
                self.metaSocket.write(msg);
                // TODO: Another timeout and event (no response)
                // should be also generated
            }

            setTimeout(timeoutCb, self.timeout);

            if (!isSecure) {
                self.metaSocket = net.connect(
                    {
                        host: self.target,
                        port: self.port,
                        // if true, the socket won't automatically send a FIN
                        // packet when the other end of the socket sends a FIN
                        // packet. Defaults to false, usefull to flood
                        allowHalfOpen: self.allowHalfOpen
        //                localAddress: ''
                    },
                    listenCb // 'connect listener'
                );
            } else {
                self.metaSocket = tls.connect(
    // http://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback
                    {
                        host: self.target,
                        port: self.port,
                        rejectUnauthorized: false
                    },
                    listenCb // 'connect listener'
                );
            }
            self.metaSocket.on('error', function (err) {
                received = true; // to avoid the launch of our timeout error
                self.emit('error', {
                    type: 'socket',
                    data: err.toString()
                });
            });

            self.metaSocket.on('end', function () {
                self.emit({
                    type: 'socket closed'
                });
            });

            self.metaSocket.on('data', function (data) {
                received = true;
                self.emit('message', {
                    type: 'received',
                    data: data
                });
            });
        },
        'TLS': function () {
            protocols.TCP(true);
        },
        'WS': function () {
            var addr = self.transport.toLowerCase() +
                '://' + self.target + ':' + self.port;

            if (self.wsPath) {
                addr += '/' + self.wsPath;
            }
            self.metaSocket = new WebSocketClient({
                tlsOptions: {
                    rejectUnauthorized : false
                }
            });

            setTimeout(timeoutCb, self.timeout);

            self.metaSocket.on('connectFailed', function (err) {
                received = true; // to avoid the launch of our timeout error
                if (!wsError) {
                    self.emit('error', {
                        type: 'socket: connectFailed',
                        data: err.toString()
                    });
                }
            });

            self.metaSocket.on('connect', function (connection) {
                connection.on('error', function (err) {
                    // To avoid returning multiple errors, see the comments
                    // in "callback" function
                    if (!wsError) {
                        self.emit('error', {
                            type: 'socket',
                            data: err
                        });
                    }
                });

                connection.on('close', function () {
                    self.emit('closed');
                });

                connection.on('message', function (message) {
                    received = true;
                    self.emit('message', {
                        type: 'received',
                        data: message.utf8Data
                    });
                });

                connection.sendUTF(msg);
            });

            self.metaSocket.connect(addr, 'sip');
        },
        'WSS': function () {
            protocols.WS();
        }
    };

    if (!this.target) {
        self.emit('error', {
            type: 'params',
            data: 'You need to specify a valid IPv4/6 target'
        });

        return;
    }
    if (!protocols[this.transport]) {
        self.emit('error', {
            type: 'Transport not found'
        });

        return;
    }
    protocols[this.transport]();
};

SteroidsSocket.prototype.close = function () {
    try {
        if (this.transport === 'TCP' || this.transport === 'TLS') {
            this.metaSocket.destroy();
        } else if (this.transport === 'UDP') {
            this.metaSocket.close();
        }
    } catch (err) {} // do nothing, only to avoid crashing
};

module.exports = SteroidsSocket;


"use strict";

var emojiRegex = require('emoji-regex')();

/**
 * Strip ansi characters
 *
 * @param {string} str
 * @returns {string}
 */
function stripANSI(str) {
    return ('' + str).replace(/\x1B\[\d+m/g, '');
}

/**
 * Strip wrap characters
 *
 * @param {string} str
 * @returns {string}
 */
function stripWRAP(str) {
    return ('' + str).replace(/[\n\r]+/g, ' ');
}

/**
 * Strip emoji characters
 *
 * @param {string} str
 * @returns {string}
 */
function stripEmoji(str) {
    return ('' + str).replace(emojiRegex, '');
}

/**
 * Split if string given
 *
 * @param {array|string} source
 * @param {string|regex} symbol
 * @returns {array}
 */
function split(source, symbol) {
    return isArray(source) ? source : ('' + source).split(symbol);
}

/**
 * Is string
 *
 * @param t
 * @returns {boolean}
 */
function isString(t) {
    return typeof t === 'string';
}

/**
 * Is Array
 *
 * @param t
 * @returns {boolean}
 */
function isArray(t) {
    return Array.isArray(t);
}

/**
 * String real width
 *
 * @param {string} str
 * @returns {number}
 *
 * Emoji: 2
 * Double-byte character: 2
 * Others: 1
 */
function realWidth(str) {
    if (str == null)
        return 0;
    str = stripANSI(str);
    return str.length + (stripEmoji(str).match(/[^\x00-\xff]/g) || []).length;
}

/**
 * Main
 *
 * @param source
 * @param config
 * @constructor
 */
function PC(source, config) {
    if (source == null)
        return;

    this.config = {
        prefix: '',
        suffix: '',
        placeholder: ' ',
        columnSeparation: ' ',
        rowSeparation: "\n",
        rowSplitSymbol: "\n",
        columnSplitSymbol: "\t"
    };

    this._STATS = {
        originalSource: source,
        source: null,
        formatted: null,
        rows: 0,
        columns: 0,
        maxWidth: [],
        width: [],
        align: []
    };

    this.params(config);
    this.parseSource();
    this.AnalyticAlignment();
    this.format();
}

var tp = PC.prototype;

/**
 * Parse configuration
 *
 * @param {object} config
 */
tp.params = function params(config) {
    Object.assign(this.config, config);
};

/**
 * Parsing the original source and create this._STATS.source
 */
tp.parseSource = function parse() {
    this._STATS.source = split(this._STATS.originalSource, this.config.rowSplitSymbol).map(function (row, rowNum) {
        if (this._STATS.columns < row.length)
            this._STATS.columns = row.length;
        this._STATS.width[rowNum] = [];
        return split(row, this.config.columnSplitSymbol).map(function (column, columnNum) {
            var length = this._STATS.width[rowNum][columnNum] = realWidth(column);
            if (!this._STATS.maxWidth.hasOwnProperty(columnNum) || this._STATS.maxWidth[columnNum] < length)
                this._STATS.maxWidth[columnNum] = length;
            return stripWRAP(column);
        }.bind(this));
    }.bind(this));
    this._STATS.rows = this._STATS.source.length;
};

/**
 * Analytic alignment
 */
tp.AnalyticAlignment = function fixAlign() {
    var align = (new Array(this._STATS.columns)).fill(0);
    var optAlign = [];
    if (this.config.hasOwnProperty('align')) {
        if (isString(this.config.align)) {
            optAlign = this.config.align.split('');
        } else if (isArray(this.config.align)) {
            optAlign = this.config.align;
        }
        Object.assign(align, optAlign.slice(0, this._STATS.columns).map(function (item) {
            switch (item) {
                case 1:
                case 'c':
                case 'center':
                    return 1;
                case 2:
                case 'r':
                case 'right':
                    return 2;
                case 0:
                case 'l':
                case 'left':
                default:
                    return 0;
            }
        }));
    }
    this._STATS.align = align;
};

/**
 * Format source and create this._STATS.formatted
 */
tp.format = function format() {
    var placeholder = this.config.placeholder;
    var columnSeparation = this.config.columnSeparation;
    var rowSeparation = this.config.rowSeparation;
    this._STATS.formatted = this.config.prefix +
        this._STATS.source.map(function (row, rowNum) {
            return row.map(function (column, columnNum) {
                var fillZero = this._STATS.maxWidth[columnNum] - this._STATS.width[rowNum][columnNum];
                if (fillZero > 0) {
                    var fl, fr;
                    switch (this._STATS.align[columnNum]) {
                        case 0:
                            fl = 0;
                            fr = fillZero;
                            return column + (new Array(fr + 1)).join(placeholder);
                        case 1:
                            fl = parseInt(fillZero / 2);
                            fr = fillZero - fl;
                            return (new Array(fl + 1)).join(placeholder) + column + (new Array(fr + 1)).join(placeholder);
                        case 2:
                            fl = fillZero;
                            fr = 0;
                            return (new Array(fl + 1)).join(placeholder) + column;
                    }
                }
                return column;
            }.bind(this)).join(columnSeparation)
        }.bind(this)).join(rowSeparation) +
        this.config.suffix;
};

/**
 * Output
 */
tp.output = function output() {
    console.log(this._STATS.formatted);
};

/**
 * Export wrapper
 *
 * @param {string|array} source
 * @param {object|null} config
 * @returns {PC}
 * @constructor
 */
function WRAP(source, config) {
    return new PC(source, config || {});
}

/**
 * Direct call of output
 *
 * @param {string|array} source
 * @param {object|null} config
 */
WRAP.output = function (source, config) {
    (new PC(source, config || {})).output();
};

/**
 * Inject to console with given string
 *
 * @param {string} key default: "columns"
 */
WRAP.injectConsole = function (key) {
    console[key || 'columns'] = WRAP.output;
};

module.exports = WRAP;


/* globals mkdir, cp */
'use strict';

var gulp     = require('gulp'),
    fs       = require('fs'),
    _        = require('lodash'),
    path     = require('path'),
    conflict = require('gulp-conflict');

function Util() {

  return {

    /**
     * Determine the current platform based on the nodejs process property.
     * @type {string}
     */
    platform: (process.platform === 'win32' || process.platform === 'win64') ? 'windows' : 'unix',

    /**
     * Based on the current platform return the current user's HOME directory or User Documents directory.
     * @returns {*}
     * @constructor
     */
    HOME: function () {
      if (this.platform === 'windows')
        return process.env.USERPROFILE;
      else
        return process.env.HOME;
    },

    /**
     * Recursivley copy a folder to a given destination, if there are conflicts prompt the user.
     * @param source
     * @param destination
     */
    cpRConflict: function (source, destination)
    {
      gulp.src(source)
        .pipe(conflict(destination))
        .pipe(gulp.dest(destination));
    },

    /**
     * Recursively copy a template folder to a given destination and
     * apply a template context object for using <%= token %> delimiters
     * and {{= token }} for filename delimiters (windows restriction).
     *
     * @param source
     * @param destination
     * @param context
     * @param opts
     */
    templateDirSync: function (source, destination, context, opts)
    {
      if (!opts) opts = {force: true};
      this.mkdirP(destination);

      var files = fs.readdirSync(source);

      for (var i = 0; i < files.length; i++) {
        var sourceFile = path.join(source, files[i]);
        var destinationFile = path.join(destination, files[i]);

        if (this.isDirectory(sourceFile)) {
          this.templateDirSync(sourceFile, destinationFile, context, opts);
        } else {
          if (fs.existsSync(destinationFile) && !opts.force) {
            console.log('skipping existing file: ' + files[i]);
          } else {
            this.templateFileSync(sourceFile, destinationFile, context);
          }
        }
      }
    },

    /**
     * Copy a and apply a lodash template to a file based on a given context.
     * The filename can include a {{= token }} for template delimiters (windows restriction).
     * @param source
     * @param destination
     * @param context
     */
    templateFileSync: function (source, destination, context)
    {
      // To avoid issues with windows use a custom delimiter for file names
      if (destination.indexOf('{{-') !== -1) {
        _.templateSettings.escape = /\{\{-(.*?)\}\}/g;
        destination = _.template(destination, context);
      }

      var content = fs.readFileSync(source, 'utf8').toString();

      var indexContent = _.template(content, context);

      fs.writeFileSync(destination, indexContent, 'utf8');
    },

    /**
     * Shortcut creating a folder recursively in any given destination,
     * if the destination already exists, it will do nothing.
     * @param destination
     */
    mkdirP: function (destination)
    {
      if (!fs.existsSync(destination))
        mkdir('-p', destination);
    },

    /**
     * Shortcut for copying a folder and it's contents recursively to a given destination,
     * if the source does not exist, it will do nothing.
     * @param source
     * @param destination
     */
    cpR: function (source, destination)
    {
      if (!fs.existsSync(destination))
        mkdir('-p', destination);

      cp('-R', source, destination);
    },

    /**
     * Shortcut for copying a file to a given destination,
     * if the source does not exist, it will do nothing.
     * @param source
     * @param destination
     */
    cp: function (source, destination)
    {
      if (!fs.existsSync(destination))
        mkdir('-p', destination);

      cp(source, destination);
    },

    /**
     * Lazy check if a given source path is a directory that exists.
     * @param source
     * @returns {*}
     */
    isDirectory: function (source)
    {
      if (!fs.existsSync(source))
        return false;

      return fs.statSync(source).isDirectory();
    },

    /**
     * Lazy check to see if a given file exists.
     * @param source
     * @returns {boolean}
     */
    fileExists: function (source)
    {
      try {
        fs.statSync(source);
      } catch (err) {
        if (err.code === 'ENOENT') return false;
      }
      return true;
    }
  };
}

module.exports = new Util();


// A key with tracking info that objects sent to this module are unlikely
// to have already.
var trackingKeyName = "__TRACKING_DATA__";

exports.track = function (object) {
  if (!object || typeof object !== "object") {
    throw new Error("track: Can only track objects.");
  }

  var tracker = {};
  // Attach tracking metadata to obscurely named nonenumerable property to
  // avoid differences in behavior between the tracker and wrapped object.
  Object.defineProperty(tracker, trackingKeyName, {
    value: {
      object: object,
      actions: [],
    },
  });

  // Set up the tracking and forwarding.
  var existing = inspectObject(object);
  addProps(tracker, existing.propList);
  addMethods(tracker, existing.methodList);

  return tracker;
};

exports.getActions = function (tracker) {
  if (!tracker || typeof tracker !== "object" || !tracker[trackingKeyName]) {
    throw new Error("getActions: Must pass valid tracker.");
  }

  return tracker[trackingKeyName].actions;
};

exports.play = function (object, actions) {
  if (!object || typeof object !== "object") {
    throw new Error("play: Can only play actions on objects.");
  } else if (!actions || typeof actions !== "object" || !actions.forEach) {
    throw new Error("play: Must pass array of actions.");
  }

  actions.forEach(function (action) {
    if (action.arguments) {
      object[action.key].apply(object, action.arguments);
    } else {
      object[action.key] = action.set;
    }
  });

  return object;
};

// Add assignable properties for tracking.
var addProps = function (tracker, propList) {
  var trackingData = tracker[trackingKeyName];
  propList.forEach(function (name) {
    Object.defineProperty(tracker, name, {
      enumerable: true,
      configurable: true,
      get: function () {
        return trackingData.object[name];
      },
      set: function (x) {
        trackingData.actions.push({
          key: name,
          set: x,
        });
        trackingData.object[name] = x;
      },
    });
  });
};

// Add callable methods for tracking.
var addMethods = function (tracker, methodList) {
  var trackingData = tracker[trackingKeyName];
  methodList.forEach(function (name) {
    tracker[name] = function () {
      var context = this;
      var argsArray = Array.prototype.slice.call(arguments);
      // Only record actions called directly on the tracker.
      if (this === tracker) {
        // Forwarded call should operate on original object.
        context = trackingData.object;
        trackingData.actions.push({
          key: name,
          arguments: argsArray,
        });
      }
      return trackingData.object[name].apply(context, argsArray);
    };
  });
};

// Find all existing properties and methods in the object.
var inspectObject = function (object) {
  var propList = [];
  var methodList = [];
  for (var k in object) {
    if (typeof object[k] === "function") {
      methodList.push(k);
    } else {
      propList.push(k);
    }
  }
  return {
    propList: propList,
    methodList: methodList,
  };
};


Ext.data.SyncProxy = Ext.extend(Ext.data.Proxy, {

	definition: undefined,
	csv: undefined,
	generator: undefined,
	model: undefined,
	store: undefined,
	idProperty: undefined,
	idDefaultProperty: undefined,

	// JCM constructor should not be async, delay until first operation

	constructor: function(config,callback,scope) {
		//
		Ext.data.utilities.check('SyncProxy', 'constructor', 'config', config, ['store','database_name','key']);
		//
		Ext.data.SyncProxy.superclass.constructor.call(this, config);
		this.store= config.store;
		//
		// System Name
		//
		this.store.readValue('Sencha.Sync.system_name',function(system_name){
			config.system_name= system_name || Ext.data.UUIDGenerator.generate();
			this.store.writeValue('Sencha.Sync.system_name',config.system_name,function(){
				//
				// Load Configuration
				//
				Ext.data.utilities.apply(this,[
					'readConfig_DatabaseDefinition',
					'readConfig_CSV',
					'readConfig_Generator'],[config],function(){
					if (this.definition.system_name===undefined) {
						this.definition.set({system_name:Ext.data.UUIDGenerator.generate()});
					}
					console.log("SyncProxy - Opened database '"+config.key+"/"+config.database_name+"/"+config.datastore_name+"'")
					if (callback) { callback.call(scope,this) }
				},this);
			},this);
		},this);
  },

  create: function(operation, callback, scope) {
		operation.records.forEach(function(record) {
			record.setCreateState(this.makeGenerator());
			// if there's no user id, then use the oid.
			if (record.get(this.idProperty)===this.idPropertyDefaultValue) {
				var p= record.getPair(Ext.data.SyncModel.OID);
				record.data[this.idProperty]= p.v;
			}
			// JCM check that the id is unique
		},this)
		var records= this.encodeRecords(operation.records)
		return this.store.create(records,function() {
			this.indexCreatedRecords(records,function(){
				//console.log('create',operation);
				this.doCallback(callback,scope,operation);
			},this);
		},this);
  },

  read: function(operation, callback, scope) {

		function makeResultSet(operation,records,no_system_records) {
			records= this.decodeRecords(records);
			records= Ext.data.array.select(records,function(record){
				return record.isNotDestroyed() && !record.phantom && (!no_system_records || !record.isSystemModel());
			},this);
	    operation.resultSet = new Ext.data.ResultSet({
	        records: records,
	        total  : records.length,
	        loaded : true
	    });
		};

		if (operation.id!==undefined) {
			this.store.indexLookup(operation.id,function(oid) {
				// JCM if the id is not in the index, then it doesn't exist, so we can return now...
				this.store.read(oid,function(record) {
					makeResultSet.call(this,operation,[record],true);
					this.doCallback(callback,scope,operation);
				},this);
			},this);
		} else if (operation[Ext.data.SyncModel.OID]!==undefined) {
				this.store.read(operation[Ext.data.SyncModel.OID],function(record) {
					makeResultSet.call(this,operation,[record],false);
					this.doCallback(callback,scope,operation);
				},this);
		} else {
			var records= [];
			this.store.forEachRecordAsync(function(record,next_callback,next_scope) {
				//console.log(Ext.encode(record))
				records.push(record);
				next_callback.call(next_scope);
			},this,function(){
				makeResultSet.call(this,operation,records,true);
				this.doCallback(callback,scope,operation);
			},this);
		}
  },

  update: function(operation, callback, scope) {
		operation.records.forEach(function(record) {
			record.setUpdateState(this.makeGenerator());
		},this)
		// JCM make sure that the id has not been changed.
		var records= this.encodeRecords(operation.records);
		return this.store.update(records,function(operation) {
			this.doCallback(callback,scope,operation);
		},this);
  },

  destroy: function(operation, callback, scope) {
		var records= [];
		Ext.data.array.forEachAsync(operation.records,function(record,next_callback,next_scope){
			record.setDestroyState(this.makeGenerator());
			var oid= record.oid();
			if (!oid) {
				var id= record.data[this.idProperty];
				this.store.indexLookup(id,function(oid) {
					// JCM if the id is not in the index, then it doesn't exist, so we don't need to try deleting it.
					if (oid) {
						record.data[Ext.data.SyncModel.OID]= oid;
						records.push(record);
					}
					next_callback.call(next_scope);
				},this);
			} else {
				records.push(record);
				next_callback.call(next_scope);
			}
		},this,function(){
			records= this.encodeRecords(records);
			this.store.update(records,function(operation) {
				operation.action= 'destroy';
				this.indexDestroyedRecords(records,function(){
					this.doCallback(callback,scope,operation);
				},this);
			},this);
		},this);
  },

	clear: function(callback, scope) {
		this.store.clear(callback, scope);
	},

  setModel: function(model, setOnStore) {
		this.model= model;
		this.idProperty= this.model.prototype.idProperty;
		var fields = this.model.prototype.fields.items,
	      length = fields.length,
	      field, i;
	  for (i = 0; i < length; i++) {
	      field = fields[i];
				if (field.name===this.idProperty) {
					this.idPropertyDefaultValue= field.defaultValue;
				}
	  }
		this.definition.set({idProperty:this.idProperty,idPropertyDefaultValue:this.idPropertyDefaultValue},function(){},this);
		// extend the user's model with the replication state data,
		Ext.apply(model.prototype, Ext.data.SyncModel);
		// and create a local storage model, based on the user's model.
		this.storageModel= model.prototype.createReplStorageModel(this.modelName); // JCM shouldn't need to pass the name in
		this.store.setModel(this.storageModel, setOnStore);
  },

	replicaNumber: function() {
		return this.generator.r;
	},

	addReplicaNumbers: function(csv,callback,scope) {
		this.csv.addReplicaNumbers(csv,callback,scope);
	},

	setReplicaNumber: function(new_replica_number,callback,scope) {
		if (!callback) { throw "ERROR - SyncProxy - setReplicaNumber - no callback provided." }
  	var old_replica_number= this.replicaNumber();
		console.log('SyncProxy.setReplicaNumber from',old_replica_number,'to',new_replica_number)
    this.changeReplicaNumber(old_replica_number,new_replica_number,function(){
			this.definition.setReplicaNumber(new_replica_number,function(){
		  	this.csv.changeReplicaNumber(old_replica_number,new_replica_number,function(){
				  this.generator.setReplicaNumber(new_replica_number,callback,scope);
				},this);
			},this);
		},this);
	},

  changeReplicaNumber: function(old_replica_number,new_replica_number,callback,scope) {
		console.log('SyncProxy.changeReplicaNumber from',old_replica_number,'to',new_replica_number)
		if (!callback) { throw "ERROR - SyncProxy - changeReplicaNumber - no callback provided." }
		this.forEachRecordAsync(function(record,next_callback,next_scope) {
			if (!record.isSystemModel()){
				var old_oid= record.oid();
				if (record.changeReplicaNumber(old_replica_number,new_replica_number)) {
					var records= this.encodeRecords([record])
					this.store.create(records,function(){
						this.indexCreatedRecords(records,function(){
							this.store.destroy(old_oid,next_callback,next_scope);
						},this);
					},this);
				} else {
					next_callback.call(next_scope);
				}
			} else {
				next_callback.call(next_scope);
			}
		},this,callback,scope);
	},

	getUpdates: function(csv,callback,scope) {
		this.csv.addReplicaNumbers(csv,function(){
	    csv.addReplicaNumbers(this.csv,function(){
				// JCM full scan - expensive - maintain a cs index?
				// JCM might also be too big... perhaps there should be a limit on the number
				// JCM of updates that can be collected...
				// JCM could also exhaust the stack
				// JCM could have a fixed sized list, discarding newest to add older
				// JCM could have a full update protocol as well as an incremental protocol
				var updates= [];
				this.forEachRecordAsync(function(record,next_callback,next_scope) {
					updates= updates.concat(record.getUpdates(csv));
					next_callback.call(next_scope);
				},this,function(){
			    callback.call(scope,new Ext.data.Updates(updates));
				},this);
			},this);
		},this);
  },

	putUpdates: function(updates,callback,scope) {
		//
		// JCM could batch updates by object, to save on wasteful repeated gets and sets of the same object
		//
		// A client or server could receive a large number of updates, which
		// because of the recursive nature of the following code, could
		// exhaust the stack. (Most browsers have a limit of 1000 frames.)
		// Also, on the client, hogging the cpu can cause the UI to feel
		// unresponsive to the user. So, we chunk the updates and process
		// each in turn, yielding the cpu between them.
		//
		var chunks= updates.chunks(10);
		Ext.data.array.forEachYielding(chunks,function(chunk,next_callback,next_scope){
			Ext.data.array.forEachAsync(chunk.updates,function(update,next_callback2,next_scope2) {
				this.applyUpdate(update,function() {
					// make sure to bump forward our clock, just in case one of our peers has run ahead
					this.generator.seen(update.c);
					// update the local csv, after the update has been processed.
					this.csv.add(update.c,next_callback2,next_scope2);
				},this);
			},this,next_callback,next_scope);
		},this,callback,scope);
	},

  applyUpdate: function(update,callback,scope,last_ref) { // Attribute Value - Conflict Detection and Resolution
		if (last_ref) {
			console.log('ref ==> ',this.us(update));
		} else {
			console.log('applyUpdate',this.us(update));
		}
		this.store.read(update.i,function(record) {
			if (record) {
				var ref= record.ref();
				if (ref && update.p[0]!='_') { // JCM this is a bit sneaky
					if (update.i===ref) {
						console.log("Error - applyUpdate - Infinite loop following reference. ",ref);
						callback.call(scope);
					} else {
						update.i= ref;
						this.applyUpdate(update,callback,scope,ref);
					}
				} else {
					if (update.p===this.idProperty) {
						this.applyUpdateToRecordForUniqueID(record,update,callback,scope);
					} else {
						this.applyUpdateToRecord(record,update,callback,scope);
					}
				}
			} else {
				this.applyUpdateCreatingNewRecord(update,callback,scope);
			}
		},this);
  },

	applyUpdateCreatingNewRecord: function(update,callback,scope) {
		// no record with that oid is in the local store...
		if (update.p===Ext.data.SyncModel.OID) {
			// ...which is ok, because the update is intending to create it
			var record= this.createNewRecord(update.v,update.c);
			//console.log('applyUpdate',Ext.encode(record.data),'( create )');
			this.store.create([record],callback,scope);
		} else {
			// ...which is not ok, because given the strict ordering of updates
			// by change stamp the update creating the object must be sent first.
			// But, let's be forgiving and create the record to receive the update.
			console.log("Warning - Update received for unknown record "+update.i,update)
			var record= this.createNewRecord(update.i,update.i);
			record.setPair(update.p,update.v,update.c);
			this.store.create([record],callback,scope);
		}
	},

	applyUpdateToRecordForUniqueID: function(record,update,callback,scope) {
		// update is to the id, for which we maintain uniqueness
		if (record.data[update.p]===update.v) {
			// re-asserting same value for the id
			this.applyUpdateToRecordForUniqueId(record,update,callback,scope);
		} else {
			// different value for the id, so check if a record already exists with that value
			this.store.indexLookup(update.v,function(existing_record_oid) {
				//console.log(this.us(update),'id already exists')
				if (existing_record_oid) {
					//console.log('existing_record_oid',existing_record_oid)
					this.readById(update.v,existing_record_oid,function(existing_record) {
						//console.log('existing_record',Ext.encode(existing_record.data))
						// JCM if the process were to fail part way through these updates...
						// JCM would the system be hoarked?
						this.applyUpdateToRecordForUniqueId(record,update,function(){
							var r_cs= new Ext.data.CS(record.oid());
							var er_cs= new Ext.data.CS(existing_record.oid());
							var r_before, r_after;
							if (r_cs.greaterThan(er_cs)) {
								// the record being updated is more recent then the existing record
								//console.log(this.us(update),'existing record is older');
								r_before= existing_record;
								r_after= record;
							} else {
								// the existing record is more recent than the record being updated
								//console.log(this.us(update),'existing record is newer');
								r_before= record;
								r_after= existing_record;
							}
							this.resolveUniqueIDConflict(r_before,r_after,function() {
								this.store.indexUpdate(update.v,r_before.oid(),callback,scope);
							},this);
						},this);
					},this);
				} else {
					// the new id value did not exist at the time of the update
					this.applyUpdateToRecordForUniqueId(record,update,callback,scope);
				}
			},this);
		}
	},

	applyUpdatesToRecord: function(record,updates,callback,scope) {
		if (updates.length>0) {
			Ext.data.array.forEachAsync(updates,function(update,next_callback,next_scope) {
				this.applyUpdateToRecord(record,update,next_callback,next_scope);
			},this,callback,scope);
		} else {
			callback.call(scope);
		}
	},

	applyUpdateToRecordForUniqueId: function(record,update,callback,scope) {
		var value_before= record.data[update.p];
		var value_after= update.v;
		this.applyUpdateToRecord(record,update,function(changed){
			if (changed) {
				this.store.indexUpdate(value_after,record.oid(),function(){
					if (value_before) {
						this.store.indexUpdate(value_before,undefined,function(){
							callback.call(scope,changed);
						},this);
					} else {
						callback.call(scope,changed);
					}
				},this);
			} else {
				callback.call(scope,changed);
			}
		},this);
	},

	applyUpdateToRecord: function(record,update,callback,scope) {
		if (record.putUpdate(update)) {
			//console.log(this.us(update),'accepted')
			this.store.update([record],function() {
				callback.call(scope,true);
			},scope);
		} else {
			//console.log(this.us(update),'rejected')
			callback.call(scope,false);
		}
	},

	readById: function(id,oid,callback,scope) { // JCM move into applyUpdateToUniqueID?
		this.store.read(oid,function(record) {
			if (record) {
				callback.call(scope,record);
			} else {
				console.log('ERROR - SyncProxy - applyUpdateToUniqueID - ID Index refers to an non-existant object:',id,'=>',oid,'(This should not be possible.)');
			}
		},this);
	},

	resolveUniqueIDConflict: function(r1,r2,callback,scope) { // JCM move into applyUpdateToUniqueID?
		var updates= this.updatesForMergeRecords(r1,r2);
		this.applyUpdatesToRecord(r1,updates,function() {
			var updates= this.updatesForMakeReference(r2,r1);
			this.applyUpdatesToRecord(r2,updates,function() {
				callback.call(scope);
			},this);
		},this);
	},

	updatesForMergeRecords: function(r1,r2) { // merge r2 into r1 // JCM move into applyUpdateToUniqueID?
		// r1 receives all updates from r2
		var csv= r1.getCSV();
		var updates1= r2.getUpdates(csv);
		var updates2= [];
		var r1_oid= r1.oid();
		updates1.forEach(function(update) {
			if (update.p!==this.idProperty && update.p!==Ext.data.SyncModel.OID) {
				update.i= r1_oid;
				updates2.push(update);
			}
		},this);
		//console.log('updatesForMergeRecords - csv',csv);
		//console.log('updatesForMergeRecords - r1',r1.data);
		//console.log('updatesForMergeRecords - r2',r2.data);
		//console.log('updatesForMergeRecords - updates',updates2);
		return updates2;
	},

	updatesForMakeReference: function(r1,r2) { // JCM move into applyUpdateToUniqueID?
		if (r1.oid()===r2.oid()) {
			console.log('updatesForMakeReference',r1.data,r2.data);
			throw "Error - SyncProxy - Tried to create reference to self."
		}
		var cs1= this.generateChangeStamp();
		var cs2= this.generateChangeStamp();
		var updates= [{
			i: r1.oid(),
			p: Ext.data.SyncModel.REF,
			v: r2.oid(),
			c: cs1
		},{
			i: r1.oid(),
			p: Ext.data.SyncModel.TOMBSTONE,
			v: cs2.to_s(),
			c: cs2
		}];
		//console.log('updatesForMakeReference',updates);
		return updates;
	},

	createNewRecord: function(oid,cs) {
		var record= new this.storageModel();
		record.phantom= false;
		Ext.apply(record,Ext.data.SyncModel);
		record.setPair(Ext.data.SyncModel.OID,oid,cs);
		return record;
	},

	indexCreatedRecords: function(records, callback, scope) {
		//console.log('indexCreatedRecords');
		Ext.data.array.forEachAsync(records,function(record,next_callback,next_scope){
			var record_id= record.data[this.idProperty];
			if (record_id) {
				this.store.indexUpdate(record_id,record.data[Ext.data.SyncModel.OID],next_callback,next_scope);
			} else {
				next_callback.call(next_scope);
			}
		},this,callback,scope);
	},

	indexDestroyedRecords: function(records, callback, scope) {
		//console.log('indexDestroyedRecords');
		Ext.data.array.forEachAsync(records,function(record,next_callback,next_scope){
			var record_id= record.data[this.idProperty];
			if (record_id) {
				this.store.indexUpdate(record_id,undefined,next_callback,next_scope);
			} else {
				next_callback.call(next_scope);
			}
		},this,callback,scope);
	},

	makeGenerator: function() {
		var me= this;
		return function() { return me.generateChangeStamp() }
	},

  generateChangeStamp: function() {
		var cs= this.generator.get();
		this.csv.add(cs);
		return cs;
	},

	equals: function(x,callback,scope) { // for testing
		if (this.csv.equals(x.csv)) {
			this.hasSameRecords(x,function(r){
				if (r) {
					x.hasSameRecords(this,callback,scope)
				} else {
					callback.call(scope,false)
				}
			},this)
		} else {
			callback.call(scope,false)
		}
	},

	hasSameRecords: function(x,callback,scope) { // for testing
		this.forEachRecordAsync(function(r1,next_callback,next_scope){
			this.store.read(r1.oid(),function(r2) {
				if (r2) {
					var r= r1.equals(r2);
					if (r) {
						next_callback.call(next_scope);
					} else {
						console.log('hasSameRecords - false - ',this.replicaNumber(),x.replicaNumber())
						callback.call(scope,false);
					}
				} else {
					console.log('hasSameRecords - false - ',this.replicaNumber(),x.replicaNumber())
					callback.call(scope,false);
				}
			},this);
		},this,function(){
			callback.call(scope,true);
		},this);
	},

	console_log: function(text,callback,scope) { // for testing
		console.log('---- ',text);
		this.forEachRecordAsync(function(r1,next_callback,next_scope){
			console.log(Ext.encode(r1.data));
			next_callback.call(next_scope);
		},this,function(){
			console.log('----');
			callback.call(scope);
		},this);
	},

	forEachRecordAsync: function(each_callback, each_scope, done_callback, done_scope) { // JCM this is expensive... nothing should really call this.....
		var Model= this.model;
		this.store.forEachRecordAsync(function(record,next_callback,next_scope){
			each_callback.call(each_scope,new Model(record.data),next_callback,next_scope);
		},this,done_callback,done_scope);
	},

	encodeRecords: function(records) {
		var Model= this.storageModel;
		return Ext.data.array.collect(records,function(){
			var record= new Model(this.data);
			record.internalId= this.internalId;
			record.phantom= false;
			return record;
		});
	},

	decodeRecords: function(records) {
		var Model= this.model;
		return Ext.data.array.collect(records,function(){
			var record= new Model(this.data);
			record.internalId= this.internalId;
			record.phantom= false;
			return record;
		});
	},

	readConfig_DatabaseDefinition: function(config,callback,scope) {
		var default_data= {
			key: config.key,
			system_name: config.system_name,
			generation: 0,
			replica_number: 0
		};
		var overwrite_data= {
			database_name: config.database_name,
			replica_type: config.replica_type
		};
		this.readConfig(Ext.data.DatabaseDefinition,'definition',default_data,overwrite_data,function(definition) {
			this.definition= definition;
			callback.call(scope);
		},this);
	},

	readConfig_Generator: function(config,callback,scope) {
		var overwrite_data= {
			r: this.definition.replica_number,
			clock: config.clock
		};
		this.readConfig(Ext.data.CSGenerator,'generator',{},overwrite_data,function(generator){
			this.generator= generator;
			callback.call(scope);
		},this);
	},

	readConfig_CSV: function(config,callback,scope) {
		this.readConfig(Ext.data.CSV,'csv',{},{},function(csv){
			this.csv= csv;
			callback.call(scope);
		},this);
	},

	writeConfig: function(id, object, callback, scope) {
		this.store.writeConfig(id,object.as_data(),function(data){
			object.set(data);
			callback.call(scope)
		},this);
	},

	readConfig: function(Klass, id, default_data, overwrite_data, callback, scope) {
        var changed, name;
		this.store.readConfig(id,function(data) {
			if (default_data!==undefined) {
				if (data===undefined) {
					data= default_data;
				} else {
					for(name in default_data) {
						if (data[name]===undefined) {
							data[name]= default_data[name];
							changed= true;
						}
					}
				}
			}
			if (overwrite_data!==undefined) {
				if (data===undefined) {
					data= overwrite_data;
				} else {
					for(name in overwrite_data) {
						if (data[name]!==overwrite_data[name]) {
							data[name]= overwrite_data[name];
							changed= true;
						}
					}
				}
			}
			var me= this;
			data.config_id= id;
			data.write_fn= function(object, write_callback, write_scope) {
				me.writeConfig.call(me,id,object,write_callback,write_scope);
			};
			callback.call(scope,new Klass(data));
		},this);
	},

	doCallback: function(callback, scope, operation) {
    if (typeof callback == 'function') {
			callback.call(scope || this, operation);
    }
	},

	us: function(u) {
		var p= Ext.isArray(u.p) ? u.p.join() : u.p;
		var v= u.v;
		switch (typeof u.v) {
			case 'object':
				v= Ext.encode(u.v);
		}
		return '('+u.i+' . '+p+' = \''+v+'\' @ '+u.c.to_s()+')';
	}

});


/**
 * @module xblocks-core/block
 */

import * as xtag from 'xtag';
import forEach from 'lodash/forEach';
import isFunction from 'lodash/isFunction';
import stubFalse from 'lodash/stubFalse';
import merge from 'lodash/merge';
import assign from 'lodash/assign';
import mergeWith from 'lodash/mergeWith';
import uniqueId from 'lodash/uniqueId';
import spread from 'lodash/spread';
import castArray from 'lodash/castArray';
import isArray from 'lodash/isArray';
import get from 'lodash/get';
import wrap from 'lodash/wrap';
import invoke from 'lodash/invoke';
import trim from 'lodash/trim';
import * as dom from './dom';
import { XBElement } from './element';
import { lazy, propTypes } from './utils';
import Constants from './constants';
import wrapperFunction from './utils/wrapperFunction';
import checkOverriddenMethods from './utils/checkOverriddenMethods';

const spreadMergeWith = spread(mergeWith);

const BLOCK_COMMON_ACCESSORS = {
    mounted: {
        /**
         * Check react mounted
         * @returns {boolean}
         * @readonly
         */
        get: function () {
            return Boolean(invoke(this, [ Constants.BLOCK, 'isMounted' ]));
        }
    },

    content: {
        /**
         * Receiving the content.
         * @returns {?string}
         */
        get: function () {
            const content = this.mounted ?
                invoke(this, [ Constants.BLOCK, 'getMountedContent' ]) :
                dom.contentNode(this).innerHTML;

            return trim(content);
        },

        /**
         * Installing a new content.
         * @param {string} content
         */
        set: function (content) {
            content = trim(content);

            if (this.mounted) {
                invoke(this, [ Constants.BLOCK, 'setMountedContent' ], content);

            } else {
                dom.contentNode(this).innerHTML = content;
                this.upgrade();
            }
        }
    },

    attrs: {
        /**
         * Getting object attributes.
         * @returns {Object}
         * @readonly
         */
        get: function () {
            return dom.attrs.toObject(this);
        }
    },

    props: {
        /**
         * Getting object properties.
         * @returns {Object}
         * @readonly
         */
        get: function () {
            const props = dom.attrs.toObject(this);
            const xprops = this.xprops;
            const eprops = get(xtag, [ 'tags', this[ Constants.TAGNAME ], 'accessors' ], {});

            for (let prop in eprops) {
                if (xprops.hasOwnProperty(prop) &&
                    eprops.hasOwnProperty(prop) &&
                    !BLOCK_COMMON_ACCESSORS.hasOwnProperty(prop)) {

                    props[ prop ] = this[ prop ];
                }
            }

            dom.attrs.typeConversion(props, xprops);
            return props;
        }
    },

    xprops: {
        /**
         * Getting react properties.
         * @returns {Object}
         * @readonly
         */
        get: function () {
            return propTypes(this[ Constants.TAGNAME ]);
        }
    },

    outerHTML: dom.outerHTML
};

const BLOCK_COMMON_METHODS = {
    /**
     * Obtaining the React components.
     * @returns {?Constructor}
     */
    getComponent: function () {
        return invoke(this, [ Constants.BLOCK, 'getUserComponent' ]);
    },

    /**
     * Recalculation of the internal structure.
     */
    upgrade: function () {
        dom.upgradeAll(this);
    },

    /**
     * Cloning a node.
     * @param {boolean} deep true if the content to be saved
     * @returns {HTMLElement}
     */
    cloneNode: function (deep) {
        // not to clone the contents
        const node = dom.cloneNode(this, false);
        dom.upgrade(node);

        node[ Constants.TMPL ] = this[ Constants.TMPL ];
        node[ Constants.INSERTED ] = false;

        if (deep) {
            node.content = this.content;
        }

        // ???
        // if ('checked' in this) clone.checked = this.checked;

        return node;
    }
};

/**
 * Creating a new tag.
 * @see http://x-tag.github.io/
 * @alias module:xblocks-core/block.create
 * @param {string} blockName the name of the new node
 * @param {?Object|array} options settings tag creation
 * @returns {HTMLElement}
 * @public
 */
export function create(blockName, options) {
    options = castArray(options);
    options.unshift({ lifecycle: { created: lifecycleCreated, inserted: lifecycleInserted } });
    options.push(mergeCustomizer);
    options = spreadMergeWith(options);

    forEach(options.accessors, accessorsIterator);

    options.accessors = assign(options.accessors, BLOCK_COMMON_ACCESSORS);
    options.methods = assign(options.methods, BLOCK_COMMON_METHODS);

    // "removed" should be called after user handler
    options.lifecycle.removed = wrap(options.lifecycle.removed, wrap(lifecycleRemoved, wrapperFunction));

    return xtag.register(blockName, options);
}

/**
 * Initialization of the element.
 * @example
 * blockInit(node);
 * @param {HTMLElement} node
 * @returns {boolean}
 * @private
 */
function blockInit(node) {
    if (!node[ Constants.TAGNAME ]) {
        node[ Constants.INSERTED ] = false;
        node[ Constants.TAGNAME ] = node.tagName.toLowerCase();
        node[ Constants.TMPL ] = {};
        node[ Constants.UID ] = uniqueId();

        return true;
    }

    return false;
}

/**
 * Creating an item.
 * @example
 * blockCreate(node);
 * @param {HTMLElement} node
 * @private
 */
function blockCreate(node) {
    if (node.hasChildNodes()) {
        Array.prototype.forEach.call(
            node.querySelectorAll('script[type="text/x-template"][ref],template[ref]'),
            tmplCompileIterator,
            node
        );
    }

    node[ Constants.BLOCK ] = new XBElement(node);
}

/**
 * Pending the creation of the item.
 * @example
 * blockCreateLazy([ node1, node2, ... ]);
 * @param {HTMLElement[]} nodes
 * @private
 */
function blockCreateLazy(nodes) {
    nodes.forEach(blockCreate);
}

/**
 * The selection of templates.
 * @example
 * // append template to node
 * tmplCompileIterator.call(node, tmplNode);
 * @param {HTMLElement} node
 * @this HTMLElement
 * @private
 */
function tmplCompileIterator(node) {
    this[ Constants.TMPL ][ node.getAttribute('ref') ] = node.innerHTML;
}

/**
 * Special handler of merge.
 * Arrays are merged by the concatenation.
 * @example
 * _.mergeWith(obj, src, mergeCustomizer);
 * @param {*} objValue
 * @param {*} srcValue
 * @param {string} key
 * @returns {Object|array|undefined}
 * @throws The following methods are overridden
 * @private
 */
function mergeCustomizer(objValue, srcValue, key) {
    if (isArray(objValue)) {
        return objValue.concat(srcValue);
    }

    if (key === 'lifecycle') {
        return mergeWith(objValue, srcValue, lifecycleCustomizer);
    }

    if (key === 'events') {
        return mergeWith(objValue, srcValue, eventsCustomizer);
    }

    if (key === 'accessors') {
        return mergeWith(objValue, srcValue, accessorsCustomizer);
    }

    if (key === 'methods') {
        checkOverriddenMethods(objValue, srcValue);
    }
}

/**
 * Inheritance lifecycle handler.
 * @example
 * _.mergeWith(objValue, srcValue, lifecycleCustomizer);
 * @param {function} [objValue] the current handler
 * @param {function} [srcValue] the new handler
 * @returns {function}
 * @private
 */
function lifecycleCustomizer(objValue, srcValue) {
    return wrap(objValue, wrap(srcValue, wrapperFunction));
}

/**
 * Inheritance event handler.
 * @example
 * _.mergeWith(objValue, srcValue, eventsCustomizer);
 * @param {function} [objValue] the current handler
 * @param {function} [srcValue] the new handler
 * @returns {function}
 * @private
 */
function eventsCustomizer(objValue, srcValue) {
    return wrap(objValue, wrap(srcValue, wrapperEvents));
}

/**
 * Inheritance events "set" property changes.
 * @example
 * _.mergeWith(objValue, srcValue, accessorsCustomizer);
 * @param {Object} [objValue] the current value
 * @param {Object} [srcValue] the new value
 * @returns {Object}
 * @private
 */
function accessorsCustomizer(objValue, srcValue) {
    const objSetter = get(objValue, 'set');
    const srcSetter = get(srcValue, 'set');

    return merge({}, objValue, srcValue, {
        set: wrap(objSetter, wrap(srcSetter, wrapperFunction))
    });
}

/**
 * Implementation of inherited event.
 * @example
 * // call objFunc, srcFunc
 * _.wrap(objFunc, _.wrap(srcFunc, wrapperEvents));
 * @param {function} [srcFunc]
 * @param {function} [objFunc]
 * @param {...*} args
 * @private
 */
function wrapperEvents(srcFunc, objFunc, ...args) {
    const event = (args[ 0 ] instanceof Event) && args[ 0 ];
    const isStopped = event ? () => event.immediatePropagationStopped : stubFalse;

    if (!isStopped() && isFunction(objFunc)) {
        objFunc.apply(this, args);
    }

    if (!isStopped() && isFunction(srcFunc)) {
        srcFunc.apply(this, args);
    }
}

/**
 * The assignment of parameters accessors.
 * @example
 * _.forEach({}, accessorsIterator);
 * @param {Object} options
 * @param {string} name
 * @param {Object} accessors
 * @private
 */
function accessorsIterator(options, name, accessors) {
    const optionsSetter = get(options, 'set');
    const updateSetter = wrap(name, wrapperAccessorsSetUpdate);

    accessors[ name ] = merge({}, options, {
        set: wrap(optionsSetter, wrap(updateSetter, wrapperFunction))
    });
}

/**
 * Update element when a property is changed.
 * @example
 * // call node.xblock.update();
 * _.wrap("accessor-name", wrapperAccessorsSetUpdate).call(node, 'newValue', 'oldValue');
 * @param {string} accessorName the name of the property
 * @param {*} nextValue
 * @param {*} prevValue
 * @this HTMLElement
 * @private
 */
function wrapperAccessorsSetUpdate(accessorName, nextValue, prevValue) {
    if (nextValue !== prevValue && this.xprops.hasOwnProperty(accessorName) && this.mounted) {
        this[ Constants.BLOCK ].update();
    }
}

/**
 * The callback of the remote in DOM.
 * @this HTMLElement
 * @private
 */
function lifecycleRemoved() {
    this[ Constants.INSERTED ] = false;

    const block = this[ Constants.BLOCK ];

    if (block) {
        block.destroy();
        this[ Constants.BLOCK ] = undefined;
    }
}

/**
 * The callback of the create element.
 * @this HTMLElement
 * @private
 */
function lifecycleCreated() {
    blockInit(this);
}

/**
 * The callback of the insert in DOM.
 * @this HTMLElement
 * @private
 */
function lifecycleInserted() {
    if (this[ Constants.INSERTED ]) {
        return;
    }

    blockInit(this);

    this[ Constants.INSERTED ] = true;

    const isScriptContent = Boolean(this.querySelector('script'));

    // asynchronous read content
    // <xb-test>
not found
</xb-test>
    if (isScriptContent) {
        lazy(blockCreateLazy, this);

    } else {
        blockCreate(this);
    }
}


"use strict";

var Q             = require('q'),
    sshExec       = require("./exec");

/**
 * Reads thee content of a remote file.
 * Invokes **cat filepath** and returns the result of the command
 * execution as provided by the **sshExec.command** function.
 *
 * @param  {object} conn     hash for connection settings
 * @param  {string} filepath path of the file to read
 * @return {object}          Promisyfied result
 */
function readFileContent(conn,  filepath ){

  var validateInputArg = function() {
    if(filepath === null ||filepath.length ===0) {
      throw {
        "success" : false,
        "value" : null,
        "error" : new Error("a filepath value is required")
      };
    } else {
      return true;
    }
  };

  var readRemoteFileContent = function() {
    // the success handler
    var successHandler = function(result) {
      return result;
    };

    // the error handler
    var errorHandler = function(err) {
      throw err;
    };

    return sshExec.command(conn,'cat '+filepath)
    .then(
      successHandler,
      errorHandler
    );
  };

  // main entry point
  return Q.fcall(validateInputArg)
  .then(readRemoteFileContent);

}

exports.readFileContent = readFileContent;


'use strict';
var BufferHelper = require('bufferhelper');
var replier = exports;
replier.Server = Server;
replier.Client = Client;

replier.server = function (options) {
    return new Server(options);
};

replier.client = function (options) {
    return new Client(options);
};

replier._migrate_events = function (events, from, to) {
    events.forEach(function (event) {
        from.on(event, function () {
            var args = [event];
            to.emit.apply(to, args.concat.apply(args, arguments));
        });
    });
};

// Check if the server is alive and is a replier server
// see [The GNU C Library - Error Reporting](http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_2.html)
replier.check = function (port, callback) {
    function cb (alive) {
        if ( !no ) {
            no = true;
            callback(alive);
            client.end();
        }
    }

    var no;
    var client = replier
    .client()
    // Check if there's no server errors
    .on('error', function (err) {
        cb(false);
    })
    .on('connect', function () {
        // send heartbeat message
        client.send('_heartbeat', function (err, alive) {
            cb(!err && !!alive);
        });

    }).connect(port);
};

var net = require('net');
var util = require('util');
var EE = require('events').EventEmitter;

function dealStream(data, callback){
    data = data.toString('utf-8');
    function done(msg){
        if(msg){
            callback(msg);
        }
    }
    if ( !data ) {
        return;
    }

    if(data.indexOf(CHUNK_DELIMITER) == -1) {
        if(!buf){
            buf = new BufferHelper();
        }
        buf.concat(new Buffer(data)); // write data to buffer
    } else {
        var parts = data.split(CHUNK_DELIMITER);
        var msg;

        if(!buf || !buf.buffers.length){
            return parts.forEach(done);
        }

        if (parts.length == 2) {
            buf.concat(new Buffer(parts[0]));
            msg = buf.toBuffer().toString(); // and do something with message
            done(msg);

            if(parts[1]){
                buf = new BufferHelper();;
                buf.concat(new Buffer(parts[1])); // write new, incomplete data to buffer
            }else{
                buf = null;
            }
        } else {
            buf.concat(new Buffer(parts[0]));
            msg = buf.toBuffer().toString(); // and do something with message
            done(msg);
            for (var i = 1; i <= parts.length -1; i++) {
                if (i !== parts.length-1) {
                    msg = parts[i];
                    done(msg);
                } else {
                    buf.concat(new Buffer(parts[i]));
                }
            }
        }
    }
}

// `socket.setNoDelay()` doesn't even work,
// so we use a delimiter to split each chunk into slices.
var CHUNK_DELIMITER = '\n';
var CHUNK_BUFFER_SIZE = Math.pow(2,16);
var buf;
// @constructor
// Create a socket server
// @param {Object} options
// - port {number}
function Server () {
    var self = this;

    this.server = new net.Server;
    this.server.on('connection', function (client) {
        client.setNoDelay(true);
        client.on('data', function(data){
            dealStream(data, function(msg){
                self._clientOnData(msg, client);
            });
        });
    });
    replier._migrate_events(['listening', 'close', 'error'], this.server, this);
}

util.inherits(Server, EE);

Server.prototype.listen = function(handle, callback) {
    this.server.listen(handle, callback);
    return this;
};

Server.prototype._clientOnData = function(data, client) {
    function reply (err, msg){
        client.write(
            JSON.stringify({
                err: err,
                msg: msg,
                mid: message_id
            }) + CHUNK_DELIMITER
        );
    }

    // data is a stream
    try {
        data = JSON.parse(data.toString());
    } catch(e) {
        this.emit('error', e);
        // fail silently.
        return;
    }

    var message_id = data.mid;
    var message = data.msg;

    if ( message === '_heartbeat' ) {
        reply(null, {
            alive: true
        });

    } else {
        this.emit('message', message, reply);
    }
};

// - retry {number}
//      - 0: no retries, default
//      - >0: max retry times
//      - -1: no limit
// - retry_timeout: {number} default to `100` ms
function Client (options) {
    var self = this;
    options = options || {};

    this.mid = 0;
    this.callbacks = {};

    this.socket = new net.Socket(options);
    replier._migrate_events(['connect', 'error', 'end', 'timeout', 'close', 'drain'], this.socket, this);

    this.socket.on('data', function (data) {
        dealStream(data, function(msg){
            self.emit('data', msg);
            self._dealServerData(msg);
        });
    });
}

util.inherits(Client, EE);

Client.prototype.connect = function(port, callback) {
    var self = this;
    this.socket.connect(port, function (err) {
        if ( err ) {
            self.emit('error', err);
        } else {
            self.emit('open');
        }
        callback && callback(err);
    });

    return this;
};

// Send data to the server
Client.prototype.send = function(msg, callback) {
    var mid = this._messageId();
    var data = JSON.stringify({
        msg: msg,
        mid: mid
    });

    this._registerResponse(mid, callback);
    this.socket.write(data + CHUNK_DELIMITER);

    return this;
};

Client.prototype._registerResponse = function(id, callback) {
    this.callbacks[id] = callback;
};

Client.prototype._dealServerData = function(data) {
    data = JSON.parse(data);

    var error;
    var msg;
    var mid;

    if ( data ){
        error = data.err;
        msg = data.msg;
        mid = data.mid;
    }

    var callback = this.callbacks[mid];
    delete this.callbacks[mid];

    if ( callback ) {
        callback(error || null, msg);
    }
};

Client.prototype._messageId = function() {
    return ++ this.mid;
};

Client.prototype.end = function() {
    this.socket.end.apply(this.socket, arguments);
    return this;
};


/**
 * Transforms a HTML5 data-* attributes hash to a hash of Javascript properties.
 *
 * - The key names are changed. The "data-" prefix is dropped, and hyphen notation is changed to camel case.
 * - Some values are changed. If a data attribute is used to store an object, it has been converted to a JSON string.
 *   So JSON-string values are converted back into actual objects.
 *
 * Example:
 *     {
 *         "data-my-prop": "some value",
 *         "data-hash":  '{ "foo": "nested foo", "bar": "nested bar" }'
 *     }
 * =>
 *     {
 *         myProp: "some value",
 *         hash:   { foo: "nested foo", bar: "nested bar" }
 *     }
 *
 * @param   {Object} dataAttributesHash
 * @returns {Object}
 */
function dataAttributesToProperties ( dataAttributesHash ) {
    var transformed = {};

    $.each( dataAttributesHash, function ( key, value ) {
        // Drop the "data-" prefix, then convert to camelCase
        key = toCamelCase( key.replace( /^data-/, "" ) );

        try {
            value = $.parseJSON( value );
        } catch ( err ) {}

        transformed[key] = value;
    } );

    return transformed;
}

/**
 * Transforms a hash of Javascript properties into a HTML5 data-* attributes hash. Is the inverse function of
 * `dataAttributesToProperties()`.
 *
 * @param   {Object} attributesHash
 * @returns {Object}
 */
function propertiesToDataAttributes ( attributesHash ) {
    var transformed = {};

    $.each( attributesHash, function ( key, value ) {
        // Convert camelCase to dashed notation, then add the "data-" prefix
        key = "data-" + toDashed( key );

        if ( $.isPlainObject( value ) ) value = JSON.stringify( value );

        transformed[key] = value;
    } );

    return transformed;
}

/**
 * Transforms a hash of HTML attributes - e.g., data attributes - into a string.
 *
 * The string has a trailing slash (or newline), but not a leading one. Double quotes are used by default around values,
 * except around JSON strings which have to be enclosed by single quotes.
 *
 * The string can be used to create the HTML of a tag with the given attributes. Data attributes containing a
 * stringified JSON structure are fully supported (simply by using single quotes around attribute values there).
 *
 * The following formatting option are available:
 *
 * - The order of the attributes can be reversed.
 * - Attributes can be separated by newlines instead of spaces.
 * - Redundant whitespace can be inserted around the "=" of each assignment.
 * - Values can be enclosed in single quotes, rather than double quotes, where possible (skipped for values containing a
 *   double quote character).
 *
 * @param   {Object}  attributesHash
 * @param   {Object}  [options]
 * @param   {boolean} [options.reverse=false]
 * @param   {boolean} [options.multiline=false]
 * @param   {string}  [options.extraSpace=""]
 * @param   {boolean} [options.preferSingleQuotes=false]
 * @returns {string}
 */
function attributesHashToString ( attributesHash, options ) {
    var reduce = options && options.reverse ? _.reduceRight : _.reduce,
        separator = options && options.multiline ? "\n" : " ",
        spacing = options && options.extrSpace || "",
        defaultQuote = options && options.preferSingleQuotes ? "'" : '"';

    return reduce( attributesHash, function ( attrString, value, key ) {
        var quote = value.indexOf( '"' ) !== -1 ? "'" :  value.indexOf( "'" ) !== -1 ? '"' : defaultQuote;
        return attrString + key + spacing + "=" + spacing + quote + value + quote + separator;
    }, "" );
}

/**
 * Returns a transformed hash in which all camelCased property names have been replaced by dashed property names. The
 * input hash remains untouched.
 *
 * Property values are not modified.
 *
 * Simple implementation, but good enough for the attribute names we deal with here.
 *
 * Example: { fooBar: "whatEver" } => { "foo-bar": "whatEver" }
 *
 * @param   {Object} hash
 * @returns {Object}
 */
function toDashedProperties ( hash ) {
    var transformed = {};

    _.each( hash, function ( value, key ) {
        transformed[toDashed( key )] = value;
    } );

    return transformed;
}

/**
 * Returns a transformed hash in which all dashed property names have been replaced by camelCased property names. The
 * input hash remains untouched.
 *
 * Property values are not modified.
 *
 * Simple implementation, but good enough for the attribute names we deal with here.
 *
 * Example: { "foo-bar": "what-ever" } => { fooBar: "what-ever" }
 *
 * @param   {Object} hash
 * @returns {Object}
 */
function toCamelCasedProperties ( hash ) {
    var transformed = {};

    _.each( hash, function ( value, key ) {
        transformed[toCamelCase( key )] = value;
    } );

    return transformed;
}

/**
 * Converts a camelCased label into a dashed label.
 *
 * Simple implementation, but good enough for the attribute names we deal with here.
 *
 * @param   {string} label
 * @returns {string}
 */
function toDashed ( label ) {
    return label.replace( /([a-z])([A-Z])/g, function ( $0, $1, $2 ) {
        return $1 + "-" + $2.toLowerCase();
    } );
}

/**
 * Converts a dashed label into a camelCased label.
 *
 * Simple implementation, but good enough for the attribute names we deal with here.
 *
 * @param   {string} label
 * @returns {string}
 */
function toCamelCase ( label ) {
    return label.replace( /-([a-z])/gi, function ( $0, $1 ) {
        return $1.toUpperCase();
    } );
}

/**
 * Returns an array of dashed key names, which are the alternative names for all camel-cased key names in a hash.
 *
 * Simple key names (not camel-cased) are ignored and don't show up in the array.
 *
 * E.g., dashedKeyAlternatives( { foo: "whatever", barBaz: "whatever" } ) returns [ "bar-baz" ].
 *
 * @param   {Object} hash
 * @returns {string[]}
 */
function dashedKeyAlternatives ( hash ) {
    var keys = _.keys( toDashedProperties( hash ) );
    return _.filter( keys, function ( key ) {
            return key.search(/[^-]-[a-z]/) !== -1;
        } );
}

/**
 * Combines various hashes with a shallow _.extend(). Doesn't modify the input hashes.
 *
 * Syntactic sugar only, as a simple way of saying _.extend( {}, hashA, hashB, hashN );
 *
 * @param   {...Object} hashes
 * @returns {Object}
 */
function combine ( hashA, hashB, hashN ) {
    var hashes = _.toArray( arguments );

    return _.extend.apply( undefined, [ {} ].concat( hashes ) );
}

/**
 * Checks the Marionette version and returns true if it is less than 3.
 *
 * NB Marionette 1 and 2 don't expose the version, so we look for the version string of Marionette 3 (or above) and
 * negate the result.
 *
 * @returns {boolean}
 */
function isMarionetteLt3 () {
    return !( Backbone.Marionette.VERSION && Backbone.Marionette.VERSION[0] >= 3 );
}

/**
 * Returns the basic Marionette view type: Marionette.ItemView for Marionette 1 and 2, Marionette.View for Marionette 3.
 *
 * @returns {Backbone.Marionette.ItemView|Backbone.Marionette.View}
 */
function getMarionetteView () {
    return Backbone.Marionette.ItemView || Backbone.Marionette.View;
}