// --------------------------------------------------------------------------------
// ow_cssswitch.js
// Matthew Hogg 20-Apr-2004
// Contains core functions for generation and population of stylesheet switcher.
// Travis Musika 2006-11-06
// Updated to version 2.1 of IOTBS - http://www.stuffandnonsense.co.uk/resources/iotbs.html
// --------------------------------------------------------------------------------
// IOTBS2.1 :: Invasion of the Body Switchers - Look Who's Switching Too
// --------------------------------------------------------------------------------
// This copyright statement must remain in place for both personal and commercial use
// GNU General Public License -- http://www.gnu.org/copyleft/gpl.html
// --------------------------------------------------------------------------------
// Original concept by Andy Clarke -- http://www.stuffandnonsense.co.uk/
// DOM scripting by brothercake -- http://www.brothercake.com/
// Create element and attributes based on a method by beetle -- http://www.peterbailey.net/
// --------------------------------------------------------------------------------
///<reference path="ow_util.js">
//global preferences manager reference
var switcher;
var separatorClassName = "swchSeparator";
var firstIdName = "swchFirst";
// --------------------------------------------------------------------------------
// This function is run upon page loading and sets up the stylewheet switcher.
// Modify this to change the options available via the switcher.
// --------------------------------------------------------------------------------
OneWeb.Util.appendInitEvent(
function() {
    var switcher = new switchManager("html", "/styles/");
    var swch = new bodySwitcher("css_switch", "", "no", "");
    // with this script, the default element must have a key of 'default'
    swch.defineClass("smaller", "a");
    swch.defineSeparator(" ");
    swch.defineClass("default", "a");
    swch.defineSeparator(" ");
    swch.defineClass("bigger", "a");
}
);
// --------------------------------------------------------------------------------
// switchManager functions.
// --------------------------------------------------------------------------------
//preferences manager
function switchManager(canvas, path) {
    //save to global reference
    switcher = this;
    //store reference to canvas element
    //if the canvas is 'body' and the document.body object exists, use that reference
    //because in older moz builds (eg ns7.1) document.getElementsByTagName('body')[0] is undefined
    //unless this script is in the body section (which it isn't)
    this.canvas = canvas == "body" && typeof document.body != "undefined" ? document.body : document.getElementsByTagName(canvas)[0];
    //store the initial classname
    this.initial = this.canvas.className;
    //if the default classname is empty, add "iotbs"
    //because we need there to be at least one classname already -
    //the leading and trailing space in each custom classname is required,
    //but we can't set the body classname as " something" (beginning with a leading space)
    //because that fails in some Opera 7 builds
    if (this.initial == "") { this.initial = "itobs"; }
    //the stylesheet path argument is used to switch on load-mode
    //so store the path, or a null value if none or an empty string is present
    this.path = (typeof path != "undefined" && path != "" ? path : null);
    //regex for finding the native integration token within cookie data
    this.itoken = /(\:\[[0-9]+\])/;
    //if load mode is in use
    if (this.path != null) {
        //ID reference and token index data extractable from integration token
        //set to empty string and -1 by default (discountable values)
        //to make testing them easier - it saves additional typeof discriminators
        this.tokenrefs = ["", -1];
        //collection of <link> elements we also need for integration mode
        this.linkeles = document.getElementsByTagName("link");
    }
    //whether one of the switchers is part of the native group
    //this will be a reference to the applicable oject, or null if none
    this.master = null;
    //string for storing the overall custom classname, for writing to the body classname
    //I was originally storing it in the body class name directly
    //but 1.7+ mozilla builds were not honouring the trailing whitespace we need
    this.string = "";
    //string for storing the switcher idents and classnames as name/value pairs
    //for storing in the cookie and identifying link elements when load-mode is used
    this.idstring = "";

    //identify opera for applying tweaks
    this.isop = typeof window.opera != "undefined";
    //identify IE for applying hacks
    this.isie = typeof window.attachEvent != "undefined" && !this.isop;
    //identify konqueror for applying hacks
    this.iskde = navigator.vendor == "KDE";

    //look for a stored cookie
    this.cookie = this.read();
    //if it exists
    if (this.cookie != null) {
        //store cookie value to string
        this.string = this.cookie;
        //if load-mode is not in use
        if (this.path == null)
        //set new body class name
            this.canvas.className = this.initial + this.string;
        //for each value in the idcookie array
        for (var i = 0; i < this.idcookie.length; i++) {
            //add data to id string
            this.idstring += this.idcookie[i][0] + "=" + this.idcookie[i][1] + "&";
            //if load-mode is in use
            if (this.path != null) {
                //get a reference to the applicable <link> element
                this.linkele = document.getElementById(this.idcookie[i][0] + "_stylesheet");
                //if it exists
                if (this.linkele != null) {
                    //if the integration token ID reference matches this switcher ident
                    if (this.tokenrefs[0] == this.idcookie[i][0]) {
                        //array of stylesheets and alternate stylesheets
                        //which comprise this native switching group
                        //the first (default) is the one we already have
                        this.alternates = [this.linkele];
                        //set an in-scope flag that we'll use to determine
                        //when we've reached, then gone past,
                        //the stylesheets we're interested in
                        var inscope = false;
                        //for each <link> element
                        var len = this.linkeles.length;
                        for (var j = 0; j < len; j++) {
                            //if this link is our master default stylesheet
                            if (this.linkeles[j] == this.alternates[0]) {
                                //set the flag to say we're in scope
                                inscope = true;
                                //remember this index
                                var ind = j;
                            } else if (inscope && (this.linkeles[j].getAttribute("rel") == null || !/(alternat)(iv)?(e stylesheet)/i.test(this.linkeles[j].rel))) {
                                //otherwise if we're in scope and this is not an "alternate" stylesheet
                                //set the flag to say we're out of scope
                                inscope = false;
                                //finally.. enable the indicated stylesheet
                                //which will be the integration token index + stored index of master
                                this.linkeles[this.tokenrefs[1] + ind].disabled = false;
                                //and we can stop now
                                break;
                            }
                            //if we're in scope and this is a titled stylesheet
                            if (inscope && this.linkeles[j].getAttribute("rel") != null && /(stylesheet)/i.test(this.linkeles[j].rel) && this.linkeles[j].getAttribute('title') != null && this.linkeles[j].title != '') {
                                //disable it
                                this.linkeles[j].disabled = true;
                            }
                        }
                    } else {    //if it doesn't match (or the token ID ref is empty)
                        // change its href to load the relevant stylesheet
                        //using path value, plus established naming conventions, to create the filename
                        this.linkele.href = this.path + this.idcookie[i][0] + "_" + this.idcookie[i][1] + ".css";
                    }
                }
            }
        }
    }
    //*** dev
    //document.title = "<" + this.canvas.className.replace(/ /g,"+") + ">   [" + this.string.replace(/ /g,"+") + "]";
    //add a DOM memory cleaner for win/ie
    if (typeof window.attachEvent != "undefined") {
        window.attachEvent("onunload", function() {
            var closures = ["onchange", "onclick", "onkeydown", "checker"];
            for (var i = 0; i < document.all.length; i++) {
                for (var j = 0; j < closures.length; j++) { document.all[i][closures[j]] = null; }
            }
        });
    }
};

//set a cookie method
switchManager.prototype.set = function(days) {
    //format expiry date
    var thedate = new Date();
    thedate.setTime(thedate.getTime() + (days * 24 * 60 * 60 * 1000));
    //store the idstring
    var info = this.idstring;
    //if the value is empty, set its expiry in the past to delete the cookie
    if (info == "") { thedate.setTime(0); }
    //create the cookie
    document.cookie = "OWCSSSwitch=" + info + ";expires=" + thedate.toGMTString() + ";path=/";
};
//read a cookie method
switchManager.prototype.read = function() {
    //set null reference so we always have something to return
    this.cookie = null;
    //if a cookie exists and it's ours
    if (document.cookie && document.cookie.indexOf("OWCSSSwitch=") != -1) {
        //idcookie array is where extracted data will be stored
        this.idcookie = [];
        //split cookie to extract relevant information
        //creating a classname string to apply to the body (this.cookie string)
        //and name/value pairs array for loading a new stylesheet in load-mode (this.idcookie array)
        this.cookie = document.cookie.split("OWCSSSwitch=")[1].split(";")[0].split("&");
        var tmp = "", len = this.cookie.length;
        for (var i = 0; i < len; i++) {
            this.cookie[i] = this.cookie[i].split("=");
            if (this.cookie[i].length > 1) {
                //if load mode is in use and the integration token is there
                if (this.path != null && this.itoken.test(this.cookie[i][1])) {
                    //save the ID reference (switcher ident)
                    //and the token index (position in alternates group)
                    this.tokenrefs = [this.cookie[i][0], parseInt(this.cookie[i][1].split(":[")[1], 10)];
                }
                //clean the integration token from the value
                this.cookie[i][1] = this.cookie[i][1].replace(this.itoken, "");
                //store to idcookie array
                tmp += " " + this.cookie[i][1] + " ";
                this.idcookie[i] = this.cookie[i];
            }
        }
        //re-save cleaned value to this.cookie
        //so it can be returned as the data
        this.cookie = tmp;
    }
    return this.cookie;
};

//save the current switcher state
switchManager.prototype.save = function(ident, chosen, ind, obj) {
    //switcher ident (label)
    this.ident = ident;
    //remove this ident from switcher idstring
    this.idstring = this.idstring.replace(this.ident + "=", "");
    //run through classnames array
    var len = obj.classes.length;
    for (var i = 0; i < len; i++) {
        //remove this key (custom class name) from string
        this.string = this.string.replace(" " + obj.classes[i] + " ", "");
        //remove this key and possible integration token from idstring
        var reg = new RegExp("(" + obj.classes[i] + ")" + "(\:\[[0-9]+\])?" + "&", "");
        this.idstring = this.idstring.replace(reg, "");
    }
    //if chosen value isn't default
    if (chosen != "default") {
        //add to classname string
        //we need both a leading and a trailing space to work with
        //to avoid confusion with identical leading or trailing substrings in classnames,
        //such as "high" and "highcontrast" or "large-serif" and "small-serif"
        this.string += " " + chosen + " ";
        //add to switcher idstring
        this.idstring += this.ident + "=" + chosen;
        //if this is the native switching group
        if (this.master == obj) {
            //add a token to the string that indicates integration containing a number for its index in the group,
            //corresponding to its index in the alternates array. the colon is a safe delimiter to use here,
            //because the key value is ultimately a CSS class name, which isn't allowed to contain a colon
            this.idstring += ":[" + ind + "]";
        }
        //add the trailing delimiter
        this.idstring += "&";
    }
    //if load-mode is in use
    if (this.path != null) {
        //if this isn't the native switching group
        if (this.master != obj) {
            //get a reference to applicable <link> element
            var linkele = document.getElementById(this.ident + "_stylesheet");
            //if it exists
            if (linkele != null) {
                //stylesheet src path
                var sheetpath = this.path + this.ident + "_" + chosen + ".css";
                //if this is opera we have to set the href on a timer
                //otherwise the change doesn't kick in unless the stylesheet is already in cache
                if (this.isop) setTimeout(function() { linkele.href = sheetpath; }, 10);
                //if this is IE, we have to preload the stylesheet for exactly the same reason as opera
                //but we can't use the timeout method because that causes win/ie5.0 on 2k and 98se to crash
                else if (this.isie) {
                    //so preload it using XMLHttpRequest
                    //I originally tried doing it using createStyleSheet
                    //which was simpler and took less code
                    //but it didn't solve the problem ... this does
                    var request = new ActiveXObject("Microsoft.XMLHTTP");
                    //once the stylesheet has loaded
                    request.onreadystatechange = function() {
                        //readyState of 4 = document has finished loading
                        //status of 200 = okay, 304 = not modified
                        if (request.readyState == 4 && /(200|304)/.test(request.status.toString())) {
                            //load it into the relevant link element but for some reason if we just set the href to the path directly
                            //and the new sheet has an @import statement in it, IE6 will crash! but equally obscurely, if we first set it to an empty path
                            //the crash doesn't happen and the whole process works smoothly!
                            linkele.href = "";
                            linkele.href = sheetpath;
                            //alert("LOADER change");
                        }
                    };
                    //the request must come after the readystate function is defined
                    //otherwise that function may not be called if the request is fulfilled very quickly
                    request.open("GET", sheetpath, true);
                    request.send(null);
                } else
                //now load the stylesheet for *everyone* [including opera and IE so that if the stylesheet is already in cache
                // the change will happen straight away this does mean that under some circumstances, those browsers
                // will load each stylesheet twice, making two server requests but that can't be helped  - there's no way to say, don't do this
                // if the stylesheet is in cache, because there's no way to know and we can't just set a flag so it only goes through the preload routine once
                // because someone may have their browser set not to cache stylesheets (or anything)]
                // TM - actually, loading the stylesheet twice like this causes IE6 to crash
                    linkele.href = sheetpath;
                //alert("default change");
            }
        } else {
            //if it is the native switching group
            //for each stylesheet in the native group
            len = this.alternates.length;
            for (i = 0; i < len; i++)
            //set it to disabled unless i matches the index of this option in the group
                this.alternates[i].disabled = i != ind;
        }
    } else {
        //if load-mode is not in use set new body class name
        this.canvas.className = this.initial + this.string;
    }
    //store changes to a cookie which expires a year from now
    this.set(365);
    //*** dev
    //document.title = '<' + switcher.canvas.className.replace(/ /g,'+') + '>   [' + switcher.string.replace(/ /g,'+') + ']';
};

//compile preferred/alternate stylesheets collection for integration mode
switchManager.prototype.integrate = function(obj, divid) {
    //save bodySwitcher object to master reference
    this.master = obj;
    //array of stylesheets and alternate stylesheets
    //which comprise this native switching group
    //the first (default) can be idetified by its ID
    this.alternates = [document.getElementById(divid + "_stylesheet")];
    //then the others are the next link element and all following
    //which have both a title, and rel="alternate stylesheet"
    //set an in-scope flag that we'll use to determine
    //when we've reached, then gone past,
    //the stylesheets we're interested in
    var inscope = false;
    //for each <link> element
    var len = this.linkeles.length;
    for (var i = 0; i < len; i++) {
        //if the *previous* link is our master default stylesheet
        //(so obviously it can't be the first one)
        if (i > 0 && this.linkeles[i - 1] == this.alternates[0])
            inscope = true;         //set the flag to say we're in scope
        //if we're in scope
        if (inscope) {
            //if this <link> has a title, and rel="alternate stylesheet"
            if (this.linkeles[i].getAttribute("title") != null && this.linkeles[i].getAttribute("rel") != null && /(alternat)(iv)?(e stylesheet)/i.test(this.linkeles[i].rel)) {
                //add it to the alternates array
                this.alternates[this.alternates.length] = this.linkeles[i];
            } else { //else it doesn't have the necessary attributes
                //set the flag to say we're out of scope again
                inscope = false;
                //and in fact we can stop now since there's only ever one native switching group
                //and if the page does contain more than one the first one takes precedence anyway
                break;
            }
        }
    }

    //remember which stylesheet is enabled as an index in the alternates array
    //we're just setting 0 here instead of finding out which one is enabled now
    //which does mean that an unecessary save will happen if the default is not enabled
    //but it's much less code to do it this way
    //the reason we need this information is so that we only save when necessary
    //partly because it's more efficient, but mostly because
    //the save action changes the selectedIndex, and if we didn't discriminate
    //you wouldn't be able to use the selector manually at all, because on every timeout loop
    //the selector would reset back to show the active stylesheet,
    var isenabled = 0;
    //create a watcher to monitor alternate stylesheets
    var watcher = window.setInterval(function() {
        //run through alternate stylesheets
        var len = switcher.alternates.length;
        for (var i = 0; i < len; i++) {
            //if this sheet is enabled
            if (!switcher.alternates[i].disabled) {
                //if the enabled index is different from the last enabled index
                if (i != isenabled) {
                    //update last enabled index
                    isenabled = i;
                    //send index to interface callback function
                    //which updates the switcher control, if present
                    obj.update(i);
                    //save the current switcher state
                    switcher.save(
                        divid, //switcher ident (divid)
                        switcher.alternates[i].href.split(divid + "_")[1].split(".css")[0], //extrapolate class name value from stylesheet href
                        i, //the index of this option in the switcher group
                        obj //a reference to this bodySwitcher object
                        );
                }
                //stop now - only one is enabled
                break;
            }
        }
    }, 55);
};

//create element and attributes method -- http://www.codingforums.com/showthread.php?s=&postid=151108
switchManager.prototype.create = function(tag, attrs) {
    //detect support for namespaced element creation, in case we're in the XML DOM
    var ele = (typeof document.createElementNS != "undefined") ? document.createElementNS("http://www.w3.org/1999/xhtml", tag) : document.createElement(tag);
    //run through attributes argument
    if (typeof attrs != "undefined") {
        for (var i in attrs) {
            switch (i) {
                //create a text node 
                case "text": ele.appendChild(document.createTextNode(attrs[i])); break;
                //create a class name 
                case "class": ele.className = attrs[i]; break;
                //create a for attribute 
                case "for": ele.setAttribute("htmlFor", attrs[i]); break;
                //create a generic attribute using IE-safe attribute creation 
                default: ele.setAttribute(i, ""); ele[i] = attrs[i]; break;
            }
        }
    }
    return ele;
};

// --------------------------------------------------------------------------------
// bodySwitcher functions.
// --------------------------------------------------------------------------------

//switcher-control constructor
function bodySwitcher(divid, label, isnative, selected) {
    //if load-mode is in use, and this is not ie, and this is the master of the native switching group
    if (switcher.path != null && !switcher.isie && typeof isnative != "undefined" && isnative == "yes") {
        //compile preferred/alternate stylesheets collection
        //and integrate with native switching
        switcher.integrate(
            this, //reference to this bodySwitcher object
            divid //switching control id
            );
    }
    //create an array for storing the switcher's option classnames and labels as they're added
    //so we can later iterate through and remove them from the custom classname string
    this.classes = [];
    //don't continue if the container doesn't exist
    if (document.getElementById(divid) == null) { return false; }
    //create an array of labels (text labels for each class)
    //to store as values in javascript: URIs
    this.labels = [];
    //definition list
    var attrs = { "id": "select_" + divid };
    this.dl = document.getElementById(divid).appendChild(switcher.create("dl", attrs));
    //definition term [switcher heading containing label text]
    attrs = { "text": label };
    this.dl.appendChild(switcher.create("dt", attrs));
    //"selected" text
    this.selected = typeof selected != "undefined" ? selected : "";
    // default item
    this.defitem = null;
    return true;
};

//add a new class option method
bodySwitcher.prototype.defineClass = function(key, val) {
    //store the classname
    this.classes[this.classes.length] = key;
    //don't continue if the list doesn't exist
    if (typeof this.dl == "undefined") { return false; }
    //create a reference to 'this'
    var self = this;
    var item = null;
    //store the link text labels
    this.labels[this.labels.length] = val;
    // create a default item if necessary
    if (this.defitem === null) {
        // create the default item
        this.defitem = switcher.create("dd");
        //add selected class name to definition
        this.defitem.className = "selected";
        // set the id
        this.defitem.id = "default";
        // create the text
        this.defitem.appendChild(document.createTextNode(this.selected));
    }
    if (key !== "default") {
        //definition inside list
        item = switcher.create("dd");
        //add selected class name to definition
        item.className = "selected";
        // add the switch first id
        //if (this.dl.childNodes.length == 2) item.id = firstIdName;
        item.id = key;
    } else {
        item = this.defitem;
        // fix the label now that we know what it should be
        if (item.className == "")
            item.firstChild.firstChild.data = val;
        else
            item.firstChild.data = val + this.selected;
    }
    this.dl.appendChild(item);
    //if key is default
    if (key == "default") {
        //add plain text inside definition
//        var link = item.appendChild(document.createTextNode(val + this.selected));
    } else if (switcher.cookie != null && switcher.cookie.indexOf(" " + key + " ") != -1) {
        //else if cookie exists and its value matches this key
        //add plain text inside definition
        link = item.appendChild(document.createTextNode(val + this.selected));
        //if this is *not* the default item
        if (key != "default") {
            //turn default text back into a link 
            // we're doing it this way, instead of just creating a default link because at the point where the default item is created
            //we don't know whether it should be a link or not so we create it as plain text to begin with, and convert it here
            //because we know, now, that the default should be a link
            //remove selected class name
            this.defitem.className = "";
            //remove the text node
            var label = this.defitem.removeChild(this.defitem.firstChild);
            //and replace it with a link to select the default
            var attrs = { "href": "javascript:void(\"" + this.defitem.id + "\", \"" + label.data + "\")", "text": label.data };
            link = this.defitem.appendChild(switcher.create("a", attrs));
        }
    } else {
        //else if cookie doesn't exist or value doesn't contains this key
        //remove selected class name
        item.className = "";
        //add link inside definition
        //javascript: uri is used to store key value
        //and so that the link is navigable with the keyboard
        attrs = { "href": "javascript:void(\"" + key + "\", \"" + val + "\")", "text": val };
        link = item.appendChild(switcher.create("a", attrs));
    }

    //bind onclick handler to definition item
    item.onclick = function() {
        //if this item has no links, don't continue
        if (this.getElementsByTagName("a").length == 0) { return false; }
        //get definitions in this list
        var items = self.dl.getElementsByTagName("dd");
        var len = items.length;
        // Matthew's inserted separator code
        var dd = new Array(0);
        for (var i = 0; i < len; i++) if (items[i].className != separatorClassName) dd[dd.length] = items[i];
        var len = dd.length;
        //for each definition
        for (var i = 0; i < len; i++) {
            //if it's this one
            if (dd[i] == this) {
                //store the value of i
                //which is the index of this option in the switcher group
                var ind = i;
                break;
            }
        }
        //save the current switcher state
        switcher.save(
            self.dl.id.replace("select_", ""), //convert id of selector into switcher ident (label)
            self.classes[ind], //get key from class array
            ind, //the index of this option in the switcher group
            self //a reference to this bodySwitcher object
            );
        //redraw the list
        self.redraw(ind, this);
        return true;
    };
    return true;
};
bodySwitcher.prototype.defineSeparator = function(sep) {
    if (typeof this.dl == "undefined") { return false; }
    var self = this;
    var item = this.dl.appendChild(switcher.create("dd"));
    item.className = separatorClassName;
    if (this.dl.childNodes.length == 2) item.id = firstIdName;
    attrs = { "text": sep };
    link = item.appendChild(switcher.create("span", attrs));
    return true;
};

//redraw the DD items with links or selected text as appropriate
bodySwitcher.prototype.redraw = function(ind, link) {
    //get definitions in this list
    var items = this.dl.getElementsByTagName("dd");
    var len = items.length;
    // Matthew's inserted separator code
    var dd = new Array(0);
    for (var i = 0; i < len; i++) if (items[i].className != separatorClassName) dd[dd.length] = items[i];
    len = dd.length;
    //for each definition
    for (var i = 0; i < len; i++) {
        //if it's the previously selected value it will only be a text node
        if (dd[i].firstChild.nodeName == "#text") {
            //remove "selected" class name from definition
            dd[i].className = "";
            //remove the text node
            dd[i].removeChild(dd[i].firstChild);
            //and replace it with a link to reselect this style
            var attrs = { "href": "javascript:void(\"" + this.classes[i] + "\", \"" + this.labels[i] + "\")", "text": this.labels[i] };
            dd[i].appendChild(switcher.create("a", attrs));
        }
    }

    //we're doing this outside the previous loop
    //because the previous process needs to have run over the whole list already
    //so that the link we're sending focus to in this process definitely exists
    //if this option the last item, send focus to the first item
    //if it's any other item, send focus to the next item
    //this is because the activated link just had the focus
    //and removing a focussed element can cause
    //the page focus caret to be lost or reset to the top
    //an added benefit is that it allows you to toggle between
    //all styles in a group by repeatedly pressing enter
    // ** setting this focus causes the wrong element to be focussed, and the focus pseudo-element incorrectly shows the element as selected
    //items[(ind == len - 1 ? 0 : ind + 1)].firstChild.focus();
    //remove the link
    link.removeChild(link.firstChild);
    //add a text node label to replace the link
    link.appendChild(document.createTextNode(this.labels[ind] + this.selected));
    //add "selected" class name to definition
    dd[ind].className = "selected";
};

//update the switcher interface from programatic option changes
bodySwitcher.prototype.update = function(ind) {
    //redraw the list, if the switcher control exists
    if (typeof this.dl != "undefined") { this.redraw(ind, this.dl.getElementsByTagName("dd")[ind]); }
};