/*jslint browser: true, for: true, fudge: true, maxlen: 80, multivar: true,
         this: true, single: true */

/*global CFWidget, cfEventHandlerCreate, window, CFWIDGET_EVENT_HIDE,
    CFWIDGET_EVENT_SHOW, cfElementIntersectsElements, cfElementHasDescendant,
    cfElementAddClass, CFELEMENT_INVISIBLE_CLASS, cfElementRemoveClass,
    cfErrorTrigger, cfBrowserGetOS, cfBrowserGetVersion, cfBrowserGetName,
    CFBROWSER_EXPLORER, CFBROWSER_NETSCAPE, CFBROWSER_OPERA, cfWarningTrigger
*/

/*property
    __bottomPosition, __clearHideTimer, __clearShowTimer, __guessHeight,
    __guessed_height, __hide, __hideEvent, __hideStyle, __hideTimer,
    __leftPosition, __rightPosition, __setPosition, __show, __showEvent,
    __showTimer, __startAnim, __topPosition, __triggerEvent, __visible, bind,
    bottom, call, clear, clearInterval, clearTimeout, clientHeight, config,
    contains, display, end, extend, extendClasses, getComputedStyle, getElement,
    getElementsByTagName, getHideTime, getShowTime, hasOwnProperty, height,
    hide, isVisible, left, length, now, old_overflow, overflow, popup,
    prototype, push, refresh, right, setBottomLeftPosition,
    setBottomRightPosition, setInterval, setTimeout, setTopLeftPosition,
    setTopRightPosition, show, splice, start, style, top, visibility
*/



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

var __CFPOPUP_ERROR_HIDDEN = "popup is already hidden";
var __CFPOPUP_ERROR_ID = "invalid CFPopup id";
var __CFPOPUP_ERROR_VISIBLE = "popup is already visible";

var __CFPOPUP_WARNING_REFRESH = "windowed element hiding is disabled";

var __CFPOPUP_HIDE_TIME = 0;
var __CFPOPUP_SHOW_TIME = 0;

var __CFPOPUP_WINDOWED_TAGS = [];

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

var __cfPopupHiddenElements = [];
var __cfPopupMap = {};

////////////////////////////////////////////////////////////////////////////////
// Classes
////////////////////////////////////////////////////////////////////////////////

function CFPopup(id, animate) {
    "use strict";
    CFWidget.call(this, id);
    this.animate = animate || false;
    delete this.__bottomPosition;
    delete this.__hideTimer;
    this.__leftPosition = 0;
    delete this.__rightPosition;
    delete this.__showTimer;
    this.__topPosition = 0;
    this.__hideStyle();
    __cfPopupMap[id] = this;
    var f = cfEventHandlerCreate(this.__hide.bind(this));
    this.__hideEvent = f;
    f = cfEventHandlerCreate(this.__show.bind(this));
    this.__showEvent = f;
}

CFPopup.extendClasses(CFWidget);

CFPopup.prototype.__clearHideTimer = function () {
    "use strict";
    var timer = this.__hideTimer;
    if (timer) {
        window.clearTimeout(timer);
        delete this.__hideTimer;
    }
};

CFPopup.prototype.__clearShowTimer = function () {
    "use strict";
    var timer = this.__showTimer;
    if (timer) {
        window.clearTimeout(timer);
        delete this.__showTimer;
    }
};

var __cfPopupRefreshHiddenElements;

CFPopup.prototype.__hide = function () {
    "use strict";
    this.__clearHideTimer();
    this.__hideStyle();
    this.refresh();
    this.__triggerEvent(CFWIDGET_EVENT_HIDE);
    __cfPopupRefreshHiddenElements();
};

CFPopup.prototype.__hideStyle = function () {
    "use strict";
    var style = this.getElement().style,
        anim_config,
        top,
        bottom;
    if (!this.animate) {
        style.display = "none";
        style.bottom = '';
        style.left = '';
        style.right = '';
        style.top = '';
    } else {
        anim_config = {
            callback: function () {
                style.display = "none";
                style.bottom = '';
                style.left = '';
                style.right = '';
                style.top = '';
            }
        };
        top = style.top;
        bottom = style.bottom;
        if (top !== '' && bottom !== '') {
            anim_config.bottom = {
                start: parseInt(bottom, 10),
                end: parseInt(top, 10),
                clear: false
            };
        } else {
            anim_config.height = {
                start: this.__guessHeight(),
                end: 0,
                clear: true
            };
        }
        this.__startAnim(anim_config);
    }
    this.__visible = false;
};

CFPopup.prototype.__setPosition = function (bottom, left, right, top) {
    "use strict";
    this.__bottomPosition = bottom;
    this.__leftPosition = left;
    this.__rightPosition = right;
    this.__topPosition = top;
    if (this.isVisible()) {
        var style = this.getElement().style;
        style.bottom = (bottom === undefined)
            ? ''
            : bottom + "px";
        style.left = (left === undefined)
            ? ''
            : left + "px";
        style.right = (right === undefined)
            ? ''
            : right + "px";
        style.top = (top === undefined)
            ? ''
            : top + "px";
        __cfPopupRefreshHiddenElements();
    }
};

var CF_ANIM_TIMER,
    CF_ANIM_CONFIGS = [],
    CF_ANIM_LENGTH = 500,
    CF_ANIM_TICK_LENGTH = 10;

function anim_tick() {
    "use strict";
    var cur_time = Date.now(),
        i,
        anim_config,
        anim_progress,
        popup_style,
        style_key,
        start_val,
        end_val,
        new_val;
    for (i = CF_ANIM_CONFIGS.length - 1; i >= 0; i -= 1) {
        anim_config = CF_ANIM_CONFIGS[i];
        anim_progress = (cur_time - anim_config.start) / CF_ANIM_LENGTH;
        popup_style = anim_config.popup.getElement().style;

        for (style_key in anim_config.config) {
            if (anim_config.config.hasOwnProperty(style_key)) {
                start_val = anim_config.config[style_key].start;
                end_val = anim_config.config[style_key].end;
                if (anim_progress >= 1) {
                    if (anim_config.config[style_key].clear) {
                        new_val = '';
                    } else {
                        new_val = end_val + 'px';
                    }
                } else {
                    new_val = (
                        start_val + (end_val - start_val) * anim_progress
                    ) + 'px';
                }
                popup_style[style_key] = new_val;
            }
        }
        if (anim_progress >= 1) {
            anim_config.config.callback();
            CF_ANIM_CONFIGS.splice(i, 1);
            popup_style.overflow = anim_config.old_overflow;
        }
    }
    if (!CF_ANIM_CONFIGS.length && CF_ANIM_TIMER) {
        window.clearInterval(CF_ANIM_TIMER);
        CF_ANIM_TIMER = undefined;
    }
}

CFPopup.prototype.__startAnim = function (anim_config) {
    "use strict";
    var style = this.getElement().style;
    CF_ANIM_CONFIGS.push({
        popup: this,
        config: anim_config,
        start: Date.now(),
        old_overflow: style.overflow
    });
    style.overflow = 'hidden';
    anim_tick();
    if (CF_ANIM_TIMER === undefined) {
        CF_ANIM_TIMER = window.setInterval(anim_tick, CF_ANIM_TICK_LENGTH);
    }
};

CFPopup.prototype.__guessHeight = function () {
    "use strict";
    var el, computed_style, style, is_rendered, edge_rules, i, height;
    if (this.__guessed_height !== undefined) {
        return this.__guessed_height;
    }
    el = this.getElement();
    style = el.style;
    computed_style = window.getComputedStyle(el, null);
    is_rendered = computed_style.display === 'block';
    if (!is_rendered) {
        style.visibility = 'hidden';
        style.display = 'block';
    }
    height = el.clientHeight;
    edge_rules = [
        'border-top', 'padding-top', 'padding-bottom', 'border-bottom'
    ];
    for (i = 0; i < edge_rules.length; i += 1) {
        height -= parseInt(computed_style[edge_rules[i]], 10) || 0;
    }
    if (!is_rendered) {
        style.display = 'none';
        style.visibility = 'visible';
    }
    this.__guessed_height = height;
    return height;
};

CFPopup.prototype.__show = function () {
    "use strict";
    this.__clearShowTimer();
    var anim_config;
    var style = this.getElement().style;
    var bottom = this.__bottomPosition;
    if (!this.animate) {
        style.bottom = (bottom === undefined)
            ? ''
            : bottom + "px";
    }
    var left = this.__leftPosition;
    style.left = (left === undefined)
        ? ''
        : left + "px";
    var right = this.__rightPosition;
    style.right = (right === undefined)
        ? ''
        : right + "px";
    var top = this.__topPosition;
    style.top = (top === undefined)
        ? ''
        : top + "px";
    if (this.animate) {
        anim_config = {
            callback: function () {
            }
        };
        if (top !== undefined && bottom !== undefined) {
            anim_config.bottom = {
                start: top,
                end: bottom,
                clear: false
            };
        } else {
            anim_config.height = {
                start: 0,
                end: this.__guessHeight(),
                clear: true
            };
        }
        this.__startAnim(anim_config);
    }
    style.display = "block";
    this.__visible = true;
    this.refresh();
    this.__triggerEvent(CFWIDGET_EVENT_SHOW);
    __cfPopupRefreshHiddenElements();
};

CFPopup.prototype.getHideTime = function () {
    "use strict";
    return __CFPOPUP_HIDE_TIME;
};

CFPopup.prototype.getShowTime = function () {
    "use strict";
    return __CFPOPUP_SHOW_TIME;
};

CFPopup.prototype.hide = function () {
    "use strict";
    this.__clearShowTimer();
    if (this.isVisible() && (this.__hideTimer === undefined)) {
        this.__hideTimer = window.setTimeout(
            this.__hideEvent,
            this.getHideTime()
        );
    }
};

CFPopup.prototype.isVisible = function () {
    "use strict";
    return this.__visible;
};

CFPopup.prototype.setBottomLeftPosition = function (bottom, left) {
    "use strict";
    this.__setPosition(bottom, left, undefined, undefined);
};

CFPopup.prototype.setBottomRightPosition = function (bottom, right) {
    "use strict";
    this.__setPosition(bottom, undefined, right, undefined);
};

CFPopup.prototype.setTopLeftPosition = function (top, left) {
    "use strict";
    this.__setPosition(undefined, left, undefined, top);
};

CFPopup.prototype.setTopRightPosition = function (top, right) {
    "use strict";
    this.__setPosition(undefined, undefined, right, top);
};

CFPopup.prototype.show = function () {
    "use strict";
    this.__clearHideTimer();
    if ((!this.isVisible()) && (this.__showTimer === undefined)) {
        this.__showTimer = window.setTimeout(
            this.__showEvent,
            this.getShowTime()
        );
    }
};

////////////////////////////////////////////////////////////////////////////////
// Private functions
////////////////////////////////////////////////////////////////////////////////

function __cfPopupRefreshHiddenElementsDisabled() {
    "use strict";
    // Nothing to do.
}

function __cfPopupRefreshHiddenElementsEnabled() {
    "use strict";
    var candidateElements = [],
        i,
        tag,
        tagElements,
        j,
        id,
        win,
        windowElement,
        elements,
        element;
    for (i = 0; i < __CFPOPUP_WINDOWED_TAGS.length; i += 1) {
        tag = __CFPOPUP_WINDOWED_TAGS[i];
        tagElements = document.getElementsByTagName(tag);
        for (j = 0; j < tagElements.length; j += 1) {
            candidateElements.push(tagElements[j]);
        }
    }
    var newHiddenElements = [];
    var oldHiddenElements = __cfPopupHiddenElements;
    for (id in __cfPopupMap) {
        win = __cfPopupMap[id];
        if (win.isVisible()) {
            windowElement = win.getElement();
            elements = cfElementIntersectsElements(
                windowElement,
                candidateElements
            );
            for (i = 0; i < elements.length; i += 1) {
                element = elements[i];
                if (!cfElementHasDescendant(windowElement, element)) {
                    if (!newHiddenElements.contains(element)) {
                        newHiddenElements.push(element);
                    }
                }
            }
        }
    }
    for (i = 0; i < newHiddenElements.length; i += 1) {
        element = newHiddenElements[i];
        if (!oldHiddenElements.contains(element)) {
            cfElementAddClass(element, CFELEMENT_INVISIBLE_CLASS);
        }
    }
    for (i = 0; i < oldHiddenElements.length; i += 1) {
        element = oldHiddenElements[i];
        if (!newHiddenElements.contains(element)) {
            cfElementRemoveClass(element, CFELEMENT_INVISIBLE_CLASS);
        }
    }
    __cfPopupHiddenElements = newHiddenElements;
}

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

function cfPopupGet(id) {
    "use strict";
    var popup = __cfPopupMap[id];
    if (!popup) {
        return cfErrorTrigger(
            "cfPopupGet: '" + id + "': " + __CFPOPUP_ERROR_ID
        );
    }
    return popup;
}

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

// Windowed components are browser-dependent.  Here's a list of windowed
// components discovered thus far: applet, button (?), embed, iframe, input,
// isindex, object, select, textarea

var browserOS = cfBrowserGetOS();
var browserVersion = cfBrowserGetVersion();
switch (cfBrowserGetName()) {
case CFBROWSER_EXPLORER:
    if (browserVersion < 7) {
        __CFPOPUP_WINDOWED_TAGS.push("select");
        if (browserVersion < 5.5) {
            __CFPOPUP_WINDOWED_TAGS.push("iframe");
        }
    }
    break;
case CFBROWSER_NETSCAPE:
    if (browserVersion < 7) {
        __CFPOPUP_WINDOWED_TAGS.extend(["iframe", "select"]);
        if (browserVersion < 6) {
            __CFPOPUP_WINDOWED_TAGS.extend(["input", "isindex", "textarea"]);
        }
    }
    break;
case CFBROWSER_OPERA:
    // XX: The browser version is a guess.  Check this later.
    __CFPOPUP_WINDOWED_TAGS.push("iframe");
    if (browserVersion < 8.5) {
        __CFPOPUP_WINDOWED_TAGS.extend([
            "input",
            "isindex",
            "select",
            "textarea"
        ]);
    }
    break;
// // Webkit-based browsers
// case CFBROWSER_OMNIWEB:
// case CFBROWSER_SAFARI:
//     break;
// // Gecko-based browsers
// case CFBROWSER_CAMINO:
// case CFBROWSER_FIREFOX:
// case CFBROWSER_MOZILLA:
// case CFBROWSER_ICAB:
// case CFBROWSER_KONQUEROR:
default:
    // Nothing to do.
}

if (!__CFPOPUP_WINDOWED_TAGS.length) {
    __cfPopupRefreshHiddenElements = __cfPopupRefreshHiddenElementsDisabled;
} else if (document.getElementsByTagName) {
    __cfPopupRefreshHiddenElements = __cfPopupRefreshHiddenElementsEnabled;
} else {
    cfWarningTrigger("[popup]: " + __CFPOPUP_WARNING_REFRESH);
    __cfPopupRefreshHiddenElements = __cfPopupRefreshHiddenElementsDisabled;
}
