// usage sample can be found in index.html
//
// we detect if we have FB.Connect namespace during render call, if not we create stub API and log warnings into Firebug console

// define dummy replacements for firebug functionality (needed for Opera)
if (window.opera && !window.console) {
    window.console = {};
    function fn() {
        opera.postError(arguments);
    }
    ['log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml', 'group', 'groupEnd', 
    'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd'].forEach(function(name) {
        window.console[name] = fn;
    });
}            
(function() {
        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
        if(!window.console){
            window.console = {};
        }
        for (var i in names) {
            window.console[names[i]] = window.console[names[i]] || function(){
                return;
            };
        }
})();

(function($) {
    
    var C = function(userConfig) {
        var defaultConfig = {
            id: 1,
            mode: 'voting',
            imageWidth: 400,
            imageHeight: 600,                       // total height = imageHeight + 57px for filmstrip
            sideWidth: 200,                         // total width = imageWidth + sideWidth
            widgetId: 1,                            // related widget ID, needed for voting service
            voteUrl: "http://transpond.site/transponder/vote",  // needs to be specified for production
            votingTimeout: 3000,                    // timeout in ms, before we notify user about voting service problems
            preformRefresh: true,                   // true, if page should be refreshed after every successful voting click
            prevNextRefresh: true,                  // true, if page should be refreshed after every prev/next click
            items: []
        };
    
        var defaultState = {
            activeItem: 0,         // zero-based index of actualy selected actor
            votingStatus: {},      // for every actor, we keep state of user's voting, undefined/'hate'/'vote' (undefiend means 'not yet voted')
            votingCache: {}        // previously read voting counter values, keys are composed assetIds
        };
    
        var gallery; // galleryView component reference, provides shortcuts to some galleryView library internals
        var poll;    // poll object, responsible for doing actual ajax calls
        var config;  // initial configuration hash, list of items, behavior and styles
        var state = $.extend({}, defaultState);   // all state information, this has to be persistent across page refreshes

        var currentAssetId = ''; // currently selected actor's name, we take this as a unique assetId for doing AJAX calls
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        // helpers

        // parseUri 1.2.2
        // (c) Steven Levithan <stevenlevithan.com>
        // MIT License
        function parseUri(str) {
            var o   = parseUri.options,
                m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
                uri = {},
                i   = 14;

            while (i--) uri[o.key[i]] = m[i] || "";

            uri[o.q.name] = {};
            uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
                if ($1) uri[o.q.name][$1] = $2;
            });

            return uri;
        }

        parseUri.options = {
            strictMode: false,
            key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
            q:   {
                name:   "queryKey",
                parser: /(?:^|&)([^&=]*)=?([^&]*)/g
            },
            parser: {
                strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
                loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
            }
        };
    
        function refreshPage() {
            setTimeout(function() {
                document.location.reload();
            }, 500);
        };
    
        function storeState() {
            var sid = 'XHON-'+config.id;
            console.log('store state ', sid);
            $.cookie(sid, JSONXXXTR.stringify(state));
        }

        function restoreState() {
            var sid = 'XHON-'+config.id;
            console.log('restore state ', sid);
            var s = $.cookie(sid);
            if (s) { 
                try {
                    s = JSONXXXTR.parse(s);
                } catch (e) {
                    console.error('unable to restore state:', s);
                    return;
                }
                state = $.extend({}, defaultState, s);
                console.log('  state:', state);
            }
        }
    
        function composeAssetId(name, kind) {
            return config.prefix+kind+": "+name;
        };
    
        function presentPercentage(p) {
            if (isNaN(p)) return '0%';
            return p+"%";
        }
    
        function showLoader() {
            $('.tr-hon-side-vote-loader', config.root).fadeIn(200);
        }
    
        function hideLoader() {
            $('.tr-hon-side-vote-loader', config.root).fadeOut(200);
        }
    
        function showError(msg) {
            $('.tr-hon-side-vote-error', config.root).fadeIn(200).attr('title', msg);
        }
    
        function hideError() {
            $('.tr-hon-side-vote-error', config.root).fadeOut(200);
        }

        function presentCount(n) {
            n += '';
            var x = n.split('.');
            var x1 = x[0];
            var x2 = x.length > 1 ? '.' + x[1] : '';
            var rgx = /(\d+)(\d{3})/;
            while (rgx.test(x1)) {
                x1 = x1.replace(rgx, '$1' + '.' + '$2');
            }
            return x1 + x2;
        }
    
        function updateVotingInfo(el, count, percentage) {
            $('.tr-hon-vote-percent', el).html(presentPercentage(percentage));
            $('.tr-hon-vote-count', el).html(presentCount(count) + ' vote'+(count!=1?'s':''));
        }
    
        function sanitizeNaN(n) {
            if (!n || isNaN(n)) return 0;
            return n;
        }
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Facebook Connect stuff
        //

        function prepareShareMessage(voted, itemNum) {
            var item = config.items[itemNum%config.items.length];
            var template = config['message.'+voted];
            if (!template) template = config.message;
            var msg = template.replace(/\{voted\}/g, voted).replace(/\{title\}/g, item.title);
            return msg;
        }
    
        function publishToFacebookStreamAndExcuteCallback(doPublish, itemNum, voted, callback) {
            FB.ensureInit(function() {
                FB.Connect.ifUserConnected(function() {
                    if (doPublish) {
                        var item = config.items[itemNum%config.items.length];
                        var msg = prepareShareMessage(voted, itemNum);
                        var attachment = {
                            'name': config.title,
                            'href': config.homeUrl,
                            'caption': config.caption,
                            'description': config.description,    
                            'media': [{
                                'type': 'image', 
                                'src': item.url,
                                'href': config.homeUrl
                            }]
                        };
                        FB.Connect.streamPublish(msg, attachment, null, null, null, function(){
                            if (callback) callback();
                        });
                    } else {
                        if (callback) callback();
                    }
                }, function() {
                    if (callback) callback();
                });
            });
        }
    
        function shareViaFacebookConnect(url, callback) {
            FB.ensureInit(function() {
                FB.Connect.showShareDialog(url, callback);
            });
        }

        // Peter's specs:
        //
        // IF ! LoggedIntoFacebook AND after vote
        // then show Facebook and Twitter Share URLs
        // 
        // If  LoggedIntoFacebook AND before vote
        // then show "Publish to Facebook" checkbox 
        //
        function fillFacebookPublishSection(voted, itemNum) {
            if (config.mode=='photos') {
                clearPublish();
                // they are asking to remove Twitter & Facebook sharing from these now
                // insertFacebookShareButton(voted, itemNum);
                // insertTwitterShareButton(voted, itemNum);
                return;
            }
            FB.ensureInit(function() {
                clearPublish();
                FB.Connect.ifUserConnected(function() {
                    if (!voted) {
                        insertFacebookShareCheckbox();
                    }
                }, function() {
                    if (voted) {
                        insertFacebookShareButton(voted, itemNum);
                        insertTwitterShareButton(voted, itemNum);
                    }
                });
            });
        }
        
        function clearPublish() {
            $('.tr-hon-side-publish', config.root).removeClass('tr-hon-check').empty();
        }

        function insertFacebookShareCheckbox() {
            var html = "<input type='checkbox' class='tr-hon-facebook-publish-button' checked='on'/> Publish to Facebook";
            $('.tr-hon-side-publish', config.root).addClass('tr-hon-check').append(html);
        }
    
        function insertFacebookShareButton(voted, itemNum) {
            var loc = (location.href+"").split("?")[0];
            var sw_img = config.items[itemNum].url;
            var msg = prepareShareMessage(voted, itemNum);
            // hack to get actual gallery if cookie menu is present
            var gal = "";
            try {
                var selectedGallery = JSONXXXTR.parse(jQuery.cookie(cookieMenuConfig.key));
                gal = "&trgal="+selectedGallery.selected;
            } catch (e) {}
            var share_link = "http://www.facebook.com/sharer.php?u="+escape("http://grid.transpond.com/sharetofb.php?title="+config.title+"&description="+msg+"&image_src="+sw_img+"&url="+escape(loc+"?trpos="+itemNum+gal));
            $('.tr-hon-side-publish', config.root).removeClass('tr-hon-check').append('<scr'+'ipt>function fbs_click() {u=location.href;t=document.title;window.open(\''+share_link+'\',\'sharer\',\'toolbar=0,status=0,width=626,height=436\');return false;}</sc'+'ript><st'+'yle> html .fb_share_link { padding:2px 0 0 20px; height:16px; background:url(http://image.com.com/tv/images/features/All_2009/emmys_2009/fb-icon2.png) no-repeat top left; }</sty'+'le><a href="http://www.facebook.com/share.php?u='+config.homeUrl+'" onclick="return fbs_click()" target="_blank" class="fb_share_link">Share on Facebook</a>');
        }
        
        function insertTwitterShareButton(voted, itemNum) {
            var msg = config.twitter;
            $('.tr-hon-side-publish', config.root).removeClass('tr-hon-check').append('<a href="http://twitter.com/home?status='+encodeURIComponent(msg)+'" target="_blank" class="tw_share_link">Share on Twitter</a>');
        }
    
        function setupFacebookConnect() {
            if (!window["FB"] || (window.location+"").match(/127\.0\.0\.1/)) {
                // create stub functions
                console.error('Facebook Connect namespace not present, using stub functions instead');
                var fnames = ['ifUserConnected', 'get_loggedInUser', 'streamPublish', 'showShareDialog', 'get_status'];
                if (!window["FB"]) window.FB = {};
                FB.ensureInit = function(fn) { fn(); };
                FB.Connect = {};
                $.each(fnames, function(i, name) {
                    FB.Connect[name] = function() {
                        console.log('FB Connect Stub: called '+name+' with ', arguments);
                    };
                });
                FB.Connect.ifUserConnected = function(fn1, fn2) {
                    if (fn2) fn2();
                };
            }
        }
    
        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        // UI updating
        //
        // this function is capable of doing heavy duty UI update according to passed state object
        var firstTime = true;
        var insideUpdateUI;
        function updateUI() {
            console.log('UI', state, config);
            if (insideUpdateUI) return; // prevent infinite recursion
            var i = state.activeItem;
            if (firstTime) {
                var saveFadePanels = gallery.opts.fade_panels;
                gallery.opts.fade_panels = false;
                firstTime = false;
                insideUpdateUI = true;
                gallery.show(i);
                insideUpdateUI = false;
                gallery.opts.fade_panels = saveFadePanels;
            }
            var frames = gallery.frames;
            var count = gallery.panels.length;
            var num = i+1;
            var cur = $('img', frames[i%count]);
            $('.tr-hon-side-top-cur', config.root).html(num+' of '+count+' photo'+(count==1?'':'s'));
            $('.tr-hon-side-vote-title', config.root).html($('img', frames[i]).attr('title')||'');
            var next = $('img', frames[num%count]);
            $('.tr-hon-side-footer-next', config.root).html('Next: '+next.attr('title')+'<span class="tr-hon-next-small-arrow">&nbsp;&nbsp;&nbsp;&nbsp;</span>');
            currentAssetId = cur.attr('title');
        
            var loveAssetId = composeAssetId(currentAssetId, 'love');
            var hateAssetId = composeAssetId(currentAssetId, 'hate');
            var love = sanitizeNaN(state.votingCache[loveAssetId]);
            var hate = sanitizeNaN(state.votingCache[hateAssetId]);
            var total = love + hate;
            var lovePercentage = Math.round(love*100/total);
            var hatePercentage = 100 - lovePercentage;
        
            var status = state.votingStatus[currentAssetId];
            switch (status) {
                case 'love':
                case 'hate':
                    var loveInfo = $('.tr-hon-side-vote-love-info', config.root);
                    updateVotingInfo(loveInfo, love, lovePercentage);
                    loveInfo.find('.tr-hon-vote-selected').toggle(status=='love');
                
                    var hateInfo = $('.tr-hon-side-vote-hate-info', config.root);
                    updateVotingInfo(hateInfo, hate, hatePercentage);
                    hateInfo.find('.tr-hon-vote-selected').toggle(status=='hate');

                    $('.tr-hon-vote-info', config.root).css('visibility', '');
                    break;
                default:
                    $('.tr-hon-vote-info', config.root).css('visibility', 'hidden');
                    break;
            }

            if (status!='love' && status!='hate') status = undefined;
            fillFacebookPublishSection(status, i);
        
            var item = config.items[i%config.items.length];
            config.root.find('.tr-hon-photo-line1').html(item.line1||"&nbsp;");
            config.root.find('.tr-hon-photo-line2').html(item.line2||"&nbsp;");
        
            storeState();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////
        // poll object taken from client services
        var Poll = function(widgetId, serviceUrl) {
            var ajax = function(params, callback) {
                var timeouted = false;
                var timer = setTimeout(function() {
                    timeouted = true;
                    if (callback) callback();
                }, config.votingTimeout);
            
                var success = function(response) {
                    if (timeouted) return;
                    if (timer) {
                        clearTimeout(timer);
                        timer = null;
                    }
                    if (callback) callback(response);
                };

                var error = function() {
                    if (timeouted) return;
                    if (timer) {
                        clearTimeout(timer);
                        timer = null;
                    }
                    if (callback) callback();
                };
            
                params = $.extend({
                    type: "GET",
                    dataType: "json",
                    success: success,
                    error: error
                }, params);
                // HACK: see http://stackoverflow.com/questions/1099787/jquery-ajax-post-sending-options-as-requestmethod-in-firefox
                params.url = params.url+'?callback=?&method='+params.type;
                params.type = 'GET';
                $.ajax(params);
            };
            
            this.sendVote = function(name, callback) {
                ajax({
                    type: 'POST',
                    url: serviceUrl+'/'+widgetId+'/'+name
                }, callback);
            };
            
            this.requestStats = function(callback) {
                ajax({
                    type: 'GET',
                    url: serviceUrl+'/'+widgetId+'/'+name
                }, callback);
            };

            this.requestCounter = function(name, callback) {
                ajax({
                    type: 'GET',
                    url: serviceUrl+'/'+widgetId+'/'+name
                }, callback);
            };

            this.setCounter = function(name, value, callback) {
                ajax({
                    type: 'PUT',
                    data: { value: value },
                    url: serviceUrl+'/'+widgetId+'/'+name
                }, callback);
            };

            this.removeCounter = function(name, callback) {
                ajax({
                    type: 'DELETE',
                    url: serviceUrl+'/'+widgetId+'/'+name
                }, callback);
            };

            this.removeAllCounters = function(callback) {
                ajax({
                    type: 'DELETE',
                    url: serviceUrl+'/'+widgetId
                }, callback);
            };
        };

        // static markup we don't want to generate using jQuery
        var markup = '\
        <div class="tr-hon-title"></div>\
        <div class="tr-hon-root">\
            <div class="tr-hon-background"></div>\
            <div class="tr-hon-photos">\
                <ul class="filmstrip">\
                </ul>\
            </div>\
            <div class="tr-hon-side">\
                <div class="tr-hon-side-top">\
                    <div class="tr-hon-side-top-next tr-hon-nav-button"></div>\
                    <div class="tr-hon-side-top-prev tr-hon-nav-button"></div>\
                    <div class="tr-hon-side-top-cur">&nbsp;</div>\
                </div>\
                <div class="tr-hon-side-vote tr-hon-clear">\
                    <img src="http://image.com.com/tv/images/features/All_2009/emmys_2009/loader.gif" class="tr-hon-side-vote-loader" style="display:none">\
                    <img src="http://image.com.com/tv/images/features/All_2009/emmys_2009/error.png" class="tr-hon-side-vote-error" style="display:none">\
                    <div class="tr-hon-side-vote-title">&nbsp;</div>\
                    <div class="tr-hon-voting-controls">\
                        <div class="tr-hon-side-vote-love tr-hon-vote-box">\
                            <div class="tr-hon-side-vote-love-wrapper tr-hon-vote-wrapper">\
                                <div class="tr-hon-side-vote-love-button tr-hon-vote-button">\
                                </div>\
                                <div class="tr-hon-side-vote-love-info tr-hon-vote-info" style="visibility:hidden">\
                                    <div class="tr-hon-vote-selected"></div>\
                                    <div class="tr-hon-vote-percent">10%</div>\
                                    <div class="tr-hon-vote-count">910.803 votes</div>\
                                </div>\
                            </div>\
                        </div>\
                        <div class="tr-hon-side-vote-hate tr-hon-vote-box">\
                            <div class="tr-hon-side-vote-hate-wrapper tr-hon-vote-wrapper">\
                                <div class="tr-hon-side-vote-hate-button tr-hon-vote-button">\
                                </div>\
                                <div class="tr-hon-side-vote-hate-info tr-hon-vote-info" style="visibility:hidden">\
                                    <div class="tr-hon-vote-selected"></div>\
                                    <div class="tr-hon-vote-percent">10%</div>\
                                    <div class="tr-hon-vote-count">910.803 votes</div>\
                                </div>\
                            </div>\
                        </div>\
                    </div>\
                    <div class="tr-hon-photo-controls">\
                        <div class="tr-hon-photo-line1"></div>\
                        <div class="tr-hon-photo-line2"></div>\
                    </div>\
                </div>\
                <div class="tr-hon-side-publish tr-hon-clear">\
                </div>\
                <div class="tr-hon-side-footer tr-hon-clear">\
                    <div class="tr-hon-side-footer-next">&nbsp;</div>\
                </div>\
            </div>\
            <div class="tr-hon-clear">&nbsp;</div>\
        </div>\
        ';

        function goPrev() {
            if (config.prevNextRefresh) { 
                state.activeItem = (state.activeItem+config.items.length-1) % config.items.length;
                storeState();
                refreshPage();
                return true;
            }
            gallery.prev();
        }
        
        function goNext() {
            if (config.prevNextRefresh) { 
                state.activeItem = (state.activeItem+1) % config.items.length;
                storeState();
                refreshPage();
                return true;
            }
            gallery.next();
        }

        config = $.extend({}, defaultConfig, userConfig);
    
        setupFacebookConnect();
    
        // restore state from cookies
        restoreState();

        // override active item if passed from URL
        var opts = parseUri(document.location+"");
        if (opts.queryKey['trpos']) {
            state.activeItem = parseInt(opts.queryKey['trpos'], 10);
            if (!state.activeItem) state.activeItem = 0;
            state.activeItem = state.activeItem % config.items.length;
        }

        // render static markup
        config.root.html(markup.replace(/#\{homeUrl\}/g, document.location+'').replace(/#\{baseUrl\}/g, config.baseUrl));
        switch (config.mode) {
            case 'photos':
                config.root.find('.tr-hon-voting-controls').hide();
                break;
            default:
                config.root.find('.tr-hon-photo-controls').hide();
        }
        if (config.title) {
            var title = config.title;
            if (config.titlePostfix) {
                title += config.titlePostfix;
            }
            config.root.find('.tr-hon-title').html(title).show();
        }
    
        // galleryView library expects film strip element to have <li> for every gallery image, let's generate it here
        var filmstrip = $('.filmstrip', config.root);
        for (var i=0; i < config.items.length; i++) {
            var item = config.items[i];
            filmstrip.append($('<li><img src="'+item.thumbnail+'" fullsrc="'+item.url+'" title="'+(item.title||'')+'" /></li>'));
        }
        
        // initialize galleryView component
        gallery = $('.tr-hon-photos', config.root).attr('id', config.root.attr('id')+'-gallery').galleryView({
            slide_method: 'strip',
            image_width_correction: -config.sideWidth,
            panel_width: config.imageWidth+config.sideWidth,
            panel_height: config.imageHeight,
            start_frame: state.activeItem,
            filmstrip_position: 'top',
            // Peter: on the HOT and Gallery slider, it shouldn't refresh the parent page when you are shifting left/right, only when you select an image.
            onShowItem: function(i) {
                state.activeItem = i;
                updateUI();
            },
            onBeforeShowItemClick: function(i) {
                if (config.prevNextRefresh) { 
                    state.activeItem = i;
                    storeState();
                    refreshPage();
                    return true;
                }
            },
            onShowNextItem: function(i) {
                if (false && config.prevNextRefresh) { 
                    state.activeItem = i;
                    storeState();
                    refreshPage();
                    return true;
                }
            },
            onShowPrevItem: function(i) {
                if (false && config.prevNextRefresh) {
                    state.activeItem = i;
                    storeState();
                    refreshPage();
                    return true;
                }
            }
        });
    
        // actualize side panel dimensions
        $('.tr-hon-side', config.root).height(config.imageHeight);
        $('.tr-hon-side', config.root).width(config.sideWidth);
        $('.tr-hon-side', config.root).css({
            left: config.imageWidth + 'px'
        });
        $('.tr-hon-background', config.root).css({
            top: '57px',
            width: config.imageWidth + 'px',
            height: config.imageHeight + 'px'
        });
    
        // wire click events for buttons
        $('.tr-hon-side-top-next', config.root).bind('click', goNext);
        $('.tr-hon-side-top-prev', config.root).bind('click', goPrev);
        $('.tr-hon-side-footer-next', config.root).bind('click', goNext);

        // create poll object
        poll = new Poll(config.widgetId, config.voteUrl);

        var processError = function(response) {
            if (!response) {
                showError('We are sorry, voting service is unavailable!');
                return true;
            }
            if (!response.success) {
                showError(response.message);
                return true;
            }
        };
        
        var voter = function(type) {
            return function() {
                hideError();
                showLoader();
                var otherType = type=='love'?'hate':'love';
                var id = composeAssetId(currentAssetId, type);
                var otherId = composeAssetId(currentAssetId, otherType);
                poll.sendVote(id, function(response) { 
                    hideLoader();
                    if (processError(response)) return;

                    var doPublish = $('.tr-hon-facebook-publish-button', config.root).attr('checked');
                    state.votingStatus[currentAssetId] = type;
                    
                    var finalizer = function() {
                        publishToFacebookStreamAndExcuteCallback(doPublish, state.activeItem, type, function() {
                            if (config.preformRefresh) {
                                storeState();
                                refreshPage();
                            } else {
                                updateUI();
                            }
                        });
                    };
                    
                    // state.votingCache[id] = response.data.counter;
                    
                    // need to fetch also other counter in case user is voting for first time and we don't have cached this value
                    if (state.votingCache[otherId] === undefined) {
                        hideError();
                        showLoader();
                        poll.requestCounter(otherId, function(response2) {
                            hideLoader();
                            if (processError(response)) return;
                            state.votingCache[otherId] = response2.data.counter;
                            finalizer();
                        });
                    } else {
                        finalizer();
                    }
                });
            };
        };
        
        // wire love button
        $('.tr-hon-side-vote-love-button', config.root).bind('click', voter('love'));

        // wire hate button
        $('.tr-hon-side-vote-hate-button', config.root).bind('click', voter('hate'));
    };
    
    if (!window["Transpond"]) window.Transpond = {};
    window.Transpond.renderHotOrNot = function(element, userConfig) { // main entry point
        var config = $.extend({}, userConfig);
        config.root = $(element);
        return new C(config);
    };
    
    $.fn.TrHotOrNot = function(options) {
        return this.each(function() {
            Transpond.renderHotOrNot(this, options);
        });
    };
    
})(jQuery);
