/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ChatZilla.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Robert Ginda, <rginda@netscape.com>, original author
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

var initialized = false;

var view;
var client;
var mainWindow;
var clickHandler;

var dd;
var getMsg;
var getObjectDetails;

var header = null;
var headers = {
    IRCClient: {
        prefix: "cli-",
        fields: ["container", "netcount", "version-container", "version",
                 "connectcount"],
        update: updateClient
    },

    IRCNetwork: {
        prefix: "net-",
        fields: ["container", "url-anchor", "status", "lag"],
        update: updateNetwork
    },

    IRCChannel: {
        prefix: "ch-",
        fields: ["container", "url-anchor", "modestr", "usercount",
                 "topicnodes", "topicinput", "topiccancel"],
        update: updateChannel
    },

    IRCUser: {
        prefix: "usr-",
        fields: ["container", "url-anchor", "serverstr", "title",
                 "descnodes"],
        update: updateUser
    },
    
    IRCDCCChat: {
        prefix: "dcc-chat-",
        fields: ["container", "remotestr", "title"],
        update: updateDCCChat
    },
    
    IRCDCCFileTransfer: {
        prefix: "dcc-file-",
        fields: ["container", "file", "progress", "progressbar"],
        update: updateDCCFile
    }
};

var initOutputWindow = stock_initOutputWindow;

function stock_initOutputWindow(newClient, newView, newClickHandler)
{
    function initHeader()
    {
        /* it's better if we wait a half a second before poking at these
         * dom nodes. */
        setHeaderState(view.prefs["displayHeader"]);
        updateHeader();
        var div = document.getElementById("messages-outer");
        div.removeAttribute("hidden");
        window.scrollTo(0, window.document.height);
    };

    client = newClient;
    view = newView;
    clickHandler = newClickHandler;
    mainWindow = client.mainWindow;
    
    client.messageManager.importBundle(client.defaultBundle, window);

    getMsg = mainWindow.getMsg;
    getObjectDetails = mainWindow.getObjectDetails;
    dd = mainWindow.dd;

    // Wheee... localize stuff!
    //var nodes = document.getElementsByAttribute("localize", "*");
    var nodes = document.getElementsByTagName("*");
    for (var i = 0; i < nodes.length; i++)
    {
        if (nodes[i].hasAttribute("localize"))
        {
            var msg = nodes[i].getAttribute("localize");
            msg = getMsg("msg." + msg);
            if (nodes[i].nodeName.toLowerCase() == "input")
                nodes[i].value = msg;
            else
                nodes[i].appendChild(document.createTextNode(msg));
        }
    }

    changeCSS("chrome://chatzilla/content/output-base.css", "cz-css-base");
    changeCSS(view.prefs["motif.current"]);
    updateMotifSettings();

    var output = document.getElementById("output");
    output.appendChild(adoptNode(view.messages));

    if (view.TYPE in headers)
    {
        header = cacheNodes(headers[view.TYPE].prefix,
                            headers[view.TYPE].fields);
        // Turn off accessibility announcements: they're useless as all these
        // changes are in the "log" as well, normally.
        // We're setting the attribute here instead of in the HTML to cope with
        // custom output windows and so we set it only on the Right header
        // for this view.
        header["container"].setAttribute("aria-live", "off");
        header.update = headers[view.TYPE].update;
    }

    var splash = document.getElementById("splash");
    var name;
    if ("unicodeName" in view)
        name = view.unicodeName;
    else
        name = view.name;
    splash.appendChild(document.createTextNode(name));

    setTimeout(initHeader, 500);

    initialized = true;
}

function onTopicNodesClick(e)
{
    if (!clickHandler(e))
    {
        if (e.which != 1)
            return;

        startTopicEdit();
    }

    e.stopPropagation();
}

function onTopicKeypress(e)
{
    switch (e.keyCode)
    {
        case 13: /* enter */
            var topic = header["topicinput"].value;
            topic = mainWindow.replaceColorCodes(topic);
            view.setTopic(topic);
            cancelTopicEdit(true);
            view.dispatch("focus-input");
            break;
            
        case 27: /* esc */
            cancelTopicEdit(true);
            view.dispatch("focus-input");
            break;
            
        default:
            client.mainWindow.onInputKeyPress(e);
    }
}

function startTopicEdit()
{
    var me = view.getUser(view.parent.me.unicodeName);
    if (!me || (!view.mode.publicTopic && !me.isOp && !me.isHalfOp) ||
        !hasAttribute("topicinput", "hidden"))
    {
        return;
    }

    header["topicinput"].value = mainWindow.decodeColorCodes(view.topic);

    header["topicnodes"].setAttribute("hidden", "true")
    header["topicinput"].removeAttribute("hidden");
    header["topiccancel"].removeAttribute("hidden");
    header["topicinput"].focus();
    header["topicinput"].selectionStart = 0;
}

function cancelTopicEdit(force)
{
    var originalTopic = mainWindow.decodeColorCodes(view.topic);
    if (!hasAttribute("topicnodes", "hidden") ||
        (!force && (header["topicinput"].value != originalTopic)))
    {
        return;
    }

    header["topicinput"].setAttribute("hidden", "true");
    header["topiccancel"].setAttribute("hidden", "true");
    header["topicnodes"].removeAttribute("hidden");
}

function cacheNodes(pfx, ary, nodes)
{
    if (!nodes)
        nodes = new Object();

    for (var i = 0; i < ary.length; ++i)
        nodes[ary[i]] = document.getElementById(pfx + ary[i]);

    return nodes;
}

function changeCSS(url, id)
{
    if (!id)
        id = "main-css";
    
    node = document.getElementById(id);

    if (!node)
    {
        node = document.createElement("link");
        node.setAttribute("id", id);
        node.setAttribute("rel", "stylesheet");
        node.setAttribute("type", "text/css");
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(node);
    }
    else
    {
        if (node.getAttribute("href") == url)
            return;
    }

    node.setAttribute("href", url);
    window.scrollTo(0, window.document.height);
}

function scrollToElement(element, position)
{
    /* The following values can be used for element:
     *   selection       - current selected text.
     *   marker          - the activity marker.
     *   [any DOM node]  - anything :)
     *
     * The following values can be used for position:
     *   top             - scroll so it is at the top.
     *   center          - scroll so it is in the middle.
     *   bottom          - scroll so it is at the bottom.
     *   inview          - scroll so it is in view.
     */
    switch (element)
    {
        case "selection":
            var sel = window.getSelection();
            if (sel)
                element = sel.anchorNode;
            else
                element = null;
            break;

        case "marker":
            if ("getActivityMarker" in view)
                element = view.getActivityMarker().marker;
            else
                element = null;
            break;
    }
    if (!element)
        return;

    // Calculate element's position in document.
    var pos = { top: 0, center: 0, bottom: 0 };
    // Find first parent with offset data.
    while (element && !("offsetParent" in element))
        element = element.parentNode;
    var elt = element;
    // Calc total offset data.
    while (elt)
    {
        pos.top += 0 + elt.offsetTop;
        elt = elt.offsetParent;
    }
    pos.center = pos.top + element.offsetHeight / 2;
    pos.bottom = pos.top + element.offsetHeight;

    // Store the positions to align the element with.
    var cont = { top: 0, center: window.innerHeight / 2,
                 bottom: window.innerHeight };
    if (!hasAttribute("container", "hidden"))
    {
        /* Offset height doesn't include the margins, so we get to do that
         * ourselves via getComputedStyle(). We're assuming that will return
         * a px value, which is all but guaranteed.
         */
        var headerHeight = header["container"].offsetHeight;
        var css = getComputedStyle(header["container"], null);
        headerHeight += parseInt(css.marginTop) + parseInt(css.marginBottom);
        cont.top    += headerHeight;
        cont.center += headerHeight / 2;
    }

    // Pick between 'top' and 'bottom' for 'inview' position.
    if (position == "inview")
    {
        if (pos.top - window.scrollY < cont.top)
            position = "top";
        else if (pos.bottom - window.scrollY > cont.bottom)
            position = "bottom";
        else
            return;
    }

    window.scrollTo(0, pos[position] - cont[position]);
}

function updateMotifSettings(existingTimeout)
{
    // Try... catch with a repeat to cope with the style sheet not being loaded
    const TIMEOUT = 100;
    try
    {
        existingTimeout += TIMEOUT;
        view.motifSettings = getMotifSettings();
    }
    catch(ex) 
    {
        if (existingTimeout >= 30000) // Stop after trying for 30 seconds
            return;
        if (ex.name == "NS_ERROR_DOM_INVALID_ACCESS_ERR") //not ready, try again
            setTimeout(updateMotifSettings, TIMEOUT, existingTimeout);
        else // something else, panic!
            dd(ex);
    }
}

function getMotifSettings()
{
    var re = new RegExp("czsettings\\.(\\w*)", "i");
    var rules = document.getElementById("main-css").sheet.cssRules;
    var rv = new Object();
    var ary;
    // Copy any settings, which are available in the motif using the
    // "CZSETTINGS" selector. We only store the regexp match after checking
    // the rule type because selectorText is not defined on other rule types.
    for (var i = 0; i < rules.length; i++)
    {
        if ((rules[i].type == CSSRule.STYLE_RULE) &&
            ((ary = rules[i].selectorText.match(re)) != null))
        {
            rv[ary[1]] = true;
        }
    }
    return rv;
}

function adoptNode(node)
{
    return client.adoptNode(node, document);
}

function setText(field, text, checkCondition)
{
    if (!header[field].firstChild)
        header[field].appendChild(document.createTextNode(""));

    if (typeof text != "string")
    {
        text = MSG_UNKNOWN;
        if (checkCondition)
            setAttribute(field, "condition", "red");
    }
    else if (checkCondition)
    {
        setAttribute(field, "condition", "green");
    }
                
    header[field].firstChild.data = text;
}

function setAttribute(field, name, value)
{
    if (!value)
        value = "true";

    header[field].setAttribute(name, value);
}

function removeAttribute(field, name)
{
    header[field].removeAttribute(name);
}

function hasAttribute(field, name)
{
    return header[field].hasAttribute(name);
}

function setHeaderState(state)
{
    if (header)
    {
        if (state)
        {
            removeAttribute("container", "hidden");
            updateHeader();
        }
        else
        {
            setAttribute("container", "hidden");
        }
    }
}

function updateHeader()
{
    document.title = view.getURL();
    
    if (!header || hasAttribute("container", "hidden"))
        return;

    for (var id in header)
    {
        var value;

        if (id == "url-anchor")
        {
            value = view.getURL();
            setAttribute("url-anchor", "href", value);
            setText("url-anchor", value);
        }
        else if (id in view)
        {
            setText(id, view[id]);
        }
    }

    if (header.update)
        header.update();
}

function updateClient()
{
    var n = 0, c = 0;
    for (name in client.networks)
    {
        ++n;
        if (client.networks[name].isConnected())
            ++c;
    }

    setAttribute("version-container", "title", client.userAgent);
    setAttribute("version-container", "condition", mainWindow.__cz_condition);
    setText("version", mainWindow.__cz_version);
    setText("netcount", String(n));
    setText("connectcount", String(c));
}

function updateNetwork()
{
    if (view.state == client.mainWindow.NET_CONNECTING)
    {
        setText("status", MSG_CONNECTING);
        setAttribute("status","condition", "yellow");
        removeAttribute("status", "title");
        setText("lag", MSG_UNKNOWN);
    }
    else if (view.isConnected())
    {
        setText("status", MSG_CONNECTED);
        setAttribute("status","condition", "green");
        setAttribute("status", "title",
                     getMsg(MSG_CONNECT_VIA, view.primServ.unicodeName));
        if (view.primServ.lag != -1)
            setText("lag", getMsg(MSG_FMT_SECONDS, view.primServ.lag));
        else
            setText("lag", MSG_UNKNOWN);
        
    }
    else
    {
        setText("status", MSG_DISCONNECTED);
        setAttribute("status","condition", "red");
        removeAttribute("status", "title");
        setText("lag", MSG_UNKNOWN);
    }
}

function updateChannel()
{
    header["topicnodes"].removeChild(header["topicnodes"].firstChild);

    if (view.active)
    {
        var str = view.mode.getModeStr();
        if (!str)
            str = MSG_NO_MODE;
        setText("modestr", str);
        setAttribute("modestr", "condition", "green");

        setText("usercount", getMsg(MSG_FMT_USERCOUNT,
                                    [view.getUsersLength(), view.opCount,
                                     view.halfopCount, view.voiceCount]));
        setAttribute("usercount", "condition", "green");

        if (view.topic)
        {
            var data = getObjectDetails(view);
            data.dontLogURLs = true;
            var mailto = client.prefs["munger.mailto"];
            client.munger.getRule(".mailto").enabled = mailto;
            var nodes = client.munger.munge(view.topic, null, data);
            client.munger.getRule(".mailto").enabled = false;
            header["topicnodes"].appendChild(adoptNode(nodes));
        }
        else
        {
            setText("topicnodes", MSG_NONE);
        }
    }
    else
    {
        setText("modestr", MSG_UNKNOWN);
        setAttribute("modestr", "condition", "red");
        setText("usercount", MSG_UNKNOWN);
        setAttribute("usercount", "condition", "red");
        setText("topicnodes", MSG_UNKNOWN);
    }

}

function updateUser()
{
    var source;
    if (view.name)
        source = "<" + view.name + "@" + view.host + ">";
    else
        source = MSG_UNKNOWN;

    if (view.parent.isConnected)
        setText("serverstr", view.connectionHost, true);
    else
        setText("serverstr", null, true);

    setText("title", getMsg(MSG_TITLE_USER, [view.unicodeName, source]));

    header["descnodes"].removeChild(header["descnodes"].firstChild);
    if (typeof view.desc != "undefined")
    {
        var data = getObjectDetails(view);
        data.dontLogURLs = true;
        var nodes = client.munger.munge(view.desc, null, data);
        header["descnodes"].appendChild(adoptNode(nodes));
    }
    else
    {
        setText("descnodes", "");
    }
}

function updateDCCChat()
{
    if (view.state.state == 4)
        setText("remotestr", view.remoteIP + ":" + view.port, true);
    else
        setText("remotestr", null, true);

    setText("title", getMsg(MSG_TITLE_DCCCHAT, view.user.unicodeName));
}

function updateDCCFile()
{
    var pcent = view.progress;
    
    setText("file", view.filename);
    setText("progress", getMsg(MSG_DCCFILE_PROGRESS,
                               [pcent, mainWindow.getSISize(view.position),
                                mainWindow.getSISize(view.size),
                                mainWindow.getSISpeed(view.speed)]));

    setAttribute("progressbar", "width", pcent + "%");
}
