/* ***** 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 JIM.
 *
 * The Initial Developer of the Original Code is
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * services.highlevel.HLUsers
 *
 *
 */
ML.load("utils/observable.js");
ML.load("utils/misc.js");
ML.load("utils/config.js");

/**
 * HLUsers
 *
 * Constructors:
 *   Users(jabberService)
 *
 * Fields:
 *   users
 *   groups
 *   thisUser
 *
 * Events:
 *   onUsersChanged(users, userid, resourceid)
 *   onUsersThisChanged(thisUser)
 *
 */
function HLUsers(jabberService)
{
    var container;

    this.base = Observable;
    this.base();

    this.jabberService = jabberService;
    this.bundle = getStringBundle("chrome://jim/locale/jim.properties");

    this.thisUser = this.jabberService.localRDF.getResource("rdf:jim-thisUser");
    this.__updateResourceInformation(this.thisUser, "unavailable",
        null, null, null);

    container = this.jabberService.localRDF.
        getContainer("rdf:jim-thisUserContainer", "bag");
    if (!container.hasElement(this.thisUser))
        container.appendElement(this.thisUser);

    jabberService.core.iq.roster.addObserver(this);
    jabberService.core.iq.auth.addObserver(this);
    jabberService.core.iq.last.addObserver(this);
    jabberService.core.iq.version.addObserver(this);
    jabberService.core.addObserver(this);
    jabberService.addObserver(this);
}

function vOn(v) { return v ? v.value : null; }
function tOn(v) { return v.length > 0 ? domToText(v[0]) : null; }

HLUsers.prototype =
{
    __proto__: Observable.prototype,

    get groups ()
    {
        return this.jabberService.localRDF.getContainer("rdf:jim-groups", "bag");
    },

    get users ()
    {
        return this.jabberService.localRDF.getContainer("rdf:jim-users", "bag");
    },

    getUserIDs: function()
    {
        var it = this.users.getElements();
        var res = [];

        while (it.hasNext())
            res.push(it.getNext().value);
        return res;
    },

    getUserIDByJID: function(jid)
    {
        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        return vOn(this.jabberService.localRDF.getLiteral(jid).getSource("user-jid"));
    },

    getUserIDByLongJID: function(longJID)
    {
        var idx = longJID.indexOf("/");

        if (idx < 0)
            return this.getUserIDByJID(longJID);

        return this.getUserIDByJID(longJID.substring(0, idx));
    },

    getInfoByUserID: function(id)
    {
        var user = this.jabberService.localRDF.getResource(id);

        if (!user)
            return null;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        return {
            id:             id,
            jid:            vOn(user.getTarget("user-jid")),
            name:           vOn(user.getTarget("user-name")),
            subscription:   vOn(user.getTarget("user-subscription")),
            ask:            vOn(user.getTarget("user-ask")),
            resourcesCount: vOn(user.getTarget("user-resourcesCount")),
            statusTextual:  vOn(user.getTarget("user-statusTextual")),
            statusIcon:     vOn(user.getTarget("statusIcon-uri")),
            showTextual:    vOn(user.getTarget("user-showTextual")),
        };
    },

    getGroupIDsByUserID: function(id)
    {
        var user = this.jabberService.localRDF.getResource(id);
        var it, res;

        if (!user)
            return null;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        it = user.getTarget("user-groups").getElements();
        res = [];

        while (it.hasNext())
            res.push(it.getNext().value);
        return res;
    },

    getResourceIDsByUserID: function(id)
    {
        var user = this.jabberService.localRDF.getResource(id);
        var it, res;

        if (!user)
            return null;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        it = user.getTarget("user-resources").getElements();
        res = [];

        while (it.hasNext())
            res.push(it.getNext().value);
        return res;
    },

    getGroupIDs: function()
    {
        var it = this.groups.getElements();
        var res = [];

        while (it.hasNext())
            res.push(it.getNext().value);
        return res;
    },

    getGroupNameByID: function(id)
    {
        var group = this.jabberService.localRDF.getResource(id);

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";
        if (group)
            return vOn(group.getTarget("group-name"));

        return null;
    },

    getGroupIDByName: function(name)
    {
        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";
        return vOn(this.jabberService.localRDF.getLiteral(name).getSource("group-name"));
    },

    getUserIDsByGroupID: function(id)
    {
        var group = this.jabberService.localRDF.getResource(id);
        var it, res;

        if (!group)
            return null;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        it = group.getTarget("group-users").getElements();
        res = [];

        while (it.hasNext())
            res.push(it.getNext().value);

        return res;
    },

    getInfoByResourceID: function(id)
    {
        var resource = this.jabberService.localRDF.getResource(id);

        if (!resource)
            return null;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        return {
            id:             id,
            name:           vOn(resource.getTarget("resource-name")),
            type:           vOn(resource.getTarget("resource-type")),
            show:           vOn(resource.getTarget("resource-show")),
            showTextual:    vOn(resource.getTarget("resource-showTextual")),
            status:         vOn(resource.getTarget("resource-status")),
            statusTextual:  vOn(resource.getTarget("resource-statusTextual")),
            statusIcon:     vOn(resource.getTarget("statusIcon-uri")),
            priority:       vOn(resource.getTarget("resource-priority")),
            localizedName:  vOn(resource.getTarget("resource-localizedName")),
            user:           vOn(resource.getTarget("resource-user")),
        };
    },

    getResourceIDByLongJID: function(longJID)
    {
        var resource;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        resource = this.jabberService.localRDF.getLiteral(longJID).getSource("resource-jid");
        return resource && resource.value;
    },

    toLocalizedName: function(prefix, jid)
    {
        var resource = this.getResourceIDByLongJID(jid);
        var val;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        if (resource) {
            resource = this.jabberService.localRDF.getResource(resource);
            val = +resource.getTarget("resource-user").
                getTarget("user-resourcesCount").value;
            jid = resource.getTarget("resource-user").
                    getTarget("user-name").value;
            if ( val > 1)
                return this.bundle.formatStringFromName(
                    prefix+"UserWithResource", 
                    [jid, resource.getTarget("resource-name").value],
                    2);
        } else if (resource = this.getUserIDByJID(jid)) {
            resource = this.jabberService.localRDF.getResource(resource);

            if (resource.getTarget("user-name"))
                jid = vOn(resource.getTarget("user-name"));
        }

        if (!jid)
            return this.bundle.GetStringFromName(prefix+"UnknownUser");
        return this.bundle.formatStringFromName(prefix+"User", [jid], 1);
    },

    sendPresence: function()
    {
        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        jabberService.core.presence(null, null, 
            vOn(this.thisUser.getTarget("resource-type")),
            vOn(this.thisUser.getTarget("resource-show")),
            vOn(this.thisUser.getTarget("resource-status")),
            null,
            vOn(this.thisUser.getTarget("resource-priority")));
    },

    setPresence: function(type, show, status, priority)
    {
        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        type = type || "available";

        if (!priority)
            priority = getPref("priority-" + (show || type));

        this.__updateResourceInformation(
            this.thisUser,
            type,
            show,
            status,
            priority);

        this.sendPresence();
    },

    onDisconnect: function(statusCode)
    {
        this.onClean();
    },

    onClean: function()
    {
        this.users.removeElements(true);
        this.groups.removeElements(true);

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        this.__updateResourceInformation(this.thisUser, "unavailable",
            null, null, null);

        this.fireEvent("onUsersChanged", this.users, null, null);
    },

    onIQAuth: function()
    {
        this.jabberService.core.iq.roster.getRoster();
        this.jabberService.core.presence();
    },

    onPresenceSend: function(tag)
    {
        var type, show, status, priority;

        type = tag.getAttribute("type") || "available";
        show = tOn(tag.getElementsByTagName("show"));
        status = tOn(tag.getElementsByTagName("status"));
        priority = tOn(tag.getElementsByTagName("priority"));

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";
        this.__updateResourceInformation(this.thisUser, type,
            show, status, priority);

        this.fireEvent("onUsersThisChanged", this.thisUser);
    },

    onIQRoster: function(jid, name, subscription, ask, groups)
    {
        if (subscription == "remove") {
            user = this.__removeUserInformation(jid);
            if (user)
                this.fireEvent("onUsersChanged", this.users, user.value, null);
        } else {
            user = this.__setUserInformation(jid, name, subscription, ask, groups);
            if (user)
                this.fireEvent("onUsersChanged", this.users, user.value, null);
        }
    },

    onPresence: function(tag)
    {
        var it, val, user, group, resource; 
        var from, type, show, status, priority;
        var addResource = false;
        debugger;

        type = tag.getAttribute("type");
        from = tag.getAttribute("from");
        status = tOn(tag.getElementsByTagName("status"));

        if (type == "error" || type == "probe")
            return;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        resource = this.getResourceIDByLongJID(from);

        if (resource) {
            resource = this.jabberService.localRDF.getResource(resource);
            user = resource.getTarget("resource-user");
        } else {
            user = this.getUserIDByLongJID(from);
            if (!user) {
                if (type == "subscribe") {
                    this.jabberService.core.iq.roster.addItem(from, from, [
                        this.bundle.GetStringFromName("waitsForSubscription")]);
                }
                return;
            }
            user = this.jabberService.localRDF.getResource(user)
            resource = this.jabberService.localRDF.getResource();
            addResource = true;
        }

        if (type == "unavailable") {
            if (!addResource) {
                user.getTarget("user-resources").
                    removeElement(resource);
                resource.remove();

                it = user.getTarget("user-groups").getElements();
                while (it.hasNext())
                    this.__addToProp(it.getNext(), "group-resourcesCount", -1, 0, null);

                val = this.__addToProp(user, "user-resourcesCount", -1, 0, null);

                if (val == 0 && getBoolPref("proto-last-recv"))
                    jabberService.core.iq.last.getLastActivity(user.getTarget("user-jid").value);
            }

            if (status == null)
                user.setAssertion("user-statusTextual", 
                    this.bundle.GetStringFromName("userStatusNone"), true);
            else
                user.setAssertion("user-statusTextual", status, true);

            this.__updateLocalizedName(user);

            this.fireEvent("onUsersChanged", this.users,
                user.value, resource.value);
        } else {
            if (addResource) {
                user.getTarget("user-resources").
                    appendElement(resource);

                it = user.getTarget("user-groups").getElements();
                while (it.hasNext())
                    this.__addToProp(it.getNext(), "group-resourcesCount", 1);

                this.__addToProp(user, "user-resourcesCount", 1);

                resource.setAssertion("resource-name", 
                    from.substring(from.indexOf("/")+1), true);
                resource.setAssertion("resource-jid", from, true);
                resource.setAssertion("resource-user", user);

                this.__updateLocalizedName(user);
                if (getBoolPref("proto-version-recv"))
                    jabberService.core.iq.version.getVersion(from);
            }

            type = type || "available";
            show = tOn(tag.getElementsByTagName("show"));
            priority = tOn(tag.getElementsByTagName("priority"));

            if (this.__updateResourceInformation(resource, type,
                show, status, priority))
            {
                this.fireEvent("onUsersChanged", this.users,
                    user.value, resource.value)
            }
        }
    },


    __setRemProp: function(resource, property, value)
    {
        if (value != null)
            resource.setAssertion(property, value, true);
        else
            resource.removeAssertion(property);
    },

    __updateProp: function(resource, property, value)
    {
        if (vOn(resource.getTarget(property)) != value) {
            this.__setRemProp(resource, property, value);
            return true;
        }
        return false;
    },

    __addToProp: function(resource, pred, value, min, max)
    {
        var val;

        val = resource.getTarget(pred);
        val = val ? (+val.value)+value : value;

        if (min != null && min > val)
            val = min;

        if (max != null && max < val)
            val = max;

        resource.setAssertion(pred, ""+val, true);

        return val;
    },

    __updateResourceInformation: function(resource, type, show, status, priority)
    {
        var val, update;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        update = this.__updateProp(resource, "resource-type", type);
        update |= this.__updateProp(resource, "resource-show", show);
        update |= this.__updateProp(resource, "resource-status", status);
        update |= this.__updateProp(resource, "resource-priority", priority);

        if (show == "dnd") {
            val = this.bundle.GetStringFromName("userShowDND");
            this.__updateProp(resource, "statusIcon-uri",
                "chrome://jim/skin/images/statusicons/dnd.png");
        } else if (show == "away") {
            val = this.bundle.GetStringFromName("userShowAway");
            this.__updateProp(resource, "statusIcon-uri",
                "chrome://jim/skin/images/statusicons/away.png");
        } else if (show == "xa") {
            val = this.bundle.GetStringFromName("userShowXA");
            this.__updateProp(resource, "statusIcon-uri",
                "chrome://jim/skin/images/statusicons/xa.png");
        } else if (show == "chat") {
            val = this.bundle.GetStringFromName("userShowChat");
            this.__updateProp(resource, "statusIcon-uri",
                "chrome://jim/skin/images/statusicons/chat.png");
        } else if (type == "unavailable") {
            val = this.bundle.GetStringFromName("userShowUnavailable");
            this.__updateProp(resource, "statusIcon-uri",
                "chrome://jim/skin/images/statusicons/unavailable.png");
        } else if (type == "available") {
            val = this.bundle.GetStringFromName("userShowAvailable");
            this.__updateProp(resource, "statusIcon-uri", null);
        }

        resource.setAssertion("resource-showTextual", val, true);

        if (status == null)
            resource.setAssertion("resource-statusTextual", 
                this.bundle.GetStringFromName("userStatusNone"), true);
        else
            resource.setAssertion("resource-statusTextual", status, true);

        return update;
    },

    __updateLocalizedName: function(user)
    {
        var it;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        it = user.getTarget("user-resources").getElements();
        while (it.hasNext()) {
            val = it.getNext();
            val.setAssertion("resource-localizedName", this.toLocalizedName("chat", 
                val.getTarget("resource-jid").value), true);
        }
    },

    __removeUserInformation: function(jid)
    {
        var user, group, i;
        var usersContainer;
        var groupsContainer;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        user = this.getUserIDByJID(jid);

        if (user) {
            user = this.jabberService.localRDF.getResource(user);

            groupsContainer = user.getTarget("user-groups");

            i = groupsContainer.getElements();
            while (i.hasNext()) {
                group = i.getNext();
                usersContainer = group.getTarget("group-users");
                if (usersContainer.getCount() == 1) {
                    this.groups.removeElement(group);
                } else {
                    groupsContainer.removeElement(group);
                    usersContainer.removeElement(user);
                }
            }

            this.users.removeElement(user);
            user.remove(true);
            this.jabberService.localRDF.getContainer(this.groups.value, "bag");
        }
        return user;
    },

    __setUserInformation: function(jid, name, subscription, ask, groups)
    {
        var user, group;
        var usersContainer, groupsContainer, resourcesContainer;
        var groupsHash, i;

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/users#";

        user = this.getUserIDByJID(jid);

        if (!user) {
            user = this.jabberService.localRDF.getResource();
            groupsContainer = this.jabberService.localRDF.getContainer(null, "bag");
            resourcesContainer = this.jabberService.localRDF.getContainer(null, "bag");

            user.setAssertion("user-jid",            jid, true);
            user.setAssertion("user-resourcesCount", "0", true);
            user.setAssertion("user-showTextual",    this.bundle.
                GetStringFromName("userShowUnavailable"), true);
            user.setAssertion("user-statusTextual",  this.bundle.
                GetStringFromName("userStatusNone"), true);
            user.setAssertion("user-groups",         groupsContainer);
            user.setAssertion("user-resources",      resourcesContainer);
            user.setAssertion("avatar-uri",          "chrome://jim/skin/images/default-avatar.png",
                true, JIM_NAMESPACE_URI+"rdf/avatars#");
            user.setAssertion("statusIcon-uri",      "chrome://jim/skin/images/statusicons/unavailable.png", true);

            if (getBoolPref("proto-last-recv"))
                jabberService.core.iq.last.getLastActivity(jid);
            this.users.appendElement(user);
        } else {
            user = this.jabberService.localRDF.getResource(user);
            groupsContainer = user.getTarget("user-groups");
        }

        groupsHash = { };

        if (groups.length == 0) {
            if (jid.indexOf("@") == -1)
                groups.push(this.bundle.GetStringFromName("transportsGroup"));
            else
                groups.push(this.bundle.GetStringFromName("defaultGroup"));
        }

        if (!name)
            name = jid;

        user.setAssertion("user-name",           name, true);
        user.setAssertion("user-subscription",   subscription, true);
        user.setAssertion("user-ask",            ask, true);

        for (i = 0; i < groups.length; i++) {
            groupsHash[groups[i]] = true;
            group = this.getGroupIDByName(groups[i]);

            if (!group) {
                group = this.jabberService.localRDF.getResource();
                usersContainer = this.jabberService.localRDF.getContainer(null, "bag");

                group.setAssertion("group-name",  groups[i], true);
                group.setAssertion("group-users", usersContainer);
                group.setAssertion("group-resourcesCount",
                    user.getTarget("user-resourcesCount").value, true);
                usersContainer.appendElement(user);
                groupsContainer.appendElement(group);

                this.groups.appendElement(group); 
            } else {
                group = this.jabberService.localRDF.getResource(group);

                if (groupsContainer.indexOf(group) == -1) {
                    this.__addToProp(group, "group-resourcesCount",
                        +user.getTarget("user-resourcesCount").value);
                    group.getTarget("group-users").
                        appendElement(user);
                    groupsContainer.appendElement(group);
                }
            }
        }
        i = groupsContainer.getElements();
        while (i.hasNext()) {
            group = i.getNext();
            if (!groupsHash[group.getTarget("group-name").value]) {
                groupsContainer.removeElement(group);
                group.getTarget("group-users").removeElement(user);

                this.__addToProp(group, "group-resourcesCount",
                    -user.getTarget("user-resourcesCount").value, 0, null);

                if (group.getTarget("group-users").getCount() == 0)
                    group.remove();
            }
        }

        this.__updateLocalizedName(user);

        return user;
    },

    onIQVersion: function(from, name, version, os)
    {
        var resource = this.getResourceIDByLongJID(from);

        if (!resource)
            return;

        resource = this.jabberService.localRDF.getResource(resource);

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/client#";
        resource.setAssertion("name", name, true);
        resource.setAssertion("version", version, true);
        resource.setAssertion("os", os, true);
        resource.setAssertion("textual", name+" "+version+"/"+os, true);
    },

    onIQLast: function(from, seconds)
    {
        var date, user = this.getUserIDByJID(from);

        if (!user)
            return;

        user = this.jabberService.localRDF.getResource(user);

        date = new Date();
        date.setTime(date.getTime() - seconds*1000);

        this.jabberService.localRDF.ns = JIM_NAMESPACE_URI+"rdf/last#";
        user.setAssertion("activity", 
            formatDate(date, null, date, "short", "long"), true);
    },
}
