////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

var __CFVIEWPORT_ERROR_DIMENSIONS = "viewport dimensions are unavailable";
var __CFVIEWPORT_ERROR_SCROLL_OFFSETS =
    "viewport scroll offsets are unavailable";

var __CFVIEWPORT_RESIZE_INTERVAL = 200;
var __CFVIEWPORT_SCROLL_INTERVAL = 200;

var __CFVIEWPORT_SUPPORTS_DIMENSIONS = true;
var __CFVIEWPORT_SUPPORTS_SCROLL_OFFSETS = true;

var __CFVIEWPORT_WARNING_DIMENSIONS = "viewport dimensions unsupported";
var __CFVIEWPORT_WARNING_SCROLL_OFFSETS = "viewport scroll offsets unsupported";

////////////////////////////////////////////////////////////////////////////////
// Static Variables
////////////////////////////////////////////////////////////////////////////////

var __cfViewportLastDimensions = undefined;
var __cfViewportLastScrollOffsets = undefined;
var __cfViewportOnResizeHandlerSet = new CFEventHandlerSet();
var __cfViewportOnScrollHandlerSet = new CFEventHandlerSet();
var __cfViewportResizeTimer = undefined;
var __cfViewportScrollTimer = undefined;

////////////////////////////////////////////////////////////////////////////////
// Patches
////////////////////////////////////////////////////////////////////////////////

function __cfViewportGetDimensionsClient(ignoreError)
{
    var docBody = document.body;
    var docElement = document.documentElement;
    var height = docElement.clientHeight;
    if (! height) {
        var bodyHeight = docBody.clientHeight;
        if (typeof(bodyHeight) == "number") {
            height = bodyHeight;
        }
    }
    var width = docElement.clientWidth;
    if (! width) {
        var bodyWidth = docBody.clientWidth;
        if (typeof(bodyWidth) == "number") {
            width = bodyWidth;
        }
    }
    if ((typeof(height) != "number") || (typeof(width) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("__cfViewportGetDimensionsClient: " +
                              __CFVIEWPORT_ERROR_DIMENSIONS);
    }
    return {height: height, width: width};
}

function __cfViewportGetDimensionsInner(ignoreError)
{
    var height = self.innerHeight;
    var width = self.innerWidth;
    if ((typeof(height) != "number") || (typeof(width) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("__cfViewportGetDimensionsInner: " +
                              __CFVIEWPORT_ERROR_DIMENSIONS);
    }
    return {height: height, width: width};
}

function __cfViewportGetDimensionsUndefined(ignoreError)
{
    if (ignoreError) {
        return undefined;
    }
    return cfErrorTrigger("__cfViewportGetDimensionsUndefined: " +
                          __CFVIEWPORT_ERROR_DIMENSIONS);
}

function __cfViewportGetScrollOffsets(ignoreError)
{
    var docBody = document.body;
    var docElement = document.documentElement;
    var x = docElement.scrollLeft;
    if (! x) {
        var bodyX = docBody.scrollLeft;
        if (typeof(bodyX) == "number") {
            x = bodyX;
        }
    }
    var y = docElement.scrollTop;
    if (! y) {
        var bodyY = docBody.scrollTop;
        if (typeof(bodyY) == "number") {
            y = bodyY;
        }
    }
    if ((typeof(x) != "number") || (typeof(y) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("__cfViewportGetScrollOffsets: " +
                              __CFVIEWPORT_ERROR_SCROLL_OFFSETS);
    }
    return {x: x, y: y};
}

function __cfViewportGetScrollOffsetsPage(ignoreError)
{
    var x = self.pageXOffset;
    var y = self.pageYOffset;
    if ((typeof(x) != "number") || (typeof(y) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("__cfViewportGetScrollOffsetsPage: " +
                              __CFVIEWPORT_ERROR_SCROLL_OFFSETS);
    }
    return {x: x, y: y};
}

function __cfViewportGetScrollOffsetsUndefined(ignoreError)
{
    if (ignoreError) {
        return undefined;
    }
    return cfErrorTrigger("__cfViewportGetScrollOffsetsUndefined: " +
                          __CFVIEWPORT_ERROR_SCROLL_OFFSETS);
}

function __cfViewportGetScrollOffsetsXY(ignoreError)
{
    var x = self.scrollX;
    var y = self.scrollY;
    if ((typeof(x) != "number") || (typeof(y) != "number")) {
        if (ignoreError) {
            return undefined;
        }
        return cfErrorTrigger("__cfViewportGetScrollOffsetsXY: " +
                              __CFVIEWPORT_ERROR_SCROLL_OFFSETS);
    }
    return {x: x, y: y};
}

////////////////////////////////////////////////////////////////////////////////
// Event Handlers
////////////////////////////////////////////////////////////////////////////////

function __cfViewportEmulateOnResizeEvent()
{
    var oldDimensions = __cfViewportLastDimensions;
    var dimensions = cfViewportGetDimensions();
    if (! ((typeof(oldDimensions) != "undefined") &&
           (oldDimensions.height == dimensions.height) &&
           (oldDimensions.width == dimensions.width))) {
        __cfViewportLastDimensions = dimensions;
        __cfViewportHandleOnResizeEvent();
    }
}

function __cfViewportEmulateOnScrollEvent()
{
    var oldScrollOffsets = __cfViewportLastScrollOffsets;
    var scrollOffsets = cfViewportGetScrollOffsets();
    if (! ((typeof(oldScrollOffsets) != "undefined") &&
           (oldScrollOffsets.x == scrollOffsets.x) &&
           (oldScrollOffsets.y == scrollOffsets.y))) {
        __cfViewportLastScrollOffsets = scrollOffsets;
        __cfViewportHandleOnScrollEvent();
    }
}

function __cfViewportHandleOnLoadEvent()
{
    // XXX: ??
    window.onresize = __cfViewportHandleOnResizeEvent;
    window.onscroll = __cfViewportHandleOnScrollEvent;
    if (window.captureEvents) {
        if (typeof(Event.RESIZE) != "undefined") {
            window.captureEvents(Event.RESIZE);
        }
        if (typeof(Event.SCROLL) != "undefined") {
            window.captureEvents(Event.SCROLL);
        }
    }
}

function __cfViewportHandleOnResizeEvent(e)
{
    var r;
    var results = __cfViewportOnResizeHandlerSet.execute();
    var truePoll = 0;
    for (var i = 0; i < results.length; i++) {
        r = results[i];
        if (typeof(r) != "undefined") {
            truePoll += r ? 1 : -1;
        }
    }
    return (truePoll >= 0);
}

function __cfViewportHandleOnScrollEvent(e)
{
    var r;
    var results = __cfViewportOnScrollHandlerSet.execute();
    var truePoll = 0;
    for (var i = 0; i < results.length; i++) {
        r = results[i];
        if (typeof(r) != "undefined") {
            truePoll += r ? 1 : -1;
        }
    }
    return (truePoll >= 0);
}

function __cfViewportStopResizeEventEmulation()
{
    window.clearInterval(__cfViewportResizeTimer);
}

function __cfViewportStopScrollEventEmulation()
{
    window.clearInterval(__cfViewportScrollTimer);
}

////////////////////////////////////////////////////////////////////////////////
// Public API
////////////////////////////////////////////////////////////////////////////////

function cfViewportAddOnResizeCallback()
{
    if (typeof(__cfViewportResizeTimer) == "undefined") {
        // XXX: We need a reliable way to detect whether or not we can use the
        // onresize attribute of the window object instead of polling for resize
        // updates.
        __cfViewportResizeTimer =
            window.setInterval(__cfViewportEmulateOnResizeEvent,
                               __CFVIEWPORT_RESIZE_INTERVAL);
        cfDocumentAddOnUnloadCallback(__cfViewportStopResizeEventEmulation);
    }
    __cfViewportOnResizeHandlerSet.add.apply(__cfViewportOnResizeHandlerSet,
                                             arguments);
}

function cfViewportAddOnScrollCallback()
{
    if (typeof(__cfViewportScrollTimer) == "undefined") {
        // XXX: We need a reliable way to detect whether or not we can use the
        // onscroll attribute of the window object instead of polling for scroll
        // updates.
        __cfViewportScrollTimer =
            window.setInterval(__cfViewportEmulateOnScrollEvent,
                               __CFVIEWPORT_SCROLL_INTERVAL);
        cfDocumentAddOnUnloadCallback(__cfViewportStopScrollEventEmulation);
    }
    __cfViewportOnScrollHandlerSet.add.apply(__cfViewportOnScrollHandlerSet,
                                             arguments);
}

////////////////////////////////////////////////////////////////////////////////
// Initialization
////////////////////////////////////////////////////////////////////////////////

var docBody = document.documentBody;
var docElement = document.documentElement;
if (self && ((typeof(self.innerHeight) == "number") ||
             (typeof(self.innerWidth) == "number"))) {
    cfViewportGetDimensions = __cfViewportGetDimensionsInner;
} else if ((docBody && ((typeof(docBody.clientWidth) == "number") ||
                        (typeof(docBody.clientHeight) == "number"))) ||
           (docElement && ((typeof(docElement.clientWidth) == "number") ||
                           (typeof(docElement.clientHeight) == "number")))) {
    cfViewportGetDimensions = __cfViewportGetDimensionsClient;
} else {
    cfWarningTrigger("[viewport]: " + __CFVIEWPORT_WARNING_DIMENSIONS);
    __CFVIEWPORT_SUPPORTS_DIMENSIONS = false;
    cfViewportGetDimensions = __cfViewportGetDimensionsUndefined;
}
if (self && ((typeof(self.pageXOffset) == "number") ||
             (typeof(self.pageYOffset) == "number"))) {
    cfViewportGetScrollOffsets = __cfViewportGetScrollOffsetsPage;
} else if ((docBody && ((typeof(docBody.scrollLeft) == "number") ||
                        (typeof(docBody.scrollTop) == "number"))) ||
           (docElement && ((typeof(docElement.scrollLeft) == "number") ||
                           (typeof(docElement.scrollTop) == "number")))) {
    cfViewportGetScrollOffsets = __cfViewportGetScrollOffsets;
} else if (self && ((typeof(self.scrollX) == "number") ||
                    (typeof(self.scrollY) == "number"))) {
    cfViewportGetScrollOffsets = __cfViewportGetScrollOffsetsXY;
} else {
    cfWarningTrigger("[viewport]: " + __CFVIEWPORT_WARNING_SCROLL_OFFSETS);
    __CFVIEWPORT_SUPPORTS_SCROLL_OFFSETS = false;
    cfViewportGetScrollOffsets = __cfViewportGetScrollOffsetsUndefined;
}
// cfDocumentAddOnLoadCallback(__cfViewportHandleOnLoadEvent);
