(function($) {
    $.fn.ch9tvguide = function(options) {
        var defaults =
		{
		    tabs:
			[
				"Monday",
				"Tuesday",
				"Wednesday",
				"Thursday",
				"Friday",
				"Saturday",
				"Sunday"
			], //the name of each tab for the seven days
		    containerClassName: "ch9tvguide",
		    tabIdName: "ch9tv_shows",
		    defaultPostcode: 2000, //default postcode if none is detected
		    showDays: false, //if false, will display "Today" and "Tomorrow" for the first 2 days
		    hasScrolling: true, //scrolls through the tabs showing 3 at a time
		    rotateDays: true, //if true, will show the current day as the first tab, else, days will be ordered Monday through Friday
		    timeStart: "00:00:00", //start time for day
		    timeEnd: "23:59:59", //end time for day
		    displayLimit: 0, //limit the number of shows displayed from results.  If 0, all shows displayed
		    showDetails: true, //if false, does not render the date and change postcode option
		    scrollTime: "04:59:59", //when a tab is opened, will scroll to this time in the tvshow list
		    scrollTimeHeight: 27 //the height in pixels for every tvshow itme before the scrollTime
		};
        var loadedTab = new Array();
        var options = $.extend(defaults, options);
        var userPostcode;

        //Constants
        var NUMBER_OF_DAYS = 7;
        var COOKIE_POSTCODE = "ch9TVGuidePostcode";

        return this.each(function() {
            var obj = $(this);
            obj.html(createHTMLBase());

            preparePostcode();
        });

        //Returns the base HTML that will be used for the tabbed TV guide module
        function createHTMLBase() {
            var today = new Date();
            var tabs = "<ul>";
            for (var i = 0; i < NUMBER_OF_DAYS; i++) {
                if (!options.showDays && i == 0) {
                    var displayDay = "Today";
                }
                else if (!options.showDays && i == 1) {
                    displayDay = "Tomorrow";
                }
                else {
                    if (options.rotateDays) {
                        var dayOffset = today.getDay() + i - 1;
                        displayDay = (dayOffset < NUMBER_OF_DAYS ? options.tabs[dayOffset] : options.tabs[dayOffset - NUMBER_OF_DAYS]);
                    }
                    else {
                        displayDay = options.tabs[i];
                    }
                }
                tabs += "<li><a href='#" + options.tabIdName + i + "'>" + displayDay + "</a></li>";
                loadedTab[i] = false;
            }
            tabs += "</ul>";

            var containers = "";
            for (i = 0; i < NUMBER_OF_DAYS; i++) {
                containers += "<div id='" + options.tabIdName + i + "'><div class='loading'>Loading...</div></div>";
            }

            return "<div class='" + options.containerClassName + "'>" + tabs + containers + "</div>";
        };

        function preparePostcode() {
            var postcode = $.cookie(COOKIE_POSTCODE);
            if (postcode) {
                userPostcode = postcode;
                prepareModule();
            }
            else {
                $.getJSON("http://data.ninemsn.com.au/Services/Service.axd?ServiceName=GeoCoding&ServiceAction=GetGeoLocationByIpAddress&IpAddress=1&ServiceFormat=JSONAUTO&callback=?", function(data) {
                    if (data && data.IpLocationList && data.IpLocationList.IpLocation && data.IpLocationList.IpLocation.RegionCode) {
                        var regionCode = data.IpLocationList.IpLocation.RegionCode;
                        postcode = regionCode.substr(1) + "000";
                        if (validatePcode(postcode)) {
                            userPostcode = postcode;
                        }
                        else {
                            userPostcode = options.defaultPostcode;
                        }
                    }
                    else {
                        userPostcode = options.defaultPostcode;
                    }

                    storePostcode();
                    prepareModule();
                });
            }
        }

        function prepareModule() {
            var initialTab = 0;
            if (!options.rotateDays) {
                var todaysDay = new Date().getDay();
                initialTab = todaysDay - 1 + (todaysDay > 0 ? 0 : NUMBER_OF_DAYS);
            }

            //render first tab
            renderTab(initialTab, document.getElementById(options.tabIdName + initialTab));

            //initialise tabs
            $("." + options.containerClassName).tabs(
				initialTab + 1, //initial tab to open
				{
				onClick: function(elem, tabToShow) {//Check if a tab has been rendered before onclick
				    var id = parseInt(tabToShow.id.substring(options.tabIdName.length));
				    if (!loadedTab[id]) {//this tab has not been rendered yet. Need to fetch data and render
				        renderTab(id, tabToShow)
				    } //else, simply show the tab content as the data has already been fetched
				}
}
			);

            if (options.hasScrolling) {
                //initialise scrolling for tabs
                $("." + options.containerClassName + " ul").jcarousel(
				{
				    scroll: 1,
				    visible: 3
				});

                /**
                * JCarousel does something perculiar by binding to the window.onresize event.  When the event fires, JCarousel exectues
                * its "Prev" function to make the carousel move back.  This functionality makes no sense and also causes problems with
                * our implementation.  We unbind from the event here to avoid this.
                */
                $(window).unbind('resize');
            }
        }

        function renderTab(id, tabToShow) {
            //Calculate date for this tab
            var tabDate = new Date();
            if (options.rotateDays) {
                tabDate.setDate(tabDate.getDate() + id);
            }
            else {
                var initialTab = tabDate.getDay() - 1 + (tabDate.getDay() > 0 ? 0 : NUMBER_OF_DAYS);
                tabDate.setDate(tabDate.getDate() + id - initialTab + (id >= initialTab ? 0 : NUMBER_OF_DAYS));
            }

            var tabDateString = tabDate.getFullYear() + "/" + parseInt(tabDate.getMonth() + 1) + "/" + tabDate.getDate();

            $.getJSON("http://data.ninemsn.com.au/Services/Service.axd?ServiceName=Channel9&ServiceAction=TVguide&startdate=" + tabDateString + " 00:00:00&enddate=" + tabDateString + " 23:59:59&postcode=" + userPostcode + "&AttributeStyle=false&ServiceFormat=JSONAUTO&callback=?", function(data) {
                if (data) {
                    renderTVShows(tabToShow, data);
                }
                else {
                    tabToShow.innerHTML = "No TV Guide data available for this day";
                }
            });
        }

        function renderTVShows(tabToShow, data) {
            var noData = false;
            if (data.TvGuideList && data.TvGuideList.TvGuide) {
                data = data.TvGuideList.TvGuide;
            }
            else {
                noData = true;
            }

            var id = tabToShow.id.substring(options.tabIdName.length);

            var previousTVShow = null;
            var nowTVShowRendered = false;
            var now = new Date();
            if (!data.length) {
                data = [data];
            }

            var details = "";
            if (options.showDetails) {
                details += "<div class='details'>";
                details += "	<div class='detailsWrap'>";
                if (!noData) {
                    var tabDate = new Date(data[0].StartTime);
                    details += "		<div class='date'>" + data[0].StartTime.substring(0, data[0].StartTime.length - 9) + "</div>";
                }
                details += "		<div class='location'>Your postcode is <span class='postcode'>" + userPostcode + "</span></div>";
                details += "	</div>";
                details += "	<div class='errorNotification' style='display:none;'></div>";
                details += "</div>";
            }

            var tvshows = "<ul class='tvshow_scroll'>";
            var loopNum = 0;
            var itemsBeforeScroll = 0;
            var totalResults = data.length;
            //debugger;
            for (var i = 0; i < totalResults; i++) {
                if (options.displayLimit > 0 && loopNum >= options.displayLimit) {
                    break;
                }
                var tvshowItem = data[i];
                var showTime = new Date(tvshowItem.StartTime);

                //if the show is within the specified show time or the last shows
                if ((isInTimeBoundary(tvshowItem.StartTime)) || totalResults - i <= options.displayLimit) {
                    if (now <= showTime) {
                        if (!nowTVShowRendered) {
                            var nextTime = formatDisplayTime(tvshowItem.StartTime);
                            if (previousTVShow) {
                                tvshows += renderShowItem("Now", previousTVShow.Title, previousTVShow.Description);
                                nextTime = "Next";
                                if (isBeforeTime(tvshowItem.StartTime.substring(tvshowItem.StartTime.length - 8, tvshowItem.StartTime.length), options.scrollTime)) {
                                    itemsBeforeScroll++;
                                }
                                loopNum++;
                            }
                            tvshows += renderShowItem(nextTime, tvshowItem.Title, tvshowItem.Description);
                            nowTVShowRendered = true;
                        }
                        else {
                            tvshows += renderShowItem(formatDisplayTime(tvshowItem.StartTime), tvshowItem.Title, tvshowItem.Description);
                        }

                        if (isBeforeTime(tvshowItem.StartTime.substring(tvshowItem.StartTime.length - 8, tvshowItem.StartTime.length), options.scrollTime)) {
                            itemsBeforeScroll++;
                        }
                        loopNum++;
                    }
                    else {
                        previousTVShow = tvshowItem;
                    }
                }
            }
            tvshows += "</ul>";

            loadedTab[id] = true;
            $(tabToShow).html(details + (noData ? "" : tvshows));

            $(".tvshow_scroll").animate({ scrollTop: (options.scrollTimeHeight * itemsBeforeScroll) });

            if (noData) {
                $("#" + tabToShow.id + " .details .errorNotification").html("No TV Guide data available for this day and postcode.  Please change your postcode.").show();
            }

            $("#" + tabToShow.id + " .details .location .postcode").click(function() {
                var defaultText = "Enter postcode...";
                $("#" + tabToShow.id + " .details .location").html("<input class='postcodeInput' type='text' size='14' /><input class='postcodeSubmit' type='submit' value='Go' />");
                $("#" + tabToShow.id + " .details .location .postcodeInput").Watermark(defaultText);
                $("#" + tabToShow.id + " .details .location .postcodeInput").focus();
                $("#" + tabToShow.id + " .details .location .postcodeInput").keyup(function(e) {
                    if (e.keyCode == 13) {
                        return submitPostcode(defaultText, tabToShow.id);
                    }
                    else {
                        return false;
                    }
                });
                $("#" + tabToShow.id + " .details .location .postcodeSubmit").click(function() {
                    return submitPostcode(defaultText, tabToShow.id);
                });
                return false;
            });
        };

        function isInTimeBoundary(showTime) {
            var showTimeShort = showTime.substring(showTime.length - 8, showTime.length);

            return isAfterTime(showTimeShort, options.timeStart) && isBeforeTime(showTimeShort, options.timeEnd);
        };

        function isBeforeTime(itemTime, beforeTime) {
            var itemTimeArray = splitTime(itemTime);
            var beforeTimeArray = splitTime(beforeTime);

            return (beforeTimeArray[0] >= itemTimeArray[0] && (beforeTimeArray[0] > itemTimeArray[0] || (beforeTimeArray[1] >= itemTimeArray[1] && (beforeTimeArray[1] > itemTimeArray[1] || beforeTimeArray[2] >= itemTimeArray[2]))));
        };

        function isAfterTime(itemTime, afterTime) {
            var itemTimeArray = splitTime(itemTime);
            var afterTimeArray = splitTime(afterTime);

            return (afterTimeArray[0] <= itemTimeArray[0] && (afterTimeArray[0] < itemTimeArray[0] || (afterTimeArray[1] <= itemTimeArray[1] && (afterTimeArray[1] < itemTimeArray[1] || afterTimeArray[2] <= itemTimeArray[2]))));
        };

        function splitTime(time) {
            return time.split(":");
        };

        function submitPostcode(defaultText, tabId) {
            var postcodeVal = $("#" + tabId + " .details .location .postcodeInput").attr("value");
            var errorDiv = $("#" + tabId + " .details .errorNotification");
            errorDiv.hide(); //Hide error messages

            if (defaultText == postcodeVal) {
                errorDiv.html("Please specify a postcode.").show();
            }
            else if (!validatePcode(postcodeVal)) {
                errorDiv.html("'" + postcodeVal + "' is not a valid postcode. Please enter a three or four digit number.").show();
            }
            else {
                //Set new postcode value
                userPostcode = postcodeVal;
                storePostcode();

                //Clear loaded flags to force refetch
                for (var i = 0; i < loadedTab.length; i++) {
                    loadedTab[i] = false;
                }

                //Reset to the first tab and show it
                var currentTab = $("." + options.containerClassName).activeTab() - 1;
                renderTab(currentTab, document.getElementById(options.tabIdName + currentTab));
            }
            return false;
        }

        function validatePcode(pcode) {
            if (!pcode || isNaN(pcode) || pcode.length < 3 || pcode.length > 4) {
                return false;
            }
            else if (pcode.match(/\d{4}/)) {
                return true;
            }
            else {
                return false;
            }
        }

        function storePostcode() {
            var date = new Date();
            date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000));
            $.cookie(COOKIE_POSTCODE, userPostcode, { expires: date });
        }

        function formatDisplayTime(time) {
            return time.substring(time.length - 8, time.length - 3);
        }

        function renderShowItem(time, title, description) {
            var tvshow = "<li class='show_item'>";
            tvshow += "<span class='time'>" + (time == "Now" ? "<b>" + time + "</b>" : time) + "</span>";
            tvshow += "<span class='title'><a href='" + generateTVShowURL(title) + "' target='_blank' title='" + description + "'>" + (time == "Now" ? "<b>" + title + "</b>" : title) + "</a></span>";
            tvshow += "</li>";
            return tvshow;
        }

        function generateTVShowURL(title) {
            var url = title.replace(/\ /g, "");
            url = url.replace(/\,/g, "");
            url = url.replace(/\_/g, "");
            url = url.replace(/\!/g, "");
            url = url.replace(/\:/g, "");
            url = url.replace(/\&/g, "");
            url = url.replace(/\'/g, "");
            url = url.replace(/\./g, "");
            url = url.replace(/\-/g, "");
            url = url.replace(/\(/g, "");
            url = url.replace(/\)/g, "");
            url = url.replace(/\*/g, "");
            url = url.replace(/\^/g, "");
            url = url.replace(/\#/g, "");
            url = url.replace(/\?/g, "");
            url = url.toLowerCase();
            return "http://channelnine.ninemsn.com.au/" + url + "/";
        }
    };
})(jQuery);



