/*

* mechanic.js UIAutomation Library
* http://cozykozy.com/pages/mechanicjs
*
* version e9320027e6b5250ef72990b314238a62a1c2db0a (master build)
*
* Copyright (c) 2012 Jason Kozemczak
* mechanic.js may be freely distributed under the MIT license.
*
* Includes parts of Zepto.js
* Copyright 2010-2012, Thomas Fuchs
* Zepto.js may be freely distributed under the MIT license.
*/

var mechanic = (function() {

// Save a reference to the local target for convenience
var target = UIATarget.localTarget();

// Set the default timeout value to 0 to avoid making walking the object tree incredibly slow.
// Developers can adjust this value by calling $.timeout(duration)
target.setTimeout(0);

var app = target.frontMostApp(),
    window = app.mainWindow(),
    emptyArray = [],
    slice = emptyArray.slice

// Setup a map of UIAElement types to their "shortcut" selectors.
var typeShortcuts = {
    'UIAActionSheet' : ['actionsheet'],
    'UIAActivityIndicator' : ['activityIndicator'],
    'UIAAlert' : ['alert'],
    'UIAButton' : ['button'],
    'UIACollectionCell' : ['collectionCell'],
    'UIACollectionView' : ['collection'],
    'UIAEditingMenu': ['editingMenu'],
    'UIAElement' : ['\\*'], // TODO: sort of a hack
    'UIAImage' : ['image'],
    'UIAKey' : ['key'],
    'UIAKeyboard' : ['keyboard'],
    'UIALink' : ['link'],
    'UIAPageIndicator' : ['pageIndicator'],
    'UIAPicker' : ['picker'],
    'UIAPickerWheel' : ['pickerwheel'],
    'UIAPopover' : ['popover'],
    'UIAProgressIndicator' : ['progress'],
    'UIAScrollView' : ['scrollview'],
    'UIASearchBar' : ['searchbar'],
    'UIASecureTextField' : ['secure'],
    'UIASegmentedControl' : ['segmented'],
    'UIASlider' : ['slider'],
    'UIAStaticText' : ['text'],
    'UIAStatusBar' : ['statusbar'],
    'UIASwitch' : ['switch'],
    'UIATabBar' : ['tabbar'],
    'UIATableView' : ['tableview'],
    'UIATableCell' : ['cell', 'tableCell'],
    'UIATableGroup' : ['group'],
    'UIATextField' : ['textfield'],
    'UIATextView' : ['textview'],
    'UIAToolbar' : ['toolbar'],
    'UIAWebView' : ['webview'],
    'UIAWindow' : ['window'],
    'UIANavigationBar': ['navigationBar']
};

// Build a RegExp for picking out type selectors.
var typeSelectorREString = (function() {
    var key;
    var typeSelectorREString = "\\";
    for (key in typeShortcuts) {
        typeSelectorREString += key + "|";
        typeShortcuts[key].forEach(function(shortcut) { typeSelectorREString += shortcut + "|"; });
    }
    return typeSelectorREString.substr(1, typeSelectorREString.length - 2);
})();

var patternName = "[^,\\[\\]]+"

var selectorPatterns = {
  simple:          (new RegExp("^#("+patternName+")$"))
 ,byType:          (new RegExp("^("+typeSelectorREString+")$"))
 ,byAttr:          (new RegExp("^\\[(\\w+)=("+patternName+")\\]$"))
 ,byTypeAndAttr:   (new RegExp("^("+typeSelectorREString+")\\[(\\w+)=("+patternName+")\\]$"))
 ,children:        (new RegExp("^(.*) > (.*)$"))
 ,descendents:     (new RegExp("^(.+) +(.+)$"))
}

var searches = {
  simple:          function(name)         { return this.getElementsByName(name)          }
 ,byType:          function(type)         { return this.getElementsByType(type)          }
 ,byAttr:          function(attr,value)   { return this.getElementsByAttr(attr,value)    }
 ,byTypeAndAttr:   function(type,a,v)     { return $(type, this).filter('['+a+'='+v+']') }
 ,children:        function(parent,child) { return $(parent, this).children().filter(child) }
 ,descendents:     function(parent,child) { return $(child, $(parent, this))             }
}

var filters = {
   simple:        function(name)      { return this.name() == name                       }
  ,byType:        function(type)      { return this.isType(type)                         }
  ,byAttr:        function(attr,value){ return this[attr] && this[attr]() == value       }
  ,byTypeAndAttr: function(type,a,v ) { return this.isType(type) && this[a]() == v  }
}

function Z(dom, selector){
    dom = dom || emptyArray;
    dom.__proto__ = Z.prototype;
    dom.selector = selector || '';
    if (dom === emptyArray) {
        UIALogger.logWarning("element " + selector + " have not been found");
    }
    return dom;
}

function $(selector, context) {
    if (!selector) return Z();
    if (context !== undefined) return $(context).find(selector);
    else if (selector instanceof Z) return selector;
    else {
        var dom;
        if (isA(selector)) dom = compact(selector);
        else if (selector instanceof UIAElement) dom = [selector];
        else dom = $$(app, selector);
        return Z(dom, selector);
    }
}

$.qsa = $$ = function(element, selector) {
    var ret = [],
        groups = selector.split(/ *, */),
        matches
    $.each(groups, function() {
      for (type in searches) {
        if (matches = this.match(selectorPatterns[type])) {
          matches.shift()
          ret = ret.concat($(searches[type].apply(element, matches)))
          break
        }
      }
    })
    return $(ret)
};

// Add functions to UIAElement to make object graph searching easier.
UIAElement.prototype.getElementsByName = function(name) {
    return this.getElementsByAttr('name', name)
};

UIAElement.prototype.getElementsByAttr = function(attr, value) {
    return $.map(this.elements(), function(el) {
        var matches = el.getElementsByAttr(attr, value),
            val = el[attr]
        if (typeof val == 'function') val = val.apply(el)
        if (typeof val != 'undefined' && val == value)
          matches.push(el)
        return matches
    })
}
UIAElement.prototype.getElementsByType = function(type) {
    return $.map(this.elements(), function(el) {
        var matches = el.getElementsByType(type);
        if (el.isType(type)) matches.unshift(el);
        return matches;
    });
};
UIAElement.prototype.isType = function(type) {
    var thisType = this.toString().split(" ")[1];
    thisType = thisType.substr(0, thisType.length - 1);
    if (type === thisType) return true;
    else if (typeShortcuts[thisType] !== undefined && typeShortcuts[thisType].indexOf(type) >= 0) return true;
    else if (type === '*' || type === 'UIAElement') return true;
    else return false;
};

function isF(value) { return ({}).toString.call(value) == "[object Function]"; }
function isO(value) { return value instanceof Object; }
function isA(value) { return value instanceof Array; }
function likeArray(obj) { return typeof obj.length == 'number'; }

function compact(array) { return array.filter(function(item){ return item !== undefined && item !== null; }); }
function flatten(array) { return array.length > 0 ? [].concat.apply([], array) : array; }

function uniq(array) { return array.filter(function(item,index,array){ return array.indexOf(item) == index; }); }

function filtered(elements, selector) {
    return selector === undefined ? $(elements) : $(elements).filter(selector);
}

$.extend = function(target){
    var key;
    slice.call(arguments, 1).forEach(function(source) {
        for (key in source) target[key] = source[key];
    });
    return target;
};

$.inArray = function(elem, array, i) {
    return emptyArray.indexOf.call(array, elem, i);
};

$.map = function(elements, callback) {
    var value, values = [], i, key;
    if (likeArray(elements)) {
        for (i = 0; i < elements.length; i++) {
            value = callback(elements[i], i);
            if (value != null) values.push(value);
        }
    } else {
        for (key in elements) {
            value = callback(elements[key], key);
            if (value != null) values.push(value);
        }
    }
    return flatten(values);
};

$.each = function(elements, callback) {
    var i, key;
    if (likeArray(elements)) {
        for(i = 0; i < elements.length; i++) {
            if(callback.call(elements[i], i, elements[i]) === false) return elements;
        }
    } else {
        for(key in elements) {
            if(callback.call(elements[key], key, elements[key]) === false) return elements;
        }
    }
    return elements;
};

$.fn = {
    forEach: emptyArray.forEach,
    reduce: emptyArray.reduce,
    push: emptyArray.push,
    indexOf: emptyArray.indexOf,
    concat: emptyArray.concat,
    map: function(fn){
        return $.map(this, function(el, i){ return fn.call(el, i, el); });
    },
    slice: function(){
        return $(slice.apply(this, arguments));
    },
    get: function(idx){ return idx === undefined ? slice.call(this) : this[idx]; },
    size: function(){ return this.length; },
    each: function(callback) {
        this.forEach(function(el, idx){ callback.call(el, idx, el); });
        return this;
    },
    filter: function(selector) {
      var matches
      for (type in filters) {
        if (matches = selector.match(selectorPatterns[type])) {
          matches.shift() // remove the original string, we only want the capture groups
          return $.map(this, function(e) {
            return filters[type].apply(e, matches) ? e : null
          })
        }
      }
    },
    end: function(){
        return this.prevObject || $();
    },
    andSelf:function(){
        return this.add(this.prevObject || $());
    },
    add:function(selector,context){
        return $(uniq(this.concat($(selector,context))));
    },
    is: function(selector){
        return this.length > 0 && $(this[0]).filter(selector).length > 0;
    },
    not: function(selector){
        var nodes=[];
        if (isF(selector) && selector.call !== undefined)
            this.each(function(idx){
                if (!selector.call(this,idx)) nodes.push(this);
            });
        else {
            var excludes = typeof selector == 'string' ? this.filter(selector) :
                    (likeArray(selector) && isF(selector.item)) ? slice.call(selector) : $(selector);
            this.forEach(function(el){
                if (excludes.indexOf(el) < 0) nodes.push(el);
            });
        }
        return $(nodes);
    },
    eq: function(idx){
        return idx === -1 ? this.slice(idx) : this.slice(idx, + idx + 1);
    },
    first: function(){ var el = this[0]; return el && !isO(el) ? el : $(el); },
    last: function(){ var el = this[this.length - 1]; return el && !isO(el) ? el : $(el); },
    find: function(selector) {
        var result;
        if (this.length == 1) result = $$(this[0], selector);
        else result = this.map(function(){ return $$(this, selector); });
        return $(result);
    },
    predicate: function(predicate) {
        return this.map(function(el, idx) {
            if (typeof predicate == 'string') return el.withPredicate(predicate);
            else return null;
        });
    },
    valueForKey: function(key, value) {
        var result = this.map(function(idx, el) {
            if (key in el && el[key]() == value) {
                return el;
            }
            return null;
        });
        return $(result);
    },
    valueInKey: function(key, val) {
        var result = this.map(function(idx, el) {
            if (key in el) {
                var elKey = el[key]();
                if (elKey === null) {
                    return null;
                }
                // make this a case insensitive search
                elKey = elKey.toString().toLowerCase();
                val = val.toString().toLowerCase();

                if (elKey.indexOf(val) !== -1) {
                    return el;
                }
            }
            return null;
        });
        return $(result);
    },
    closest: function(selector, context) {
        var el = this[0], candidates = $$(context || app, selector);
        if (!candidates.length) el = null;
        while (el && candidates.indexOf(el) < 0)
            el = el !== context && el !== app && el.parent();
        return $(el);
    },
    ancestry: function(selector) {
        var ancestors = [], elements = this;
        while (elements.length > 0)
            elements = $.map(elements, function(node){
                if ((node = node.parent()) && !node.isType('UIAApplication') && ancestors.indexOf(node) < 0) {
                    ancestors.push(node);
                    return node;
                }
            });
        return filtered(ancestors, selector);
    },
    parent: function(selector) {
        return filtered(uniq(this.map(function() { return this.parent(); })), selector);
    },
    children: function(selector) {
        return filtered(this.map(function(){ return slice.call(this.elements()); }), selector);
    },
    siblings: function(selector) {
        return filtered(this.map(function(i, el) {
            return slice.call(el.parent().elements()).filter(function(child){ return child!==el; });
        }), selector);
    },
    next: function(selector) {
        return filtered(this.map(function() {
            var els = this.parent().elements().toArray();
            return els[els.indexOf(this) + 1];
        }), selector);
    },
    prev: function(selector) {
        return filtered(this.map(function() {
            var els = this.parent().elements().toArray();
            return els[els.indexOf(this) - 1];
        }), selector);
    },
    index: function(element) {
        return element ? this.indexOf($(element)[0]) : this.parent().elements().toArray().indexOf(this[0]);
    },
    pluck: function(property) {
        return this.map(function() {
            if (typeof this[property] == 'function') return this[property]();
            else return this[property];
        });
    }
};

'filter,add,not,eq,first,last,find,closest,parents,parent,children,siblings'.split(',').forEach(function(property) {
    var fn = $.fn[property];
    $.fn[property] = function() {
        var ret = fn.apply(this, arguments);
        ret.prevObject = this;
        return ret;
    };
});

Z.prototype = $.fn;
return $;

})();

var $ = $ || mechanic; // expose $ shortcut // mechanic.js // Copyright © 2012 Jason Kozemczak // mechanic.js may be freely distributed under the MIT license.

(function($) {

$.extend($, {
    log: function(s, level) {
        level = level || 'message';
        if (level === 'error') $.error(s);
        else if (level === 'warn') $.warn(s);
        else if (level === 'debug') $.debug(s);
        else $.message(s);
    },
    error: function(s) { UIALogger.logError(s); },
    warn: function(s) { UIALogger.logWarning(s); },
    debug: function(s) { UIALogger.logDebug(s); },
    message: function(s) { UIALogger.logMessage(s); },
    capture: function(imageName, rect) {
                    var target = UIATarget.localTarget();
        imageName = imageName || new Date().toString();
        if (rect) target.captureRectWithName(rect, imageName);
        else target.captureScreenWithName(imageName);
    }
});

$.extend($.fn, {
    log: function() { return this.each(function() { this.logElement(); }); },
    logTree: function () { return this.each(function() { this.logElementTree(); }); },
    capture: function(imageName) {
        imageName = imageName || new Date().toString();
        return this.each(function() { $.capture(imageName + '-' + this.name(), this.rect()); });
    }
});

})(mechanic); // mechanic.js // Copyright © 2012 Jason Kozemczak // mechanic.js may be freely distributed under the MIT license.

(function($) {

var app = UIATarget.localTarget().frontMostApp();
$.extend($.fn, {
    name: function() { return (this.length > 0) ? this[0].name() : null; },
    label: function() { return (this.length > 0) ? this[0].label() : null; },
    value: function() { return (this.length > 0) ? this[0].value() : null; },
    isFocused: function() { return (this.length > 0) ? this[0].hasKeyboardFocus() : false; },
    isEnabled: function() { return (this.length > 0) ? this[0].isEnabled() : false; },
    isVisible: function() { return (this.length > 0) ? this[0].isVisible() : false; },
    isValid: function(certain) {
        if (this.length != 1) return false;
        else if (certain) return this[0].checkIsValid();
        else return this[0].isValid();
    }
});

$.extend($, {
    version: function() {
        return app.version();
    },
    bundleID: function()  {
        return app.bundleID();
    },
    prefs: function(prefsOrKey) {
        // TODO: should we handle no-arg version that returns all prefs???
        if (typeof prefsOrKey == 'string') return app.preferencesValueForKey(prefsOrKey);
        else {
            $.each(prefsOrKey, function(key, val) {
                app.setPreferencesValueForKey(val, key);
            });
        }
    }
});

})(mechanic); // mechanic.js // Copyright © 2012 Jason Kozemczak // mechanic.js may be freely distributed under the MIT license.

(function($) {

var target = UIATarget.localTarget();
$.extend($, {
    timeout: function(duration) { target.setTimeout(duration); },
    delay: function(seconds) { target.delay(seconds); },
    cmd: function(path, args, timeout) { target.host().performTaskWithPathArgumentsTimeout(path, args, timeout); },
    orientation: function(orientation) {
        if (orientation === undefined || orientation === null) return target.deviceOrientation();
        else target.setDeviceOrientation(orientation);
    },
    location: function(coordinates, options) {
        options = options || {};
        target.setLocationWithOptions(coordinates, options);
    },
    shake: function() { target.shake(); },
    rotate: function(options) { target.rotateWithOptions(options); },
    pinchScreen: function(options) {
        if (!options.style) options.style = 'open';
        if (options.style === 'close') target.pinchCloseFromToForDuration(options.from, options.to, options.duration);
        else target.pinchOpenFromToForDuration(options.from, options.to, options.duration);
    },
    drag: function(options) { target.dragFromToForDuration(options.from, options.to, options.duration); },
    flick: function(options) { target.flickFromTo(options.from, options.to); },
    lock: function(duration) { target.lockForDuration(duration); },
    backgroundApp: function(duration) { target.deactivateAppForDuration(duration); },
    volume: function(direction, duration) {
        if (direction === 'up') {
            if (duration) target.holdVolumeUp(duration);
            else target.clickVolumeUp();
        } else {
            if (duration) target.holdVolumeDown(duration);
            else target.clickVolumeDown();
        }
    },
    input: function(s) {
        target.frontMostApp().keyboard().typeString(s);
    }
});

$.extend($.fn, {
    tap: function(options) {
        options = options || {};
        return this.each(function() {
            // TODO: tapWithOptions supports most of the behavior of doubleTap/twoFingerTap looking at the API, do we need to support these methods??
            if (options.style === 'double') this.doubleTap();
            else if (options.style === 'twoFinger') this.twoFingerTap();
            else this.tapWithOptions(options);
        });
    },
    touch: function(duration) {
        return this.each(function() { this.touchAndHold(duration); });
    },
    dragInside: function(options) {
        return this.each(function() { this.dragInsideWithOptions(options); });
    },
    flick: function(options) {
        return this.each(function() { this.flickInsideWithOptions(options); });
    },
    rotate: function(options) {
        return this.each(function() { this.rotateWithOptions(options); });
    },
    scrollToVisible: function() {
        if (this.length > 0) this[0].scrollToVisible();
        return this;
    },
    input: function(s) {
        if (this.length > 0) {
            this[0].tap();
            $.input(s);
        }
    }
});

'delay,cmd,orientation,location,shake,pinchScreen,drag,lock,backgroundApp,volume'.split(',').forEach(function(property) {
    var fn = $[property];
    $.fn[property] = function() {
        fn.apply($, arguments);
        return this;
    };
});

})(mechanic);