//
//  mraid.js
//  MRAID
//
//  Created by Jay Tucker on 9/16/13.
//  Copyright (c) 2013 Nexage, Inc. All rights reserved.
//
(function() {

/***************************************************************************
 * console logging helper
 **************************************************************************/

var console = {};
console.log = function(msg) {
 if (typeof enableLog != 'undefined') {
    var iframe = document.createElement("IFRAME");
    iframe.setAttribute("src", "console-log://" + msg);
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
 }
};

LogLevelEnum = {
    "DEBUG"   : 0,
    "INFO"    : 1,
    "WARNING" : 2,
    "ERROR"   : 3
}

var logLevel = LogLevelEnum.DEBUG;
var log = {};

log.d = function(msg) {
    if (logLevel <= LogLevelEnum.DEBUG) {
        console.log("(D) " + msg);
    }
}

log.i = function(msg) {
    if (logLevel <= LogLevelEnum.INFO) {
        console.log("(I) " + msg);
    }
}

log.w = function(msg) {
    if (logLevel <= LogLevelEnum.WARN) {
        console.log("(W) " + msg);
    }
}

log.e = function(msg) {
    console.log("(E) " + msg);
}

/***************************************************************************
 * MRAID declaration
 **************************************************************************/

log.i("Setting up mraid object");

var mraid = window.mraid = {};

/***************************************************************************
 * constants
 **************************************************************************/

var VERSION = "2.0";

var STATES = mraid.STATES = {
    "LOADING" : "loading",
    "DEFAULT" : "default",
    "EXPANDED" : "expanded",
    "RESIZED" : "resized",
    "HIDDEN" : "hidden"
};

var PLACEMENT_TYPES = mraid.PLACEMENT_TYPES = {
    "INLINE" : "inline",
    "INTERSTITIAL" : "interstitial"
};

var RESIZE_PROPERTIES_CUSTOM_CLOSE_POSITION = mraid.RESIZE_PROPERTIES_CUSTOM_CLOSE_POSITION = {
    "TOP_LEFT" : "top-left",
    "TOP_CENTER" : "top-center",
    "TOP_RIGHT" : "top-right",
    "CENTER" : "center",
    "BOTTOM_LEFT" : "bottom-left",
    "BOTTOM_CENTER" : "bottom-center",
    "BOTTOM_RIGHT" : "bottom-right"
};

var ORIENTATION_PROPERTIES_FORCE_ORIENTATION = mraid.ORIENTATION_PROPERTIES_FORCE_ORIENTATION = {
    "PORTRAIT" : "portrait",
    "LANDSCAPE" : "landscape",
    "NONE" : "none"
};

var EVENTS = mraid.EVENTS = {
    "ERROR" : "error",
    "READY" : "ready",
    "SIZECHANGE" : "sizeChange",
    "STATECHANGE" : "stateChange",
    "VIEWABLECHANGE" : "viewableChange",

    "SHAKE" : "shake",
    "TILTCHANGE" : 'tiltChange',
    "HEADINGCHANGE" : 'headingChange',
    "NETWORKCHANGE" : 'networkChange',
    "LOCATIONCHANGE" : 'locationChange',
    "KEYBOARDSTATECHANGE" : 'keyboardStateChange'
};

var SUPPORTED_FEATURES = mraid.SUPPORTED_FEATURES = {
    "SMS" : "sms",
    "TEL" : "tel",
    "CALENDAR" : "calendar",
    "STOREPICTURE" : "storePicture",
    "INLINEVIDEO" : "inlineVideo"
};

/***************************************************************************
 * state
 **************************************************************************/

var state = STATES.LOADING;
var placementType = PLACEMENT_TYPES.INLINE;
var supportedFeatures = {};
var isViewable = false;
var isResizeReady = false;

var expandProperties = {
    "width" : 0,
    "height" : 0,
    "useCustomClose" : false,
    "isModal" : true
};

var orientationProperties = {
    "allowOrientationChange" : true,
    "forceOrientation" : ORIENTATION_PROPERTIES_FORCE_ORIENTATION.NONE
};

var resizeProperties = {
    "width" : 0,
    "height" : 0,
    "customClosePosition" : RESIZE_PROPERTIES_CUSTOM_CLOSE_POSITION.TOP_RIGHT,
    "offsetX" : 0,
    "offsetY" : 0,
    "allowOffscreen" : true
};

var currentPosition = {
    "x" : 0,
    "y" : 0,
    "width" : 0,
    "height" : 0
};

var defaultPosition = {
    "x" : 0,
    "y" : 0,
    "width" : 0,
    "height" : 0
};

var maxSize = {
    "width" : 0,
    "height" : 0
};

var screenSize = {
    "width" : 0,
    "height" : 0
};

var currentOrientation = 0;

var listeners = {};

/********************************
 * Introduced for Pokkt
 ********************************/
var shakeProperties = {
    "interval" : 0,
    "intensity" : 0
};

var tiltProperties = {
    "interval" : 0,
    "intensity" : 0
};

var headingProperties = {
    "interval" : 0,
    "intensity" : 0
};

var locationProperties = {
    "interval" : 0,
    "distance" : 0
};

var tiltValues = {
    "x" : 0,
    "y" : 0,
    "z" : 0
};

var currentLocation = {
    "latitude" : 0.0,
    "longitude" : 0.0,
    "accuracy" : 0.0
};

var headingValue = 0;
var currentNetwork = "";
var currentKeyboardState = 0;

/***************************************************************************
 * "official" API: methods called by creative
 **************************************************************************/

mraid.addEventListener = function(event, listener) {
    log.i("mraid.addEventListener " + event + ": " + String(listener));
    if (!event || !listener) {
        mraid.fireErrorEvent("Both event and listener are required.", "addEventListener");
        return;
    }
    if (!contains(event, EVENTS)) {
        mraid.fireErrorEvent("Unknown MRAID event: " + event, "addEventListener");
        return;
    }
    var listenersForEvent = listeners[event] = listeners[event] || [];
    // check to make sure that the listener isn't already registered
    for (var i = 0; i < listenersForEvent.length; i++) {
        var str1 = String(listener);
        var str2 = String(listenersForEvent[i]);
        if (listener === listenersForEvent[i] || str1 === str2) {
            log.i("listener " + str1 + " is already registered for event " + event);
            return;
        }
    }
    listenersForEvent.push(listener);
};

mraid.createCalendarEvent = function(parameters) {
    log.i("mraid.createCalendarEvent with " + parameters);
    callNative("createCalendarEvent?eventJSON="+JSON.stringify(parameters));
};

mraid.close = function() {
    log.i("mraid.close");
    if (state === STATES.LOADING ||
        (state === STATES.DEFAULT && placementType === PLACEMENT_TYPES.INLINE) ||
        state === STATES.HIDDEN) {
        // do nothing
        return;
    }
    callNative("close");
};

mraid.expand = function(url) {
    if (url === undefined) {
        log.i("mraid.expand (1-part)");
    } else {
        log.i("mraid.expand " + url);
    }
    // The only time it is valid to call expand is when the ad is
    // a banner currently in either default or resized state.
    if (placementType !== PLACEMENT_TYPES.INLINE ||
        (state !== STATES.DEFAULT && state !== STAES.RESIZED)) {
        return;
    }
    if (url === undefined) {
        callNative("expand");
    } else {
        callNative("expand?url=" + encodeURIComponent(url));
    }
};

mraid.getCurrentPosition = function() {
    log.i("mraid.getCurrentPosition");
    return currentPosition;
};

mraid.getDefaultPosition = function() {
    log.i("mraid.getDefaultPosition");
    return defaultPosition;
};

mraid.getExpandProperties = function() {
    log.i("mraid.getExpandProperties");
    return expandProperties;
};

mraid.getMaxSize = function() {
    log.i("mraid.getMaxSize " + maxSize.width + " x " + maxSize.height);
    return maxSize;
};

mraid.getOrientationProperties = function() {
    log.i("mraid.getOrientationProperties");
    return orientationProperties;
};

mraid.getPlacementType = function() {
    log.i("mraid.getPlacementType");
    return placementType;
};

mraid.getResizeProperties = function() {
    log.i("mraid.getResizeProperties");
    return resizeProperties;
};

mraid.getScreenSize = function() {
    log.i("mraid.getScreenSize");
    return screenSize;
};

mraid.getState = function() {
    log.i("mraid.getState");
    return state;
};

mraid.getVersion = function() {
    log.i("mraid.getVersion");
    return VERSION;
};

mraid.isViewable = function() {
    log.i("mraid.isViewable");
    return isViewable;
};

mraid.open = function(url) {
    log.i("mraid.open " + url);
    callNative("open?url=" + encodeURIComponent(url));
};

mraid.playVideo = function(url) {
    log.i("mraid.playVideo " + url);
    callNative("playVideo?url=" + encodeURIComponent(url));
};

mraid.playAudio = function(url) {
    log.i("mraid.playAudio " + url);
    callNative("playAudio?url=" + encodeURIComponent(url));
};

mraid.openCamera = function () {
    log.i("mraid.openCamera");
    callNative("openCamera");
}

mraid.removeEventListener = function(event, listener) {
    log.i("mraid.removeEventListener " + event + " : " + String(listener));
    if (!event) {
        mraid.fireErrorEvent("Event is required.", "removeEventListener");
        return;
    }
    if (!contains(event, EVENTS)) {
        mraid.fireErrorEvent("Unknown MRAID event: " + event, "removeEventListener");
        return;
    }
    if (listeners.hasOwnProperty(event)) {
        if (listener) {
            var listenersForEvent = listeners[event];
            // try to find the given listener
            var len = listenersForEvent.length;
            for (var i = 0; i < len; i++) {
                var registeredListener = listenersForEvent[i];
                var str1 = String(listener);
                var str2 = String(registeredListener);
                if (listener === registeredListener || str1 === str2) {
                    listenersForEvent.splice(i, 1);
                    break;
                }
            }
            if (i === len) {
                log.i("listener " + str1 + " not found for event " + event);
            }
            if (listenersForEvent.length === 0) {
                delete listeners[event];
            }
        } else {
            // no listener to remove was provided, so remove all listeners for given event
            delete listeners[event];
        }
    } else {
        log.i("no listeners registered for event " + event);
    }
};

mraid.resize = function() {
    log.i("mraid.resize");
    // The only time it is valid to call resize is when the ad is
    // a banner currently in either default or resized state.
    // Trigger an error if the current state is expanded.
    if (placementType === PLACEMENT_TYPES.INTERSTITIAL || state === STATES.LOADING || state === STATES.HIDDEN) {
        // do nothing
        return;
    }
    if (state === STATES.EXPANDED) {
        mraid.fireErrorEvent("mraid.resize called when ad is in expanded state", "mraid.resize");
        return;
    }
    if (!isResizeReady) {
        mraid.fireErrorEvent("mraid.resize is not ready to be called", "mraid.resize");
        return;
    }
    callNative("resize");
};

mraid.setExpandProperties = function(properties) {
    log.i("mraid.setExpandProperties");
    
    if (!validate(properties, "setExpandProperties")) {
        log.e("failed validation");
        return;
	}
    
    var oldUseCustomClose = expandProperties.useCustomClose;
    
    // expandProperties contains 3 read-write properties: width, height, and useCustomClose;
    // the isModal property is read-only
    var rwProps = [ "width", "height", "useCustomClose" ];
    for (var i = 0; i < rwProps.length; i++) {
        var propname = rwProps[i];
        if (properties.hasOwnProperty(propname)) {
            expandProperties[propname] = properties[propname];
        }
    }
    
    // In MRAID v2.0, all expanded ads by definition cover the entire screen,
    // so the only property that the native side has to know about is useCustomClose.
    // (That is, the width and height properties are not needed by the native code.)
    if (expandProperties.useCustomClose !== oldUseCustomClose) {
        callNative("useCustomClose?useCustomClose=" + expandProperties.useCustomClose);
    }
};

mraid.setOrientationProperties = function(properties) {
    log.i("mraid.setOrientationProperties");
    
    if (!validate(properties, "setOrientationProperties")) {
        log.e("failed validation");
        return;
	}
    
    var newOrientationProperties = {};
    newOrientationProperties.allowOrientationChange = orientationProperties.allowOrientationChange,
    newOrientationProperties.forceOrientation = orientationProperties.forceOrientation;
    
    // orientationProperties contains 2 read-write properties: allowOrientationChange and forceOrientation
    var rwProps = [ "allowOrientationChange", "forceOrientation" ];
    for (var i = 0; i < rwProps.length; i++) {
        var propname = rwProps[i];
        if (properties.hasOwnProperty(propname)) {
            newOrientationProperties[propname] = properties[propname];
        }
    }
    
    // Setting allowOrientationChange to true while setting forceOrientation to either portrait or landscape
    // is considered an error condition.
    if (newOrientationProperties.allowOrientationChange &&
        newOrientationProperties.forceOrientation !== mraid.ORIENTATION_PROPERTIES_FORCE_ORIENTATION.NONE) {
        mraid.fireErrorEvent("allowOrientationChange is true but forceOrientation is " + newOrientationProperties.forceOrientation,
                             "setOrientationProperties");
        return;
    }
    
    orientationProperties.allowOrientationChange = newOrientationProperties.allowOrientationChange;
    orientationProperties.forceOrientation = newOrientationProperties.forceOrientation;
    
    var params =
    "allowOrientationChange=" + orientationProperties.allowOrientationChange +
    "&forceOrientation=" + orientationProperties.forceOrientation;
    
    callNative("setOrientationProperties?" + params);
};
 
mraid.setResizeProperties = function(properties) {
    log.i("mraid.setResizeProperties");
 
    isResizeReady = false;
 
    // resizeProperties contains 6 read-write properties:
    // width, height, offsetX, offsetY, customClosePosition, allowOffscreen
 
    // The properties object passed into this function must contain width, height, offsetX, offsetY.
    // The remaining two properties are optional.
    var requiredProps = [ "width", "height", "offsetX", "offsetY" ];
    for (var i = 0; i < requiredProps.length; i++) {
        var propname = requiredProps[i];
        if (!properties.hasOwnProperty(propname)) {
            mraid.fireErrorEvent(
                      "required property " + propname + " is missing",
                      "mraid.setResizeProperties");
            return;
        }
    }
 
    if (!validate(properties, "setResizeProperties")) {
        mraid.fireErrorEvent("failed validation", "mraid.setResizeProperties");
        return;
    }
 
    var adjustments = { "x": 0, "y": 0 };
 
    var allowOffscreen = properties.hasOwnProperty("allowOffscreen") ? properties.allowOffscreen : resizeProperties.allowOffscreen;
    if (!allowOffscreen) {
        if (properties.width > maxSize.width || properties.height > maxSize.height) {
            mraid.fireErrorEvent("resize width or height is greater than the maxSize width or height", "mraid.setResizeProperties");
            return;
        }
        adjustments = fitResizeViewOnScreen(properties);
    } else if (!isCloseRegionOnScreen(properties)) {
        mraid.fireErrorEvent("close event region will not appear entirely onscreen", "mraid.setResizeProperties");
        return;
    }
 
    var rwProps = [ "width", "height", "offsetX", "offsetY", "customClosePosition", "allowOffscreen" ];
    for (var i = 0; i < rwProps.length; i++) {
        var propname = rwProps[i];
        if (properties.hasOwnProperty(propname)) {
            resizeProperties[propname] = properties[propname];
        }
    }
 
    var params =
    "width=" + resizeProperties.width +
    "&height=" + resizeProperties.height +
    "&offsetX=" + (resizeProperties.offsetX + adjustments.x) +
    "&offsetY=" + (resizeProperties.offsetY + adjustments.y) +
    "&customClosePosition=" + resizeProperties.customClosePosition +
    "&allowOffscreen=" + resizeProperties.allowOffscreen;
 
    callNative("setResizeProperties?" + params);
 
    isResizeReady = true;
 };

mraid.storePicture = function(url) {
    log.i("mraid.storePicture " + url);
    callNative("storePicture?url=" + encodeURIComponent(url));
};

mraid.supports = function(feature) {
    log.i("mraid.supports " + feature + " " + supportedFeatures[feature]);
    var retval = supportedFeatures[feature];
    if (typeof retval === "undefined") {
        retval = false;
    }
    return retval;
};

mraid.useCustomClose = function(isCustomClose) {
    log.i("mraid.useCustomClose " + isCustomClose);
    if (expandProperties.useCustomClose !== isCustomClose) {
        expandProperties.useCustomClose = isCustomClose;
        callNative("useCustomClose?useCustomClose=" + expandProperties.useCustomClose);
    }
};

/***************************************************************************
 * helper methods called by SDK
 **************************************************************************/

// setters to change state

mraid.setCurrentPosition = function(x, y, width, height) {
    log.i("mraid.setCurrentPosition " + x + "," + y + "," + width + "," + height);
    
    var previousSize = {};
    previousSize.width = currentPosition.width;
    previousSize.height = currentPosition.height;
    log.i("previousSize " + previousSize.width + "," + previousSize.height);
    
    currentPosition.x = x;
    currentPosition.y = y;
    currentPosition.width = width;
    currentPosition.height = height;
    
    if (width !== previousSize.width || height !== previousSize.height) {
        mraid.fireSizeChangeEvent(width, height);
    }
};

mraid.setDefaultPosition = function(x, y, width, height) {
    log.i("mraid.setDefaultPosition " + x + "," + y + "," + width + "," + height);
    defaultPosition.x = x;
    defaultPosition.y = y;
    defaultPosition.width = width;
    defaultPosition.height = height;
};

mraid.setExpandSize = function(width, height) {
    log.i("mraid.setExpandSize " + width + "x" + height);
    expandProperties.width = width;
    expandProperties.height = height;
};

mraid.setMaxSize = function(width, height) {
    log.i("mraid.setMaxSize " + width + "x" + height);
    maxSize.width = width;
    maxSize.height = height;
};

mraid.setPlacementType = function(pt) {
    log.i("mraid.setPlacementType " + pt);
    placementType = pt;
};

mraid.setScreenSize = function(width, height) {
    log.i("mraid.setScreenSize " + width + "x" + height);
    screenSize.width = width;
    screenSize.height = height;
};

mraid.setSupports = function(feature, supported) {
    log.i("mraid.setSupports " + feature + " " + supported);
    supportedFeatures[feature] = supported;
};

// methods to fire events

mraid.fireErrorEvent = function(message, action) {
    log.i("mraid.fireErrorEvent " + message + " " + action);
    fireEvent(mraid.EVENTS.ERROR, message, action);
};

mraid.fireReadyEvent = function() {
    log.i("mraid.fireReadyEvent");
    fireEvent(mraid.EVENTS.READY);
};

mraid.fireSizeChangeEvent = function(width, height) {
    log.i("mraid.fireSizeChangeEvent " + width + "x" + height);
    fireEvent(mraid.EVENTS.SIZECHANGE, width, height);
};

mraid.fireStateChangeEvent = function(newState) {
    log.i("mraid.fireStateChangeEvent " + newState);
    if (state !== newState) {
        state = newState;
        fireEvent(mraid.EVENTS.STATECHANGE, state);
    }
};

mraid.fireViewableChangeEvent = function(newIsViewable) {
    log.i("mraid.fireViewableChangeEvent " + newIsViewable);
    if (isViewable !== newIsViewable) {
        isViewable = newIsViewable;
        fireEvent(mraid.EVENTS.VIEWABLECHANGE, isViewable);
    }
};

/**
 * Pokkt's extended methods
 **/
mraid.setShakeProperties = function(properties) {
    log.i("mraid.setShakeProperties: " + properties);
    if (!isNaN(properties.interval) && !isNaN(properties.intensity)) {
        shakeProperties = properties;
        callNative("setShakeProperties?properties=" + JSON.stringify(properties));
    }
};

mraid.getShakeProperties = function() {
    return shakeProperties;
};

mraid.setTiltProperties = function(properties) {
    log.i("mraid.setTiltProperties: " + properties);
    if (!isNaN(properties.interval) && !isNaN(properties.intensity)) {
        headingProperties = properties;
        callNative("setTiltProperties?properties=" + JSON.stringify(properties));
    }
};

mraid.getTiltProperties = function() {
    return tiltProperties;
}

mraid.setHeadingProperties = function(properties) {
    log.i("mraid.setHeadingProperties: " + properties);
    if (!isNaN(properties.interval) && !isNaN(properties.intensity)) {
        headingProperties = properties;
        callNative("setHeadingProperties?properties=" + JSON.stringify(properties));
    }
};

mraid.getHeadingProperties = function() {
    return headingProperties;
};

mraid.setLocationProperties = function (properties) {
    log.i("mraid.setLocationProperties: " + properties);
    if (!isNaN(properties.interval) && !isNaN(properties.distance)) {
        locationProperties = properties;
        callNative("setLocationProperties?properties=" + JSON.stringify(properties));
    }
};

mraid.getLocationProperties = function () {
    return locationProperties;
};

mraid.setTiltProperties = function(properties) {
    log.i("mraid.setTiltProperties: " + properties);
    callNative("setTiltProperties?properties=" + JSON.stringify(properties));
};

mraid.fireShakeEvent = function() {
    log.i("mraid.fireShakeEvent");
    fireEvent(mraid.EVENTS.SHAKE);
}

mraid.fireTiltChangeEvent = function(x, y, z) {
    log.i("mraid.fireTiltChangeEvent, x: " + x + " y: " + y + " z: " + z);
    fireEvent(mraid.EVENTS.TILTCHANGE, x, y, z);
}

mraid.fireHeadingChangeEvent = function(val) {
    log.i("mraid.fireHeadingChangeEvent, val: " + val);
    fireEvent(mraid.EVENTS.HEADINGCHANGE, val);
}

mraid.fireLocationChangeEvent = function(latitude, longitude, accuracy) {
    log.i("mraid.fireLocationChangeEvent, latitude: " + latitude + " longitude: " + longitude + " accuracy: " + accuracy);
    fireEvent(mraid.EVENTS.LOCATIONCHANGE, latitude, longitude, accuracy);
}

mraid.fireNetworkChangeEvent = function(network) {
    log.d("mraid.fireNetworkChangeEvent: " + network);
    fireEvent(mraid.EVENTS.NETWORKCHANGE, network);
}

mraid.fireKeyboardStateChangeEvent = function(state) {
    log.d("mraid.fireKeyboardStateChangeEvent: " + state);
    fireEvent(mraid.EVENTS.KEYBOARDSTATECHANGE, state);
}

mraid.getTilt = function () {
    return tiltValues;
};

mraid.setTilt = function (newValue) {
    tiltValues = newValue;
};

mraid.getNetwork = function () {
    return currentNetwork;
};

mraid.setNetwork = function (newValue) {
    currentNetwork = newValue;
};

mraid.getLocation = function () {
    return currentLocation;
};

mraid.setLocation = function (newValue) {
    currentLocation = newValue;
};

mraid.getHeading = function () {
    return headingValue;
};

mraid.setHeading = function (newValue) {
    headingValue = newValue;
};

mraid.getKeyboardState = function () {
    return currentKeyboardState;
}

mraid.setKeyboardState = function (newValue) {
    currentKeyboardState = newValue;
}

/***************************************************************************
 * internal helper methods
 **************************************************************************/

var callNative = function(command) {
    var iframe = document.createElement("IFRAME");
    iframe.setAttribute("src", "mraid://" + command);
    document.documentElement.appendChild(iframe);
    iframe.parentNode.removeChild(iframe);
    iframe = null;
};

var fireEvent = function(event) {
    var args = Array.prototype.slice.call(arguments);
    args.shift();
    log.i("fireEvent " + event + " [" + args.toString() + "]");
    var eventListeners = listeners[event];
    if (eventListeners) {
        var len = eventListeners.length;
        log.i(len + " listener(s) found");
        for (var i = 0; i < len; i++) {
            eventListeners[i].apply(null, args);
        }
    } else {
        log.i("no listeners found");
    }
};

var contains = function(value, array) {
    for (var i in array) {
        if (array[i] === value) {
            return true;
        }
    }
    return false;
};

// The action parameter is a string which is the name of the setter function which called this function
// (in other words, setExpandPropeties, setOrientationProperties, or setResizeProperties).
// It serves both as the key to get the the appropriate set of validating functions from the allValidators object
// as well as the action parameter of any error event that may be thrown.
var validate = function(properties, action) {
    var retval = true;
    var validators = allValidators[action];
    for (var prop in properties) {
        var validator = validators[prop];
        var value = properties[prop];
        if (validator && !validator(value)) {
            mraid.fireErrorEvent("Value of property " + prop + " (" + value + ") is invalid", "mraid." + action);
            retval = false;
        }
    }
    return retval;
};

var allValidators = {
    "setExpandProperties": {
        // In MRAID 2.0, the only property in expandProperties we actually care about is useCustomClose.
        // Still, we'll do a basic sanity check on the width and height properties, too.
        "width" : function(width) {
            return !isNaN(width);
        },
        "height" : function(height) {
            return !isNaN(height);
        },
        "useCustomClose" : function(useCustomClose) {
            return (typeof useCustomClose === "boolean");
        }
    },
    "setOrientationProperties": {
        "allowOrientationChange" : function(allowOrientationChange) {
            return (typeof allowOrientationChange === "boolean");
        },
        "forceOrientation" : function(forceOrientation) {
            var validValues = [ "portrait","landscape","none" ];
            return validValues.indexOf(forceOrientation) !== -1;
        }
    },
    "setResizeProperties": {
        "width" : function(width) {
            return !isNaN(width) && width >= 50;
        },
        "height" : function(height) {
            return !isNaN(height) && height >= 50;
        },
        "offsetX" : function(offsetX) {
            return !isNaN(offsetX);
        },
        "offsetY" : function(offsetY) {
            return !isNaN(offsetY);
        },
        "customClosePosition" : function(customClosePosition) {
            var validPositions = [ "top-left","top-center","top-right","center","bottom-left","bottom-center","bottom-right" ];
            return validPositions.indexOf(customClosePosition) !== -1;
        },
        "allowOffscreen" : function(allowOffscreen) {
            return (typeof allowOffscreen === "boolean");
        }
    }
};

function isCloseRegionOnScreen(properties) {
    log.d("isCloseRegionOnScreen");
    log.d("defaultPosition " + defaultPosition.x + " " + defaultPosition.y);
    log.d("offset " + properties.offsetX + " " + properties.offsetY);
 
    var resizeRect = {};
    resizeRect.x = defaultPosition.x + properties.offsetX;
    resizeRect.y = defaultPosition.y + properties.offsetY;
    resizeRect.width = properties.width;
    resizeRect.height = properties.height;
    printRect("resizeRect", resizeRect);
 
    var customClosePosition = properties.hasOwnProperty("customClosePosition") ?
    properties.customClosePosition : resizeProperties.customClosePosition;
    log.d("customClosePosition " + customClosePosition);
 
    var closeRect = { "width": 50, "height": 50 };
 
    if (customClosePosition.search("left") !== -1) {
        closeRect.x = resizeRect.x;
    } else if (customClosePosition.search("center") !== -1) {
        closeRect.x = resizeRect.x + (resizeRect.width / 2) - 25;
    } else if (customClosePosition.search("right") !== -1) {
        closeRect.x = resizeRect.x + resizeRect.width - 50;
    }
 
    if (customClosePosition.search("top") !== -1) {
        closeRect.y = resizeRect.y;
    } else if (customClosePosition === "center") {
        closeRect.y = resizeRect.y + (resizeRect.height / 2) - 25;
    } else if (customClosePosition.search("bottom") !== -1) {
        closeRect.y = resizeRect.y + resizeRect.height - 50;
    }
 
    var maxRect = { "x": 0, "y": 0 };
    maxRect.width = maxSize.width;
    maxRect.height = maxSize.height;
 
    return isRectContained(maxRect, closeRect);
}
 
function fitResizeViewOnScreen(properties) {
    log.d("fitResizeViewOnScreen");
    log.d("defaultPosition " + defaultPosition.x + " " + defaultPosition.y);
    log.d("offset " + properties.offsetX + " " + properties.offsetY);
 
    var resizeRect = {};
    resizeRect.x = defaultPosition.x + properties.offsetX;
    resizeRect.y = defaultPosition.y + properties.offsetY;
    resizeRect.width = properties.width;
    resizeRect.height = properties.height;
    printRect("resizeRect", resizeRect);
 
    var maxRect = { "x": 0, "y": 0 };
    maxRect.width = maxSize.width;
    maxRect.height = maxSize.height;
 
    var adjustments = { "x": 0, "y": 0 };
 
    if (isRectContained(maxRect, resizeRect)) {
        log.d("no adjustment necessary");
        return adjustments;
    }
 
    if (resizeRect.x < maxRect.x) {
        adjustments.x = maxRect.x - resizeRect.x;
    } else if ((resizeRect.x + resizeRect.width) > (maxRect.x + maxRect.width)) {
        adjustments.x = (maxRect.x + maxRect.width) - (resizeRect.x + resizeRect.width);
    }
    log.d("adjustments.x " + adjustments.x);
 
    if (resizeRect.y < maxRect.y) {
        adjustments.y = maxRect.y - resizeRect.y;
    } else if ((resizeRect.y + resizeRect.height) > (maxRect.y + maxRect.height)) {
        adjustments.y = (maxRect.y + maxRect.height) - (resizeRect.y + resizeRect.height);
    }
    log.d("adjustments.y " + adjustments.y);
 
    resizeRect.x = defaultPosition.x + properties.offsetX + adjustments.x;
    resizeRect.y = defaultPosition.y + properties.offsetY + adjustments.y;
    printRect("adjusted resizeRect", resizeRect);
 
    return adjustments;
}
 
function isRectContained(containingRect, containedRect) {
    log.d("isRectContained");
    printRect("containingRect", containingRect);
    printRect("containedRect", containedRect);
    return (containedRect.x >= containingRect.x &&
        (containedRect.x + containedRect.width) <= (containingRect.x + containingRect.width) &&
        containedRect.y >= containingRect.y &&
        (containedRect.y + containedRect.height) <= (containingRect.y + containingRect.height));
}
 
function printRect(label, rect) {
    log.d(label +
          " [" + rect.x + "," + rect.y + "]" +
          ",[" + (rect.x + rect.width) + "," + (rect.y + rect.height) + "]" +
          " (" + rect.width + "x" + rect.height + ")");
}
 
mraid.dumpListeners = function() {
    var nEvents = Object.keys(listeners).length
    log.i("dumping listeners (" + nEvents + " events)");
    for (var event in listeners) {
        var eventListeners = listeners[event];
        log.i("  " + event + " contains " + eventListeners.length + " listeners");
        for (var i = 0; i < eventListeners.length; i++) {
            log.i("    " +  eventListeners[i]);
        }
    }
};

 }());
