
/*!
*   Motors.co.uk whitelabel search library
*	Copyright motors.co.uk 2011
*/

/*
*	inject support libraries if not present
*/

function injectScript(id, url, callback) {
    var head = document.getElementsByTagName("head")[0],
        script = document.createElement('script');
    script.type = 'text/javascript';
    script.id = id;
    script.src = url;
    if (typeof callback === 'function') {
        script.onload = function () {
            if (!script.onloadDone) {
                script.onloadDone = true;
                callback();
            }
        };
        script.onreadystatechange = function () {
            if (("loaded" === script.readyState || "complete" === script.readyState) && !script.onloadDone) {
                script.onloadDone = true;
                callback();
            }
        };
    }
    head.appendChild(script);
}

var script = {
    jq: function () {
        injectScript('jq', 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js', script.jqui);
    },
    jqui: function () {
        injectScript('jqui', 'http://cdn2.motors.co.uk/v2live/wl/js/wl.jquery-ui.js', script.auto);
    },
    auto: function () {
        injectScript('autocomplete', 'http://cdn2.motors.co.uk/v2live/scripts/jquery/jquery.autocomplete.js', script.init);
    },
    json2: function () {
        injectScript('json2', 'http://cdn2.motors.co.uk/v2live/wl/js/wl.json2.min.js');
    },
    init: function () {
        $(function () { motors.init(); });
    }
};

if (typeof jQuery === 'undefined') {
    script.jq(); // load jquery
} else {
    if (typeof jQuery.ui === 'undefined') {
        script.jqui(); // load jquery ui
    } else {
        script.auto(); // do autocomplete then bind motors event handlers & init search panel
    }
}

if (!this.JSON) {
    script.json2(); // ie6/7 support for json
}

// google analytics
if (typeof _gaq == 'undefined')
    var _gaq = _gaq || [];
(function () {
    var ga = document.createElement('script'),
        s = document.getElementsByTagName('script')[0];
    ga.type = 'text/javascript';
    ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    s.parentNode.insertBefore(ga, s);
}());
function trackEvent(name, val) {
    _gaq.push(['_trackEvent', name, val]);
    _gaq.push(['motors._trackEvent', name, val]);
}

// abce tracking
function motorsAbce() {
    var abce = document.createElement('img');
        abce.id = "motorsAbce";
        abce.setAttribute('src', 'http://images.motors.net/motabc.gif?&url=' + escape(window.location.host));
    document.body.appendChild(abce);
}

// log error
window.onerror = function (msg, url, line) {
    var error = "Error in " + url + "\n[line " + line + "] " + msg;
    //alert(error);
};

// fix array.indexOf in IE
if (!Array.indexOf) {
    var i;
    Array.prototype.indexOf = function (obj, start) {
        for (i = (start || 0); i < this.length; i++) {
            if (this[i] == obj) {
                return i;
            }
        }
        return -1;
    };
}

/*
*  motors object
*/

var motors = function (window, document, undefined) {

    var config = {
        "search": {
            "data": {},
            "sliders": {},
            "requestedpage": null,
            "numberofresultsperpage": "15",
            "totalpages": null,
            "requestdatetime": null,
            "totalresults": null,
            "keywordsearch": null,
            "ignorevalidation": null,
            "LocationUniqueId": null,
            "postcode": null,
            "searchType": "UsedCar",
            "dealeronlysearch": "Yes",
            "orderby": "distance"
        },
        "forcemake": null,
        "ajax": "/Motors",
        "update": true
    };

    // drop a cookie
    function setCookie(name, value, days) {
        var date,
            expires = "";
        if (days) {
            date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = "; expires=" + date.toGMTString();
        }
        document.cookie = name + "=" + value + expires + "; path=/";
    }

    function getCookie(name) {
        var cookieArr,
            cookie,
            i = 0;
        name += "=";
        cookieArr = document.cookie.split(';');
        for (; i < cookieArr.length; i++) {
            cookie = cookieArr[i];
            while (cookie.charAt(0) === ' ') {
                cookie = cookie.substring(1, cookie.length);
            }
            if (cookie.indexOf(name) === 0) {
                return cookie.substring(name.length, cookie.length);
            }
        }
        return null;
    }

    // get query string values with queryString.paramName;
    var queryString = (function () {
        var queryArr = window.location.search.substr(1).split('&'),
			queryObj = {},
            i = 0,
			temp;
        for (; i < queryArr.length; i++) {
            temp = queryArr[i].split('=');
            queryObj[temp[0]] = temp[1];
        }
        return queryObj;
    } ());

    // general purpose rounding func (number, to nearest)
    function toNearest(val, num) {
        return Math.round(val / num) * num;
    }

    /* Autocomplete call */
    function autoComplete(sel) {
        var $sel = $(document.getElementById(sel));
        if ($sel.length) {
            //call autocomplete plugin using postcode/address service service
            $sel
            .autocomplete(config.ajax + '/completion?', {
                width: 190,
                cacheLength: 1,
                matchContains: true,
                extraParams: {
                    'c': function () {
                        return 'motlocsimple' + $sel.val().substring(0, 1).toLowerCase();
                    }
                },
                parse: function (data) {
                    var dtArr = [], z = 2, val;
                    if (typeof data === "string") { data = $.parseJSON(data); } // incase the content type gets mangled
                    for (; z < data.length; z = z + 2) {
                        dtArr.push(data[z]);
                    }
                    return $.map(dtArr, function (row) {
                        val = row.split('|')[1];
                        return {
                            data: row,
                            value: val,
                            result: val
                        };
                    });
                },
                formatItem: function (item) {
                    var id = item.split('|')[1];
                    return '<span id="' + id + '">' + id + '</span>';
                }
            })
            .result(function (event, data) {
                if (sel.indexOf('location') > -1) {
                    config.search.postcode = data.split('|')[1];
                }
            })
            .bind('click', function () {
                $(this).select();
            });
        }
    }

    /*
    *  Vehicle count handler
    */

    var vehicleCountHandler = function () {
        // do stuff with the ajax response
        function populateVehicleCount(navigator, data) {

            var countObj = $.parseJSON(data), itemObj, c, d, navEl, navSpan, span;

            // limit context for model popups
            if (navigator.indexOf('makemodels') > -1) {
                var visibleModelPopup = $('div.modelsPopup:visible');
                if (visibleModelPopup.length > 0) {
                    navEl = visibleModelPopup[0];
                } else {
                    return false; // no popup!
                }
            } else {
                navEl = document.getElementById(navigator);
            }
            navSpan = navEl.getElementsByTagName('SPAN');

            // clear counts
            for (d = 0; d < navSpan.length; d++) {
                if (navSpan[d].className === "count" && navSpan[d].id !== "cntany") {
                    navSpan[d].innerHTML = '(0)';
                }
            }

            // add correct entries

            if (countObj && countObj.navigator !== null) {
                itemObj = countObj.navigator.item;
                if (itemObj instanceof Array) { // if the json object only contains one entry it breaks stuff, so test here if it's an array
                    for (c = 0; c < itemObj.length; c++) {
                        // have to loop through spans rather than targeting directly because of the duplicate IDs in the popular/regular make-model stuff
                        for (e = 0; e < navSpan.length; e++) {
                            if (navSpan[e].id === 'cnt' + itemObj[c].value) {
                                navSpan[e].innerHTML = '(' + itemObj[c].count + ')';
                            }
                        }
                    }
                } else {
                    for (e = 0; e < navSpan.length; e++) {
                        if (navSpan[e].id === 'cnt' + itemObj.value) {
                            navSpan[e].innerHTML = '(' + itemObj.count + ')';
                        }
                    }
                }
                /*
                // does not work for make/popularmake becauses of duplicate IDs - if we ever phase this out roll back to this code!
                span = document.getElementById('cnt' + itemObj[c].value);
                if (span && span.id !== "cntany") {
                span.innerHTML = '(' + itemObj[c].count + ')';
                }
                */
            }
        }
        // kill all nav updates
        function killVehicleCount() {
            var obj = motors.vehicleCount, xhr;
            for (xhr in obj) {
                if (obj.hasOwnProperty(xhr)) {
                    if (typeof motors.vehicleCount[xhr] === 'object' && xhr.readystate !== 4) { // abort any existing ajax requests
                        motors.vehicleCount[xhr].abort();
                    }
                }
            }
        }
        // get vehicle numbers
        function getVehicleCount(navigator) {
            var checkedJson,
                modifiedJson = {},
                urlNavigator = navigator,
                modelName = false;

            if (config.update === false) {

                // don't run if update is disabled
                return false;

            } else {

                // string then parse to clone object rather than deepCopy, just setting var == obj sets it up as a reference;
                modifiedJson = JSON.parse(JSON.stringify(config.search));

                var visibleModelPopup = $('#' + navigator + ' ' + 'div.modelsPopup:visible'),
                    visibleModelChecked = visibleModelPopup.find('input:checked');

                // querying a models list for a popup
                if (navigator.indexOf('makemodels') > -1) {
                    urlNavigator = "nav-models";

                    // revent the return false from leaving a hanging request
                    if (motors.vehicleCount && typeof motors.vehicleCount[urlNavigator] === 'object') {
                        motors.vehicleCount[urlNavigator].abort();
                    }

                    // add only the current make to the make/model popup totals
                    if (visibleModelPopup.length > 0) {
                        modelName = visibleModelPopup[0].previousSibling.innerHTML; //parentNode.className.replace(/\sactive/gi, '');
                        if (modifiedJson.data === null) modifiedJson.data = {};
                        if (modifiedJson.data && modelName !== false) {
                            //alert('only updating ' + modelName);
                            if (modifiedJson.data.manufacturer === undefined) modifiedJson.data.manufacturer = {};
                            modifiedJson.data.manufacturer.item = { "name": modelName }
                        }
                    } else {
                        return false; // don't do anyhting with hidden-parent popups
                    }

                } else {
                    config.forcemake = null; // don't coerce all-models for a single make if it's not a make/model popup
                }

                // kill any empty manufacturer objects
                if (modifiedJson && modifiedJson.data && modifiedJson.data.manufacturer) {
                    for (i = 0; i < modifiedJson.data.manufacturer.item.length; i++) {
                        if (modifiedJson.data.manufacturer.item[i] && modifiedJson.data.manufacturer.item[i].models && modifiedJson.data.manufacturer.item[i].models.item.length == 0) {
                            // handle more than 1 manufacturer in the current array
                            if (modifiedJson.data.manufacturer.item instanceof Array) {
                                modifiedJson.data.manufacturer.item.splice(i, i + 1);
                            } else {
                                delete modifiedJson.data.manufacturer.item[i];
                            }
                        }
                    }
                }
                //if (modifiedJson.data.manufacturer !== undefined)
                //    console.log(JSON.stringify(modifiedJson.data.manufacturer));

                // kill any previous requests of same type
                if (motors.vehicleCount === undefined) {
                    motors.vehicleCount = {};
                } else if (typeof motors.vehicleCount[urlNavigator] === 'object') {
                    motors.vehicleCount[urlNavigator].abort();
                }

                checkedJson = '{"document":' + JSON.stringify(modifiedJson) + ' }';

                // create the current request
                motors.vehicleCount[navigator] = $.ajax({
                    type: "POST",
                    url: "/Motors/Ajax/Navigator.ashx?" + urlNavigator,
                    dataType: "text",
                    data: checkedJson,
                    success: function (data, status, xhr) {
                        populateVehicleCount(navigator, data);
                    },
                    complete: function (jqXHR, textStatus) {
                        motors.vehicleCount[urlNavigator] = false;
                    }
                });

            }
        }
        // update vehicle numbers
        var updateVehicleCount = function () {
            var el = document.getElementById('search-more'),
                divs = el.getElementsByTagName('DIV'),
                navs, a, b, navigator;
            if (document.getElementById('location').value) { // don't bother if there is no location
                for (a = 0; a < divs.length; a++) {
                    if (divs[a].className.indexOf('active') > -1) { // get active panel
                        navs = divs[a].childNodes;
                        for (b = 0; b < navs.length; b++) {
                            if (navs[b].className == "nav") { // get navigators
                                navigator = navs[b].id;
                                getVehicleCount(navigator);
                            }
                        }
                    }
                }
            }
        };
        return {
            update: updateVehicleCount,
            kill: killVehicleCount
        };

    } ();

    /*
    *  Search Results
    */

    var resultsHandler = function () {

        // validate dealer email form
        function validEmailForm(form) {
            var valid = true,
                formFields = form[0].getElementsByTagName('INPUT'),
                emailRegex,
                inputCount,
                inputName,
                inputValue,
                input;
            emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
            for (inputCount = 0; inputCount < formFields.length; inputCount++) {
                input = formFields[inputCount];
                inputName = formFields[inputCount].name;
                inputValue = input.value;
                if (inputName === "emailaddress") {
                    if (emailRegex.test(inputValue) === true) {
                        input.className = "";
                    } else {
                        valid = false;
                        input.className = "invalid";
                    }
                } else if (inputName === "name") {
                    input = formFields[inputCount];
                    if (input.value) {
                        input.className = "";
                    } else {
                        valid = false;
                        input.className = "invalid";
                    }
                }
            }
            if (valid === false && typeof input === "object") {
                input.focus();
            }
            return valid;
        }
        // get vehicle address and do callback (ensure address details are present in map and email tabs cause they're not in fast)
        function getAddress(carId, callback) {
            var response = "<h2>nothing happened</h2>",
                ajaxType = "GET",
                start,
                end;
            motors.request = $.ajax({
                type: ajaxType,
                url: config.ajax + "/Ajax/VehicleTabDetails.ashx?carID=" + carId + "&tab=usedCarDetails",
                dataType: "text",
                success: function (data, status, xhr) {
                    response = data;
                },
                error: function (xhr, status, error) {
                    response = "<h2>An error occured</h2>";
                },
                complete: function (jqXHR, textStatus) {
                    start = response.indexOf('<span class="address" id="address-') + 44; // include '00000000">'
                    end = response.indexOf('</span><p class="printdetails">');
                    if (end > -1) {
                        $('#vehicle-main-' + carId + ' .address').text(response.substring(start, end).replace(/<br>/gi, ', ').replace(/\s\s/g, ''));
                    }
                    callback();
                }
            });
            return false;
        }
        // retrieve (and submit) vehicle details. its just a wrapper for the regular ajax func with the bind handler doing the special case stuff.
        function getDetails(type, targ, url, getPost, data) {
            var response = "<h2>nothing happened</h2>",
                postData = (data === undefined) ? "" : data,
                ajaxType = (getPost === undefined) ? "GET" : getPost,
                vehicleId = targ.id.split('-')[2];
            motors.maprequest = $.ajax({
                type: ajaxType,
                url: url,
                data: postData,
                dataType: "text",
                success: function (data, status, xhr) {
                    response = data;
                },
                error: function (xhr, status, error) {
                    response = "<h2>An error occured</h2>";
                },
                complete: function (jqXHR, textStatus) {
                    $('.content', targ)[0].innerHTML = response;
                    // post email back
                    if (type === "email") {
                        insertAddress(targ);
                        $('input[type=button]', targ)[0].onclick = function () {
                            var form = $('form', targ);
                            if (validEmailForm(form)) {
                                getDetails('sendemail', targ, config.ajax + '/Ajax/EmailDealer.ashx', 'POST', form.serialize());
                            }
                        };
                    } else if (type == "details") {
                        insertRotator(targ);
                        /*
                        // this now happens in the map and email tabs so no reason to do it here. request will be cached in teh browser anyway.
                        // update address field with full address
                        var thisAddress = document.getElementById('address-' + vehicleId);
                        if (thisAddress) {
                        var fullAddress = thisAddress.innerHTML.replace(/<br>/gi, ', ').replace(/&nbsp;|\s\s|\n/gi, '');
                        $('#vehicle-main-' + vehicleId + ' .address').text(fullAddress);
                        }
                        */
                    }
                    motors.maprequest = false;
                }
            });
            return false;
        }
        function insertImage(path, isVideo) {
            var html, o = [];
            if (isVideo === true) {
                o.width = '300';
                o.height = '225';
                o.type = 'rtmp';
                o.streamer = 'rtmp://flv2.motors.co.uk/ondemand';
                o.file = path;
                o.id = 'used-videos';
                o.javascriptid = 'mediaplayer';
                o.image = 'http://cdn2.motors.co.uk/v2live/images/logo.png';
                o.enablejs = 'true';
                o.plugins = 'gapro-1&amp;gapro.accountid=UA-1084356-1&amp;gapro.trackstarts=true&amp;gapro.trackpercentage=true&amp;gapro.tracktime=true';
                html = '<embed width="' + o.width + '" height="' + o.height + '" flashvars="width=' + o.width + '&amp;height=' + o.height + '&amp;type=' + o.type + '&amp;streamer=' + o.streamer + '&amp;file=' + o.file + '&amp;id=' + o.id + '&amp;javascriptid=' + o.javascriptid + '&amp;image=' + o.image + '&amp;enablejs=' + o.enablejs + '&amp;plugins=' + o.plugins + '" wmode="transparent" fixie8bug="true" allowscriptaccess="always" allowfullscreen="false" quality="high" name="mediaplayer" id="mediaplayer" style="" src="http://cdn2.motors.co.uk/v2live/images/media/player.swf" type="application/x-shockwave-flash">';
            } else {
                html = '<img src="' + path + '" />';
            }
            return html;
        }
        // big image thingy
        // TODO properly track the position metadata and make the image clicky
        function insertRotator(el) {
            var carId = el.parentNode.id.split('-')[1],
                dealerPostcode = $('#vehicle-main-' + carId).find('span.address').text().replace(/\s/gi, '+'),
                dealerRotator = document.getElementById('rotator-' + carId),
                dealerImages = [];
            $('#vehicle-details-' + carId).find('div.usedCarThumbnails').find('img').each(function () {
                dealerImages.push(this.alt);
            });
            $.data(dealerRotator, 'images', dealerImages);
            $.data(dealerRotator, 'position', 0);
            dealerRotator.innerHTML = insertImage(dealerImages[0], (dealerImages[0].indexOf('.flv') > -1) ? true : false);
            $('#vehicle-details-' + carId).find('div.usedCarThumbnails').find('img').click(function () {
                var el = $(this),
                    details = el.parentsUntil('div.search-result-more').last().parent(),
                    imageId = el.parent().prevUntil('div.usedCarThumbnails').length,
                    carId = details[0].id.split('-')[2],
                    dealerId = document.getElementById('hidden-data-' + carId).value.split(',')[2],
                    isVideo = (this.parentNode.className.indexOf('video') > -1) ? true : false;
                details
                    .find('div.usedCarRotator')
                    .html(insertImage(this.alt, isVideo));
                $.post('/motors/audit/image/' + imageId + '/' + carId + '/' + dealerId);
                trackEvent('image-view', imageId + ', ' + carId + ', ' + dealerId);
            });
            trackEvent('details-view', carId);
        }
        // google map
        // only puts a static map in for now.
        // TODO: use the rich api etc.
        function insertGoogleMap(el) {
            var carId = el.parentNode.id.split('-')[1],
                dealerPostcode = $('#vehicle-main-' + carId).find('span.address').text(),
                googlePostcode = dealerPostcode.replace(/\s/gi, '+'),
                dealerMap = document.getElementById('dealer-map-' + carId),
                googleLink = document.createElement('a');
            dealerMap.style.backgroundImage = "url('http://maps.google.com/maps/api/staticmap?center=" + googlePostcode + ",UK&zoom=14&size=300x250&maptype=roadmap&sensor=false')";
            dealerMap.innerHTML = '<img src="http://cdn2.motors.co.uk/v2live/images/wl/blue-pin.png" class="map-pin" alt="Dealer location" title="' + dealerPostcode + '" />';

            googleLink.setAttribute('href', 'http://maps.google.co.uk/maps?f=q&source=s_q&hl=en&q=' + googlePostcode);
            googleLink.setAttribute('class', 'googleLink');
            googleLink.setAttribute('target', '_blank');
            googleLink.innerHTML = "View in Google Maps";
            dealerMap.parentNode.appendChild(googleLink);

            trackEvent('map-view', carId + ', ' + dealerPostcode);
        }
        // get dealer address for map and email tabs
        function insertAddress(el) {
            var carId = el.parentNode.id.split('-')[1],
                $searchResult = $('#vehicle-main-' + carId),
                dealerName = $searchResult.find('span.dealer').text(),
                dealerPhone = $searchResult.find('span.telephone').text(),
                dealerAddress = $searchResult.find('span.address').text().replace(/,/gi, '<br/>');
            //dealerPostcode = $searchResult.find('span.address').text();
            $('.content', el).prepend('<div class="usedCarAddress"><h4>' + dealerName + '</h4><span class="phone">' + dealerPhone + '</span><span class="address">' + dealerAddress + '</span></p></div>');
        }
        // toggle car details (show = true|false);
        function showDetails(el, show) {
            //didn't put the handler here because i'd have got the id by the time this ever happened.
            //rework this whole function to take into account garethfication.
            //if (typeof el == 'string') {               el = document.getElementById(el);            }
            var carArr = el.id.split('-'),
                carId = carArr[carArr.length - 1],
                details = document.getElementById('vehicle-details-' + carId),
                callback;
            $('.active', document.getElementById('tabs-' + carId)).removeClass('active');
            if (show === true) {
                $('.content', details)[0].innerHTML = '<span class="loading"></span>';
                if (el.className == "details") {
                    getDetails('details', details, config.ajax + '/Ajax/VehicleTabDetails.ashx' + "?carID=" + carId + "&tab=usedCarDetails");
                    // We don't need to log this...
                    //$.post(config.ajax + '?RequestType=image&auditEvent=details-view&imageId=0&carId=' + details.id.split('-')[2] + '&dealerId=' + $(details.parentNode).find('span.dealerid').text()); // first image is pre-loaded
                } else if (el.className == "email") {
                    callback = function () {
                        getDetails('email', details, config.ajax + '/Ajax/EmailDealer.ashx' + "?carID=" + carId);
                    };
                    getAddress(carId, callback);
                } else if (el.className == "map") {
                    callback = function () {
                        $('.content', details).html('<div id="dealer-map-' + carId + '" class="dealer-map">No map data available for this dealer</div>');
                        insertAddress(details);
                        insertGoogleMap(details);
                        $.post('/motors/audit/map-view/' + details.id.split('-')[2] + '/' + $(details.parentNode).find('span.dealerid').text());
                    };
                    getAddress(carId, callback);
                }
                $(el).addClass('active');
                details.style.display = 'block';
            } else {
                details.style.display = 'none';
            }
        }
        // bind search results events
        function setupResults() {

            $('div.search-result-main').find('a').live('click', function (e) {
                if (this.parentNode.className != "option") {
                    var showMe = true,
                        el = this;
                    e.preventDefault();
                    if (el.className.indexOf('active') > -1) { showMe = false; }
                    if (el.className == 'title' || el.parentNode.className == 'controls') { el = $(this).parentsUntil('#search-result-main').find('ul.tabs').find('a')[0] } //allow clicking on title etc to pop details // TODO: rewrite this bit 
                    showDetails(el, showMe);
                }
            });

            $('a.search-result-close').live('click', function (e) {
                e.preventDefault();
                showDetails(this, false);
            });
        }
        return {
            init: setupResults
        };

    } ();

    /*
    *  Search, Pagination & Sort
    */

    var searchHandler = function () {

        // sort results (just pass to ajaxSearch)
        function sortResults(el) {
            var option = el.options[el.selectedIndex].value;
            config.search.orderby = option;
            ajaxSearch(1, option);
        }
        // request specific page (prevNext = "prev"|"next"|integer)
        function requestPage(prevNext) {
            if (config.search.requestedpage) { //quicky way of finding if a search has run
                var newPage = parseInt(config.search.requestedpage, 10);
                if (prevNext && prevNext == "prev") {
                    newPage--;
                } else if (prevNext && prevNext == "next") {
                    newPage++;
                } else {
                    newPage = prevNext;
                }
                config.search.requestedpage = newPage;
                ajaxSearch(newPage);
            } else {
                ajaxSearch(1); // if there is no current page request failover to page 1
            }
        }
        // build numeric pagination list
        // the logic in here is a bit scrappy, but it works.
        function calculatePagination() {
            var currentPage = config.search.requestedpage,
                totalPages = config.search.totalpages,
                startPos = currentPage - 5,
                pageStr = "";
            if (startPos < 1) {
                startPos = 1;
            } else if (startPos + 10 > totalPages) {
                startPos = totalPages - 9;
            }
            if (startPos > 1) { pageStr += '...'; }
            if (totalPages > 1) {
                for (var i = 0; i < 10; i++) {
                    if ((startPos <= totalPages) && startPos > 0) {
                        attr = (currentPage == startPos) ? 'class="current"' : 'class="gotopage" href="#search-results" onclick="motors.page(' + startPos + ')"';
                        pageStr += '<a ' + attr + '>' + startPos + '</a>';
                    }
                    startPos++;
                }
            }
            if (totalPages > 10 && (startPos - 1 < totalPages)) { pageStr += '...'; }
            return pageStr;
        }
        // update page and vehicle totals (request time is how long the ajax request took) hide if no pages returned
        function populateSearchCount(requestTime) {
            var resultsPerPage = parseInt(config.search.numberofresultsperpage, 10),
                requestedPage = parseInt(config.search.requestedpage, 10),
                totalPages = parseInt(config.search.totalpages, 10),
                totalResults = parseInt(config.search.totalresults, 10),
                pagination = document.getElementById('search-results-pagination'),
                displayPages = "none";
            // show pagination if there are any search results
            if (config.search.totalresults > 0) {
                document.getElementById('disp-result-start').innerHTML = ((resultsPerPage * requestedPage) - resultsPerPage) + 1;
                document.getElementById('disp-result-end').innerHTML = (totalResults < 15 || (requestedPage == totalPages)) ? totalResults : resultsPerPage * requestedPage;
                document.getElementById('disp-result-total').innerHTML = totalResults;  //totalPages*resultsPerPage;
                document.getElementById('disp-result-time').innerHTML = Math.round((parseInt(requestTime, 10) / 1000) * 10) / 10;
                document.getElementById('search-results-page-list').innerHTML = calculatePagination();
                //$('a.search-results-prev', pagination)[0].style.visibility = (requestedPage > 1) ? "visible" : "hidden";
                //$('a.search-results-next', pagination)[0].style.visibility = (requestedPage < totalPages) ? "visible" : "hidden";
                displayPages = "block";
            }
            pagination.style.display = displayPages;
        }
        //return an array/string of checked options from a navigator and remove unused Json objects
        function getCheckedOptions(jsonVar) {
            var modified = $.data(document.getElementById(jsonVar), 'modified'),
                searchObj = config.search,
                jsonArr = [];
            if (searchObj.data === null) {
                searchObj.data = {};
            }
            if (modified === null || modified === undefined || modified === "") {
                delete config.search.data[jsonVar];
            } else {
                searchObj.data[jsonVar] = {};
                // is modified an array (checkbox) or a string (radio button)
                if (typeof modified == "object") {
                    for (var i = 0; i < modified.length; i++) {
                        jsonArr.push(modified[i]);
                    }
                    searchObj.data[jsonVar].item = jsonArr;
                } else {
                    searchObj.data[jsonVar].item = modified;
                }
                if (config.search.data[jsonVar] == null || (typeof modified == "object" && jsonArr.length == 0)) {
                    // re-get whole object to check if it is now null (may contain more than just jsonVar)
                    delete config.search.data[jsonVar];
                }
            }
        }
        // parse keywords as an array and chuck at the json object
        function getKeywordOptions() {
            var keywords = document.getElementById('keyword-disp').getElementsByTagName('a'),
                keywordArr = [];
            for (var i = 0; i < keywords.length; i++) {
                keywordArr.push(keywords[i].innerHTML);
            }
            config.search.keywordsearch = keywordArr;
        }
        // update json object containing search parameters
        function populateSearchObject() {
            var price = $('#price').slider('values'),
                navData;
            getKeywordOptions();
            config.search.requestdatetime = new Date().getTime();
        }
        // turn the ajax request's data into something useful. comes back wrapped in XML so treat it as text to avoid parsing multiple times
        function processAjaxSearch(results, startTime) {
            var requestTime = new Date().getTime() - startTime,
                searchResult = results.substring(results.indexOf('<searchResults>') + 15, results.indexOf('</searchResults>')),
                jsonStr = results.substring(results.indexOf('<filter>') + 8, results.indexOf('</filter>'));
            config.search = $.parseJSON(jsonStr).document;  // update json object with search data
            config.search.totalpages = Math.ceil(parseInt(config.search.totalresults, 10) / parseInt(config.search.numberofresultsperpage, 10)); // correct hilarious FAST rounding error :/
            populateSearchCount(requestTime);               // update page and result count details
            if (config.search.totalresults === 0 || config.search.totalresults == "0") {
                searchResult = "<h2>No matching cars found.</h2>";
            }
            return searchResult;
        }
        // clone the pagination (quicker than re-building the whole thing)
        function clonePagination() {
            var paginationTop = document.getElementById('search-results-page'),
                paginationBottom = paginationTop.cloneNode(true),
                paginationContainer = document.getElementById('search-results-container'),
                backToTop = document.createElement('a');

            paginationBottom.id = "search-results-page-bottom";
            paginationContainer.appendChild(paginationBottom);

            backToTop.setAttribute('href', '#search-panel');
            backToTop.setAttribute('class', 'search-results-refine');
            backToTop.innerHTML = "Back to search";
            paginationContainer.appendChild(backToTop);
        }
        // perform ajax search
        function ajaxSearch(pageNo, orderBy, keyword) {

            if (pageNo === undefined) { pageNo = "1"; }
            if (keyword === undefined) { keyword = "false"; }
            if (orderBy === undefined) { orderBy = config.search.orderby; }
            var searchUrl = config.ajax + '/Ajax/search.ashx' + "?pageNo=" + pageNo + "&iskeyword=" + keyword + "&OrderBy=" + orderBy,
                priceSlider = $('#price').slider('values'),
                startTime = new Date().getTime(),
                searchResults = "<h2>No cars found</h2>",
                jsonData,
                doComplete = true;


            if (typeof motors.request === 'object') {
                motors.request.abort();
            }

            setCookie('motorsPostcode', config.search.postcode, 30);

            searchButtonTransition();   // toggle search button (this is hilariously slow)
            resultsTransition();        // toggle 'updating search results'
            vehicleCountHandler.kill(); // kill any nav count updates

            jsonData = '{"document":' + JSON.stringify(config.search) + ' }';
            trackEvent('search', jsonData);

            motors.request = $.ajax({
                type: "POST",
                url: searchUrl,
                dataType: "text",
                //contentType: 'application/json',
                data: jsonData,
                success: function (data, status, xhr) {
                    if (data.indexOf('No postcode given by Fast') > -1) {
                        searchResults = '<h2>Location not recognised.</h2>';
                        document.getElementById('search-results-pagination').style.display = "none";
                    } else if (data.indexOf('<searchResults><div class="search-result"') == -1 && pageNo > 1) {
                        ajaxSearch(1); // if the user uses the next button after updating the json object to a criteria that won't return any vehicles for that page search again
                    } else {
                        searchResults = processAjaxSearch(data, startTime);
                    }
                },
                error: function (xhr, status, error) {
                    console.log(xhr + ", " + status + ", " + error);
                    searchResults = "<h2>An error occured</h2>";
                    document.getElementById('search-results-pagination').style.display = "none";
                    motors.request = false;
                },
                complete: function (jqXHR, textStatus) {
                    if ((searchResults == null) || (searchResults == "")) {
                        document.getElementById('search-results-container').innerHTML = "<h2>To get the best results for you, we need your full postcode...</h2><p>To help us in your search for a used car please tell us where you are by using the postcode box highlighted above. </p>";
                        document.getElementById('search-results-pagination').style.display = "none";
                    }
                    else {
                        document.getElementById('search-results-container').innerHTML = searchResults;
                    }
                    resultsTransition();                // toggle 'updating search results'
                    searchButtonTransition();           // toggle search button
                    motors.request = false;             // allow a new search
                    if (searchResults.length > 50) {    // both error messages are less than 50 characters. just a quick way of excluding them.
                        clonePagination();              // create cloned pagination at bottom of results
                    }
                    vehicleCountHandler.update();       // update search counts (after resetting the request because they're slow)
                }
            });
            /*
            } else {
            motors.request.abort(); // if there is a search active abort it.
            document.getElementById('search-results-container').innerHTML = "";
            }*/
        }
        // set all search options to default values and close more options
        function resetSearchOptions() {
            var optionsInputs = document.getElementById('search-more').getElementsByTagName('input'),
				optionsTabs = document.getElementById('search-more-options').getElementsByTagName('a'),
                distanceSelect,
                currentPanel,
                cookieLocation = getCookie('motorsPostcode');

            config.update = false; // do not update search counts

            if (cookieLocation !== undefined && cookieLocation !== null && cookieLocation !== "null") {
                document.getElementById('location').value = cookieLocation;  // replace location with cookied location
                config.search.postcode = cookieLocation;
            }

            // set default distance
            distanceSelect = document.getElementById('distance');
            distanceSelect.selectedIndex = distanceSelect.options.length - 1;

            // reset sliders to default values
            $('div.ui-slider').each(function () {
                var $this = $(this),
                    min = $this.slider('option', 'min'),
                    max = $this.slider('option', 'max'),
                    current = $this.slider('values'),
                    inputs,
                    i = 0;
                if (current[0] > min || current[1] < max) { // if the slider has been modified reset it
                    $this.slider('option', 'values', [min, max]);
                    inputs = this.parentNode.getElementsByTagName('input'); // clear display value inputs
                    for (; i < inputs.length; i++) {
                        inputs[i].value = "";
                    }
                }
            });

            //clear checkboxes, textboxes and reset radio buttons
            for (var i = 0; i < optionsInputs.length; i++) {
                if (optionsInputs[i].type == 'checkbox') {
                    optionsInputs[i].checked = false;
                } else if (optionsInputs[i].type == 'text') {
                    optionsInputs[i].value = "";
                } else if (optionsInputs[i].type == 'radio') {
                    if (optionsInputs[i].id == 'rdoany') {
                        optionsInputs[i].checked = true;
                    }
                }
            }

            document.getElementById('keyword-disp').innerHTML = ""; //clear keywords
            $('span.modelcount', document.getElementById('search-more')).remove(); //remove models selected counts

            //clear modified tabs
            if (optionsTabs.length > 0) {
                for (var x = 0; x < optionsTabs.length; x++) {
                    optionsTabs[x].className = optionsTabs[x].className.replace(/modified|\s\s|^[ \t]+|[ \t]+$/gi, ''); // set classname to '' if you want to clear the current tab
                    //clear the jQ data object tracking modified fields
                    currentPanel = $('#' + optionsTabs[x].href.split('#')[1]);
                    if (currentPanel.data('hilight')) {
                        currentPanel
                            .data('modified', '')
                            .data('hilight', false)
                        .find('div.nav')
                            .data('modified', '');
                    }
                }
            }
            config.search.data = {};
            config.search.sliders = {};
            config.forcemake = null;
            var pop = $('div.modelsPopup:visible');
            if (pop.length > 0) {
                pop.parent().removeClass('active');
                pop.remove();
            }
            var popular = document.getElementById('makemodelspopular');
            if (popular.style.display == "none") {
                popular.style.display = "block";
                document.getElementById('makemodels').style.display = "none";
                document.getElementById('modelsubset').style.display = "block";
            }
            config.update = true; // re-enable update search counts
            vehicleCountHandler.update();
        }
        // grey out search button
        function searchButtonTransition() {
            var searchButton = document.getElementById('search-button'),
				searchText = "Update";
            if (searchButton.className.indexOf('inactive') == -1) {
                searchButton.className += " inactive";
                searchText = "Updating";
            } else {
                searchButton.className = "button"; //.replace(/inactive|\s\s|^[ \t]+|[ \t]+$/gi, ''); // no need for a regex here
            }
            searchButton.innerHTML = searchText;
        }
        // display updating results div
        function resultsTransition() {
            var updating = document.getElementById('updating-results'),
				state = (updating) ? updating.style.display : false,
				newState = 'none';
            if (state == 'none') {
                newState = 'block';
            }
            if (state != newState) {
                updating.style.display = newState;
            }
        }
        function isValidPostcode(postcode) {
            var match = postcode.match(/^((([A-PR-UWYZ][0-9][0-9]?)|(([A-PR-UWYZ][A-HK-Y][0-9][0-9]?)|(([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY])))) ?([0-9][ABD-HJLNP-UW-Z]{2}))$/i);
            return (match && (match.length > 0));
        }

        // validation for search
        function validateSearch(postcode) {
            $('div#search-criteria span.error_text').remove();
            var location = document.getElementById('location'),
                isValid = (location.value === "") ? false : true;
            if (!isValid) {
                location.focus();
                location.className = "invalid";
            } else {
                location.className = "";
            }
            if (!isValidPostcode(postcode)) {
                $('<span class="error_text">Please enter your full postcode</span>').insertAfter('#location');
                isValid = false;
            }
            return isValid;
        }
        // bind search options
        var setupSearch = function () {
            $('#search-button').click(function (e) {
                if (this.className.indexOf('inactive') == -1) {
                    if (validateSearch($('#location').val())) {
                        populateSearchObject();
                        ajaxSearch();
                    }
                    else {
                        e.preventDefault();
                    }
                }
            });
            $('#search-reset').click(function () {
                resetSearchOptions();
            });
            $('a.search-results-prev').live('click', function () {
                requestPage('prev');
            });
            $('a.search-results-next').live('click', function () {
                requestPage('next');
            });
            $('#search-results-sort').change(function () {
                sortResults(this);
            });
            $('#location')
                .bind('keyup', function (e) {
                    config.search.postcode = this.value;
                    if (e.keyCode == '13') {
                        ajaxSearch();
                    }
                })
                .bind('change', function (e) {
                    var trimmedVal = $.trim(this.value);
                    config.search.postcode = trimmedVal;
                    this.value = trimmedVal;
                    vehicleCountHandler.update();
                });
            $('#distance').bind('change', function (e) {
                var distanceSelect = document.getElementById('distance'),
                    distanceOption = distanceSelect.options[distanceSelect.selectedIndex];
                if (config.search.sliders === null || config.search.sliders === undefined) {
                    config.search.sliders = {};
                }
                config.search.sliders["nav-distance"] = {
                    "posEnd": distanceOption.value
                };
                vehicleCountHandler.update();
            });

            resetSearchOptions();
            document.getElementById('updating-results').style.display = 'none';
        };
        return {
            updateJson: getCheckedOptions,
            page: requestPage,
            reset: resetSearchOptions,
            init: setupSearch
        };

    } ();

    /*
    *  make/model popup handler
    */

    var modelPopupHandler = function () {

        // format popup into columns
        // TODO: DO THIS ON THE BACKEND!
        function createColumns(el) {
            var li = el[0].getElementsByTagName('LI'),
                ul = li[0].parentNode,
                div = ul.parentNode,
                ulClass = ul.className,
                cols = (li.length > 20) ? 3 : 2,
                rows = Math.ceil(li.length / cols),
                itemLength = li.length,
                itemCount = 0,
                itemClass,
                currentCol,
                remove;
            // nobody likes tiny columns
            if (rows > 6) {
                ul.id = "removeMe";
                div.style.width = (140 * cols) + "px";
                // col
                for (var colsCount = 1; colsCount <= cols; colsCount++) {
                    div.innerHTML += '<ul id="' + ulClass + '-' + colsCount + '" class="' + ulClass + '" title="' + ul.title + '"></ul>';
                    // row
                    for (var rowsCount = 0; rowsCount < rows; rowsCount++) {
                        if (itemCount < itemLength) {
                            currentCol = document.getElementById(ulClass + '-' + colsCount);
                            currentCol.innerHTML += '<li class="' + li[itemCount].className + '">' + li[itemCount].innerHTML + '</li>';
                            itemCount++
                        }
                    }
                }
                remove = document.getElementById('removeMe');
                div.removeChild(remove);
            }
        }
        // display number of models selected
        function updateSelectedTotal(el, checkedOptions) {
            var modelCount = $('span.modelcount', el.parentNode);
            if (checkedOptions > 0) {
                if (modelCount.length === 0) {
                    $(el.parentNode).append('<span class="modelcount">' + checkedOptions + ' selected</span>');
                } else {
                    modelCount[0].innerHTML = checkedOptions + ' selected';
                }
            } else {
                modelCount.remove();
            }
        }
        // build json search object for manufacturers from selected checkboxes
        function getMakeModelsJSON(id) {
            var makeModelJSON,
                makeModelJSONArr = [],
                makeModelArr,
                makeModelPop,
                checkedOptions,
                checkedTotal,
                ignoreModels;

            if (!config.search.data) {
                config.search.data = {};
            }
            makeModelPop = $(id).find('div.modelsPopup');
            makeModelPop.each(function () {
                makeModelArr = [];
                checkedOptions = $(this).find('input:checked');
                checkedTotal = makeModelPop.find('input:checked');
                if (checkedOptions.length > 0) {
                    //alert("checked options = " + checkedOptions.length + " of " + checkedTotal);
                    checkedOptions.each(function () {
                        if (this.nextSibling.title !== "") {
                            makeModelArr.push(this.nextSibling.title);
                        }
                    });

                    // push values out if there are any
                    //"name": this.parentNode.className.replace(/\sactive/gi, ''),
                    if (makeModelArr.length > 0) {
                        makeModelJSON = {
                            "name": this.previousSibling.innerHTML,
                            "models": {
                                "item": makeModelArr
                            }
                        };
                        makeModelJSONArr.push(makeModelJSON);
                        if (config.search.data.manufacturer === undefined || config.search.data.manufacturer === null) {
                            config.search.data.manufacturer = {};
                        }
                        config.search.data.manufacturer.item = makeModelJSONArr;
                    }
                    updateSelectedTotal(this, checkedOptions.length);
                    if ($(this).is(':visible')) {
                        vehicleCountHandler.update();
                    }
                } else {

                    //alert("no checked " + checkedOptions.length + " of " + checkedTotal);
                    // no items checked on current popup
                    if (checkedTotal.length == 0) { // was the last item checked
                        delete config.search.data.manufacturer;
                    } else {
                        checkedOptions.each(function () {
                            if (this.nextSibling.title !== "") {
                                makeModelArr.push(this.nextSibling.title);
                            }
                        });
                        if (makeModelArr.length > 0) {
                            makeModelJSON = {
                                "name": this.previousSibling.innerHTML,
                                "models": {
                                    "item": makeModelArr
                                }
                            };
                            makeModelJSONArr.push(makeModelJSON);
                            if (config.search.data.manufacturer === undefined || config.search.data.manufacturer === null) {
                                config.search.data.manufacturer = {};
                            }
                            config.search.data.manufacturer.item = makeModelJSONArr;
                        }
                    }
                    updateSelectedTotal(this, checkedOptions.length);
                    if ($(this).is(':visible')) {
                        vehicleCountHandler.update();
                    }
                }
            });
            $(id).data('hilight', (makeModelJSONArr.length > 0) ? true : false);
            tabHandler.refreshTabHilight();
        }
        // if a popup has previously been created load that, otherwise call the ajax getModelList
        function showModelsList(el, make) {
            var li, popup;
            // hide any currently open popups
            $('#makemodel').find('li.active').removeClass('active');
            li = $(el.parentNode);
            li.addClass('active');
            // get model list if there isn't one
            var hasPopup = $('div.modelsPopup', li);
            if (hasPopup.length == 0 || hasPopup[0].innerHTML == '<span class="loading"></span>') {
                getModelList(li, make);
            } else {
                vehicleCountHandler.update();
            }
        }
        // retrieve model list for a manufacturer and append to appropriate li
        function getModelList(li, make) {
            var url = config.ajax + '/Ajax/Models.ashx' + '?' + make + '&searchType=UsedCar',
                response = "<h2>something happened</h2>",
                popupClass = "modelsPopup",
                close = '<a href="javascript:void(0);" class="closeModelsPopup">OK</a>',
                content = '<div class="' + popupClass + '"><span class="loading"></span></div>';

            makeClass = make.toLowerCase().replace(/\s/g, '');
            $('#makemodel').find('li.' + makeClass).append(content);

            motors.modelsAjax = $.ajax({
                type: 'GET',
                url: url,
                dataType: "text",
                success: function (data, status, xhr) {
                    // alert("1 " + xhr + ", " + status + ", " + data);
                    response = data;
                },
                error: function (xhr, status, error) {
                    // alert("2 " + xhr + ", " + status + ", " + error);
                    response = "<h2>An error occured</h2>";
                    popupClass = "modelsPopupError";
                },
                complete: function (jqXHR, textStatus) {
                    // alert("3 " + jqXHR + ", " + textStatus);
                    if (textStatus !== "abort" && textStatus !== "error") {
                        $('#makemodel').find('li.' + makeClass).find('div.' + popupClass).html(response + close); //<-- do for both instances
                        vehicleCountHandler.update();
                    }
                    motors.modelsAjax = false;
                }
            });
        }
        // check/uncheck all models if Any Model is checked
        function allModelsToggle(el) {
            var checkStatus = el.checked;
            $(el.parentNode)
                .siblings().each(function () {
                    this.firstChild.checked = checkStatus;
                });
        }
        // bind click events for models
        var setupModelPopup = function () {
            $('#makemodels, #makemodelspopular').live('click', function (e) {
                var el = e.target,
                    updateTarget,
                    displayPopup,
                    make,
                    makeClass,
                    modelClass;
                if (el.tagName == "A") {
                    if (el.className == "closeModelsPopup") { // close popup and update vehicle counts
                        $(el.parentNode.parentNode).removeClass('active');  // hide popup
                        vehicleCountHandler.update();
                        //config.forcemake = null;
                    } else if (el.parentNode.className.indexOf('active') > -1) { // inactivate active popup
                        if (el.nextSibling && el.nextSibling.innerHTML == '<span class="loading"></span>') {
                            el.parentNode.removeChild(el.nextSibling);
                            $(el.parentNode).removeClass('active');
                        } else {
                            $(el.parentNode).removeClass('active');
                            config.forcemake = $(el).prev().text();
                        }
                        forcemake = null;
                        vehicleCountHandler.update();
                    } else {
                        //make = el.parentNode.className.replace(/\sactive/gi, '') //innerHTML;
                        make = el.innerHTML;
                        config.forcemake = make;
                        if (motors.modelsAjax) {
                            motors.modelsAjax.abort(); // stop clicking before ajax returns fully breaking list
                            vehicleCountHandler.kill();
                            motors.modelsAjax = false;
                        }
                        showModelsList(el, make); // get model list
                    }
                    //vehicleCountHandler.update();
                } else if (el.type == "checkbox") {
                    if (el.parentNode.className == "justCheckbox") { // tick options in hidden make/model menu
                        allModelsToggle(el);
                    }
                    makeClass = el.parentNode.parentNode.parentNode.parentNode.parentNode.className.split(' active')[0];
                    modelClass = el.parentNode.className;
                    // ^^ comedy parentnode chain to target correct make only!
                    $('li.' + makeClass + ' li.' + modelClass).each(function () {
                        if (this !== el.parentNode) {
                            if (this.className == "justCheckbox") {     // user clicked Select All
                                this.firstChild.checked = el.checked;
                                allModelsToggle(this.firstChild);
                            } else {
                                this.firstChild.checked = el.checked;   // only 1 checkbox to update
                            }
                        }
                    });
                    updateTarget = '#' + $(el).parentsUntil('div.nav').last().parent()[0].id;
                    getMakeModelsJSON(updateTarget);    // update search totals
                }
            });

            $('#modelsubset').bind('click', function () {
                var elHtml = "Search all makes and models",
                    elClass = "more",
                    elPop = "block",
                    elAll = "none",
                    elUpd = "#makemodelspopular";
                if (this.className == "more") {
                    /*
                    elHtml = "Search popular makes and models";
                    elClass = "less";
                    */
                    elPop = "none";
                    elAll = "block";
                    elUpd = "#makemodels";
                    this.style.display = "none";
                }
                this.innerHTML = elHtml;
                this.className = elClass;
                document.getElementById('makemodelspopular').style.display = elPop;
                document.getElementById('makemodels').style.display = elAll;
                getMakeModelsJSON(elUpd);
            });
        };

        return {
            init: setupModelPopup
        };

    } ();

    /*
    *  Sliders
    */

    var sliderHandler = function () {
        // return slider properties metadata stored in className (requires var.val notation)
        function getSliderProperties(id) {
            var sliderProperties = {}, sliderClass, sliderClasses, z, min, max;
            if (id !== undefined) {
                sliderClass = [];
                sliderClasses = document.getElementById(id).className.split(' ');
                for (var y = 0; y < sliderClasses.length; y++) {
                    if (sliderClasses[y].indexOf('.') > -1) {
                        sliderClass = sliderClasses[y].split('.');
                        sliderProperties[sliderClass[0]] = sliderClass[1];
                    }
                }
            }
            return sliderProperties;
        }
        // mark a modified slider, used to calculate if the tab should be highlighted
        // uses the code in tabHandler to mark the actual tabs
        function markModifiedSlider(el) {
            var tabSlider = el.parentNode,
                sliderProperties = getSliderProperties(el.id),
                sliderValues = $('#' + el.id).slider('values'),
                sliderStatus = el.id,
                sliderMin = (sliderProperties.min) ? parseInt(sliderProperties.min, 10) : 0,
                sliderMax = parseInt(sliderProperties.max, 10);
            if (sliderMin == sliderValues[0] && sliderMax == sliderValues[1]) {
                sliderStatus = "";
            }
            $.data(tabSlider, 'modified', sliderStatus);
            tabHandler.checkModified(el);
        }
        // update the json object from a slider
        function updateJSON(ui, el) {
            var sliderProperties = getSliderProperties(el.id),
                sliderId = sliderProperties.nav, // OLD -> 'nav-' + el.id,
                sliderObj,
                handle = (ui.handle.className.indexOf('ui-handle-right') > -1) ? 0 : 1,
                pos = (handle === 0) ? "posStart" : "posEnd",
                minmax = (handle === 0) ? "min" : "max",
                val = (function (inputs) {
                    // The internal values of the sliders are different to the slightly massaged display values. use the display ones if there are any.
                    return (inputs[handle].value) ? inputs[handle].value : ui.values[handle];
                } (el.parentNode.getElementsByTagName('input')));

            // setup sliders object if it's missing
            if (config.search.sliders === null || config.search.sliders === undefined) {
                config.search.sliders = {};
            }
            // allow previously undefined sliders
            if (config.search.sliders[sliderId] === null || config.search.sliders[sliderId] === undefined) {
                config.search.sliders[sliderId] = {};
            }
            sliderObj = config.search.sliders[sliderId];
            if (val == sliderProperties[minmax] || val <= 0) {
                delete sliderObj[pos]; // remove empty pos
            } else {
                sliderObj[pos] = val; // update slider pos                
            }
            if ($.isEmptyObject(config.search.sliders[sliderId])) {
                delete config.search.sliders[sliderId]; // remove empty sliders altogether
            }
            if (el.id !== "price") {
                markModifiedSlider(el);
            }
        }
        // update the display fields underneath a slider
        function updateUI(ui, el) {
            var sliderProperties = getSliderProperties(el.id),
                handle = (ui.handle.className.indexOf('ui-handle-right') > -1) ? 0 : 1,
                toUpdate = (handle === 0) ? 'min' : 'max',
                currentHandle = ui.values[handle],
                elId = el.id.split('-')[0];
            // apply custom rounding for large values. logorithmic lol >_>
            if (elId == "price" || elId == "mileage") {
                if (currentHandle >= 35000) {
                    clip = 1000;
                } else if (currentHandle < 35000 && currentHandle >= 10000) {
                    clip = 500;
                } else {
                    clip = 100;
                }
                currentHandle = toNearest(currentHandle, clip); //update currentHandle value with the clipped val
            }
            if (!currentHandle || sliderProperties[toUpdate] == currentHandle) {
                currentHandle = ""; // if the value is at one of the bounds don't show it
            }
            document.getElementById(elId + '-' + toUpdate).value = currentHandle;
        }
        function buildSlider(id, text) {
            var sliderProperties = getSliderProperties(id);
            // setup minimum and maximum values here to avoid multiple parseInts
            min = (sliderProperties.min) ? parseInt(sliderProperties.min, 10) : 0;
            max = parseInt(sliderProperties.max, 10);
            $('#' + id)
                .slider({
                    range: true,
                    min: min,
                    max: max,
                    values: [min, max],
                    step: ((sliderProperties.step) ? parseInt(sliderProperties.step, 10) : 1),
                    slide: function (event, ui) {
                        //slide
                        updateUI(ui, this);
                    },
                    change: function (event, ui) {
                        //release
                        updateJSON(ui, this);
                        vehicleCountHandler.update();
                    }
                })
                .find('.ui-slider-handle:first')
                    .addClass('ui-handle-right')
                    .attr('title', 'Minimum ' + text)
                .end()
                .find('.ui-slider-handle:last')
                    .addClass('ui-handle-left')
                    .attr('title', 'Maximum ' + text);
        }
        var setupSliders = function () {
            buildSlider('price', 'price');
            buildSlider('age-slider', 'age');
            buildSlider('mileage-slider', 'mileage');
            buildSlider('insurancegroup-slider', 'insurance group');
            buildSlider('fuelefficiency-slider', 'fuel efficency (mpg)');
            buildSlider('annualtaxcost-slider', 'annual tax cost');
            buildSlider('co2-slider', 'CO2 emissions (g/km)');
        };
        return {
            init: setupSliders,
            properties: getSliderProperties
        };

    } ();

    /*
    *  More Options Tabs & Option Panels
    */

    var tabHandler = function () {

        //add keywords to search
        function keywordAdd() {
            var keywordField = document.getElementById('keyword-val'),
                keywordTarget = document.getElementById('keyword-disp'),
                keywordValue = keywordField.value.substring(0, 50);
            if (keywordValue && keywordValue !== "") {
                keywordTarget.innerHTML += '<a href="javascript:void(0);" class="keyword" title="Remove keyword \'' + keywordValue + '\'">' + keywordValue + '</a>';
                keywordField.value = "";
                $('#keywordsearch').data('hilight', true);
            } else {
                keywordField.focus();
            }
            tabHandler.refreshTabHilight();
        }
        function keywordRemove(el) {
            var keywordTarget = document.getElementById('keyword-disp');
            keywordTarget.removeChild(el);
            if (keywordTarget.getElementsByTagName('a').length === 0) {
                $('#keywordsearch').data('hilight', false);
                tabHandler.refreshTabHilight();
            }
        }
        // use the navigators $.data('hilight') to hilight modified tabs
        function markModifiedTabs() {
            var tabContentEls = document.getElementById('search-more').childNodes,
				thisData = [],
				tabId;
            for (var i = 0; i < tabContentEls.length; i++) {
                if (tabContentEls[i].id && tabContentEls[i].tagName == 'DIV') {
                    thisData = $(tabContentEls[i]).data('hilight');
                    if (tabContentEls[i].id.indexOf("makemodel") > -1) {
                        thisTab = document.getElementById('makemodel-tab');
                        //check if a model is selected and set data to true
                        if ($("#makemodel span").hasClass("modelcount")) {
                            thisData = true;
                        }
                    } else {
                        thisTab = document.getElementById(tabContentEls[i].id + '-tab');
                    }
                    if (thisData === true) {
                        if (thisTab.className.indexOf('modified') == -1) {                            
                            thisTab.className += " modified";
                        }
                    } else {
                        thisTab.className = thisTab.className.replace(/modified|\s\s|^[ \t]+|[ \t]+$/gi, '');
                    }
                }
            }
        }
        // compare modified inputs to the $.data('modified') for the parent
        function checkModifiedInput(el) {
            var tag = el.tagName,
				type = el.type,
				stringIndex = -1,
				modifiedElements = [],
                hilightTab = false,
                parentNav = $(el).parentsUntil('.nav').last().parent()[0],
                parentPanel = $(el).parentsUntil('.panel').last().parent()[0],
                dataPanel = $.data(parentNav, 'modified');
            // track modified fields per navigator
            if (dataPanel) { modifiedElements = dataPanel; }
            if (tag.toLowerCase() == 'input') {
                stringIndex = modifiedElements.indexOf(el.value);
                if (type == 'checkbox') {
                    //push checkbox values to array or splice out unchecked vals
                    if (el.checked === true && stringIndex == -1) {
                        modifiedElements.push(el.value);
                    } else {
                        modifiedElements.splice(stringIndex, 1);
                    }
                } else if (type == 'radio') {

                    //radios don't use an array for obvious reasons ;) 
                    //the default value of the buttons should be "any" or ""
                    if (el.checked === true && stringIndex == -1) {
                        // any option uses value 'cany', track this to show tab as unmodified.
                        if (el.parentNode.className !== "cany") {
                            modifiedElements = el.value;
                        } else {
                            modifiedElements = "";
                        }
                    } else {
                        modifiedElements = "";
                    }
                }
            }
            $.data(parentNav, 'modified', modifiedElements);
            // handle multiple navigators within a panel - try the obvious one first and failover to checking siblings
            dataPanel = $.data(parentPanel, 'modified');
            if (dataPanel && dataPanel.length > 0) {
                hilightTab = true;
            } else {
                $(parentPanel.getElementsByTagName('DIV')).each(function () {
                    if (this.className == 'nav' || this.className == 'slider-container') {
                        dataNav = $.data(this, 'modified');
                        if (dataNav && dataNav.length > 0) {
                            hilightTab = true;
                        }
                    }
                });
            }
            $.data(parentPanel, 'hilight', hilightTab);
            markModifiedTabs();
        }
        // show/hide more options
        var toggleMoreOptions = function (el) {
            var tabs = document.getElementById('search-more'),
				close = document.getElementById('search-more-close'),
				state = (tabs.style.display == 'none') ? 'block' : 'none';
            if (el) {
                el.className = (el.className == "more") ? "less" : "more";
                el.innerHTML = (el.className == "less") ? "Hide advanced search options" : "Show advanced search options";
                tabs.style.display = state;
                close.style.display = state;
            }
        };
        // open initial tab, if the url contains &tab=[tab id] use that otherwise default to makemodel-tab
        function getTabFromURL() {
            var currentTab,
				currentEl = 'makemodel-tab';
            if (queryString.tab) {
                currentEl = queryString.tab + '-tab';
            }
            currentTab = document.getElementById(currentEl);
            activateTab(currentTab);
        }
        // make selected search-more-options tab active
        function activateTab(el) {
            var allTabs = el.parentNode.parentNode.getElementsByTagName('a');
            for (var i = 0; i < allTabs.length; i++) {
                allTabs[i].className = allTabs[i].className.replace(/active|\s\s|^[ \t]+|[ \t]+$/gi, '');
            }
            if (el.className.indexOf('active') == -1) {
                el.className += ' active';
            }
            activatePanel(el.href);
        }
        // make selected more-options panel active
        function activatePanel(el) {
            var currentPanel = document.getElementById(el.split('#')[1]),
                searchToggle = document.getElementById('search-toggle'),
		        allPanels = currentPanel.parentNode.getElementsByTagName('div');
            for (var i = 0; i < allPanels.length; i++) {
                if (allPanels[i].id) {
                    allPanels[i].className = allPanels[i].className.replace(/active|\s\s|^[ \t]+|[ \t]+$/gi, '');
                }
            }
            currentPanel.className += ' active';
            currentPanel.parentNode.style.display = 'block';
            document.getElementById('search-more-close').style.display = 'block';
            searchToggle.className = "less";
            searchToggle.innerHTML = "Hide advanced search options";
        }
        // bind tab events
        var setupTabs = function () {
            $('#search-more-options').find('a').click(function (e) {
                e.preventDefault();
                activateTab(this);
                vehicleCountHandler.update();
            });
            $('#search-toggle').click(function (e) {
                e.preventDefault();
                toggleMoreOptions(this);
                //if (document.all)
                //document.location.hash = document.body.offsetHeight;
            });
            $('#search-more-close').click(function (e) {
                e.preventDefault();
                toggleMoreOptions(document.getElementById('search-toggle'));
            });
            $('#search-more').find('div').find('input').not('[type=text]').change(function (e) {
                //not text input to avoid erroring when focus is changed
                var pop = $('div.modelsPopup:visible');
                if (pop.length > 0) {
                    pop.parent().removeClass('active');
                    //pop[0].style.display = "none";
                }
                checkModifiedInput(this);
                searchHandler.updateJson(this.parentNode.parentNode.parentNode.id); //TODO: something nicer than this, but it's quick this way.
                vehicleCountHandler.update();
            });
            $('#keyword-val').keyup(function (e) {
                if (e.which == 13) {
                    keywordAdd();
                }
            });
            $('#keyword-add').click(function () {
                keywordAdd();
            });
            $('#keyword-disp').click(function (e) {
                var el = e.target;
                if (el.className == "keyword") {
                    keywordRemove(el);
                }
            });
            getTabFromURL();
        };
        return {
            init: setupTabs,
            checkModified: checkModifiedInput,
            refreshTabHilight: markModifiedTabs
        };

    } ();

    //new function to set search criteria from querystring
    function setParamsFromQueryString() {
        //check if querystring has values
        if (queryString.location) {
            var elem = document.getElementById("location");
            if (elem) {
                var cleanPostcode = queryString.location.replace("+", " "); ;
                elem.value = cleanPostcode;
                config.search.postcode = cleanPostcode;
            }
        }
        if (queryString.distance) {
            //motors function to populate dropdwon
            var distInput = document.getElementById('distance');
            if (distInput) {
                var options = distInput.options;
                var distanceQueryValue = parseInt(queryString.distance);

                config.search.sliders = {};
                config.search.sliders['nav-distance'] = {
                    "posEnd": queryString.distance
                };

                for (var i = 0; i < options.length; i++) {
                    if (options[i].value >= distanceQueryValue) {
                        // Pick the nearest value, not just the next one.
                        var devforw = options[i].value - distanceQueryValue;
                        var devback = (i > 1) ? distanceQueryValue - options[i - 1].value : 999999;
                        distInput.value = (devforw <= devback) ? options[i].value : options[i - 1].value;
                        break;
                    }
                }
            }
        }
        if ((queryString.distance) && (queryString.location)) {
            //call click function to perform search
            $('#search-button').click();
        }
    }

    var motorsSearchSetup = function () {

        var motorsGAKey = document.getElementById('motorsGA').value,
            localGAKey = document.getElementById('localGA').value;
        _gaq.push(
            ['_setAccount', localGAKey],
            ['_trackPageview'],
            ['motors._setAccount', motorsGAKey],
            ['motors._setDomainName', window.location.host],
            ['motors._trackPageview']
        );

        tabHandler.init();
        sliderHandler.init();
        modelPopupHandler.init();
        searchHandler.init();
        resultsHandler.init();
        autoComplete('location');
        motorsAbce();
        setParamsFromQueryString();

        // fix z-index issue in old browsers
        // $.browser is depracated, but we're not looking for feature support only a specific browser
        if ($.browser.msie) {
            if (parseInt($.browser.version, 10) <= 7) {
                (function (id) {
                    var makeModel = document.getElementById(id),
                makeList = makeModel.getElementsByTagName("li"),
                zIndex = 1000,
                z = 0;
                    for (; z < makeList.length - 1; z++) {
                        makeList[z].style.zIndex = zIndex;
                        zIndex--;
                    }
                })("makemodel");
            }
        }

        _gaq.push(
            ['_trackPageview'],
            ['motors._trackPageview']
        );

    };

    /*
    *	Expose motors lib functions to DOM
    */

    return {
        page: searchHandler.page,
        init: motorsSearchSetup
    };

} (this, document);

