//create PBS namespace
if (typeof pbs == "undefined" || !pbs) {
    var pbs = {};
}
pbs.namespace = function() {
    var a=arguments, o=null, i, j, d;
    for (i=0; i<a.length; i=i+1) {
        d=a[i].split(".");
        o=pbs;
        for (j=(d[0] == "pbs") ? 1 : 0; j<d.length; j=j+1) {
            o[d[j]]=o[d[j]] || {};
            o=o[d[j]];
        }
    }
    return o;
}

pbs.namespace('chrome');

//console var, used for logging.
//console is undefined in IE or any Firefox that doesn't have Firebug installed.
if (typeof console == "undefined") {
    this.console = {log: function() {}};
}

pbs.chrome.bindReady = function(handler){

	var called = false

	function ready() { 
		if (called) return
		called = true
		handler()
	}

	if ( document.addEventListener ) { // native event
		document.addEventListener( "DOMContentLoaded", ready, false )
	} else if ( document.attachEvent ) {  // IE

		try {
			var isFrame = window.frameElement != null
		} catch(e) {}

		// IE, the document is not inside a frame
		if ( document.documentElement.doScroll && !isFrame ) {
			function tryScroll(){
				if (called) return
				try {
					document.documentElement.doScroll("left")
					ready()
				} catch(e) {
					setTimeout(tryScroll, 10)
				}
			}
			tryScroll()
		}

		// IE, the document is inside a frame
		document.attachEvent("onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				ready()
			}
		})
	}

	// Old browsers
    if (window.addEventListener)
        window.addEventListener('load', ready, false)
    else if (window.attachEvent)
        window.attachEvent('onload', ready)
    else {
		var fn = window.onload // very old browser, copy old onload
		window.onload = function() { // replace by new onload and call the old one
			fn && fn()
			ready()
		}
    }
}

pbs.chrome.readyList = []

pbs.chrome.onReady = function(handler) {
	
	function executeHandlers() {
		for(var i=0; i<pbs.chrome.readyList.length; i++) {
			pbs.chrome.readyList[i]()
		}
	}

	if (!pbs.chrome.readyList.length) { // set handler on first run 
		pbs.chrome.bindReady(executeHandlers)
	}

	pbs.chrome.readyList.push(handler)
}
//main class definition
pbs.chrome.Class = (function(){
  var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};

  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;

    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;

    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" &&
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;

            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];

            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);
            this._super = tmp;

            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }

    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init )
        this.init.apply(this, arguments);
    }

    // Populate our constructed prototype object
    Class.prototype = prototype;

    // Enforce the constructor to be what we expect
    Class.constructor = Class;

    // And make this class extendable
    Class.extend = arguments.callee;

    return Class;
  };
})();

pbs.chrome.BaseWidget = Class.extend({
    options: {
        pbs_search_url: "http://www.pbs.org/search/"
    },
    //list containing actions to be executed before/after widget load
    _actions:{},
    init: function(){
        return;
    },
    addEvent: function(type, listener){
        // Store event handler (listener) of type(type) for various events in actions list

        //Check for a named property for provided event type in actions,
        //and if not, creates one containing an array
        if (typeof this._actions[type] == "undefined"){
            this._actions[type] = [];
        }
        this._actions[type].push(listener);
    },
    executeEvents: function(type){
        // Executes event handlers with a given type if found in actions list
        //type parameter is a string

        //Check actions object for event handlers, if found, execute them.
        if (this._actions[type] instanceof Array){
            var listeners = this._actions[type];
            for (var i=0, len=listeners.length; i < len; i++){
                try{
                    listeners[i].call(this);
                }
                catch(err){
                    console.log(err);
                }
            }
        }
    },
    removeEvents: function(type){
        //Remove handlers of type provided from _actions array

        if (this._actions[type] instanceof Array){
            var listeners = this._actions[type];
            for (var i=0, len=listeners.length; i < len; i++){
                    listeners.splice(i, 1);
            }
        }
    },
    get_server_data: function(url, callback, callback_name){
        //retrieve widget data from the server. call success or error methods

        //pass context to be used with ajax response. default context is header.
        //we need current class instance

        var self = this;
        var jxhr = pbs.chrome.jquery.ajax({
                url: url,
                dataType: 'jsonp',
                jsonp: 'callback',
                crossDomain: true,
                cache: false,
                beforeSend: function(xhr){
                    //add before send logic here if required
                },
                success: function(data){
                    //call the success method by passing the context and the data returned
                    if(typeof(callback) == "function")
                        callback.call(self, data);
                },
                error: function(data){
                    //call the error method by passing the context and the error returned
                    self.data_error.call(self, data);
                },
                complete: function(data){

                }
            });
        return jxhr;
    },
    build_url: function(url_parameters){
        //Appends params to current url from options
        var url = this.options.url + "?" + pbs.chrome.jquery.param(url_parameters);
        return url;
    },
    data_success: function(widget){
        //construct widget based on data retunred by the server
        // Override data_success for adding to options info needed for event handling
        //And for executing post actions
        //execute pre events
        var self = this;
        pbs.chrome.jquery.extend(self.options, widget);
        self.executeEvents('pre');
        self.resource_string = self.build_resources(widget.media);
        self.widget = self.data_to_template(self.options.template, widget.data);
        self.render(self.widget, self.resource_string);
        
        //Execute post events
        pbs.chrome.onReady(function(){
            self.executeEvents('post');
            //Unregister post events
            self.removeEvents('post');
        });
    },
    data_error: function(error){
        //TODO: add error handling
        return true;
    },

    data_to_template: function(template, data){
        //replace variables in template with the data from the response
        for(var item in data){
            var search_for = new RegExp('{{' + item + '}}', "gi");
            template = template.replace(search_for, data[item]);
        }

        return template;
    },
    build_resources: function(media){
        //build the data resources string
        //to be refactored when we get also the resource type on the data response

        var resource_string = '';
        var css_include_template = '<link rel="stylesheet" type="text/css" href="{{source}}" />';
        var js_include_template = '<script type="text/javascript" src="{{source}}"></script>';

        for(var item in media){
            if (media[item].is_ie == 1)
                resource_string += '<!--[if IE]>' + css_include_template.replace('{{source}}', media[item].name) + '<![endif]-->';
            else
                resource_string += css_include_template.replace('{{source}}', media[item].name);
        }

        return resource_string;
    },
    show: function(){
        //default show action for all widgets. render on complete
        var jxhr = this.get_server_data(this.options.url, this.data_success, "data_success");
    }
})
pbs.chrome.HeaderWidget = pbs.chrome.BaseWidget.extend({
    init: function(options){
        //merge options passed from config to defined options
        pbs.chrome.jquery.extend(this.options, options, {how: "prepend", widget:'header'});
    },
    data_success: function(widget){
        this._super(widget);
    },
    render: function(widget, resources){
        /*
         Render method is at widget level because different widgets can render in
         different way and keeping this at base class level would induce complexity
         in that method.
         Widgets should know how to render themselves.
        */
        //inject resources, render widget
        pbs.chrome.onReady(function(){
            pbs.chrome.jquery("head").append(resources);
        //TODO: investigate how to to this dynamically
            pbs.chrome.jquery("body").prepend(widget);}
        );
        
    },
    show: function(){
        //Add key and widget parameters to widget api url provided in options property
        this.options.url = this.build_url({key: this.options.key,
                                          widget:this.options.widget});

        //Register actions for attaching the click event for the search buttons on post actions list
        this.addEvent('post', function(e){this._do_search();});

        //Register localization event on post actions list
        this.addEvent('post', function(e){this._localize();});

        //Register GA event handling
        this.addEvent('post', function(e){this._add_GA_navigation();});

        //Register event for template validation
        this.addEvent('pre', function(e){this.validate_search_form()});

        //Remove text from search input box on click
        this.addEvent('post',function(e){this.remove_text_search_form()});

        /* The form used for search both on local and global search services needs to be
         capable to work both with POST and GET. However, on GET we want to just append our
         query term to a more complex url that already contains some GET params, so we just
         reconstruct the url and use redirects instead. */
        this.addEvent('post', function(e){this._handle_search_form_submit()});

        this._super();
    },
    _handle_search_form_submit: function(e){
        var self = this;
        pbs.chrome.jquery("#pbs_distribution_search_form").submit(function(){
            //Add GA to search box click/enter
            self._add_GA_search(this, 'GlobalChrome_Header', 'Search');

            var form = pbs.chrome.jquery(this);
            var method = form.attr('method');
            if (method !== 'POST'){
                var search_term = pbs.chrome.jquery.trim(pbs.chrome.jquery("#pbs_distribution_search_box",this).val());
                document.location = form.attr('action') + encodeURIComponent(search_term);
                return false;
            }
            return true;
        });
    },
    _do_search: function(e){
        this._click_search(pbs.chrome.jquery('#global_search_btn'),
                            this.options.data.global_search_url,
                            'GET');
        if(pbs.chrome.jquery.trim(this.options.data.local_search_url) != ''){
            this._click_search(pbs.chrome.jquery('#local_search_btn'),
                              this.options.data.local_search_url,
                              this.options.data.method);
            }
    },
    remove_text_search_form: function(){
        pbs.chrome.jquery('#pbs_distribution_search_box').click(
                                                function() {
                                                    this.value = '';
                                                });
    },
    validate_search_form: function(){
        if(pbs.chrome.jquery.trim(this.options.data.local_search_url) == ''){
            var search_for = new RegExp('<input type="button" class="search_btn" id="local_search_btn" value="Search This Site" />', "gi");
            this.options.template = this.options.template.toString().replace(search_for, '')
        }
    },
    localization_success: function(data){
        if (pbs.chrome.jquery('#pbs_localization_station').length > 0)
        {
            if (data.station.short_name.length > 0){
                pbs.chrome.jquery('#pbs_localization_station_name').html(data.station.short_name);
                pbs.chrome.jquery('#pbs_localization_station_name').attr('title', data.station.short_name);
                pbs.chrome.jquery('#pbs_localization').addClass("localize");
            }
            else if (data.state.common_name.length > 0){
                pbs.chrome.jquery('#pbs_localization_station_name').html(data.state.common_name);
                pbs.chrome.jquery('#pbs_localization_station_name').attr('title', data.state.common_name);
                pbs.chrome.jquery('#pbs_localization').addClass("localize");
            }
        }
        return;
    },
    _localize: function(){
        //Get localization info by calling localization api
        var localization_info = this.get_server_data(this.options.data.localization_url,
                                                    this.localization_success,
                                                    'localization_success');

    },
    _click_search: function(button, action, method){
        //Handle click event for search buttons
        var self = this;
        if(button.length>0){
            button.click(function(){
                pbs.chrome.jquery(this.form)
                    .attr('action',action)
                    .attr('method',method);
                pbs.chrome.jquery(this.form).submit();
            });
        }
    },
    _add_GA_search: function(form, category, action) {
        // Add Google Analytics event for search buttons click/search box enter

        var clicked_search_url = pbs.chrome.jquery(form).attr('action');
        var label = pbs.chrome.jquery('#local_search_btn').attr('value');
        if(clicked_search_url == this.options.data.global_search_url){
            label = pbs.chrome.jquery('#global_search_btn').attr('value');
        }
        if(typeof _gaq == 'undefined')
            console.log("_gaq is undefined");
        else
            _gaq.push(['_setAccount', this.options.data.GA_code],
                      ['_trackEvent', category, action, label]
                    );

    },
    _add_GA_navigation: function(){

        var self = this;
        //Static links clicks
        pbs.chrome.jquery('#pbs_distribution_header').find('a').bind('click', function(){
                                        var text = pbs.chrome.jquery(this).attr('title') || pbs.chrome.jquery(this).text();
                                        var label = text +' | '+ pbs.chrome.jquery(this).attr('href');
                                        if(typeof _gaq == 'undefined')
                                            console.log("_gaq is undefined");
                                        else
                                            _gaq.push(['_setAccount', self.options.data.GA_code],
                                                      ['_trackEvent', 'GlobalChrome_Header', 'Link', label]
                                                    );
                                        });
    }
})

pbs.chrome.Chrome = Class.extend({
    options: {
        jquery_url: 'http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js',
        url: 'http://chrome.pbs.org/api/1.0/',
        widgets_location: 'getwidgets/'
    },
    init: function(){
        /*
         load jquery and execute rest of calls in the jquery_loaded callback
         being async loaded we do not know when jquery has actually loaded so
         the callback is where we are certain that it is loaded.
        */

        this.load_script(this.options.jquery_url, this.jquery_loaded);
    },
    load_script: function(url, callback){
        /*
        load a javascript file from a specified location
        execute callback function passed once the loading is complete
        returns script object
        */
        var self = this;
        //create script dom object and set properties for it
        var script = document.createElement("script");
        script.type = "text/javascript";

       //execute callback once the script is available (onload and readyState for IE)
       if (typeof script.readyState === 'undefined'){
            script.onload = function(){
                callback.call(self);
           };
       } else { // IE LAST! onload is activex so we need to check even if activex is not enabled.
           script.onreadystatechange = function(){
                if(script.readyState === "loaded" || script.readyState === "complete"){
                    script.onreadystatechange = null;
                    callback.call(self);
               }
           };
       };

       script.src = url;
       document.getElementsByTagName("head")[0].appendChild(script);
       return script;
    },
    jquery_loaded: function(){
        /* JQuery is loaded. set jQuery to pbs.chrome.jquery by calling noConflict
          and releasing the $ and jQuery pointers for other existing versions to use
          All jquery methods must from here on use pbs.chrome.jquery internally and not the
          original pointers in order to avoid version conflicts.
        */
        pbs.chrome.jquery = jQuery.noConflict(true);

        if(PBS_CHROME_CONFIG.api_key != 'undefined'){
            var widgets_list_url = this.build_url(this.options.url + this.options.widgets_location,
                                                  {key:PBS_CHROME_CONFIG.api_key});

            this.get_server_data(widgets_list_url, this.get_widgets_success, 'getwidgets_success');
        }
    },
    get_widgets_success: function(data){
        for(var widget in data.widgets){
            switch(data.widgets[widget].type.toLowerCase()){
                case 'header':
                    var header = new pbs.chrome.HeaderWidget({url: data.widgets[widget].url,
                                                             key: PBS_CHROME_CONFIG.api_key});
                    header.show();
                    break;
            }
        }
    },
    get_server_data: function(url, callback, callback_name){
        //retrieve widget data from the server. call success or error methods

        //pass context to be used with ajax response. default context is header.
        //we need current class instance

        var self = this;
        var jxhr = pbs.chrome.jquery.ajax({
                url: url,
                dataType: 'jsonp',
                jsonp: 'callback',
                crossDomain: true,
                cache: false,
                beforeSend: function(xhr){
                    //add before send logic here if required
                },
                success: function(data){
                    //call the success method by passing the context and the data returned
                    if(typeof(callback) == "function")
                        callback.call(self, data);
                },
                error: function(data){
                    //call the error method by passing the context and the error returned
                    self.data_error.call(self, data);
                },
                complete: function(data){

                }
            });
        return jxhr;
    },
    data_error: function(error){
        //TODO: add error handling
        return true;
    },
    build_url: function(url, url_parameters){
        //Appends params to current url from options
        url = url + "?" + pbs.chrome.jquery.param(url_parameters);
        return url;
    }
})

var chrome = new pbs.chrome.Chrome()

