/**
 * @private
 */
Ext.define('Ext.mixin.Filterable', {
    extend: 'Ext.mixin.Mixin',

    requires: [
        'Ext.util.Filter'
    ],

    mixinConfig: {
        id: 'filterable'
    },

    config: {
        /**
         * @cfg {Array} filters
         * An array with filters. A filter can be an instance of Ext.util.Filter,
         * an object representing an Ext.util.Filter configuration, or a filter function.
         */
        filters: null,

        /**
         * @cfg {String} filterRoot
         * The root inside each item in which the properties exist that we want to filter on.
         * This is useful for filtering records in which the data exists inside a 'data' property.
         */
        filterRoot: null
    },

    /**
     * @property {Boolean} dirtyFilterFn
     * A flag indicating whether the currently cashed filter function is still valid.
     * @readonly
     */
    dirtyFilterFn: false,

    /**
     * @property currentSortFn
     * This is the cached sorting function which is a generated function that calls all the
     * configured sorters in the correct order.
     * @readonly
     */
    filterFn: null,

    /**
     * @property {Boolean} filtered
     * A read-only flag indicating if this object is filtered.
     * @readonly
     */
    filtered: false,

    applyFilters: function(filters, collection) {
        if (!collection) {
            collection = this.createFiltersCollection();
        }

        collection.clear();

        this.filtered = false;
        this.dirtyFilterFn = true;

        if (filters) {
            this.addFilters(filters);
        }

        return collection;
    },

    createFiltersCollection: function() {
        this._filters = Ext.create('Ext.util.Collection', function(filter) {
            return filter.getId();
        });
        return this._filters;
    },

    /**
     * This method adds a filter.
     * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of Ext.util.Filter,
     * an object representing an Ext.util.Filter configuration, or a filter function.
     */
    addFilter: function(filter) {
        this.addFilters([filter]);
    },

    /**
     * This method adds all the filters in a passed array.
     * @param {Array} filters An array with filters. A filter can be an instance of {@link Ext.util.Filter},
     * an object representing an Ext.util.Filter configuration, or a filter function.
     * @return {Object}
     */
    addFilters: function(filters) {
        var currentFilters = this.getFilters();
        return this.insertFilters(currentFilters ? currentFilters.length : 0, filters);
    },

    /**
     * This method adds a filter at a given index.
     * @param {Number} index The index at which to insert the filter.
     * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of {@link Ext.util.Filter},
     * an object representing an Ext.util.Filter configuration, or a filter function.
     * @return {Object}
     */
    insertFilter: function(index, filter) {
        return this.insertFilters(index, [filter]);
    },

    /**
     * This method inserts all the filters in the passed array at the given index.
     * @param {Number} index The index at which to insert the filters.
     * @param {Array} filters Each filter can be an instance of {@link Ext.util.Filter},
     * an object representing an Ext.util.Filter configuration, or a filter function.
     * @return {Array}
     */
    insertFilters: function(index, filters) {
        // We begin by making sure we are dealing with an array of sorters
        if (!Ext.isArray(filters)) {
            filters = [filters];
        }

        var ln = filters.length,
            filterRoot = this.getFilterRoot(),
            currentFilters = this.getFilters(),
            newFilters = [],
            filterConfig, i, filter;

        if (!currentFilters) {
            currentFilters = this.createFiltersCollection();
        }

        // We first have to convert every sorter into a proper Sorter instance
        for (i = 0; i < ln; i++) {
            filter = filters[i];
            filterConfig = {
                root: filterRoot
            };

            if (Ext.isFunction(filter)) {
                filterConfig.filterFn = filter;
            }
            // If we are dealing with an object, we assume its a Sorter configuration. In this case
            // we create an instance of Sorter passing this configuration.
            else if (Ext.isObject(filter)) {
                if (!filter.isFilter) {
                    if (filter.fn) {
                        filter.filterFn = filter.fn;
                        delete filter.fn;
                    }

                    filterConfig = Ext.apply(filterConfig, filter);
                }
                else {
                    newFilters.push(filter);
                    if (!filter.getRoot()) {
                        filter.setRoot(filterRoot);
                    }
                    continue;
                }
            }
            // Finally we get to the point where it has to be invalid
            // <debug>
            else {
                Ext.Logger.warn('Invalid filter specified:', filter);
            }
            // </debug>

            // If a sorter config was created, make it an instance
            filter = Ext.create('Ext.util.Filter', filterConfig);
            newFilters.push(filter);
        }

        // Now lets add the newly created sorters.
        for (i = 0, ln = newFilters.length; i < ln; i++) {
            currentFilters.insert(index + i, newFilters[i]);
        }

        this.dirtyFilterFn = true;

        if (currentFilters.length) {
            this.filtered = true;
        }

        return currentFilters;
    },

    /**
     * This method removes all the filters in a passed array.
     * @param {Array} filters Each value in the array can be a string (property name),
     * function (sorterFn), an object containing a property and value keys or
     * {@link Ext.util.Sorter Sorter} instance.
     */
    removeFilters: function(filters) {
        // We begin by making sure we are dealing with an array of sorters
        if (!Ext.isArray(filters)) {
            filters = [filters];
        }

        var ln = filters.length,
            currentFilters = this.getFilters(),
            i, filter;

        for (i = 0; i < ln; i++) {
            filter = filters[i];

            if (typeof filter === 'string') {
                currentFilters.each(function(item) {
                    if (item.getProperty() === filter) {
                        currentFilters.remove(item);
                    }
                });
            }
            else if (typeof filter === 'function') {
                currentFilters.each(function(item) {
                    if (item.getFilterFn() === filter) {
                        currentFilters.remove(item);
                    }
                });
            }
            else {
                if (filter.isFilter) {
                    currentFilters.remove(filter);
                }
                else if (filter.property !== undefined && filter.value !== undefined) {
                    currentFilters.each(function(item) {
                        if (item.getProperty() === filter.property && item.getValue() === filter.value) {
                            currentFilters.remove(item);
                        }
                    });
                }
            }
        }

        if (!currentFilters.length) {
            this.filtered = false;
        }
    },

    /**
     * This updates the cached sortFn based on the current sorters.
     * @return {Function} sortFn The generated sort function.
     * @private
     */
    updateFilterFn: function() {
        var filters = this.getFilters().items;

        this.filterFn = function(item) {
            var isMatch = true,
                length = filters.length,
                i;

            for (i = 0; i < length; i++) {
                var filter = filters[i],
                    fn     = filter.getFilterFn(),
                    scope  = filter.getScope() || this;

                isMatch = isMatch && fn.call(scope, item);
            }

            return isMatch;
        };

        this.dirtyFilterFn = false;
        return this.filterFn;
    },

    /**
     * This method will sort an array based on the currently configured {@link Ext.data.Store#sorters sorters}.
     * @param {Array} data The array you want to have sorted.
     * @return {Array} The array you passed after it is sorted.
     */
    filter: function(data) {
        return this.getFilters().length ? Ext.Array.filter(data, this.getFilterFn()) : data;
    },

    isFiltered: function(item) {
        return this.getFilters().length ? !this.getFilterFn()(item) : false;
    },

    /**
     * Returns an up to date sort function.
     * @return {Function} sortFn The sort function.
     */
    getFilterFn: function() {
        if (this.dirtyFilterFn) {
            return this.updateFilterFn();
        }
        return this.filterFn;
    }
});


/**
 * @module xblocks-core/view
 */

import React, { PropTypes } from 'react';
import merge from 'lodash/merge';
import mergeWith from 'lodash/mergeWith';
import spread from 'lodash/spread';
import castArray from 'lodash/castArray';
import isArray from 'lodash/isArray';
import isFunction from 'lodash/isFunction';
import wrap from 'lodash/wrap';
import wrapperFunction from './utils/wrapperFunction';
import checkOverriddenMethods from './utils/checkOverriddenMethods';

const spreadMergeWith = spread(mergeWith);

const METHODS_INHERITANCE = [
    'componentDidMount',
    'componentDidUpdate',
    'componentWillMount',
    'componentWillReceiveProps',
    'componentWillUnmount',
    'componentWillUpdate'
];

const METHODS_MERGE_RESULT = [
    'getChildContext',
    'getDefaultProps',
    'getInitialState'
];

const VIEW_COMMON = {
    /**
     * @memberOf ReactElement.prototype
     * @property {Object} contextTypes context types
     * @property {HTMLElement} contextTypes.container the node associated with the view
     * @property {function} contextTypes.content output function user content
     * @property {function} contextTypes.template create node by template
     * @property {function} contextTypes.isEmptyContent check empty content
     */
    contextTypes: {
        container: PropTypes.any,
        content: PropTypes.func,
        template: PropTypes.func,
        isEmptyContent: PropTypes.func
    }
};

const VIEW_COMPONENTS_CLASS = {};

/**
 * Create class view node.
 *
 * @example
 * import view from 'xblocks-core/view';
 *
 * var XBButtonContent = view.create({
 *     displayName: 'XBButtonContent',
 *     render: function () {
 *         return (
 *             <span {...this.props}>{this.props.children}</span>
 *         );
 *     }
 * });
 *
 * view.register('xb-button', {
 *     displayName: 'xb-button',
 *     render: function () {
 *         return (
 *             <button>
 *                 <XBButtonContent {...this.props} />
 *             </button>
 *         );
 *     }
 * });
 *
 * @see http://facebook.github.io/react/docs/component-specs.html
 * @alias module:xblocks-core/view.create
 * @param {Object|array} component settings view creation
 * @returns {function}
 */
export function create(component) {
    component = castArray(component);
    component.unshift({});
    component.push(VIEW_COMMON, mergeCustomizer);
    component = spreadMergeWith(component);

    return React.createClass(component);
}

/**
 * Registration of a new node.
 *
 * @example
 * import view from 'xblocks-core/view';
 * view.register('xb-button', {
 *     displayName: 'xb-button',
 *     render: function () {
 *         return (
 *             <button {...this.props}>{this.props.children}</button>
 *         );
 *     }
 * });
 *
 * @see http://facebook.github.io/react/docs/component-specs.html
 * @alias module:xblocks-core/view.register
 * @param {string} blockName the name of the new node
 * @param {Object|array|React.Component} component settings view creation
 * @returns {function}
 * @throws Specified item "${blockName}" is already defined
 */
export function register(blockName, component) {
    if (React.DOM.hasOwnProperty(blockName)) {
        throw new Error(`Specified item "${blockName}" is already defined`);
    }

    const componentClass = isFunction(component) ?
        component :
        create(component);

    VIEW_COMPONENTS_CLASS[ blockName ] = componentClass;

    React.DOM[ blockName ] = React.createFactory(componentClass);

    return componentClass;
}

/**
 * Get factory view node.
 * @alias module:xblocks-core/view.getFactory
 * @param {string} blockName the name of the new node
 * @returns {function}
 */
export function getFactory(blockName) {
    return React.DOM[ blockName ];
}

/**
 * Get class view node.
 * @alias module:xblocks-core/view.getClass
 * @param {string} blockName the name of the new node
 * @returns {function}
 */
export function getClass(blockName) {
    return VIEW_COMPONENTS_CLASS[ blockName ];
}

/**
 * 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
 * @throws The "render" method you can override
 * @throws The "displayName" property can not be redefined
 * @private
 */
function mergeCustomizer(objValue, srcValue, key) {
    if (isArray(objValue)) {
        return objValue.concat(srcValue);
    }

    if (METHODS_INHERITANCE.indexOf(key) !== -1) {
        return wrap(objValue, wrap(srcValue, wrapperFunction));
    }

    if (METHODS_MERGE_RESULT.indexOf(key) !== -1) {
        return wrap(objValue, wrap(srcValue, wrapperMergeResult));
    }

    if (key === 'shouldComponentUpdate') {
        return wrap(objValue, wrap(srcValue, wrapperOrResult));
    }

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

    if (key === 'render' && objValue && srcValue) {
        throw new Error('The "render" method you can override');
    }

    if (key === 'displayName' && objValue && srcValue) {
        throw new Error('The "displayName" property can not be redefined');
    }

    if (isFunction(objValue) && isFunction(srcValue)) {
        throw new Error(`The following methods are overridden: "${key}"`);
    }
}

/**
 * The implementation of the merger result.
 * @example
 * // call objFunc, srcFunc
 * _.wrap(objFunc, _.wrap(srcFunc, wrapperMergeResult));
 * @param {function} [srcFunc]
 * @param {function} [objFunc]
 * @param {...*} args
 * @returns {Object}
 * @private
 */
function wrapperMergeResult(srcFunc, objFunc, ...args) {
    let resultObjFunction = {};
    let resultSrcFunction = {};

    if (isFunction(objFunc)) {
        resultObjFunction = objFunc.apply(this, args);
    }

    if (isFunction(srcFunc)) {
        resultSrcFunction = srcFunc.apply(this, args);
    }

    return merge({}, resultObjFunction, resultSrcFunction);
}

/**
 * Merging the result of the logical "or".
 * @example
 * // call objFunc, srcFunc
 * _.wrap(objFunc, _.wrap(srcFunc, wrapperOrResult));
 * @param {function} [srcFunc]
 * @param {function} [objFunc]
 * @param {...*} args
 * @returns {boolean}
 * @private
 */
function wrapperOrResult(srcFunc, objFunc, ...args) {
    let resultObjFunction = false;
    let resultSrcFunction = false;

    if (isFunction(objFunc)) {
        resultObjFunction = objFunc.apply(this, args);
    }

    if (isFunction(srcFunc)) {
        resultSrcFunction = srcFunc.apply(this, args);
    }

    return resultObjFunction || resultSrcFunction;
}


//Copyright (c) 2015 Benjamin Vellacott
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of
//this software and associated documentation files (the "Software"), to deal in
//the Software without restriction, including without limitation the rights to
//use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
//of the Software, and to permit persons to whom the Software is furnished to do
//so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.

// This file contains mocks to test libraries in conjunction with salesforce libraries
var antlr4 = require('antlr4');
var SelectLexer = require('./select/SelectLexer').SelectLexer;
var SelectParser = require('./select/SelectParser').SelectParser;
var SelectListener = require('./select/SelectListener').SelectListener;

var _extend = Object.assign;

// A mock of the ember Snapshot object
var Snapshot = function(instance) {
	this._model = instance._model;
	this._instance = instance;
	this.id = instance.id;
	this.eachAttribute = function(cb) { return this._model.eachAttribute(cb); };
	this.eachRelationship = function (cb) { return this._model.eachRelationship(cb); }
	this.attr = function(name) { return this._instance[name]; };
	this.belongsTo = function(name, opts) {
		return (opts.id ? this._instance[name].id : new Snapshot(this._instance[name]));
	};
};

// A mock of part of the salesforce soap api including stripped down javascript mock of
// a database with query parsing
var sforce = {
	// Normalises a json string to a valid deserialisable json string
	normalise : function(val) {
		var trimmed = val.trim();
		if(trimmed.indexOf("'") === 0 && trimmed.lastIndexOf("'") === (trimmed.length - 1))
			return '"' + trimmed.substring(1, trimmed.length - 1) + '"';
		return val;
	},
	// A mock of the salesforce SObject object
	SObject : function(typeName, obj) {
		this.type = typeName;
		if(typeof obj !== 'undefined')
			for(var key in obj)
				this[key] = obj[key];
	},
	// A stripped down mock of the salesforce soap api database
	connection : {
		create : function(objAry, cbSuccess, cbErr) {
			return sforce.db.create(objAry, cbSuccess, cbErr);
		},
		query : function(q, cbSuccess, cbErr) {
			return sforce.db.query(q, cbSuccess, cbErr);
		},
		deleteIds : function(idAry, cbSuccess, cbErr) {
			return sforce.db.del(idAry, cbSuccess, cbErr);
		},
		update : function(objAry, cbSuccess, cbErr) {
			return sforce.db.update(objAry, cbSuccess, cbErr);
		},
	},
	// The underying javascript database used to mock the salesforce db
	db : {
		// faking a salesforce id
		_idTemplate : 'abc000000000000000',
		id : 0,
		// The schema to work against - the database won't work without this being set.
		// See the file schemas.js
		schema : {},
		// The store for created/modified SObjects
		sobjects : {},
		// Clear the database - used in testing
		clear : function() { sforce.db.sobjects = {}; },
		// Set this to true if you wan't do decide the id's to use instead of generating them.
		// This is handy for testing.
		useGivenIds : false,
		// Generates a new fake salesforce id
		newId : function() {
			var idStr = '' + sforce.db.id++;
			return sforce.db._idTemplate.substring(0, 18 - idStr.length) + idStr;
		},
		// Returns an antlr4 parse tree for a select query string
		getParseTree : function(select) {
			var chars = new antlr4.InputStream(input);
			var lexer = new SelectLexer(chars);
			var tokens  = new antlr4.CommonTokenStream(lexer);
			var parser = new SelectParser(tokens);
			parser.buildParseTrees = true;
			return parser.select();
		},
		// Validates a given objects against the schema
		validateSobject : function(obj) {
			var schema = sforce.db.schema;
			var objDesc = schema[obj.type];
			if(typeof objDesc === 'undefined')
				throw 'No type exists by the name: ' + obj.type;
			for(var key in obj) {
				if({ Id:false, type:true }[key])
					continue;
				var fieldDesc = null;
				for(var i = 0; i < objDesc.fields.length; i++) {
					var fd = objDesc.fields[i];
					if(fd.name === key) {
						fieldDesc = fd;
						break;
					}
				}
				if(fieldDesc == null)
					throw 'No field exists by the name: ' + key + 'in the type: ' + obj.type;
			}
		},
		// Validates a list of field names against a type in the schema
		validateFields : function(type, fields) {
			for(var i = 0; i < fields.length; i++)
				sforce.db.validateField(type, fields[i]);
		},
		// Validates a field name against a type in the schema
		validateField : function(type, field) {
			var objDesc = sforce.db.schema[type];
			for(var i = 0; i < objDesc.fields.length; i++)
				if(objDesc.fields[i].name === field)
					return;
			throw 'No field exists by the name: ' + field + 'in the type: ' + type;
		},
		// Validates a relationship name against a type in the schema
		validateRelationship : function(type, rel) {
			var objDesc = sforce.db.schema[type];
			for(var i = 0; i < objDesc.childRelationships.length; i++)
				if(objDesc.childRelationships[i].relationshipName === rel)
					return;
			throw 'No child relationship exists by the name: ' + rel + 'in the type: ' + type;
		},
		// Get all SObjects of the given type
		getSobjects : function(type) {
			var sos = sforce.db.sobjects;
			if(typeof sos[type] !== 'object')
				sos[type] = {};
			return sos[type];
		},
		// Create the objects in the array in the database and call success(...) or err(...)
		// on success/failure
		create : function(objAry, success, err) {
			var result = [];
			for(var i = 0; i < objAry.length; i++) {
				var obj = objAry[i];
				try {
					sforce.db.validateSobject(obj);
					obj = _extend({}, obj);
					var objs = sforce.db.getSobjects(obj.type);
					obj.Id = ((!sforce.db.useGivenIds || typeof obj.Id === 'undefined') ? sforce.db.newId() : obj.Id);
					objs[obj.Id] = obj;
					result.push({success : 'true', id : obj.Id});
				} catch(e) {
					result.push({success : 'false', id : obj.Id});
				}
			}
			if(success)
				success(result);
			return result;
		},
		// Update all objects in the given array and call success(...) or err(...)
		// on success/failure
		update : function(objAry, success, err) {
			var result = [];
			for(var i = 0; i < objAry.length; i++) {
				var obj = objAry[i];
				var Id = obj.Id;
				var existingObj = sforce.db.getSobjects(obj.type)[Id];
				if(!existingObj) {
					result.push({success : 'false', id : Id});
					continue;
				}
				try {
					obj = _extend({}, existingObj, obj);
					sforce.db.validateSobject(obj);
					sforce.db.getSobjects(obj.type)[Id] = obj;
					result.push({success : 'true', id : obj.Id});
				} catch(e) {
					result.push({success : 'false', id : obj.Id});
				}
			}
			if(success)
				success(result);
			return result;
		},
		// Delete all objects with thi id's in the id array and call success(...) or err(...)
		// on success/failure
		del : function(idAry, success, err) {
			var result = [];
			for(var i = 0; i < idAry.length; i++) {
				var found = false;
				var Id = idAry[i];
				for(var type in this.schema) {
					var allOfType = sforce.db.getSobjects(type);
					if(Id in allOfType) {
						delete allOfType[Id];
						found = true;
						result.push({success : 'true', id : Id});
						break;
					}
				}
				if(!found)
					result.push({success : 'false', id : Id});
			}
			if(success)
				success(result);
			return result;
		},
		// Run a select query on the database and call success(...) or err(...)
		// on success/failure
		query : function(select, success, err) {
			try {
				var chars = new antlr4.InputStream(select);
				var lexer = new SelectLexer(chars);
				var tokens  = new antlr4.CommonTokenStream(lexer);
				var parser = new SelectParser(tokens);
				parser.buildParseTrees = true;
				var tree = parser.select();
				console.log(tree.toStringTree());
				var listener = new QueryBuilderListener(sforce.db);
				antlr4.tree.ParseTreeWalker.DEFAULT.walk(listener, tree);
				var result = listener.query.getResult();
				if(success)
					success(result);
				return result;
			} catch(e) {
				if(err)
					err(e);
				return e;
			}
		},
		// Creates a wrapped result in the form that you would expect the salesforce soap
		// api to return a result
		wrapResult : function(resultAry, isRoot) {
			if(resultAry.length == 0) {
				if(isRoot)
					return {
						done : 'true',
						queryLocator : null,
						size : 0,
					};
				return null;
			}
			var records = null;
			if(resultAry.length == 1)
				records = resultAry[0];
			else
				records = resultAry;
			return {
				done : 'true',
				queryLocator : null,
				records : records,
				size : resultAry.length,
			};
		},
	},
};

// The SObjectQuery object which is produced as a result of parsing a select query string
// takes a db object as a constructor paramater. Objects are queried from the given db
// object
var SobjectQuery = function(db) {
	this.db = db;
	this.fields = [];
	this.subqueries = [];
};
SobjectQuery.prototype = {
	// A query building method to mark a field to be queried
	addField : function(field) { this.fields.push(field); },
	// A query building method query on a child relationship
	addSubquery : function(subquery){
		this.subqueries.push(subquery);
		subquery.setParentQuery(this);
	},
	// A query building method to set the object type to be queried
	setType : function(type) { this.type = type; },
	// A query building method to set the query condition defined in the where clause
	setCondition : function(cond) { this.condition = cond; },
	// A mothod to create a result object out of a resolved object in the db
	_createResultObj : function(obj) {
		var result = {type : this.type};
		this.db.validateFields(this.type, this.fields);
		for(var i = 0; i < this.fields.length; i++) {
			var fName = this.fields[i];
			result[fName] = obj[fName];
		}
		for(var i = 0; i < this.subqueries.length; i++) {
			var sq = this.subqueries[i];
			result[sq.relationship] = sq.getResult(obj);
		}
		return result
	},
	// Runs the query against the db
	getResult : function() {
		var all = this.db.getSobjects(this.type);
		var result = [];
		for(var Id in all) {
			var obj = all[Id];
			if(!this.condition || this.condition.matches(obj))
				result.push(this._createResultObj(obj));
		}
		return this.db.wrapResult(result, true);
	},
};

//The SobjectSubquery object which is produced as a result of parsing a child relationship
// select query string takes a db object as a constructor paramater.
// Objects are queried from the given db
var SobjectSubquery = function() {
	this.fields = [];
};
SobjectSubquery.prototype = {
	// A query building method to mark a field to be queried
	addField : function(field) { this.fields.push(field); },
	// A query building method to set the parent SobjectQuery
	setParentQuery : function(parent) { this.parent = parent; },
	// A method to retrieve the underlying db object
	getDb : function() { return this.parent.db; },
	// A method to retrieve the type queried
	getType : function() { return this.parent.type; },
	// A query building method to set the childrelationship name
	setRelationship : function(rel) { this.relationship = rel; },
	// A query building method to set the query condition defined in the where clause
	setCondition : function(cond) { this.condition = cond; },
	// A mothod to create a result object out of a resolved object in the db
	_createResultObj : function(obj) {
		var result = {type : obj.type};
		this.getDb().validateFields(obj.type, this.fields);
		for(var i = 0; i < this.fields.length; i++) {
			var fName = this.fields[i];
			result[fName] = obj[fName];
		}
		return result
	},
	// Runs the query against the db
	getResult : function(parentObj) {
		this.getDb().validateRelationship(this.getType(), this.relationship)
		var objDesc = this.getDb().schema[this.getType()];
		var relDesc = null;
		for(var i = 0; i < objDesc.childRelationships.length; i++)
			if(this.relationship === objDesc.childRelationships[i].relationshipName)
				relDesc = objDesc.childRelationships[i];
		if(relDesc == null)
			throw 'No child relationship by the name: ' + this.relationship + ' exists in the type: ' + this.getType();
		var relObjDesc = this.getDb().schema[relDesc.childSObject];

		var all = this.getDb().getSobjects(relDesc.childSObject);
		var result = [];
		for(var key in all) {
			var obj = all[key];
			if(obj[relDesc.field] === parentObj.Id && (!this.condition || this.condition.matches(obj)))
				result.push(this._createResultObj(obj));
		}
		return this.getDb().wrapResult(result);
	},
};

// The conditionset object used to hold a set of conditions and return the boolean result
// of running a collection of conditions and condition sets separated by and/or clauses.
var SobjectConditionSet = function() {
	this.sequence = [];
	this.parent = null;
};
SobjectConditionSet.prototype = {
	// Answers the question, does this condition set have a parent condition set
	hasParent : function() { return this.parent !== null },
	// A condition set building method to set the parent condition set
	setParent : function(conditionSet) { this.parent = conditionSet; },
	// Retrieve the parent condition set
	getParent : function() { return this.parent; },
	// A condition building method to add a condition to the sequence of this condition set
	condition : function(condition) { this.sequence.push(condition); },
	// A condition building method to add an and clause to the sequence of this condition set
	and : function() { this.sequence.push('&'); },
	// A condition building method to add an or clause to the sequence of this condition set
	or : function() { this.sequence.push('|'); },
	// Runs the conditions in the condition set and returns a boolean as a result
	matches : function(obj) {
		var matches = this.sequence[0].matches(obj);
		for(var i = 1; i < this.sequence.length; i += 2) {
			if(this.sequence[i] === '&')
				matches = matches && this.sequence[i+1].matches(obj);
			else
				matches = matches || this.sequence[i+1].matches(obj);
		}
		return matches;
	},
};

// The SobjectCondition object which has two terms separated by an operator
var SobjectCondition = function() {};
SobjectCondition.prototype = {
	// Set the first term to be a field : is passed a field name string
	setFirstField : function(field) {
		this.firstType = 'f';
		this.firstValue = field;
	},
	// Set the first term to be a constant : is passed any javascript constant
	setFirstConstant : function(constant) {
		this.firstType = 'c';
		this.firstValue = constant;
	},
	// Set the second term to be a field : is passed a field name string
	setSecondField : function(field) {
		this.secondType = 'f';
		this.secondValue = field;
	},
	// Set the second term to be a constant : is passed any javascript constant
	setSecondConstant : function(constant) {
		this.secondType = 'c';
		this.secondValue = constant;
	},
	// Retrieve first term value - if it is a constant the constant is returned.
	// If it is a field the value of the field is returned from the passed object
	getFirstValue : function(obj) {
		if(this.firstType === 'c')
			return this.firstValue;
		else
			return obj[this.firstValue];
	},
	// Retrieve second term value - if it is a constant the constant is returned.
	// If it is a field the value of the field is returned from the passed object
	getSecondValue : function(obj) {
		if(this.secondType === 'c')
			return this.secondValue;
		else
			return obj[this.secondValue];
	},
};

// The equals condition object which extends the SobjectCondition object
var SobjectEqCond = function(){};
SobjectEqCond.prototype = new SobjectCondition();
// Runs an equals (===) check between the two terms
SobjectEqCond.prototype.matches = function(obj) {
	return this.getFirstValue(obj) === this.getSecondValue(obj);
};

var SobjectLTCond = function(){};
SobjectLTCond.prototype = new SobjectCondition();
// Runs an less than (<) check between the two terms
SobjectLTCond.prototype.matches = function(obj) {
	return this.getFirstValue(obj) < this.getSecondValue(obj);
};

var SobjectLECond = function(){};
SobjectLECond.prototype = new SobjectCondition();
// Runs an less than or equal (<=) check between the two terms
SobjectLECond.prototype.matches = function(obj) {
	return this.getFirstValue(obj) <= this.getSecondValue(obj);
};

var SobjectGTCond = function(){};
SobjectGTCond.prototype = new SobjectCondition();
// Runs an greated than (>) check between the two terms
SobjectGTCond.prototype.matches = function(obj) {
	return this.getFirstValue(obj) > this.getSecondValue(obj);
};

var SobjectGECond = function(){};
SobjectGECond.prototype = new SobjectCondition();
	// Runs an greater than or equal (>=) check between the two terms
SobjectGECond.prototype.matches = function(obj) {
	return this.getFirstValue(obj) >= this.getSecondValue(obj);
};

var SobjectInCond = function(){};
SobjectInCond.prototype = new SobjectCondition();
// Runs an in (indexOf >= 0) check between the two terms where the second term
// must be a list of constants
SobjectInCond.prototype.matches = function(obj) {
	return this.getSecondValue(obj).indexOf(this.getFirstValue(obj)) >= 0;
};

// The QueryBuilderListener parse tree listener used to walk the select parse tree
// and build a SobjectQuery object
var QueryBuilderListener = function(db) {
	this.db = db;
	this.path = [];
};
QueryBuilderListener.prototype = new SelectListener();
QueryBuilderListener.prototype.enterSelect = function(ctx) {
	this.path.push('select');
	this.query = new SobjectQuery(this.db);
};
QueryBuilderListener.prototype.exitSelect = function(ctx) {
	this.path.pop();
};
QueryBuilderListener.prototype.enterSubselect = function(ctx) {
	this.path.push('subselect');
	this.subquery = new SobjectSubquery();
	this.query.addSubquery(this.subquery);
};
QueryBuilderListener.prototype.exitSubselect = function(ctx) {
	this.subquery = null;
	this.path.pop();
};
QueryBuilderListener.prototype.enterFrom = function(ctx) {
	var section = this.path[this.path.length - 1];
	if(section === 'select')
		this.query.setType(ctx.FIELD().getText());
	else if(section === 'subselect')
		this.subquery.setRelationship(ctx.FIELD().getText());
};
QueryBuilderListener.prototype.enterFields = function(ctx) {
	var section = this.path[this.path.length - 1];
	var field = ctx.FIELD();
	if(field) {
		if(section === 'select')
			this.query.addField(field.getText());
		else if(section === 'subselect')
			this.subquery.addField(field.getText());
	}
};
QueryBuilderListener.prototype.enterConditionset = function(ctx) {
	this.path.push('conditionset');
	var newConditionSet = new SobjectConditionSet();
	if(this.curConditionSet) {
		newConditionSet.setParent(newConditionSet);
		this.curConditionSet.condition(newConditionSet);
	}
	else
		this.query.setCondition(newConditionSet);
	this.curConditionSet = newConditionSet;
};
QueryBuilderListener.prototype.exitConditionset = function(ctx) {
	if(this.curConditionSet.hasParent())
		this.curConditionSet = this.curConditionSet.getParent();
	this.path.pop();
};
QueryBuilderListener.prototype.enterCondition = function(ctx) {
	var op = ctx.children[1].symbol;
	var condition = null;
	if(op.type === 	SelectParser.ARRAYOPERATOR) {
		switch(op.text) {
		case 'in':
		default:
			condition = new SobjectInCond();
		}
		var first = ctx.children[0].symbol;
		var second = ctx.children[2];
		if(first.type === SelectParser.FIELD)
			condition.setFirstField(first.text);
		else
			throw 'Only fields are supported as the first term to an array operator';
		if(second instanceof SelectParser.ArrayconstantContext) {
			var constant = [];
			for(var i = 0; i < second.getChildCount(); i++) {
				var child = second.getChild(i).symbol;
				if(child.type === SelectParser.CONSTANT)
					constant.push(JSON.parse(sforce.normalise(child.text)));
			}
			condition.setSecondConstant(constant);
		}
		else
			throw 'Only array constants are supported as the second term to an array operator';
		this.curConditionSet.condition(condition);
	}
	else if(op.type === SelectParser.OPERATOR){
		switch(op.text) {
		case '<':
			condition = new SobjectLTCond();
			break;
		case '<=':
			condition = new SobjectLECond();
			break;
		case '>':
			condition = new SobjectGTCond();
			break;
		case '>=':
			condition = new SobjectGECond();
			break;
		case '=':
		default:
			condition = new SobjectEqCond();
		}
		var first = ctx.children[0].symbol;
		var second = ctx.children[2].symbol;
		if(first.type === SelectParser.FIELD)
			condition.setFirstField(first.text);
		else
			condition.setFirstConstant(JSON.parse(sforce.normalise(first.text)));
		if(second.type === SelectParser.FIELD)
			condition.setSecondField(second.text);
		else
			condition.setSecondConstant(JSON.parse(sforce.normalise(second.text)));
		this.curConditionSet.condition(condition);
	}
	else
		throw 'Failed to resolve the operator type';
};
QueryBuilderListener.prototype.enterAnd = function(ctx) {
	this.curConditionSet.and()
};
QueryBuilderListener.prototype.enterOr = function(ctx) {
	this.curConditionSet.or()
};
QueryBuilderListener.prototype.enterArrayconstant = function(ctx) {
	console.log(ctx);
};
QueryBuilderListener.prototype.exitArrayconstant = function(ctx) {
	console.log(ctx);
};

// A mock Ember store object, used to run the ForceAdapter specific tests and retrieve some
// of the results
var Store = function() {};
Store.prototype = {
	pushPayload : function(type, payload) {
		this.type = type;
		this.payload = payload;
	},
};

module.exports = {
	Store: Store,
	Snapshot: Snapshot,
	sforce: sforce,
	SobjectQuery: SobjectQuery,
	SobjectSubquery: SobjectSubquery,
	SobjectConditionSet: SobjectConditionSet,
	SobjectCondition: SobjectCondition,
	SobjectEqCond: SobjectEqCond,
	SobjectLTCond: SobjectLTCond,
	SobjectLECond: SobjectLECond,
	SobjectGTCond: SobjectGTCond,
	SobjectGECond: SobjectGECond,
	SobjectInCond: SobjectInCond,
	QueryBuilderListener: QueryBuilderListener
};


/**
 * Markdown Plugin based on PPL(Page Plugin Loader).
 *
 * @author luics<luics.king@gmail.com>
 * @date 2012-10-20
 * @license MIT
 *
 * Feature:
 * Add contents for page changed from markdown formation.
 *
 * Usage:
 * You can define an option:
 * __markdown_ppl_opt = {
 *   contents: ['h2', 'h3', 'h4'],
 *   top: ['h2', 'h3', 'h4', 'h5', 'h6']
 * }
 */
;//safe in min js
(function () {
  console = window.console || {};
  console.log = console.log || function () {
  };
  function fm() {
    var len = arguments.length;
    if (len == 0) {
      return '';
    } else if (len == 1) {
      return arguments[0];
    }

    var res = arguments[0], i;
    for (i = 1; i < len; ++i) {
      var re = new RegExp('\\{' + (i - 1) + '\\}', 'g');
      res = res.replace(re, arguments[i]);
    }
    return res;
  }

  console.log('Markdown Plugin loaded');
  /**
   * PPL(Page Plugin Loader) entry
   * @param $ js lib reference, default is window.jQuery
   */
  window.__ppl_main = function ($) {
    console.log('Markdown Plugin', $);
    var opt = window.__markdown_ppl_opt || {},
      contents = opt.contents || ['h2', 'h3', 'h4'],
      top = opt.top || ['h2', 'h3', 'h4', 'h5', 'h6'];

    if (contents.length > 0) addContents($, contents);
    if (top.length > 0) addTop($, top);
  }

  /**
   * PPL(Page Plugin Loader) option
   */
  window.__ppl_main_opt = {
    lib:'jquery'
  }

  /**
   * Add Contents for page
   */
  function addContents($, contents) {
    console.log('addContents', contents);
    var body = document.getElementsByTagName('BODY');
    if (!body) return;

    var $body = $(body[0]),
      contentsStyle = [
        'position:fixed;right:1em;top:1em;',
        'padding:0.5em;min-width:120px;',
        'font-size:90%;line-height:18px;',
        'border:1px solid #aaa;background: #F9F9F9;'
      ].join(''),
      html = [],
      order = [],
      hash = [];

    for (var i = 0; i < contents.length; ++i) {
      order[i] = 0;
      hash[i] = '';
    }

    function indexOf(tag) {
      for (var i = 0; i < contents.length && contents[i].toLowerCase() !== tag; ++i);
      return i;
    }

    $(contents.join(',')).each(function (i, obj) {
      var index = indexOf(obj.tagName.toLowerCase());
      order[index]++;
      hash[index] = $(obj).text();
      for (var j = index + 1; j < contents.length; ++j) {
        // Clear low level order
        order[j] = 0;
        hash[j] = '';
      }
      var anchor = hash.slice(0, index + 1).join('-');
      //anchor = '__id_' + tag + Math.floor(9999999 * Math.random());

      // Add anchor
      $(obj).append(fm('', anchor));
      // Add contents item
      html.push(fm('
{1}
',
        index * 1.5, order.slice(0, index + 1).join('.') + ' ' + hash[index], anchor));
    });

    var $contentsWrap = $(fm(['
',
        '
',
        'Contents hide',
        '
',
        '
{1}
',
        '
'].join(''), contentsStyle, html.join(''))).prependTo($body),
      $toggle = $contentsWrap.find('> :first').find('> :last'),
      $contents = $contentsWrap.find('> :last');
    console.log($contentsWrap, $toggle, $contents);

    $toggle.click(function () {
      $contents.slideToggle();
      $toggle.html($toggle.html() === 'show' ? 'hide' : 'show');
    });
  }

  /**
   * Add "to Top" link for mark h
   */
  function addTop($, top) {
    console.log('addTop', top);

    $(top.join(',')).each(function (i, obj) {
      //$(obj).append(' Top');
      $(obj).prepend(['
',
        '',
        '✦',
        '
'].join(''));

      var $prefix = $(this).find(':first').find(':first');
      //var $top = $(this).find('a:last');
      //console.log($prefix, $top);
      var rawCol = $(obj).css('background-color');
      $(obj).mouseover(
        function () {
          $prefix.css('height', $(this).css('height'));
          $prefix.css('line-height', $(this).css('line-height'));
          $prefix.css('visibility', 'visible');
          $(this).css('background-color', '#FFF8D7');
        }).mouseout(function () {
          $prefix.css('visibility', 'hidden');
          $(this).css('background-color', rawCol);
        });
    });
  }

  /**
   * Load ppl.js
   */
  document.write(unescape("%3Cscript src='http://raw.github.com/luics/markdown-util/master/lib/tpl/ppl.js' type='text/javascript'%3E%3C/script%3E"));
})();


/**
 * @class Ext.direct.RemotingProvider
 *
 * The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
 * server side methods on the client (a remote procedure call (RPC) type of
 * connection where the client can initiate a procedure on the server).
 *
 * This allows for code to be organized in a fashion that is maintainable,
 * while providing a clear path between client and server, something that is
 * not always apparent when using URLs.
 *
 * To accomplish this the server-side needs to describe what classes and methods
 * are available on the client-side. This configuration will typically be
 * outputted by the server-side Ext.Direct stack when the API description is built.
 */
Ext.define('Ext.direct.RemotingProvider', {
    alias: 'direct.remotingprovider',

    extend: 'Ext.direct.JsonProvider',

    requires: [
        'Ext.util.MixedCollection',
        'Ext.util.DelayedTask',
        'Ext.direct.Transaction',
        'Ext.direct.RemotingMethod'
    ],

    config: {
        /**
         * @cfg {String/Object} namespace
         * Namespace for the Remoting Provider (defaults to the browser global scope of _window_).
         * Explicitly specify the namespace Object, or specify a String to have a
         * {@link Ext#namespace namespace created} implicitly.
         */
        namespace: undefined,

        /**
         * @cfg {String} url (required) The url to connect to the {@link Ext.direct.Manager} server-side router.
         */
        url: null,

        /**
         * @cfg {String} enableUrlEncode
         * Specify which param will hold the arguments for the method.
         */
        enableUrlEncode: null,

        /**
         * @cfg {Number/Boolean} enableBuffer
         *
         * `true` or `false` to enable or disable combining of method
         * calls. If a number is specified this is the amount of time in milliseconds
         * to wait before sending a batched request.
         *
         * Calls which are received within the specified timeframe will be
         * concatenated together and sent in a single request, optimizing the
         * application by reducing the amount of round trips that have to be made
         * to the server.
         */
        enableBuffer: 10,

        /**
         * @cfg {Number} maxRetries
         * Number of times to re-attempt delivery on failure of a call.
         */
        maxRetries: 1,

        /**
         * @cfg {Number} timeout
         * The timeout to use for each request.
         */
        timeout: undefined,

        /**
         * @cfg {Object} actions
         * Object literal defining the server side actions and methods. For example, if
         * the Provider is configured with:
         *
         *     actions: { // each property within the 'actions' object represents a server side Class
         *         // array of methods within each server side Class to be stubbed out on client
         *         TestAction: [{
         *             name: "doEcho",
         *             len: 1
         *         }, {
         *             "name": "multiply", // name of method
         *             "len": 2            // The number of parameters that will be used to create an
         *                                 // array of data to send to the server side function.
         *                                 // Ensure the server sends back a Number, not a String.
         *         }, {
         *             name: "doForm",
         *             formHandler: true, // direct the client to use specialized form handling method
         *             len: 1
         *         }]
         *     }
         *
         * __Note:__ A Store is not required, a server method can be called at any time.
         * In the following example a **client side** handler is used to call the
         * server side method "multiply" in the server-side "TestAction" Class:
         *
         *     TestAction.multiply(
         *         2, 4, // pass two arguments to server, so specify len=2
         *         // callback function after the server is called
         *         //     result: the result returned by the server
         *         //     e: Ext.direct.RemotingEvent object
         *         function(result, e) {
         *             var t = e.getTransaction();
         *             var action = t.action; // server side Class called
         *             var method = t.method; // server side method called
         *             if (e.getStatus()) {
         *                 var answer = Ext.encode(result); // 8
         *             } else {
         *                 var msg = e.getMessage(); // failure message
         *             }
         *         }
         *     );
         *
         * In the example above, the server side "multiply" function will be passed two
         * arguments (2 and 4).  The "multiply" method should return the value 8 which will be
         * available as the `result` in the example above.
         */
        actions: {}
    },

    /**
     * @event beforecall
     * Fires immediately before the client-side sends off the RPC call.
     * By returning `false` from an event handler you can prevent the call from
     * executing.
     * @param {Ext.direct.RemotingProvider} provider
     * @param {Ext.direct.Transaction} transaction
     * @param {Object} meta The meta data.
     */

    /**
     * @event call
     * Fires immediately after the request to the server-side is sent. This does
     * NOT fire after the response has come back from the call.
     * @param {Ext.direct.RemotingProvider} provider
     * @param {Ext.direct.Transaction} transaction
     * @param {Object} meta The meta data.
     */

    constructor : function(config) {
        var me = this;

        me.callParent(arguments);

        me.transactions = Ext.create('Ext.util.Collection', function(item) {
            return item.getId();
        });
        me.callBuffer = [];
    },

    applyNamespace: function(namespace) {
        if (Ext.isString(namespace)) {
            return Ext.ns(namespace);
        }
        return namespace || window;
    },

    /**
     * Initialize the API
     * @private
     */
    initAPI : function() {
        var actions = this.getActions(),
            namespace = this.getNamespace(),
            action, cls, methods,
            i, ln, method;

        for (action in actions) {
            if (actions.hasOwnProperty(action)) {
                cls = namespace[action];
                if (!cls) {
                    cls = namespace[action] = {};
                }
                methods = actions[action];

                for (i = 0, ln = methods.length; i < ln; ++i) {
                    method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
                    cls[method.getName()] = this.createHandler(action, method);
                }
            }
        }
    },

    /**
     * Create a handler function for a direct call.
     * @private
     * @param {String} action The action the call is for.
     * @param {Object} method The details of the method.
     * @return {Function} A JavaScript function that will kick off the call.
     */
    createHandler : function(action, method) {
        var me = this,
            handler;

        if (!method.getFormHandler()) {
            handler = function() {
                me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
            };
        } else {
            handler = function(form, callback, scope) {
                me.configureFormRequest(action, method, form, callback, scope);
            };
        }
        handler.directCfg = {
            action: action,
            method: method
        };
        return handler;
    },

    // @inheritdoc
    isConnected: function() {
        return !!this.connected;
    },

    // @inheritdoc
    connect: function() {
        var me = this;

        if (me.getUrl()) {
            me.initAPI();
            me.connected = true;
            me.fireEvent('connect', me);
        } else {
            //<debug>
            Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
            //</debug>
        }
    },

    // @inheritdoc
    disconnect: function() {
        var me = this;

        if (me.connected) {
            me.connected = false;
            me.fireEvent('disconnect', me);
        }
    },

    /**
     * Run any callbacks related to the transaction.
     * @private
     * @param {Ext.direct.Transaction} transaction The transaction
     * @param {Ext.direct.Event} event The event
     */
    runCallback: function(transaction, event) {
        var success = !!event.getStatus(),
            functionName = success ? 'success' : 'failure',
            callback = transaction && transaction.getCallback(),
            result;

        if (callback) {
            // this doesnt make any sense. why do we have both result and data?
            // result = Ext.isDefined(event.getResult()) ? event.result : event.data;
            result = event.getResult();
            if (Ext.isFunction(callback)) {
                callback(result, event, success);
            } else {
                Ext.callback(callback[functionName], callback.scope, [result, event, success]);
                Ext.callback(callback.callback, callback.scope, [result, event, success]);
            }
        }
    },

    /**
     * React to the AJAX request being completed.
     * @private
     */
    onData: function(options, success, response) {
        var me = this,
            i = 0,
            ln, events, event,
            transaction, transactions;

        if (success) {
            events = me.createEvents(response);
            for (ln = events.length; i < ln; ++i) {
                event = events[i];
                transaction = me.getTransaction(event);
                me.fireEvent('data', me, event);
                if (transaction) {
                    me.runCallback(transaction, event, true);
                    Ext.direct.Manager.removeTransaction(transaction);
                }
            }
        } else {
            transactions = [].concat(options.transaction);
            for (ln = transactions.length; i < ln; ++i) {
                transaction = me.getTransaction(transactions[i]);
                if (transaction && transaction.getRetryCount() < me.getMaxRetries()) {
                    transaction.retry();
                } else {
                    event = Ext.create('Ext.direct.ExceptionEvent', {
                        data: null,
                        transaction: transaction,
                        code: Ext.direct.Manager.exceptions.TRANSPORT,
                        message: 'Unable to connect to the server.',
                        xhr: response
                    });

                    me.fireEvent('data', me, event);
                    if (transaction) {
                        me.runCallback(transaction, event, false);
                        Ext.direct.Manager.removeTransaction(transaction);
                    }
                }
            }
        }
    },

    /**
     * Get transaction from XHR options.
     * @private
     * @param {Object} options The options sent to the AJAX request.
     * @return {Ext.direct.Transaction/null} The transaction, `null` if not found.
     */
    getTransaction: function(options) {
        return options && options.getTid ? Ext.direct.Manager.getTransaction(options.getTid()) : null;
    },

    /**
     * Configure a direct request.
     * @private
     * @param {String} action The action being executed.
     * @param {Object} method The method being executed.
     * @param {Array} args
     */
    configureRequest: function(action, method, args) {
        var me = this,
            callData = method.getCallData(args),
            data = callData.data,
            callback = callData.callback,
            scope = callData.scope,
            transaction;

        transaction = Ext.create('Ext.direct.Transaction', {
            provider: me,
            args: args,
            action: action,
            method: method.getName(),
            data: data,
            callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
        });

        if (me.fireEvent('beforecall', me, transaction, method) !== false) {
            Ext.direct.Manager.addTransaction(transaction);
            me.queueTransaction(transaction);
            me.fireEvent('call', me, transaction, method);
        }
    },

    /**
     * Gets the AJAX call info for a transaction.
     * @private
     * @param {Ext.direct.Transaction} transaction The transaction.
     * @return {Object} The call params.
     */
    getCallData: function(transaction) {
        return {
            action: transaction.getAction(),
            method: transaction.getMethod(),
            data: transaction.getData(),
            type: 'rpc',
            tid: transaction.getId()
        };
    },

    /**
     * Sends a request to the server.
     * @private
     * @param {Object/Array} data The data to send.
     */
    sendRequest : function(data) {
        var me = this,
            request = {
                url: me.getUrl(),
                callback: me.onData,
                scope: me,
                transaction: data,
                timeout: me.getTimeout()
            }, callData,
            enableUrlEncode = me.getEnableUrlEncode(),
            i = 0,
            ln, params;

        if (Ext.isArray(data)) {
            callData = [];
            for (ln = data.length; i < ln; ++i) {
                callData.push(me.getCallData(data[i]));
            }
        } else {
            callData = me.getCallData(data);
        }

        if (enableUrlEncode) {
            params = {};
            params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
            request.params = params;
        } else {
            request.jsonData = callData;
        }
        Ext.Ajax.request(request);
    },

    /**
     * Add a new transaction to the queue.
     * @private
     * @param {Ext.direct.Transaction} transaction The transaction.
     */
    queueTransaction: function(transaction) {
        var me = this,
            enableBuffer = me.getEnableBuffer();

        if (transaction.getForm()) {
            me.sendFormRequest(transaction);
            return;
        }

        me.callBuffer.push(transaction);
        if (enableBuffer) {
            if (!me.callTask) {
                me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
            }
            me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
        } else {
            me.combineAndSend();
        }
    },

    /**
     * Combine any buffered requests and send them off.
     * @private
     */
    combineAndSend : function() {
        var buffer = this.callBuffer,
            ln = buffer.length;

        if (ln > 0) {
            this.sendRequest(ln == 1 ? buffer[0] : buffer);
            this.callBuffer = [];
        }
    },

    /**
     * Configure a form submission request
     *
     * @param {String} action The action being executed
     * @param {Object} method The method being executed
     * @param {HTMLElement} form The form being submitted
     * @param {Function} [callback] A callback to run after the form submits
     * @param {Object} [scope] A scope to execute the callback in
     *
     * @private
     */
    configureFormRequest : function(action, method, form, callback, scope) {
        var me = this,
            transaction, isUpload, params;

        transaction = new Ext.direct.Transaction({
            provider: me,
            action: action,
            method: method.getName(),
            args: [form, callback, scope],
            callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
            isForm: true
        });

        if (me.fireEvent('beforecall', me, transaction, method) !== false) {
            Ext.direct.Manager.addTransaction(transaction);
            isUpload = String(form.getAttribute('enctype')).toLowerCase() == 'multipart/form-data';

            params = {
                extTID: transaction.id,
                extAction: action,
                extMethod: method.getName(),
                extType: 'rpc',
                extUpload: String(isUpload)
            };

            // change made from typeof callback check to callback.params
            // to support addl param passing in DirectSubmit EAC 6/2
            Ext.apply(transaction, {
                form: Ext.getDom(form),
                isUpload: isUpload,
                params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
            });

            me.fireEvent('call', me, transaction, method);
            me.sendFormRequest(transaction);
        }
    },

    /**
     * Sends a form request
     *
     * @param {Ext.direct.Transaction} transaction The transaction to send
     *
     * @private
     */
    sendFormRequest : function(transaction) {
        var me = this;

        Ext.Ajax.request({
            url: me.getUrl(),
            params: transaction.params,
            callback: me.onData,
            scope: me,
            form: transaction.form,
            isUpload: transaction.isUpload,
            transaction: transaction
        });
    }

});


var postcss = require('postcss');

module.exports = postcss.plugin('postcss-inline-block', function () {

    // Work with options here
    function inlineBlockFix(decl){
    	var origRule = decl.parent;
    	origRule.append(
	    	{
	    		prop:'*display',
	    		value:'inline'
	    	},
	    	{
	    		prop:'*zoom',
	    		value:'1'
	    	}
    	);
    }

    return function (css) {
        // Transform CSS AST here
        css.walkDecls('display', function(decl){
        	if(decl.value == "inline-block"){
        		inlineBlockFix(decl);
        	}
        });
    };
});


/**
 * @private
 */
Ext.define('Ext.event.publisher.Dom', {
    extend: 'Ext.event.publisher.Publisher',

    requires: [
        'Ext.env.Browser',
        'Ext.Element',
        'Ext.event.Dom'
    ],

    targetType: 'element',

    idOrClassSelectorRegex: /^([#|\.])([\w\-]+)$/,

    handledEvents: ['focus', 'blur', 'paste', 'input', 'change',
                    'keyup', 'keydown', 'keypress', 'submit',
                    'transitionend', 'animationstart', 'animationend'],

    classNameSplitRegex: /\s+/,

    SELECTOR_ALL: '*',

    constructor: function() {
        var eventNames = this.getHandledEvents(),
            eventNameMap = {},
            i, ln, eventName, vendorEventName;

        this.doBubbleEventsMap = {
            'click': true,
            'submit': true,
            'mousedown': true,
            'mousemove': true,
            'mouseup': true,
            'mouseover': true,
            'mouseout': true,
            'transitionend': true
        };

        this.onEvent = Ext.Function.bind(this.onEvent, this);

        for (i = 0,ln = eventNames.length; i < ln; i++) {
            eventName = eventNames[i];
            vendorEventName = this.getVendorEventName(eventName);
            eventNameMap[vendorEventName] = eventName;

            this.attachListener(vendorEventName);
        }

        this.eventNameMap = eventNameMap;

        return this.callParent();
    },

    getSubscribers: function(eventName) {
        var subscribers = this.subscribers,
            eventSubscribers = subscribers[eventName];

        if (!eventSubscribers) {
            eventSubscribers = subscribers[eventName] = {
                id: {
                    $length: 0
                },
                className: {
                    $length: 0
                },
                selector: [],
                all: 0,
                $length: 0
            }
        }

        return eventSubscribers;
    },

    getVendorEventName: function(eventName) {
        if (Ext.browser.is.WebKit) {
            if (eventName === 'transitionend') {
                eventName = Ext.browser.getVendorProperyName('transitionEnd');
            }
            else if (eventName === 'animationstart') {
                eventName = Ext.browser.getVendorProperyName('animationStart');
            }
            else if (eventName === 'animationend') {
                eventName = Ext.browser.getVendorProperyName('animationEnd');
            }
        }

        return eventName;
    },

    bindListeners: function (doc, bind) {
        var handlesEvents = this.getHandledEvents(),
            handlesEventsLength = handlesEvents.length,
            i;

        for (i = 0; i < handlesEventsLength; i++) {
            this.bindListener(doc, this.getVendorEventName(handlesEvents[i]), bind);
        }
    },

    bindListener: function (doc, eventName, bind) {
        if (bind) {
            this.attachListener(eventName, doc);
        } else {
            this.removeListener(eventName, doc);
        }
        return this
    },

    attachListener: function(eventName, doc) {
        if (!doc) {
            doc = document;
        }

        var defaultView = doc.defaultView;

        // Some AndroidStock browsers (HP Slate for example) will not process any touch events unless a listener is added to document or body
        // this listener must be to a touch event (touchstart, touchmove, touchend)
        if ((Ext.os.is.iOS && Ext.os.version.getMajor() < 5) || Ext.browser.is.AndroidStock) {
            document.addEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        else if (defaultView && defaultView.addEventListener) {
            doc.defaultView.addEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        else {
            doc.addEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        return this;
    },

    removeListener: function(eventName, doc) {
        if (!doc) {
            doc = document;
        }

        var defaultView = doc.defaultView;

        if ((Ext.os.is.iOS && Ext.os.version.getMajor() < 5) && Ext.browser.is.AndroidStock) {
            document.removeEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        else if (defaultView && defaultView.addEventListener) {
            doc.defaultView.removeEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        else {
            doc.removeEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
        }
        return this;
    },

    doesEventBubble: function(eventName) {
        return !!this.doBubbleEventsMap[eventName];
    },

    subscribe: function(target, eventName) {
        if (!this.handles(eventName)) {
            return false;
        }

        var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
            subscribers = this.getSubscribers(eventName),
            idSubscribers = subscribers.id,
            classNameSubscribers = subscribers.className,
            selectorSubscribers = subscribers.selector,
            type, value;

        if (idOrClassSelectorMatch !== null) {
            type = idOrClassSelectorMatch[1];
            value = idOrClassSelectorMatch[2];

            if (type === '#') {
                if (idSubscribers.hasOwnProperty(value)) {
                    idSubscribers[value]++;
                    return true;
                }

                idSubscribers[value] = 1;
                idSubscribers.$length++;
            }
            else {
                if (classNameSubscribers.hasOwnProperty(value)) {
                    classNameSubscribers[value]++;
                    return true;
                }

                classNameSubscribers[value] = 1;
                classNameSubscribers.$length++;
            }
        }
        else {
            if (target === this.SELECTOR_ALL) {
                subscribers.all++;
            }
            else {
                if (selectorSubscribers.hasOwnProperty(target)) {
                    selectorSubscribers[target]++;
                    return true;
                }

                selectorSubscribers[target] = 1;
                selectorSubscribers.push(target);
            }
        }

        subscribers.$length++;

        return true;
    },

    unsubscribe: function(target, eventName, all) {
        if (!this.handles(eventName)) {
            return false;
        }

        var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
            subscribers = this.getSubscribers(eventName),
            idSubscribers = subscribers.id,
            classNameSubscribers = subscribers.className,
            selectorSubscribers = subscribers.selector,
            type, value;

        all = Boolean(all);

        if (idOrClassSelectorMatch !== null) {
            type = idOrClassSelectorMatch[1];
            value = idOrClassSelectorMatch[2];

            if (type === '#') {
                if (!idSubscribers.hasOwnProperty(value) || (!all && --idSubscribers[value] > 0)) {
                    return true;
                }

                delete idSubscribers[value];
                idSubscribers.$length--;
            }
            else {
                if (!classNameSubscribers.hasOwnProperty(value) || (!all && --classNameSubscribers[value] > 0)) {
                    return true;
                }

                delete classNameSubscribers[value];
                classNameSubscribers.$length--;
            }
        }
        else {
            if (target === this.SELECTOR_ALL) {
                if (all) {
                    subscribers.all = 0;
                }
                else {
                    subscribers.all--;
                }
            }
            else {
                if (!selectorSubscribers.hasOwnProperty(target) || (!all && --selectorSubscribers[target] > 0)) {
                    return true;
                }

                delete selectorSubscribers[target];
                Ext.Array.remove(selectorSubscribers, target);
            }
        }

        subscribers.$length--;

        return true;
    },

    getElementTarget: function(target) {
        if (target.nodeType !== 1) {
            target = target.parentNode;

            if (!target || target.nodeType !== 1) {
                return null;
            }
        }

        return target;
    },

    getBubblingTargets: function(target) {
        var targets = [];

        if (!target) {
            return targets;
        }

        do {
            targets[targets.length] = target;

            target = target.parentNode;
        } while (target && target.nodeType === 1);

        return targets;
    },

    dispatch: function(target, eventName, args) {
        args.push(args[0].target);
        this.callParent(arguments);
    },

    publish: function(eventName, targets, event) {
        var subscribers = this.getSubscribers(eventName),
            wildcardSubscribers;

        if (subscribers.$length === 0 || !this.doPublish(subscribers, eventName, targets, event)) {
            wildcardSubscribers = this.getSubscribers('*');

            if (wildcardSubscribers.$length > 0) {
                this.doPublish(wildcardSubscribers, eventName, targets, event);
            }
        }

        return this;
    },

    doPublish: function(subscribers, eventName, targets, event) {
        var idSubscribers = subscribers.id,
            classNameSubscribers = subscribers.className,
            selectorSubscribers = subscribers.selector,
            hasIdSubscribers = idSubscribers.$length > 0,
            hasClassNameSubscribers = classNameSubscribers.$length > 0,
            hasSelectorSubscribers = selectorSubscribers.length > 0,
            hasAllSubscribers = subscribers.all > 0,
            isClassNameHandled = {},
            args = [event],
            hasDispatched = false,
            classNameSplitRegex = this.classNameSplitRegex,
            i, ln, j, subLn, target, id, className, classNames, selector;

        for (i = 0,ln = targets.length; i < ln; i++) {
            target = targets[i];
            event.setDelegatedTarget(target);

            if (hasIdSubscribers) {
                // We use getAttribute instead of referencing id here as forms can have there properties overridden by children
                // Example:
                //  <form id="myForm">
                //      <input name="id">
                //  </form>
                // form.id === input node named id whereas form.getAttribute("id") === "myForm"
                id = target.getAttribute("id");

                if (id) {
                    if (idSubscribers.hasOwnProperty(id)) {
                        hasDispatched = true;
                        this.dispatch('#' + id, eventName, args);
                    }
                }
            }

            if (hasClassNameSubscribers) {
                className = target.className;

                if (className) {
                    classNames = className.split(classNameSplitRegex);

                    for (j = 0,subLn = classNames.length; j < subLn; j++) {
                        className = classNames[j];

                        if (!isClassNameHandled[className]) {
                            isClassNameHandled[className] = true;

                            if (classNameSubscribers.hasOwnProperty(className)) {
                                hasDispatched = true;
                                this.dispatch('.' + className, eventName, args);
                            }
                        }
                    }
                }
            }

            // Stop propagation
            if (event.isStopped) {
                return hasDispatched;
            }
        }

        if (hasAllSubscribers && !hasDispatched) {
            event.setDelegatedTarget(event.browserEvent.target);
            hasDispatched = true;
            this.dispatch(this.SELECTOR_ALL, eventName, args);
            if (event.isStopped) {
                return hasDispatched;
            }
        }

        if (hasSelectorSubscribers) {
            for (j = 0,subLn = targets.length; j < subLn; j++) {
                target = targets[j];

                for (i = 0,ln = selectorSubscribers.length; i < ln; i++) {
                    selector = selectorSubscribers[i];

                    if (this.matchesSelector(target, selector)) {
                        event.setDelegatedTarget(target);
                        hasDispatched = true;
                        this.dispatch(selector, eventName, args);
                    }

                    if (event.isStopped) {
                        return hasDispatched;
                    }
                }
            }
        }

        return hasDispatched;
    },

    matchesSelector: function() {
        var test = Element.prototype,
            matchesSelector =
                ('webkitMatchesSelector' in test) ? 'webkitMatchesSelector' :
                (('msMatchesSelector' in test) ? 'msMatchesSelector' : ('mozMatchesSelector' in test ? 'mozMatchesSelector' : null));

        if (matchesSelector) {
            return function(element, selector) {
                return element[matchesSelector](selector);
            }
        }

        return function(element, selector) {
            Ext.DomQuery.is(element, selector);
        }
    }(),

    onEvent: function(e) {
        var eventName = this.eventNameMap[e.type];
        // Set the current frame start time to be the timestamp of the event.
        Ext.frameStartTime = e.timeStamp;

        if (!eventName || this.getSubscribersCount(eventName) === 0) {
            return;
        }

        var target = this.getElementTarget(e.target),
            targets;

        if (!target) {
            return;
        }

        if (this.doesEventBubble(eventName)) {
            targets = this.getBubblingTargets(target);
        }
        else {
            targets = [target];
        }

        this.publish(eventName, targets, new Ext.event.Dom(e));
    },

    //<debug>
    hasSubscriber: function(target, eventName) {
        if (!this.handles(eventName)) {
            return false;
        }

        var match = target.match(this.idOrClassSelectorRegex),
            subscribers = this.getSubscribers(eventName),
            type, value;

        if (match !== null) {
            type = match[1];
            value = match[2];

            if (type === '#') {
                return subscribers.id.hasOwnProperty(value);
            }
            else {
                return subscribers.className.hasOwnProperty(value);
            }
        }
        else {
            return (subscribers.selector.hasOwnProperty(target) && Ext.Array.indexOf(subscribers.selector, target) !== -1);
        }

        return false;
    },
    //</debug>

    getSubscribersCount: function(eventName) {
        if (!this.handles(eventName)) {
            return 0;
        }

        return this.getSubscribers(eventName).$length + this.getSubscribers('*').$length;
    }

});


'use strict';
/* globals console, mkdir, __dirname */

/**
 * Jetbrains WebStorm IDE project generation.
 *  http://www.jetbrains.com/webstorm
 *  @type {IDE}
 */
require('shelljs/global');

var util = require('util'),
    IDE  = require('../IDE'),
    fs   = require('fs'),
    path = require('path'),
    _    = require('lodash');

var platform = require('../../lib/platform');
var io = require('../../lib/io');
var templateUtil = require('../../util');

module.exports = WebStorm;

function WebStorm() {
  WebStorm.super_.apply(this, ['webstorm']);
}

util.inherits(WebStorm, IDE);

/**
 * Use the WebStorm executable to open a project programatically.
 * @param location
 */
WebStorm.prototype.open = function (location) {
  console.log('Please give WebStorm a chance to complete indexing your project.');
  WebStorm.super_.prototype.open.apply(this, [location]);
};

WebStorm.prototype.executable = function () {
  if (this.customExecutable !== null) {
    return this.customExecutable;

  } else if (platform.isWindows()) {
    return resolveWindowsExe();

  } else if (platform.isMacOS()) {
    var expectedPaths = [
      '/usr/local/bin/wstorm',
      '/Applications/WebStorm.app/Contents/MacOS/webstorm', //WebStorm 10
      '/Applications/WebStorm.app/Contents/MacOS/webide' //WebStorm 9
    ];

    for (var i = 0; i < expectedPaths.length; i++) {
      var execPath = expectedPaths[i];
      if (io.existsFileSync(execPath)) {
        return execPath;
      }
    }

  } else if (platform.isUnix()) {
    var selectedExecPath = null;
    [
      '/usr/local/bin/wstorm',
      '/opt/webstorm/bin/webstorm.sh'
    ].forEach(function(execPath) {
      if (io.existsFileSync(execPath)) {
        selectedExecPath = execPath;
      }
    });
    return selectedExecPath;

  } else {
    return null;
  }
};

/**
 * Utility to validate the executable path that will be used to open WebStorm
 * @returns {boolean}
 */
WebStorm.prototype.validateExecutable = function () {
  var executablePath = this.executable();
  var isValid = false;

  if (executablePath !== null) {
    isValid = io.existsFileSync(executablePath, 'The path to WebStorm is not valid ' + executablePath +
    'you can assign a custom one with `ideTemplate.webStorm.customExecutable = <path to webstorm>`');
  }

  return isValid;
};

/**
 * Create a new WebStorm .idea project at a given destination with
 * a specific template context.
 *
 * @param destination
 * @param context
 */
WebStorm.prototype.createProject = function (destination, context) {
  context = this.createContext(context);

  var source = path.join(String(__dirname), 'template', 'project');
  destination = path.join(destination, '.idea');

  templateUtil.templateDirSync(source, destination, context);

  if (context.resourceRoots.length > 0) {
    stubPlainTextFiles(context.plainText, destination);
  }
};

/**
 * Method for copying WebStorm file template configurations to the local
 * user preferences folders.
 *
 * http://www.jetbrains.com/webstorm/webhelp/file-and-code-templates.html
 *
 */
WebStorm.prototype.copyFileTemplates = function (source) {
  var destination = userPreferencesDirectory('fileTemplates');
  var isValid = io.existsDirectorySync(destination);

  if (!isValid) {
    console.log('Failed to locate Webstorm templates. Expected directory:');
    console.log('  ' + destination);
  } else {
    io.replaceMatchFilesSync(/^angularity/, source, destination);
  }
};

/**
 * Create a new template context for an idea project.
 * if a custom context is provided it will override any default values of the resulting object.
 *
 * @param override
 * @returns {Object}
 */
WebStorm.prototype.createContext = function (override) {
  var context = {
    projectName             : 'NewProject',
    jshintPath              : './.jshintrc',
    jsDebugPort             : '63343',
    javascriptVersion       : 'ES5',
    projectFolder           : '',
    JsKarmaPackageDirSetting: undefined,
    selectedDebugName       : '',
    resourceRoots           : [],
    watcherSuppressedTasks  : [],
    plainText               : '',
    contentPaths            : [
      {
        content : 'file://$MODULE_DIR$',
        excluded: []
      }
    ],
    projectView             : undefined,
    libraries               : [],
    vcs                     : [],
    jsDebugConfiguration    : [],
    karmaDebugConfiguration : [],
    nodejsDebugConfiguration: []
  };
  if(override.projectView) {
    override.projectView = writeProjectViewTemplate(override.projectView);
  }

  return _.merge(context, override);
};

/**
 * Shortcut to create a project context with the default values
 * This prevents an error in lodash template with undefined template tokens.
 *
 * @param projectName
 * @param contentPaths
 * @param jsDebugPort
 * @param jshintPath
 */
WebStorm.prototype.createProjectContext = function (projectName, contentPaths, jsDebugPort, jshintPath) {
  return {
    projectName : projectName,
    jshintPath  : jshintPath,
    jsDebugPort : jsDebugPort,
    contentPaths: contentPaths
  };
};

/**
 * Method for copying WebStorm configuration to the local
 * user preferences folders.
 * @see http://www.jetbrains.com/webstorm/webhelp/external-tools.html
 */
WebStorm.prototype.copyCodeStyle = function (codeStyleFile, styleName, projectLocation) {
  if (!fs.existsSync(codeStyleFile)) {
    console.error('WebStorm.copyCodeStyle() the provided path for the codestyle xml does not exist at', codeStyleFile);
  }
  if (!fs.existsSync(projectLocation)) {
    console.error('WebStorm.copyCodeStyle() the provided projectLocation does not exist at', projectLocation);
  }

  // copy the actual code stlye configuration to the user preferences folder
  var basename = path.basename(codeStyleFile);
  var codeStyleDestination = path.join(userPreferencesDirectory('codestyles'), basename);
  io.copyFileSync(codeStyleFile, codeStyleDestination);

  // The .idea project requires a codeStyleSettings.xml with the default code style name
  var codeStyleTemplateName = 'codeStyleSettings.xml';
  var codeStyleSettingsSource = path.join(String(__dirname), 'template', codeStyleTemplateName);
  var codeStyleSettingsDestination = path.join(projectLocation, '.idea', codeStyleTemplateName);
  io.writeTemplateFileSync(codeStyleSettingsSource, {codeStyleName: styleName}, codeStyleSettingsDestination);
};

/**
 * Method for copying the WebStorm external tools configuration to the local
 * user preferences folders.
 *
 * @see http://www.jetbrains.com/webstorm/webhelp/external-tools.html
 */
WebStorm.prototype.copyExternalTools = function (source) {
  var destination = userPreferencesDirectory('tools');
  io.copyFileSync(source, destination);
};

/**
 * Create an external tool template xml file with a given context object.
 * @param override
 * @returns {*} plain text of the xml
 */
WebStorm.prototype.createExternalTool = function (context, fileName) {
  var baseContext = {
    name : '',
    tools: [
      {
        name               : 'default',
        showInMainMenu     : 'true',
        showInEditor       : 'true',
        showInProject      : 'true',
        showInSearchPopup  : 'true',
        disabled           : 'false',
        useConsole         : 'true',
        showConsoleOnStdOut: 'false',
        showConsoleOnStdErr: 'false',
        synchronizeAfterRun: 'true',
        exec               : [
          {
            name : '',
            value: ''
          }
        ],
        filter             : [
          {
            name : '',
            value: ''
          }
        ]
      }
    ]
  };
  // Use the base context to create a new tool template
  context = _.merge(baseContext, context);

  var templateSource = path.join(String(__dirname), 'template', 'externalTool.xml');
  var toolTemplate = fs.readFileSync(templateSource);
  var content = _.template(toolTemplate, context);

  var toolsPath = userPreferencesDirectory('tools');
  io.validateDirectorySync(toolsPath);
  var destination = path.join(toolsPath, fileName);

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

/**
 * When generating the .idea project WebStorm will remove any plain text files specified
 * on first open if they do not exist.
 * For example this lets files from a build be marked as plain text before they exist
 * by stubbing an empty file at their expected position.
 * @param resourceRoots
 * @param destination
 */
function stubPlainTextFiles(resourceRoots, destination) {
  _.forEach(resourceRoots, function (resource) {
    // Replace the webstorm file:// scheme with an absolute file path
    var filePath = resource.replace('file://$PROJECT_DIR$', destination);
    filePath = filePath.replace('.idea/', '');
    // Extract the location from the file path to recursively create it if it doesn't exist.
    var location = filePath.replace(/[^\/]*$/, '');

    if (!fs.existsSync(location))
      mkdir('-p', location);

    if (!fs.existsSync(filePath))
      fs.writeFileSync(filePath, ' ', 'utf8');
  });
}

/**
 * The user preferences directory for WebStorm on the current platform.
 * https://www.jetbrains.com/webstorm/help/project-and-ide-settings.html
 * @returns {string} an absolute path to the .Webstorm xxx preferences folder.
 */
function userPreferencesDirectory(configDirectory) {
  var home = platform.userHomeDirectory();
  var webStormPreferences = io.maximisePath(home, /^\.WebStorm\s*[.\d]+$/, 'config') ||   // windows|unix
    io.maximisePath(home, 'Library', 'Preferences', /^WebStorm\s*[.\d]+$/);               // darwin

  // If the config directory does not previously exist, create it.
  // Eg the tools directory wont exist unless External Tools were previously used.
  if(webStormPreferences) {
    webStormPreferences = path.join(webStormPreferences, configDirectory);
    if(!fs.existsSync(webStormPreferences)) {
      fs.mkdirSync(webStormPreferences);
    }
  }

  return webStormPreferences;
}

/**
 * Utility to write the ProjectView node require for project pane default in the .idea/workspace.xml.
 * @param rootPath (the name of the project root so tree node will be open in the project)
 * @returns {string}
 */
function writeProjectViewTemplate(rootPath) {
  var context = {
    rootPath: rootPath
  };

  var templateSource = path.join(String(__dirname), 'template', 'projectView.xml');
  var toolTemplate = fs.readFileSync(templateSource);
  return io.templateSync (toolTemplate, context);
}
/**
 * Check if a Webstorm.exe exists in the default install location for windows.
 * @return {boolean} A true concatenated path where found, else false.
 */
function resolveWindowsExe() {
  var jetbrainsDirectory = io.reduceDirectories('C:/Program Files/JetBrains', 'C:/Program Files (x86)/JetBrains');
  return resolveJetbrainsExe(jetbrainsDirectory);
}

/**
 * Check if a Webstorm.exe exists in a given windows program files directory.
 * @param {string} Jetbrains install directory eg, C:/Program Files/JetBrains.
 * @returns {string|boolean} A true concatenated path where found, else false.
 */
function resolveJetbrainsExe(jetbrainsDirectory) {
  var exists = false;
  var webstormInstallPaths = io.resolveDirMatches(jetbrainsDirectory, /^WebStorm\s*[.\d]+$/);

  // Check that the Webstorm folder have a bin folder, empty folders are a known issue.
  for (var j = 0; j < webstormInstallPaths.length; j++) {
    var webstormPath = [jetbrainsDirectory, webstormInstallPaths[j], 'bin'];
    var resolvedWebstorm = resolveMaxedPath(webstormPath);
    if(resolvedWebstorm === null) break;

    exists = path.resolve(resolvedWebstorm.join(path.sep), 'Webstorm.exe');
    if(fs.existsSync(exists)) {
      return exists;
    }
  }

  return exists;
}

function resolveMaxedPath(elements) {
  for (var i = 1; i < elements.length; i++) {
    // the directory is elements 0 .. i-1 joined
    var directory = path.resolve(path.join.apply(path, elements.slice(0, i)));

    // no directory implies failure
    if (!fs.existsSync(directory)) {
      return null;
    }
    // anything else is cast to string
    else {
      elements[i] = String(elements[i]);
    }
  }

  return elements;
}


var utils = require('./utils');

/**
 * Mostly from Express' router `Route`.
 *
 * Initialize `Route` with the given HTTP `method`, `path`,
 * and an array of `callbacks` and `options`.
 *
 * Options:
 *
 *   - `sensitive`    enable case-sensitive routes
 *   - `strict`       enable strict matching for trailing slashes
 *
 * @param {String} method
 * @param {String} path
 * @param {Array} callbacks
 * @param {Object} options
 */

function Route (method, path, callback, options) {
  this.path = path;
  this.method = method;
  this.callback = callback;
  this.regexp = utils.pathRegexp(path, this.keys = [], options.sensitive, options.strict);
}
module.exports = Route;

/**
 * Check if this route matches `path`, if so
 * populate `.params`.
 *
 * @param {String} path
 * @return {Boolean}
 */

Route.prototype.match = function(path){
  var keys = this.keys;
  var params = this.params = [];

  var m = this.regexp.exec(path);

  if (!m) return false;

  for (var i = 1, len = m.length; i < len; ++i) {
    var key = keys[i - 1];

    try {
      var val = 'string' == typeof m[i]
        ? decodeURIComponent(m[i])
        : m[i];
    } catch(e) {
      var err = new Error("Failed to decode param '" + m[i] + "'");
      err.status = 400;
      throw err;
    }

    if (key)
      params[key.name] = val;
    else
      params.push(val);
  }

  return true;
};


/*
 * grunt-bower
 * https://github.com/dherges/grunt-bower
 *
 * Copyright (c) 2014 David Herges
 * Licensed under the MIT license.
 */

'use strict';

var archy = require('archy'),
    chalk = require('chalk'),
    inquirer = require('inquirer'),
    mout = require('mout');

var GruntLogListener = function (grunt) {
  this.grunt = grunt;

  this.grunt.event.on('bower.log', this.log.bind(this));
  this.grunt.event.on('bower.error', this.error.bind(this));
  this.grunt.event.on('bower.end', this.end.bind(this));
  this.grunt.event.on('bower.prompt', this.prompt.bind(this));
};

GruntLogListener.prototype.end = function (data, task) {
  if (task === 'list') {
    // print bower dependency tree if 'list' is the command
    this.grunt.log.oklns("dependency tree:");
    console.log(archy(tree2archy(data)));
  }
  this.grunt.log.oklns("Bower command finished.");
};

GruntLogListener.prototype.error = function (err) {
  this.grunt.log.errorlns(err.message);
};

GruntLogListener.prototype.log = function (data) {
  this.grunt.log.writelns(data.id + " >> " + data.message);
  var grunt = this.grunt;

  if (data.data && data.data.picks && data.data.picks.forEach) {
    data.data.picks.forEach(function (pick, index) {
      grunt.log.writelns(
        grunt.log.wordlist(
          ['[', index, ']'],
          {separator: '', color: 'green'}
        ) + ' ' +
        grunt.log.wordlist(
          ['name=' + pick.pkgMeta.name, 'version=' + pick.pkgMeta.version],
          {separator: ', '}
        )
      );
    });
  }
};

GruntLogListener.prototype.prompt = function (prompts, callback) {
  inquirer.prompt(prompts, callback);
};

/**
 * the _tree2archy function, taken directly from bower source
 * https://github.com/bower/bower/blob/master/lib/renderers/StandardRenderer.js#L424-480
 *
 * bower renderers are not a separate module, or else we wouldn't need this,
 * see: https://github.com/bower/bower/issues/874
 */
function tree2archy (node) {
  var dependencies = mout.object.values(node.dependencies);
  var version = !node.missing ? node.pkgMeta._release || node.pkgMeta.version : null;
  var label = node.endpoint.name + (version ? '#' + version : '');
  var update;

  if (node.root) {
    label += ' ' + node.canonicalDir;
  }

  // State labels
  if (node.missing) {
    label += chalk.red(' not installed');
    return label;
  }

  if (node.different) {
    label += chalk.red(' different');
  }

  if (node.linked) {
    label += chalk.magenta(' linked');
  }

  if (node.incompatible) {
    label += chalk.yellow(' incompatible') + ' with ' + node.endpoint.target;
  } else if (node.extraneous) {
    label += chalk.green(' extraneous');
  }

  // New versions
  if (node.update) {
    update = '';

    if (node.update.target && node.pkgMeta.version !== node.update.target) {
        update += node.update.target + ' available';
    }

    if (node.update.latest !== node.update.target) {
        update += (update ? ', ' : '');
        update += 'latest is ' + node.update.latest;
    }

    if (update) {
        label += ' (' + chalk.cyan(update) + ')';
    }
  }

  if (!dependencies.length) {
    return label;
  }

  return {
    label: label,
    nodes: mout.object.values(dependencies).map(tree2archy)
  };
}

module.exports = GruntLogListener;


/**
 * @module kad-telemetry/transport-decorator
 */

'use strict'

var assert = require('assert')
var inherits = require('util').inherits
var Persistence = require('./persistence')
var metrics = require('./metrics')

/**
 * Returns a decorated transport adapter that writes telemetry data
 * @constructor
 * @param {kad.RPC} Transport
 */
function TransportDecorator (Transport) {

  function TelemetryTransport (contact, options) {
    if (!(this instanceof TelemetryTransport)) {
      return new TelemetryTransport(contact, options)
    }

    assert.ok(options, 'Missing required options parameter')

    this._telopts = options.telemetry
    this.telemetry = new Persistence(this._telopts.storage)

    Transport.call(this, contact, options)
  }

  inherits(TelemetryTransport, Transport)

  TelemetryTransport.DEFAULT_METRICS = [
    metrics.Latency,
    metrics.Availability,
    metrics.Reliability,
    metrics.Throughput
  ]

  /**
   * Wraps _open with telemetry hooks setup
   * #_open
   * @param {Function} callback
   */
  TelemetryTransport.prototype._open = function (callback) {
    var self = this
    var metrics = this._telopts.metrics

    if (!metrics || metrics.length === 0) {
      this._telopts.metrics = TelemetryTransport.DEFAULT_METRICS
    }

    this._telopts.metrics.forEach(function (Metric) {
      var metric = new Metric()

      metric.hooks.forEach(function (hook) {
        self[hook.trigger](
          hook.event,
          hook.handler(metric, self.telemetry)
        )
      })
    })

    return Transport.prototype._open.call(this, callback)
  }

  return TelemetryTransport
}

module.exports = TransportDecorator