

/*

{
    event: "article",
    subscription: "12", // Subscription ID (optional, wenn vom Client vergeben)
    data: {
        id: 2245457,
        timestamp: 12345678,
!        time: "16:30" // virtuelles Feld
        type: 1, // id_type aus article_meta?!
!        "type-de": "Echtzeitnachricht" // virtuelle Feld
        company: 1, // id_company aus article_meta?!
        title: "H+R WASAG - Kommt es zu einer rasanten Aufwärtswelle?",
        href: "HR-WASAG-Kommt-es-zu-einer-rasanten-Aufwaertswelle",
        teaser: "Im Dezember 2006 markierte die Aktie von H+R Wasag ein hoch bei 53,60 Euro. Danach stürzte...",
        content: "Im Dezember 2006 markierte die Aktie von H+R Wasag ein hoch bei 53,60 Euro. Danach stürzte  bla bla bla bla Volltext",
        categories:[
            10, 11, 12, 13, 14, 15, 101, 102, 104
        ],
        boxes:[
            1, 2, 3, 67, 103, 1020
        ],
        regions:[ 1,2 ],
        is_premium: 0,
        instruments: [
            {
                id: 118634,
                exchange: 22,
                quote_source: "last",
                quotepush: "133962:22:bid",
                identifier: "118634:22:last",
                name: "H&R WASAG AG",
                shortname: "H&R WASAG AG",
                sort: 'stock',
                sector: 3,
                isin: "DE0007757007",
                countryCode: 'DE'
            },
            {
                id: 118636,
                exchange: 22,
                name: "Dax" ,
                ....
            },
            ...
        ]
    }
}


*/


(function($) {

    var

    debug = 0,
    defaultOptions= { count:10 },


    constants = {
    	types: {
    		'1': {
    			"name": "Echtzeitnachricht",
    			"name-de": [ "Echtzeitnachricht", "Echtzeitnachrichten" ]
			},
    		'2': {
    			"name": "Kommentar",
    			"name-de": [ "Kommentar", "Kommentare" ]
			},
    		'5': {
    			"name": "Echtzeitrating",
    			"name-de": [ "Echtzeitrating", "Echtzeitratings" ]
			},
            '8': {
                "name": "Video",
                "name-de": [ "Video", "Videos" ]
            },
            '11': {
                "name": "Chart-Analyse",
                "name-de": [ "Chart-Analyse", "Chart-Analysen" ]
            },
            '12': {
                "name": "Ad-hoc-Meldung",
                "name-de": [ "Ad-hoc-Meldung", "Ad-hoc-Meldungen" ]
            },
    		'13': {
    			"name": "Marktbericht",
    			"name-de": [ "Marktbericht", "Marktberichte" ]
			},
    		'14': {
    			"name": "Meinung",
    			"name-de": [ "Meinung", "Meinungen" ]
			},
            '15': {
                "name": "Tradingidee",
                "name-de": [ "Tradingidee", "Tradingideen" ]
            },
            '16': {
                "name": "Tradeempfehlung",
                "name-de": [ "Tradeempfehlung", "Tradeempfehlungen" ]
            }
		},
		sectors: {
			"1": { name:"Automobilproduktion", "name-de":[ "Automobilproduktion" ] },
			"3": { name:"Automobilzulieferer", "name-de":[ "Automobilzulieferer" ] },
			"5": { name:"Autovermietungen", "name-de":[ "Autovermietungen" ] },
			"7": { name:"Banken", "name-de":[ "Banken" ] },
			"9": { name:"Bau- + Ingenieurswesen", "name-de":[ "Bau- + Ingenieurswesen" ] },
			"11": { name:"Bauhauptgewerbe", "name-de":[ "Bauhauptgewerbe" ] },
			"13": { name:"Baumaterial und -komponenten", "name-de":[ "Baumaterial und -komponenten" ] },
			"15": { name:"Bekleidungsartikel", "name-de":[ "Bekleidungsartikel" ] },
			"17": { name:"Beteiligungen", "name-de":[ "Beteiligungen" ] },
			"19": { name:"Biotechnologie", "name-de":[ "Biotechnologie" ] },
			"21": { name:"Broadcasting (TV und Radio)", "name-de":[ "Broadcasting (TV und Radio)" ] },
			"23": { name:"Chemie", "name-de":[ "Chemie" ] },
			"25": { name:"Computer-Hardware", "name-de":[ "Computer-Hardware" ] },
			"27": { name:"Dienstleistungen", "name-de":[ "Dienstleistungen" ] },
			"29": { name:"Drogerie und Kosmetikgüter", "name-de":[ "Drogerie und Kosmetikgüter" ] },
			"31": { name:"Edelmetalle (Gold)", "name-de":[ "Edelmetalle (Gold)" ] },
			"33": { name:"Einzelhandel", "name-de":[ "Einzelhandel" ] },
			"35": { name:"Eisen/Stahlindustrie", "name-de":[ "Eisen/Stahlindustrie" ] },
			"37": { name:"Eisenbahn und Straße", "name-de":[ "Eisenbahn und Straße" ] },
			"39": { name:"Elektroausstattung und Vertrieb", "name-de":[ "Elektroausstattung und Vertrieb" ] },
			"41": { name:"Elektrotechnologie", "name-de":[ "Elektrotechnologie" ] },
			"43": { name:"Energieversorger", "name-de":[ "Energieversorger" ] },
			"45": { name:"Entertainment/Dienstleistungen", "name-de":[ "Entertainment/Dienstleistungen" ] },
			"47": { name:"Entsorgung/Umwelttechnologie/-dienstleistung", "name-de":[ "Entsorgung/Umwelttechnologie/-dienstleistung" ] },
			"49": { name:"Finanzdienstleistungen", "name-de":[ "Finanzdienstleistungen" ] },
			"51": { name:"Fluggesellschaften", "name-de":[ "Fluggesellschaften" ] },
			"53": { name:"Gesundheitsdienstleistungen", "name-de":[ "Gesundheitsdienstleistungen" ] },
			"55": { name:"Getränke/Tabak", "name-de":[ "Getränke/Tabak" ] },
			"57": { name:"Gütertransport", "name-de":[ "Gütertransport" ] },
			"59": { name:"Halbleiterindustrie", "name-de":[ "Halbleiterindustrie" ] },
			"61": { name:"Holdings", "name-de":[ "Holdings" ] },
			"63": { name:"Holzindustrie/Holzverarbeitung", "name-de":[ "Holzindustrie/Holzverarbeitung" ] },
			"65": { name:"IT-Dienstleistungen", "name-de":[ "IT-Dienstleistungen" ] },
			"67": { name:"IT-Software (Telekommunikation und Internet)", "name-de":[ "IT-Software (Telekommunikation und Internet)" ] },
			"69": { name:"Immobilien", "name-de":[ "Immobilien" ] },
			"71": { name:"Internetkommerz", "name-de":[ "Internetkommerz" ] },
			"73": { name:"Internetservice", "name-de":[ "Internetservice" ] },
			"75": { name:"Kaufhäuser", "name-de":[ "Kaufhäuser" ] },
			"77": { name:"Kunststoffe", "name-de":[ "Kunststoffe" ] },
			"79": { name:"Luft- und Raumfahrtindustrie", "name-de":[ "Luft- und Raumfahrtindustrie" ] },
			"81": { name:"Makler", "name-de":[ "Makler" ] },
			"83": { name:"Maschinenbau", "name-de":[ "Maschinenbau" ] },
			"85": { name:"Medical Equipment", "name-de":[ "Medical Equipment" ] },
			"87": { name:"Metallverarbeitung", "name-de":[ "Metallverarbeitung" ] },
			"89": { name:"Mischkonzerne", "name-de":[ "Mischkonzerne" ] },
			"91": { name:"Möbel und Einrichtungsindustrie", "name-de":[ "Möbel und Einrichtungsindustrie" ] },
			"93": { name:"Nahrungsmittel", "name-de":[ "Nahrungsmittel" ] },
			"95": { name:"Nanotechnologie", "name-de":[ "Nanotechnologie" ] },
			"97": { name:"Netzwerktechnik und -systeme", "name-de":[ "Netzwerktechnik und -systeme" ] },
			"99": { name:"Papierindustrie", "name-de":[ "Papierindustrie" ] },
			"101": { name:"Pharma", "name-de":[ "Pharma" ] },
			"103": { name:"Pharmahandel", "name-de":[ "Pharmahandel" ] },
			"105": { name:"Printmedien (Zeitungen und Magazine)", "name-de":[ "Printmedien (Zeitungen und Magazine)" ] },
			"107": { name:"Restaurants und Foodvertrieb", "name-de":[ "Restaurants und Foodvertrieb" ] },
			"109": { name:"Rohstoffe", "name-de":[ "Rohstoffe" ] },
			"111": { name:"Software", "name-de":[ "Software" ] },
			"113": { name:"Softwareservice/-dienstleistung", "name-de":[ "Softwareservice/-dienstleistung" ] },
			"115": { name:"Sonstige Branchen", "name-de":[ "Sonstige Branchen" ] },
			"117": { name:"Sonstige Energie/Rohstoffe", "name-de":[ "Sonstige Energie/Rohstoffe" ] },
			"119": { name:"Sonstige Handel", "name-de":[ "Sonstige Handel" ] },
			"121": { name:"Sonstige Industrie", "name-de":[ "Sonstige Industrie" ] },
			"123": { name:"Sonstige Konsumgüter", "name-de":[ "Sonstige Konsumgüter" ] },
			"125": { name:"Sonstige Kraftfahrzeugindustrie", "name-de":[ "Sonstige Kraftfahrzeugindustrie" ] },
			"127": { name:"Sonstige Technologie", "name-de":[ "Sonstige Technologie" ] },
			"129": { name:"Sonstige Versorger", "name-de":[ "Sonstige Versorger" ] },
			"131": { name:"Spezialmaschinenbau", "name-de":[ "Spezialmaschinenbau" ] },
			"133": { name:"Spezialsoftware", "name-de":[ "Spezialsoftware" ] },
			"135": { name:"Sport/Glückspiel", "name-de":[ "Sport/Glückspiel" ] },
			"137": { name:"Sportartikel", "name-de":[ "Sportartikel" ] },
			"139": { name:"Standardsoftware", "name-de":[ "Standardsoftware" ] },
			"141": { name:"Telekomdienstleister", "name-de":[ "Telekomdienstleister" ] },
			"143": { name:"Telekommunikationsausrüster", "name-de":[ "Telekommunikationsausrüster" ] },
			"145": { name:"Textilindustrie", "name-de":[ "Textilindustrie" ] },
			"147": { name:"Touristik und Freizeit", "name-de":[ "Touristik und Freizeit" ] },
			"149": { name:"Unterhaltungselektronik", "name-de":[ "Unterhaltungselektronik" ] },
			"151": { name:"Versandhandel", "name-de":[ "Versandhandel" ] },
			"153": { name:"Versicherungen", "name-de":[ "Versicherungen" ] },
			"155": { name:"Öl und Gas", "name-de":[ "Öl und Gas" ] },
			"175": { name:"Öko-Energie", "name-de":[ "Öko-Energie" ] },
			"173": { name:"Edelmetalle", "name-de":[ "Edelmetalle" ] }

		},
		regions: {
			'1': {
				"name": "EUROPE",
				"name-de": [ "Europa" ]
			},
			'2': {
				"name": "NAMERICA",
				"name-de": [ "Nordamerika" ]
			},
			'3': {
				"name": "ASIA",
				"name-de": [ "Asien" ]
			},
			'4': {
				"name": "EMERGING_MARKETS",
				"name-de": [ "EMERGING_MARKETS" ]
			},
			'5': {
				"name": "SAMERICA",
				"name-de": [ "Südamerika" ]
			}
		}
	},

	virtualFields = {

    	'time': {
    		require: 'timestamp',
    		create: function( timestamp ) {
				var d = new Date( timestamp*1000 );
				//console.log(d);
				var c = new Date();





				var h = d.getHours();
				if ( h < 10 ) h = '0'+h;
				var m = d.getMinutes();
				if ( m < 10 ) m = '0'+m;


				if ( d.getDate() != c.getDate()  ||  d.getMonth() != c.getMonth() ||  d.getYear() != c.getYear())
					return d.getDate() + '.' + (d.getMonth()+1) + '. ' + h + ':' + m;

				return h + ':' + m;
			}
		},
		'type-de': {
			require: 'type',
			create: function( type ) {
				try {
					return constants.types[type] && constants.types[type]['name-de'][0];
				} catch(e) {
					return null;
				}
			}
		}



	},



    /*
    	sListEntry is the entry in s.list
    */
    pushEvent = function( sListEntry, pushData ) {

        if ( pushData instanceof Array ) {
            for (var i=0; i < pushData.length; i++ ) {
                pushEvent( sListEntry, pushData[i] );
            }
            return;
        }


        debug>2 && console.log("[bgarticle] ==========================================");
        debug>2 && console.log("[bgarticle] new article arrived " + pushData.data.id);
        debug>2 && console.log(  pushData );
        debug>2 && console.log(  sListEntry );




        if ( typeof pushData !== 'object' ) {
            debug && console.log( "[bgarticle] no valid data arrived");
            return false;
        }

        if ( ! pushData.data ) {
        	debug && console.log("[bgarticle] invalid article arrived");
        	return false;
		}


		if ( pushData.data.filterText ) {
			/*subscribeOptions.jsonpCallback( function(d) {
				options.filterText(  d.filterText );
			});*/

			if ( sListEntry && sListEntry.options && sListEntry.options.filterText )
				sListEntry.options.filterText( pushData.data.filterText, sListEntry );
			debug && console.log ( "[bgarticle] Filtertext: " + pushData.data.filterText );
			return;
		}


		if ( !pushData.data.id ) {
			debug && console.log("[bgarticle] invalid article arrived: no id specified");
			return false;
		}

		if ( !pushData.data.timestamp ) {
			debug && console.log("[bgarticle] invalid article arrived: no timestamp specified");
			return false;
		}




        var pushToNode = function( subscr, data, isUpdate ) {

            var fillTemplate = function( $template, data, useIndex ) {
                useIndex = useIndex || {};
                var field = $template.attr('data-article'), f = field;

                $template.removeAttr('data-article');

                if ( f !== undefined ) {
                    var i;
                    var to = data;

                    // if its a array like "instruments[]"
                    if ( i = /(.*?)\[(\d*)\]/.exec( f ) ) {
                        var fieldArray = i[1], fieldSize= i[2] || 100, dataSize;

                        // TODO fix for more dimensions
                        if ( !data[ fieldArray ] ) dataSize = 0
                        else dataSize = data[ fieldArray ].length;

                        var $innerTemplate = $template.children().remove();
                        //var $innerTemplate = $template;
                        for( var j=0; j < fieldSize  && j <  dataSize ; j++ ) {

                            var $t = $innerTemplate.clone();

                            $t.each( function(i,e) {
                                useIndexI = {};
                                useIndexI[ fieldArray ] = j;

                                fillTemplate( $(e), data, $.extend( true, {}, useIndex, useIndexI )  );
                            });
                            $t.appendTo( $template );
                        }

                        return $template;

                    }


                    // if attributes are to be filles
                    if ( f.substring(0, 6) == '_attr=')   {
                        var attributes = f.substring(6).split( /,/ );

                        for ( var l = 0; l < attributes.length; l++ ) {
                            to = data;

                            var oldAttrValue = $template.attr(attributes[l]), newAttrValue = oldAttrValue;


                            var regExpr = oldAttrValue.match( /\{.*?\}/g );
                            for (var k=0;k< regExpr.length; k++ ) {
                                f = regExpr[ k ].replace( /^\{|\}$/g, '');

                                var prefix = "";
                                while ((i = f.indexOf('.')) !== -1 ) {
                                    to = data[  f.substring(0,i) ];
                                    if (prefix!=="" ) prefix+='.';
                                    prefix +=  f.substring(0,i);
                                    if (!to) {
                                        break;
                                    }

                                    to = to[ useIndex[prefix]||0  ];
                                    f = f.substring(i+1);
                                }

                                if (!to || ! (f in to) ) {
                                    debug && console.log("[bgarticle] attribute " + field + " has not arrived");
                                } else {
                                    newAttrValue = newAttrValue.replace( regExpr[k], to[f] );
                                }
                            }

                            $template.attr(attributes[l], newAttrValue);
                        }

                    }

                    // simple data-article attribute specified
                    else {
                        var prefix = "";

                        while ((i = f.indexOf('.')) !== -1 ) {

                            to = to[  f.substring(0,i) ];


                            if (prefix!=="" ) prefix+='.';
                            prefix +=  f.substring(0,i);
                            if (!to) {
                                break;
                            }

                            if ( to instanceof Array )
                                to = to[ useIndex[prefix]||0  ];

                            f = f.substring(i+1);
                        }

                        if (!to || ! (f in to) ) {
                            debug && console.log("[bgarticle] attribute " + field + " has not arrived");
                        } else {
                        	var val = to[f];

                        	if (subscr.options.field && subscr.options.field[ field ] && typeof subscr.options.field[ field ].prepare === 'function' )
                        		val = subscr.options.field[ field ].prepare(val,data);
                        	else if ( fieldsOptions[ field ] && typeof fieldsOptions[field].prepare === 'function' )
                        		val = fieldsOptions[field].prepare(val,data);


                        	var useHTML = fieldsOptions[ field ] && fieldsOptions[field].useHTML;
                        	if ( subscr.options.field && subscr.options.field[ field ] && subscr.options.field[ field ].useHTML !== undefined ) {
                        		useHTML = subscr.options.field[ field ].useHTML;
							}

                        	if ( useHTML )
                        		$template.html( val );
                        	else
                            	$template.text( val );
                        }
                    }



                }

                return $template.children().each(function(i,e) {
                    fillTemplate( $(e), data, useIndex );
                });


            };

            // create article template
            var $t = subscr.template.clone();



            fillTemplate( $t, data );
            try {
            	$t.bg_quotepush();
			} catch(e) {}


			$t.attr('data-article', data.id );

			debug>1 && console.log("[bgpush] template created for " + newArticleId + "("+data.title+")");
			debug>1 && console.log($t);


            // using options.preprocess
            var opt = {};

            if ( typeof subscr.options.preprocess === 'function' ) {
                $.extend( true, opt, subscr.options.preprocess( {
                    $node: $t,
                    data: data,
                    pushEvent: pushData,
                    rePush: function() {
                    	pushEvent( subscr, pushData );
					}
                } ));
            }

            if ( opt.sticky ) {
            	$t.data('sticky',true);

                // TODO handle opt.sticky.time and opt.sticky.onTimeout

                // if sticky container does not exist, create it
                if (  !subscr.nodeSticky ) {
                    $containerNode = $('<div />').addClass('sticky').prependTo( subscr.node );
                    subscr.nodeSticky = $containerNode;
                } else {
                    $containerNode = subscr.nodeSticky;
                }
            } else {
            	$t.data('sticky',false);
			}


            // find correct position
            var nodeToPrepend;
            var nodeToPrepend_ts;
            var nodeToPrepend_id;


            for ( var articleId in subscr.articles ) {

                if ( ! subscr.articles.hasOwnProperty(articleId)) continue;

                if ( ! opt.sticky &&   subscr.articles[articleId].sticky ) continue;
                if (   opt.sticky && ! subscr.articles[articleId].sticky ) continue;

                var ts = subscr.articles[articleId].data.timestamp;


/*if ( newArticleId==2444235) {
console.log("matching with " + articleId + " (ts:" + ts+  ')');
console.log((nodeToPrepend===undefined || nodeToPrepend_ts < ts) && ts < newArticleTs );
console.log( ts != nodeToPrepend_ts && ts == newArticleTs && newArticleId > articleId );
console.log( nodeToPrepend_ts == ts && nodeToPrepend_id < articleId && (newArticleId > articleId  || newArticleTs > ts ) );
}*/

                if ( ((nodeToPrepend===undefined || nodeToPrepend_ts < ts) && ts < newArticleTs  )
                	 ||  ( ts != nodeToPrepend_ts && ts == newArticleTs && newArticleId > articleId )
                	 ||  nodeToPrepend_ts == ts && nodeToPrepend_id < articleId && (newArticleId > articleId  || newArticleTs > ts ) )
                {

	                    nodeToPrepend_ts = ts;
	                    nodeToPrepend_id = articleId;
	                    nodeToPrepend = subscr.articles[articleId].node;
                }




            }

//console.log("new article " + newArticleId + "(ts: "+newArticleTs+") appendingTo " + nodeToPrepend_id);


            if ( ! nodeToPrepend  ) {
            	debug>1 && console.log("[bgpush] appending to ");
            	debug>1 && console.log(opt.sticky ? subscr.nodeSticky : subscr.node);

                $t.appendTo (  opt.sticky ? subscr.nodeSticky : subscr.node );
            } else {
            	debug>1 && console.log("[bgpush] inserting before " + nodeToPrepend_id);
            	debug>1 && console.log(nodeToPrepend);

                $t.insertBefore( nodeToPrepend );
            }

            if ( !isUpdate && typeof subscr.options.show === 'function' ) {
                subscr.options.show({
                    $node: $t,
                    $container: subscr.node,
                    data: data,
                    pushEvent: pushData
                });
            } else if ( isUpdate && typeof subscr.options.update === 'function' ) {
                subscr.options.update({
                    $node: $t,
                    $container: subscr.node,
                    data: data,
                    pushEvent: pushData
                });
            }


            return $t;

        };





        for ( var field in virtualFields ) {
        	if ( pushData.data[ virtualFields[field].require ]  !== undefined )
        		pushData.data[ field ] = virtualFields[field].create( pushData.data[ virtualFields[field].require ] );
		}




        var subscr = sListEntry;
        var data = pushData.data;

        var newArticleId = parseInt( data.id );
        var newArticleTs = parseInt( data.timestamp );


        var isUpdate = false;


        // this new pushed article is already shown
        if ( subscr.articles[ newArticleId ] ) {
            debug>1 && console.log("[bgarticle] ==== received update for article id " + newArticleId);

            isUpdate = true;

            if ( subscr.node )
            	subscr.articles[ newArticleId ].node.remove();

            delete subscr.articles[ newArticleId ];
            subscr.articlesCount--;

        }  else {
			debug>1 && console.log("[bgarticle] ==== received article id " + newArticleId);
		}



        if ( subscr.articlesCount >= subscr.options.count ) {
            // box is full, one article has to leave, find youngest

            var minTs = newArticleTs;
            var removeArticleId = data.id;

            for ( var articleId in subscr.articles ) {
                if ( ! subscr.articles.hasOwnProperty(articleId)) continue;

                // dont remove sticky articles
                if ( subscr.articles[articleId].sticky ) continue;

                if ( parseInt( subscr.articles[articleId].data.timestamp ) < minTs ) {
                    minTs = parseInt( subscr.articles[articleId].data.timestamp );
                    removeArticleId = parseInt( articleId );
                }
            }

            // ok all visible articles are newer than this one..
            if ( removeArticleId == data.id ) return false;



            if ( subscr.node ) {

	            // remove article
	            if ( typeof subscr.options.remove === 'function' ) {
	                subscr.options.remove({
	                    $node: subscr.articles[ removeArticleId ].node,
	                    $container: subscr.node
	                });

	            } else {
	                subscr.articles[ removeArticleId ].node.remove();
	            }

			}



            if( typeof subscr.options.removeCallback === 'function' ) {
            	debug>1 && console.log("[bgarticle] calling removeCallback");
            	subscr.options.removeCallback( removeArticleId,  subscr );
			}



            delete subscr.articles[ removeArticleId ];
            subscr.articlesCount--;

        }


        var $t;
		if ( subscr.node )
 			$t = pushToNode(subscr,data, isUpdate );


        if( typeof subscr.options.addCallback === 'function' ) {
        	debug>1 && console.log("[bgarticle] calling addCallback");
        	subscr.options.addCallback( data,isUpdate, subscr );
		}


        subscr.articles[ newArticleId ] = { node:$t, data:data, sticky: ($t&&$t.data('sticky')) };
        subscr.articlesCount++;







    },


	fieldsOptions= {
		'content': {
			useHTML: true,
			prepare: function( html ) {
				var ret = html.replace( /(<br\s*\/?>)+\s*/ig, '' );

				return ret;
			}
		}
	},

    // Subscriptions
    s = {
        /*
            list of all subscriptions, the index is the subscription id
            {subscriptionId:  {
                cmd: "subscriptionCommandObject",
                fct: callbackfunction,
                node: jquery object of the top box
                nodeSticky: jquery object of the sticky box
                options: options specified
                subscription:  subscriptionId

                articles:  { <article id>: { node:$node, data:data} }   // the visible articles in an hash, indexed by the article id
                articlesCount: <countOfVisibleArticles>
            }}
        */
        list: {},  // list of subscriptions

        /*
        	example usage:
        $('#TickerFeed > div.articles').bg_articlepush( 'subscribe', currentFeed.getFilterObject() , {
        	count:20,
        	subscriptionId: 100,
        	fields: [ 'categories', {'instruments':['isin', 'sort', 'countryCode']} ],

        	filterText: function( text ) {
				$('#TickerFeedFilterText').text(text);
			},

	        preprocess: function( o ) {
	        	var attr = {};
				//..
	            return attr;
	        },

	        show:function( o ){
				if ( !hideAnimations ) {
	            	o.$node.hide().slideDown(750);
	            	Jandaya.playSound("Ticker/newsNotice.mp3");
				}
	        },

	        remove: function( o ) {
	            o.$node.slideUp(750, function() {
	                o.$node.remove();
	            });
	        },

	        beginLoad: function( first ) {
	        	Jandaya.Ticker.loader.inc('feed');
	        	if ( first )
	        		$('#TickerFeedContentLoader').show();
			},
			endLoad: function( ) {
				Jandaya.Ticker.loader.dec('feed');
	        	$('#TickerFeedContentLoader').hide();
			}

        });
        */
        subscribe: function( filter, options ) {

        	debug && console.log( "[bgarticle] new subscription: ");
        	debug && console.log( filter );

        	var isjquery = this instanceof jQuery ;

            // options
            var o = $.extend( true, {}, defaultOptions, options );

            try {
            	if ( typeof filter === 'string' ) filter = $.parseJSON( filter );
			} catch(e) {
				debug && console.log("[bgarticle] error parsing filter");
				return false;
			}
            if ( !filter ) filter = {};


            var $n, $t;

            if ( isjquery ) {
	            // node
	            var $n = this;

	            // template
	            var $t =  $n.children( '[data-article=_template]' ).removeAttr('data-article').remove();
	            if ( $t.length === 0 ) {
	                alert("Articlepush failed: no template found");
	                return false;
	            }
			}

            // which fields are required? go through template

            if ( ("fields" in o) && ! (o.fields instanceof Array))
                o.fields = [ o.fields ];

            var fields = o.fields || [];

            fields.push('id');
            fields.push('timestamp');

            var iterateTemplate = function( $node ) {

                var f = $node.attr('data-article');
                if ( virtualFields[ f ] ) f =  virtualFields[ f ].require;

                var f_list = [];

                if ( f !== undefined ) {
                    if ( f.substring(0,5) === '_attr' ) {
                        var attributes = f.substring(6).split( /,/ );
                        for (var i = 0; i< attributes.length; i++ ) {
                            var value = $node.attr( attributes[i ] );

                            var a = value.match( /\{.*?\}/g );

                            if(a) for( var k=0; k < a.length; k++ ) {
                                f_list.push(  a[k].replace( /^\{|\}$/g, '')  );
                            }
                        }
                    } else if ( !/\[\d*\]$/.exec(f)) {
                        f_list.push( f );
                    }
                }

                for ( var h= 0; h < f_list.length; h++ ) {
                    f = f_list[h];

                    var i;
                    var appendTo = fields;
                    while( (i=f.indexOf( '.')) !== -1 ) {

                        var found = 0;
                        for( var j=0; j < appendTo.length; j++ ) {
                            if ( appendTo[j] instanceof Object ) {
                                appendTo[j][ f.substring(0,i) ] = appendTo[j][ f.substring(0,i) ] || [];
                                appendTo = appendTo[j][ f.substring(0,i) ];
                                found = 1;
                                break;
                            }
                        }
                        if ( !found ) {
                            var insert = {}, insertArray = [];
                            insert[ f.substring(0,i) ] = insertArray;
                            appendTo.push( insert );
                            appendTo = insertArray;


                        }

                        f = f.substring(i+1);


                    }

                    for ( var j = 0; j < appendTo.length; j++ )
                        if ( appendTo[j] === f ) break;

                    if ( j === appendTo.length )
                        appendTo.push ( f );
                }

                $node.children().each(function(i,e) {
                    iterateTemplate( $(e) );
                });

                return fields;
            }

            if ( isjquery )
            	fields = iterateTemplate ( $t );


            var cmd = {
                data: {
                    event: 'article',
                    data: filter
                },
                fields: fields,
                history: o.count,
                token: o.token || null
            };

			var subscribeOptions = {};
			if ( options.subscriptionId ) subscribeOptions.subscriptionId = options.subscriptionId;
			if ( options.beginLoad ) {
				subscribeOptions.beginLoad = function(first) {
					options.beginLoad(first, sListEntry );
				};
			}

			if ( options.endLoad ) {
				subscribeOptions.endLoad = function() {
					options.endLoad( sListEntry );
				};
			}

            var sListEntry = {
                node: $n,
                template: $t,
                options: o,
                subscribeOptions: subscribeOptions,
                cmd: cmd,

                articles: {},
                articlesCount: 0
			};

            var subscriptionId = BG.push.subscribe( cmd, function(data) {  pushEvent( sListEntry, data)  }, subscribeOptions );


            if (isjquery)
            	this.data('bg_articlepush', subscriptionId ).attr('data-bg_articlepush', subscriptionId );


            s.list[ subscriptionId ] = sListEntry;


			if ( isjquery )
            	return this;
            else
            	return subscriptionId;


        },

        unsubscribe: function( subscription ) {
        	if ( this instanceof jQuery )
            	subscription = this.removeAttr('data-bg_articlepush').data('bg_articlepush');


            if ( subscription===undefined ) return false;
            debug && console.log("unsubscribe " + subscription );

            if ( this instanceof jQuery ) {
            	this.empty();
            	s.list[ subscription ].template.attr('data-article','_template').appendTo( this );
			}
            delete s.list[ subscription ];

            BG.push.unsubscribe( subscription );
            return this;
        }

    },


    getOldestTimestamp = function( subscription ) {
		if ( !s.list[ subscription ].articles ) return false;

        var minTs;
        for ( var i in s.list[ subscription ].articles ) {
        	var ts = s.list[ subscription ].articles[i].data.timestamp;
        	if ( minTs === undefined)  minTs = ts;
        	else minTs = Math.min( ts,minTs );
		}

		return minTs;
	},

    methods= {
        'subscribe': s.subscribe,
        'setCount': function( count ) {
        	var subscription = this.data('bg_articlepush');
        	if ( subscription === undefined)  return false;

        	if ( !s.list[ subscription ] ) return false;
        	var sListEntry = s.list[subscription];

        	if ( typeof count === "object" ) {
        		count = +count.increase + +s.list[subscription].options.count;
			}

			count = parseInt ( count );

			if ( typeof count !== 'number' ) return false;

			if ( count > s.list[subscription].cmd.history && s.list[subscription].cmd.history > 10 ) {

				// ugly, ugly hack ;)

				var subscr = $.extend(true,{},BG.push.list()[ subscription ].subscription);
				subscr.history = count - s.list[subscription].cmd.history + 3;
                subscr.data = subscr.data || {}; subscr.data.data = subscr.data.data || {};
                subscr.data.data.timestamp = { "lte": getOldestTimestamp( subscription ) };
                subscr.data.filterText = 0;


        		s.list[subscription].options.count = count;
        		s.list[subscription].cmd.history = count;

				BG.push.manualJsonp( subscr );

			} else if ( count < s.list[subscription].cmd.history ) {

				// TODO can be better (just remove oldest X articles)

				for ( var i in s.list[subscription].articles ) {
					if ( ! s.list[subscription].articles.hasOwnProperty(i ) ) continue;
					s.list[subscription].articles[i].node.remove();
					delete s.list[subscription].articles[i];
				}
				s.list[subscription].articlesCount = 0;


        		BG.push.unsubscribe( subscription );
        		s.list[subscription].options.count = count;
        		s.list[subscription].cmd.history = count;

        		s.list[subscription].subscribeOptions.noBeginLoad= true;
				var newSubscription = BG.push.subscribe( s.list[subscription].cmd, function(data) {  pushEvent( sListEntry , data)  }, s.list[subscription].subscribeOptions );
				if ( newSubscription != subscription ) {
					this.data('bg_articlepush', newSubscription);
					s.list[newSubscription] = s.list[subscription];
					delete s.list[subscription];
				}

			} else {

        		BG.push.unsubscribe( subscription );
        		s.list[subscription].options.count = count;
        		s.list[subscription].cmd.history = count;

        		s.list[subscription].subscribeOptions.noBeginLoad= true;
				var newSubscription = BG.push.subscribe( s.list[subscription].cmd, function(data) {  pushEvent( sListEntry , data)  }, s.list[subscription].subscribeOptions );
				if ( newSubscription != subscription ) {
					this.data('bg_articlepush', newSubscription);
					s.list[newSubscription] = s.list[subscription];
					delete s.list[subscription];
				}
			}






		},
        'getOldestTimestamp': function() {
        	var subscription = this.data('bg_articlepush');
        	if ( subscription === undefined)  return false;

        	return getOldestTimestamp( subscription );
		},
        'unsubscribe': s.unsubscribe,
/*        'pushEvent': function( o ) {
        	pushEvent( s.list[ o.subscriptionId ], o );
		},*/
        'debug': function(lvl) {
            lvl = lvl || 0;
            debug = lvl;

            $.bg = $.bg || {};
            $.bg.article = {
                s: s,
                subscriptions: s.list

            };
        },

        'constants': function( which ) {
        	if ( which !== undefined )
        		return constants[which];
        	else
        		return constants;
		},

		'constantsRev': function( which ) {
			if ( which === undefined ) return false;

			var ret = {};
			for (var i in constants[which]) {
				ret[ constants[which][i].name ] = i;
			}

			return ret;

		},

		'getSubscriptions': function() {
			return s.list;
		}

    };

/*    var methodsCb = $.extend( true, {}, methods, {

	}); */





    $.bg_articlepush = function() {
        if ( typeof arguments[0] === 'string' &&  methods[ arguments[0] ] ) {
            return methods[ arguments[0] ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
        }

        return methods[ 'subscribe' ].apply( this, arguments );
	};


	for ( var i in methods ) {
		$.bg_articlepush[i] = methods[i];
	}


    for ( var  i in constants )
    	$.bg_articlepush.constants[i] = constants[i];




    $.fn.bg_articlepush = function() {

        if ( typeof arguments[0] === 'string' &&  methods[ arguments[0] ] ) {
            return methods[ arguments[0] ].apply( this, Array.prototype.slice.call( arguments, 1 ) );
        }

        return methods[ 'subscribe' ].apply( this, arguments );

    };



})(jQuery);


/* exmaple usage:





	var Sektoren = $.bg_articlepush.constantsRev('sectors');
    var id = $.bg_articlepush( 'subscribe', {
    	//sectors: Sektoren['Automobilproduktion']
    	//categories: {"in": [ 1,2,3,4,5,6,7]}
	}, {
		fields: [ "content", {"instrument": [ "id" ]} ],
		count:2,
		subscriptionId: 2,
		addCallback: function(data, isUpdate) {
			console.log( (isUpdate?"updated":"new") + " article " + data.id);
			console.log(data);
		},
		removeCallback: function( articleId, data ) {
			console.log("removing article: " + articleId);
			console.log( data );
		},
	    beginLoad: function( first ) {
	          console.log("loading " + (first?"fist time":""));
		},
		endLoad: function(  ) {
			console.log("finished loading");
		}
	} );

	$.bg_articlepush('unsubscribe', id );





*/
