/*!
 * MarketData v1.0
 * http://alertir.com/
 *
 * Copyright 2011, Alert Investor Relations AB
 *
 * Date: 2011-10-03
 */

if (typeof MarketData === "undefined") {
    if (console && console.error) {
        console.error("MarketData","not found");
    } else {
        alert("MarketData not found");
    }
}

if (typeof MarketData_Chart === "undefined") {
    if (console) {
        console.error("MarketData_Chart","not found");
    } else {
        alert("MarketData_Chart not found");
    }
}

/**
 * @version 2011-05-17
 */
var MarketData_UI = function(config) {

    var self = this;
    var debug = 0;

    /*
     * Check config
     */

    if (!config.ui) {
        config.ui = {};
    }
    if (!config.md) {
        config.md = {};
    }
    if (!config.chart) {
        config.chart = {};
    }
    if (!config.ui.instruments) {
        config.ui.instruments = [];
    }
    if (!config.ui.comparisons) {
        config.ui.comparisons = [];
    }
    if (!config.ui.analyses) {
        config.ui.analyses = [];
    }
    if (!config.ui.markers) {
        config.ui.markers = [];
    }
    if (!config.ui.modules) {
        config.ui.modules = {};
    }

    if (!config.ui.ui) {
        config.ui.ui = {
            diagram: [],
            volume: {}
        };
    }

    if (!config.ui.colors) {
        config.ui.colors = {
            instruments: [
                "275BA8",
                "275BA8",
                "275BA8"
            ],
            comparisons: [
                "379B29",
                "A85B27",
                "712C6B",
                "555555",
                "603913",
                "77202D",
                "EE2E24",
                "000000"
            ],
            markers: {
                press: "0097DC",
                reports: "00AC68",
                insiders: "EF984B"
            }
        };
    }

    if (!config.ui.i18n) {
        /*
        * TODO: hierarchical naming of i18n keys to avoid clashes
        * TODO: externalize i18n configuration (as well as others)
        */
        config.ui.i18n = {
            last:   { en: "Last",   sv: "Senast",     fi: "Viimeisin" },
            change: { en: "Change", sv: "Förändring", fi: "Muutos" },
            bid:    { en: "Bid",    sv: "Köp",        fi: "Osto" },
            ask:    { en: "Ask",    sv: "Sälj",       fi: "Myynti" },
            high:   { en: "High",   sv: "Högst",      fi: "Ylin" },
            low:    { en: "Low",    sv: "Lägst",      fi: "Alin" },
            volume: { en: "Volume", sv: "Volym",      fi: "Määrä" },
            //
            comparisons: { en: "Comparisons", sv: "Jämförelser",   fi: "Vertailut" },
            analyses:    { en: "Analyses",    sv: "Analyser",      fi: "Analyses" },
            markers:     { en: "Markers",     sv: "Markörer",      fi: "Tiedotteet" },
            settings:    { en: "Settings",    sv: "Inställningar", fi: "Asetukset" },
            downloads:   { en: "Downloads",   sv: "Ladda ner",     fi: "Lataa" },
            //
            intraday: { en: "Intraday", sv: "Intradag",   fi: "Päivänsisäinen" },
            interday: { en: "Interday", sv: "Interdag",   fi: "Muu päiväasteikko" },
            "1h":     { en: "1H",       sv: "1 tim",      fi: "1h" },
            "6h":     { en: "6H",       sv: "6 tim",      fi: "6h" },
            "12h":    { en: "12H",      sv: "12 tim",     fi: "12h" },
            "1w":     { en: "1W",       sv: "1 v",         fi: "1 vko" },
            "1m":     { en: "1M",       sv: "1 mån",      fi: "1 kk" },
            "3m":     { en: "3M",       sv: "3 mån",      fi: "3 kk" },
            ytd:      { en: "YTD",      sv: "YTD",        fi: "Vuoden alusta" },
            "1y":     { en: "1Y",       sv: "1 år",       fi: "1 v" },
            "3y":     { en: "3Y",       sv: "3 år",       fi: "3 v" },
            max:      { en: "MAX",      sv: "MAX",        fi: "Kaikki" },
            //
            shareinfo: { en: "Share information", sv: "Aktieinformation",   fi: "Osaketunnukset" },
            sharedata: { en: "Share data",        sv: "Aktiedata",           fi: "Osaketiedot" },
            orderbook: { en: "Orderbook",         sv: "Orderbok",            fi: "Tilauskant" },
            //
            name:         { en: "Name",         sv: "Namn",          fi: "Nimi" },
            symbol:       { en: "Symbol",       sv: "Symbol",        fi: "Tunnus" },
            isin_code:    { en: "ISIN-code",    sv: "ISIN-kod",      fi: "ISIN-koodi" },
            listed_since: { en: "Listed since", sv: "Noterad sedan", fi: "Listautumispäivä" },
            market_name:  { en: "Market name",  sv: "Börs",          fi: "Markkina" },
            listing:      { en: "Listing",      sv: "Notering",      fi: "Lista" },
            trading_lot:  { en: "Trading lot",  sv: "Handelspost",   fi: "Pörssierä kpl" },
            currency:     { en: "Currency",     sv: "Valuta",        fi: "Valuutta" },
            share:        { en: "Share",        sv: "Aktie",         fi: "Osaket" },
            //
            market_cap:         { en: "Market capitalization, MSEK", sv: "Marknadsvärde, mSEK",       fi: "Markkina-arvo, MSEK" },
            share_price_1231:   { en: "Share price, Dec 31, SEK",    sv: "Kurs 31/12, SEK",           fi: "Osakehinta, Joulukuun 31, SEK" },
            change_1231:        { en: "Change in % since Dec 31",    sv: "Förändring under året",     fi: "Muutos % Joulukuun 31 lähtien" },
            year_high:          { en: "Year high, SEK",              sv: "Årshögsta, SEK",            fi: "Vuoden ylin, SEK" },
            year_low:           { en: "Year low, SEK",               sv: "Årslägsta, SEK",            fi: "Vuoden alin, SEK" },
            all_time_high:      { en: "All time high, SEK",          sv: "Högsta notering, SEK",      fi: " Kaikkien aikojen paras, SEK" },
            all_time_high_date: { en: "All time high date",          sv: "Datum för högsta notering", fi: "Kaikkien aikojen paras pvm" },
            //
            buy:                { en: "Buy",                         sv: "Köp",                       fi: "Osto" },
            sell:               { en: "Sell",                        sv: "Sälj",                      fi: "Myynti" },
            //
            press:       { en: "Press",       sv: "Pressmeddelanden", fi: "Lehdistötiedotteet" },
            reports:     { en: "Reports",     sv: "Rapporter",        fi: "Raportit" },
            insiders:    { en: "Insiders",    sv: "Insiders",         fi: "Sisäpiiri" },
            index:       { en: "index",       sv: "index",            fi: "Indeksit" },
            show_volume: { en: "Show volume", sv: "Visa volym",       fi: "Piilota/Näytä määrä" },
            line:        { en: "Line",        sv: "Linje",            fi: "Viiva" },
            bar:         { en: "Bar",         sv: "Stapel",           fi: "Palkki" },
            scale:       { en: "Scale",       sv: "Skalning",         fi: "Asteikko" },
            values:      { en: "Values",      sv: "Värden",           fi: "Arvot" },
            percent:     { en: "Percent",     sv: "Procent",          fi: "Prosentti" },
            chart:       { en: "Chart",       sv: "Graf",             fi: "Tilastot" },
            // Downloads
            excel: { en: "Excel document", sv: "Excel-dokument", fi: "Excel Dokumentti" },
            jpg:   { en: "JPEG image",     sv: "JPEG-bild",      fi: "JPEG Kuva" },
            png:   { en: "PNG image",      sv: "PNG-bild",       fi: "PNG Kuva" },
            pdf:   { en: "PDF document",   sv: "PDF-dokument",   fi: "PDF Dokumentti" },
            // Technical Analysis
            ta_heading:  { en: "Technical Analysis", sv: "Teknisk analys", fi: "" },
            ta_simple:   { en: "Method",             sv: "Metod",          fi: "" },
            ta_sma20:    { en: "SMA(20)",            sv: "SMA(20)",        fi: "" },
            ta_ema20:    { en: "EMA(20)",            sv: "EMA(20)",        fi: "" },
            ta_volume:   { en: "Volume",             sv: "Volym",          fi: "" },
            ta_advanced: { en: "Advanced",           sv: "Avancerade",     fi: "" },
            ta_rsi14:    { en: "RSI(14)",            sv: "RSI(14)",        fi: "" },
            ta_mfi14:    { en: "MFI(14)",            sv: "MFI(14)",        fi: "" }
        };
    }

    /**
     * The amended config
     */
    this.config = config;

    /**
     * An instance of MarketData_Chart
     */
    this.chart = null;

    /**
     * The MarketData instance handles all data delivery
     */
     this.md = new MarketData({
         baseuri: this.config.md.baseuri ? this.config.md.baseuri : "/marketdata/MarketDataServer.php"
     });

    /**
     *
     */
    this.module = {};

    /**
     * Default date/datetime formats
     */
    this.dateformat = {
        date: "yyyy-MM-dd",
        datetime: "yyyy-MM-dd HH:mm"
    };

    /**
     * Helper to get the MarketData instance
     */
    this.getMarketData = function() {
        return this.md;
    };

    /**
     * Helper to get the MarketData_Chart instance
     */
    this.getChart = function() {
        return this.chart;
    };

    /**
     *
     */
    this.setCustomDateRange = function(startDate, endDate) {
        this.updateState({
            key: "dateRange",
            value: [ startDate, endDate ]
        });
        jQuery(this.state).trigger("onSetCustomDateRange", [ startDate, endDate ]);
    };

    /**
     *
     */
    this.setDateRange = function(range, dateRangeMode) {
        var states = [];
        if ((dateRangeMode == "intraday" || dateRangeMode == "interday") && this.state.current.dateRangeMode != dateRangeMode) {
            states.push({
                key: "dateRangeMode",
                value: dateRangeMode
            });
        }
        states.push({
            key: "dateRange",
            value: this.shortDateRangeToDateRange(range)
        });
        this.updateState(states);
        jQuery(this.state).trigger("onSetDateRange", range);
    };

    /**
     *
     */
    this.setDateRangeMode = function(mode) {
        if (mode == "intraday" || mode == "interday") {
            this.updateState({
                key: "dateRangeMode",
                value: mode
            });
            jQuery(this.state).trigger("onSetDateRangeMode", mode);
        }
    };

    /**
     * Return an array with ALL instrument ids from config,
     * including both primary and comparison instruments
     */
    this.getInstrumentIds = function() {
        var c = [], i, j, id;
        for (i = 0; i < this.config.ui.instruments.length; i++) {
            for (j = 0; j < this.config.ui.instruments[i].items.length; j++) {
                c.push(this.config.ui.instruments[i].items[j].id);
            }
        }
        if (this.config.ui.comparisons.length) {
            for (i = 0; i < this.config.ui.comparisons.length; i++) {
                for (j = 0; j < this.config.ui.comparisons[i].items.length; j++) {
                    id = this.config.ui.comparisons[i].items[j].id;
                    if (c.indexOf(id) == -1) {
                        c.push(id);
                    }
                }
            }
        }
        return c;
    };

    /**
     *
     */
    this.getDefaultLang = function() {
         return this.config.ui.lang || "en";
    };

    /**
     *
     */
    this.getDefaultNamespace = function() {
         return this.config.ui.namespace || "alertir";
    };

    /**
     *
     */
    this.setPageTitle = function(title) {
        jQuery(".quote-title").html(title);
    };

    /**
     *
     */
    this.addMarker = function(type, autoCommit) {

        autoCommit = autoCommit || false;
        var chart = this.chart,
            markerconfig = this.getMarkerConfig(type),
            afwconfig = markerconfig.afwconfig,
            self = this,
            options = {};

        //chart.setChartWaitingState(true);

        switch (type) {
            case "reports":
                options = {
                    autoCommit: autoCommit,
                    type: type,
                    callback: function() {
                        jQuery(self.state).trigger("jobDone", {
                            type: "addMarker",
                            data: type
                        });
                    },
                    markers: {
                        token: markerconfig.token || "R",
                        bgcolor: "#"+this.config.ui.colors.markers.reports
                    },
                    marketdata: {
                        category: afwconfig.type || "report;report_annual;report_year_end",
                        lang:  afwconfig.lang || this.getDefaultLang(),
                        id: afwconfig.issuer || this.getDefaultNamespace(),
                        from: this.md.fd(this.chart.getFirstDate())
                    }
                };
            break;
            case "press":
                options = {
                    autoCommit: autoCommit,
                    type: type,
                    callback: function() {
                        jQuery(self.state).trigger("jobDone", {
                            type: "addMarker",
                            data: type
                        });
                    },
                    markers: {
                        token: markerconfig.token || "P",
                        bgcolor: "#"+this.config.ui.colors.markers.press
                    },
                    marketdata: {
                        category: afwconfig.type || "press",
                        lang: afwconfig.lang || this.getDefaultLang(),
                        id: afwconfig.issuer || this.getDefaultNamespace(),
                        from: this.md.fd(this.chart.getFirstDate())
                    }
                };
            break;
            case "insiders":
                options = {
                    autoCommit: autoCommit,
                    type: type,
                    callback: function() {
                        jQuery(self.state).trigger("jobDone", {
                            type: "addMarker",
                            data: type
                        });
                    },
                    markers: {
                        token: markerconfig.token || "I",
                        bgcolor: "#"+this.config.ui.colors.markers.insiders
                    },
                    marketdata: {
                        id: afwconfig.org_id || this.getDefaultNamespace(),
                        from: this.md.fd(this.chart.getFirstDate()),
                        lang: afwconfig.lang || this.getDefaultLang()
                    }
                };
            break;
        }
        chart.addMarker(options);
    };

    /**
     *
     */
    this.removeMarker = function(id) {
        this.chart.removeMarker(id);
    };

    /**
     *
     */
    this.getMarkerConfig = function(type, key, default_value) {
        type = type || null;
        key = key || null;
        default_value = default_value || null;
        var markers;

        if (type === null) {
            markers = this.config.ui.markers;

        } else if (type !== "") {
            var c = [];
            for (var i = 0; i < this.config.ui.markers.length; i++) {
                for (var j = 0; j < this.config.ui.markers[i].items.length; j++) {
                    if (this.config.ui.markers[i].items[j].id == type) {
                        markers = this.config.ui.markers[i].items[j];
                        break;
                    }
                }
            }

        } else {
            markers = null;
        }

        if (key) {
            return markers[key] || default_value;
        } else {
            return markers;
        }
    };

    /**
     *
     */
    this.setRangeMode = function(mode) {
        mode = mode || "interday";
        var chart = this.chart;
        var id = this.state.current.primary;
        var self = this;
        var setPrimaryOptions = {
            id: id,
            title: this.getInstrumentAttr(id, "name", id),
            color: "#"+this.getInstrumentAttr(id, "color", id),
            intraday: mode == "intraday",
            callback: function() {
                jQuery(self.state).trigger("jobDone", {
                    type: "setRangeMode",
                    data: mode
                });
            }
        };
        chart.setPrimary(setPrimaryOptions);
        jQuery(self.state).trigger("onSetRangeMode", mode);
    };

    /**
     *
     */
    this.addComparison = function(o) {
        return this.chart.addComparison(o);
    };

    /**
     *
     */
    this.removeComparison = function(o) {
        return this.chart.removeComparison(o);
    };

    /**
     *
     */
    this.setScaleType = function(chartId, type) {
        return this.chart.setScaleType(chartId, type);
    };

    /**
     *
     */
    this.setChartVisibility = function(chartId, state) {
        return this.chart.setChartVisibility(chartId, state);
    };

    /**
     *
     */
    this.setSeriesType = function(chartId, type) {
        return this.chart.setSeriesType(chartId, type);
    };

    /**
     *
     */
    this.getPrimaryId = function() {
        if (this.chart) {
            return this.chart.getPrimaryId();
        }
        //else if (this.config.ui.instruments.length && this.config.ui.instruments[0].items[0].id !== "") {
        //    console.log("FLAGG");
        //    return this.config.ui.instruments[0].items[0].id;
        //}
        if ((match = document.location.href.match(/afw_id=(MDA:.+?)($|&)/)) && match[1]) {
            return match[1];
        } else {
            return this.config.ui.instruments[0].items[0].id;
        }

    };

    /**
     *
     */
    this.getInstrumentAttr = function(id, name, default_value) {
        default_value = default_value || null;
        for (var i = 0; i < this.config.ui.instruments.length; i++) {
            for (var j = 0; j < this.config.ui.instruments[i].items.length; j++) {
                if (this.config.ui.instruments[i].items[j].id == id) {
                    return this.config.ui.instruments[i].items[j][name] || default_value;
                }
            }
        }
        return default_value;
    };

    /**
     *
     */
    this.getComparisonAttr = function(id, name, default_value) {
        default_value = default_value || null;
        for (var i = 0; i < this.config.ui.comparisons.length; i++) {
            for (var j = 0; j < this.config.ui.comparisons[i].items.length; j++) {
                if (this.config.ui.comparisons[i].items[j].id == id) {
                    return this.config.ui.comparisons[i].items[j][name] || default_value;
                }
            }
        }
        return default_value;
    };

    /**
     *
     */
    this.getTranslation = function(name, lang, defaultValue) {
        lang = lang || this.getDefaultLang();
        var value;
        if (this.config.ui.i18n && this.config.ui.i18n[name] && (value = this.config.ui.i18n[name][lang])) {
            return value;
        } else {
            return defaultValue || name;
        }
    };

    /**
     *
     */
    this.initDateRangePicker = function(chart) {

        if (!chart) {
            return;
        }

        jQuery("#md-range-start-date").datepicker("setDate", chart.getFirstVisibleDate());
        jQuery("#md-range-end-date").datepicker("setDate", chart.getLastVisibleDate());

        /*
         * update custom date picker when charts range changes
         * TODO: refactor away direct dependency on underlying
         * chart object, which is component specific
         */
        chart.onSelectedRangeChange = function(startDate, endDate, phase) {
            if (phase == "finish") {
                jQuery("#md-range-start-date")
                    .datepicker("disable")
                    .datepicker("setDate", startDate)
                    .datepicker("enable");
                jQuery("#md-range-end-date")
                    .datepicker("disable")
                    .datepicker("setDate", endDate)
                    .datepicker("enable");
                self.state.dateRange = [ startDate, endDate ];
            }
        };
    };

    /**
     *
     */
    this.initDateRangePickerUI = function() {

        var self = this;

        /*
         * Custom range picker
         */
        jQuery("#md-range-start-date, #md-range-end-date").datepicker({
            dateFormat: "yy-mm-dd",
            showAnim: "slideDown",
            changeMonth: true,
			changeYear: true,
			maxDate: "+0d",
            onSelect: function(date, datepicker) {
                var startDate = jQuery("#md-range-start-date").datepicker("getDate");
                var endDate = jQuery("#md-range-end-date").datepicker("getDate");
                self.setCustomDateRange(startDate, endDate);
            }
        });
        jQuery("#md-range-picker input").removeAttr("disabled");

        /*
         * Range picker preset buttons
         */
        jQuery("#md-range-picker-presets button").each(function() {
            var icon = this.className.match(/intraday|interday/) ? "ui-icon-image" : null;
            jQuery(this).button({
                icons: {
                    primary: icon
                }
            });
        });

        jQuery(this.state).bind("onSetRangeMode", function(evt, e) {
            if (e == "interday") {
                jQuery(".md-range-picker-mode button.interday").css("display","none");
                jQuery(".md-range-picker-mode button.intraday").css("display","block");
            } else if (e == "intraday") {
                jQuery(".md-range-picker-mode button.interday").css("display","block");
                jQuery(".md-range-picker-mode button.intraday").css("display","none");
            }
        });
        jQuery("#md-range-picker").toggle();
    };

    /**
     *
     */
    this.translateUI = function(lang) {
        lang = lang || self.config.ui.lang;
        var name;
        jQuery(".i18n").each(function(index, e) {
            if ((name = this.className.split(" ")[1])) {
                jQuery(this).html(self.getTranslation(name, lang, jQuery(this).html()));
            }
        });
    };

    /**
     *
     */
    this.initUI = function() {

        //
        this.translateUI(self.config.ui.lang);

        // Tabs w shareinfo, orderbook etc
        jQuery("#md-shareinfo-group").tabs();

        // show
        jQuery("#md-quote").toggle();
        jQuery("#md-chartmenu").toggle();
        jQuery("#md-shareinfo-group").toggle();
    };

    /**
     *
     */
    this.disableInteractivity = function(bool) {
        jQuery("#md-range-picker-presets").find("button").each(function() {
            jQuery(this).attr("disabled", bool);
        });
        jQuery("#md-chartmenu-overlays").find("input").each(function() {
            jQuery(this).attr("disabled", bool);
        });
    };

    /**
     *
     */
    this.populateUI = function() {

        this.setPageTitle((this.config.ui.title || "The Share"));

        var html = MarketData_UI_HTMLHelper,
            lis = [],
            buttons = [],
            groups = [],
            title,
            compar,
            formitem,
            markers,
            analyses,
            i,
            j,
            radios,
            groups;

        /*
         * Comparison checkboxes
         *
         * Clicking a comparison checkbox will set scaling to
         * 'percent' and reset all Marker checkboxes (why reset?)
         *
         * Unchecking all checkboxes and scaling will be set back
         * to 'values'
         */
        if (this.config.ui.comparisons.length > 0) {
            jQuery("#md-options-comparisons").toggle();

            groups = [];

            if (this.config.ui.instruments[0].items.length > 1) {
                lis = [];
                title = this.getTranslation(this.config.ui.instruments[0].group);
                for (i = 0; i < this.config.ui.instruments[0].items.length; i++) {
                    instr = this.config.ui.instruments[0].items[i];
                    formitem = html.radio({
                        name: "instrument",
                        value: instr.id,
                        label: instr.name,
                        checked: (self.getPrimaryId() === instr.id) ? true : false,
                        classname: "c"+instr.color,
                        events: [{
                            type: "click",
                            callback: function(s) {
                                self.updateState({
                                    key: "primary",
                                    value: this.value
                                });
                            }
                        }]
                    });
                    lis.push(html.li({
                        text: formitem
                    }));
                }
                groups.push(html.div({
                    classname: "section",
                    elements: [
                        html.h2({ text: title }),
                        html.ul({ lis: lis, classname: "options instruments" })
                    ]
                }));
            }

            for (i = 0; i < this.config.ui.comparisons.length; i++) {
                lis = [];
                title = this.getTranslation(this.config.ui.comparisons[i].group);
                for (j = 0; j < this.config.ui.comparisons[i].items.length; j++) {
                    compar = this.config.ui.comparisons[i].items[j];
                    formitem = html.checkbox({
                        name: "comparison-"+i,
                        value: compar.id,
                        label: compar.name,
                        checked: compar.selected,
                        classname: "c"+compar.color,
                        events: [{
                            type: "click",
                            callback: function(s) {
                                if (this.checked === true) {
                                    self.updateState({
                                        key: "comparison",
                                        value: this.value,
                                        action: "add"
                                    });
                                } else {
                                    self.updateState({
                                        key: "comparison",
                                        value: this.value,
                                        action: "remove"
                                    });
                                }
                            }
                        },{
                            el: this.state,
                            type: "onStateCommit",
                            callback: function(evt, o) {
                                if (o.type == "comparison") {
                                    evt.data.myself.checked = (o.data[evt.data.myself.value]) ? true : false;
                                }
                            }
                        }]
                    });
                    lis.push(html.li({
                        text: formitem
                    }));
                }
                groups.push(html.div({
                    classname: "section",
                    elements: [
                        html.h2({ text: title }),
                        html.ul({ lis: lis, classname: "options comparisons" })
                    ]
                }));
            }

            // Add menu button to DOM
            jQuery("#md-chartmenu").append(html.button({
                text: this.getTranslation("comparisons"),
                classname: "i18n comparisons"
            }));
            // Add button appearance config for jqueryUI (will be initiated later on)
            buttons.push({
                icons: {
                    primary: "ui-icon-image",
                    secondary: "ui-icon-triangle-1-s"
                }
            });
            // Add content to menu overlay
            jQuery("#md-chartmenu-overlays").append(
                html.div({
                    classname: "md-chartmenu-menu ui-widget ui-widget-content ui-corner-all",
                    id: "chartmenu-"+(buttons.length),
                    elements: [
                        html.div({
                            id: "md-options-comparisons-list",
                            elements: groups
                        })
                    ]
                })
            );

        }

        /*
         * Technical Analyses
         */
        if (this.config.ui.analyses.length > 0) {
            jQuery("#md-options-analyses").toggle();
            var groups = [];
            for (i = 0; i < this.config.ui.analyses.length; i++) {
                lis = [];
                title = this.getTranslation(this.config.ui.analyses[i].group);
                for (j = 0; j < this.config.ui.analyses[i].items.length; j++) {
                    analyses = this.config.ui.analyses[i].items[j];
                    if (analyses.excludeFromMenu && analyses.excludeFromMenu === true) {
                        continue;
                    }
                    formitem = html.checkbox({
                        name: "payload[analysis]["+analyses.id+"]",
                        value: analyses.id,
                        label: this.getTranslation(analyses.id, this.getDefaultLang(), analyses.name),
                        checked: analyses.selected,
                        disabled: analyses.disabled,
                        events: [{
                            type: "click",
                            callback: function() {
                                if (this.checked === true) {
                                    self.updateState({
                                        key: "analysis",
                                        value: this.value,
                                        action: "add"
                                    });
                                } else {
                                    self.updateState({
                                        key: "analysis",
                                        value: this.value,
                                        action: "remove"
                                    });
                                }
                            }
                        },{
                            el: this.state,
                            type: "onStateCommit",
                            callback: function(evt, o) {
                                if (o.type == "analysis") {
                                    evt.data.myself.checked = (o.data[evt.data.myself.value]) ? true : false;
                                }
                            }
                        }]
                    });
                    lis.push(html.li({
                        text: formitem
                    }));
                }
                if (lis.length > 0) {
                    groups.push(html.div({
                        classname: "section",
                        elements: [
                            html.h2({ text: title }),
                            html.ul({ lis: lis, classname: "options analyses" })
                        ]
                    }));
                }
            }
            if (groups.length > 0) {
                jQuery("#md-chartmenu").append(html.button({
                    text: this.getTranslation("analyses"),
                    classname: "i18n ta_heading"
                }));
                buttons.push({
                    icons: {
                        primary: "ui-icon-image",
                        secondary: "ui-icon-triangle-1-s"
                    }
                });
                jQuery("#md-chartmenu-overlays").append(
                    html.div({
                        classname: "md-chartmenu-menu ui-widget ui-widget-content ui-corner-all",
                        id: "chartmenu-"+(buttons.length),
                        elements: [
                            html.div({
                                id: "md-options-analyses-list",
                                elements: groups
                            })
                        ]
                    })
                );
            }

        } // analyses

        /*
         * Markers checkboxes
         */
        if (this.config.ui.markers.length > 0) {
            jQuery("#md-options-markers").toggle();
            groups = [];
            for (i = 0; i < this.config.ui.markers.length; i++) {
                lis = [];
                title = this.getTranslation(this.config.ui.markers[i].group);
                for (j = 0; j < this.config.ui.markers[i].items.length; j++) {
                    markers = this.config.ui.markers[i].items[j];
                    formitem = html.checkbox({
                        name: "payload[marker]["+markers.id+"]",
                        value: markers.id,
                        label: this.getTranslation(markers.id, this.getDefaultLang(), markers.name),
                        checked: markers.selected,
                        disabled: markers.disabled,
                        events: [{
                            type: "click",
                            callback: function() {
                                if (this.checked === true) {
                                    self.updateState({
                                        key: "marker",
                                        value: this.value,
                                        action: "add"
                                    });
                                } else {
                                    self.updateState({
                                        key: "marker",
                                        value: this.value,
                                        action: "remove"
                                    });
                                }
                            }
                        },{
                            el: this.state,
                            type: "onStateCommit",
                            callback: function(evt, o) {
                                if (o.type == "marker") {
                                    evt.data.myself.checked = (o.data[evt.data.myself.value]) ? true : false;
                                }
                            }
                        }]
                    });
                    lis.push(html.li({
                        text: formitem
                    }));
                }
                groups.push(html.div({
                    classname: "section",
                    elements: [
                        html.h2({ text: title }),
                        html.ul({ lis: lis, classname: "options markers" })
                    ]
                }));
            }
            jQuery("#md-chartmenu").append(html.button({
                text: this.getTranslation("markers"),
                classname: "i18n markers"
            }));
            buttons.push({
                icons: {
                    primary: "ui-icon-flag",
                    secondary: "ui-icon-triangle-1-s"
                }
            });
            jQuery("#md-chartmenu-overlays").append(
                html.div({
                    classname: "md-chartmenu-menu ui-widget ui-widget-content ui-corner-all",
                    id: "chartmenu-"+(buttons.length),
                    elements: [
                        html.div({
                            id: "md-options-markers-list",
                            elements: groups
                        })
                    ]
                })
            );
        }

        /*
         * Settings
         */
        groups = [];
        lis = [];
        title = this.getTranslation("chart");
        radios = [{
            name: "diagramtype",
            value: "line",
            label: this.getTranslation("line"),
            checked: true,
            events: [{
                type: "click",
                callback: function() {
                    self.updateState({
                        key: "seriesType",
                        value: this.value
                    });
                }
            },{
                el: this.state,
                type: "onStateCommit",
                callback: function(evt, o) {
                    if (o.type == "seriesType") {
                        evt.data.myself.checked = (evt.data.myself.value == o.data) ? true : false;
                    }
                }
            }]
        },{
            name: "diagramtype",
            value: "bar",
            label: this.getTranslation("bar"),
            checked: false,
            events: [{
                type: "click",
                callback: function() {
                    self.updateState({
                        key: "seriesType",
                        value: this.value
                    });
                }
            },{
                el: this.state,
                type: "onStateCommit",
                callback: function(evt, o) {
                    if (o.type == "seriesType") {
                        evt.data.myself.checked = (evt.data.myself.value == o.data) ? true : false;
                    }
                }
            }]
        }];
        var formitems = [];
        for (i = 0; i < radios.length; i++) {
            formitems.push(html.radio(radios[i]));
        }
        lis.push(html.li({
            text: html.div({
                elements: formitems
            })
        }));

        // percent/values
        radios = [{
            name: "scaling",
            value: "percent",
            label: this.getTranslation("percent"),
            checked: false,
            events: [{
                type: "click",
                callback: function() {
                    self.updateState({
                        key: "scaleType",
                        value: this.value
                    });
                }
            },{
                el: this.state,
                type: "onStateCommit",
                callback: function(evt, o) {
                    if (o.type == "scaleType") {
                        evt.data.myself.checked = (evt.data.myself.value == o.data) ? true : false;
                    }
                }
            }]
        },{
            name: "scaling",
            value: "values",
            label: this.getTranslation("values"),
            checked: true,
            events: [{
                type: "click",
                callback: function() {
                    self.updateState({
                        key: "scaleType",
                        value: this.value
                    });
                }
            },{
                el: this.state,
                type: "onStateCommit",
                callback: function(evt, o) {
                    if (o.type == "scaleType") {
                        evt.data.myself.checked = (evt.data.myself.value == o.data) ? true : false;
                    }
                }
            }]
        }];
        formitems = [];
        for (i = 0; i < radios.length; i++) {
            formitems.push(html.radio(radios[i]));
        }
        div = html.div({
            elements: formitems
        });
        lis.push(html.li({
            text: div
        }));

        // Toggle Volume
        formitem = html.checkbox({
            name: "togglevolume",
            value: "ta_volume",
            label: this.getTranslation("show_volume", this.getDefaultLang()),
            checked: false,
            disabled: false,
            events: [{
                type: "click",
                callback: function() {
                    if (this.checked === true) {
                        self.updateState({
                            key: "analysis",
                            value: this.value,
                            action: "add"
                        });
                    } else {
                        self.updateState({
                            key: "analysis",
                            value: this.value,
                            action: "remove"
                        });
                    }
                }
            },{
                el: this.state,
                type: "onStateCommit",
                callback: function(evt, o) {
                    if (o.type == "analysis") {
                        evt.data.myself.checked = (o.data[evt.data.myself.value]) ? true : false;
                    }
                }
            }]
        });
        lis.push(html.li({
            text: formitem
        }));


        groups.push(html.div({
            classname: "section",
            elements: [
                html.h2({ text: title }),
                html.ul({ lis: lis, classname: "options settings" })
            ]
        }));

        jQuery("#md-chartmenu").append(html.button({
            text: this.getTranslation("settings"),
            classname: "i18n settings"
        }));
        buttons.push({
            icons: {
                primary: "ui-icon-gear",
                secondary: "ui-icon-triangle-1-s"
            }
        });
        jQuery("#md-chartmenu-overlays").append(
            html.div({
                classname: "md-chartmenu-menu ui-widget ui-widget-content ui-corner-all",
                id: "chartmenu-"+(buttons.length),
                elements: [
                    html.div({
                        id: "md-options-settings-list",
                        elements: groups
                    })
                ]
            })
        );

        /*
         * Downloads
         */
        if (this.config.ui.modules.downloads.enabled === true) {
            jQuery("#md-downloads-settings").toggle();

            groups = [];
            lis = [];
            title = this.getTranslation("downloads");
            var lang = this.getDefaultLang();
            var bullets = [{
                text: this.getTranslation("excel"),
                classname: "xls",
                events: [{
                    type: "click",
                    callback: function() {
                        document.location = self.config.ui.modules.downloads.excel.uri[lang];
                    }
                }]
            },{
                text: this.getTranslation("png"),
                classname: "png",
                events: [{
                    type: "click",
                    callback: function() {
                        self.chart.download("png");
                    }
                }]
            }];

            for (i = 0; i < bullets.length; i++) {
                lis.push(html.li(bullets[i]));
            }

            groups.push(html.div({
                classname: "section",
                elements: [
                    html.h2({ text: title }),
                    html.ul({ lis: lis, classname: "options downloads" })
                ]
            }));

            jQuery("#md-chartmenu").append(html.button({
                text: this.getTranslation("downloads"),
                classname: "i18n downloads"
            }));
            buttons.push({
                icons: {
                    primary: "ui-icon-folder-open",
                    secondary: "ui-icon-triangle-1-s"
                }
            });

            // add menu overlay
            jQuery("#md-chartmenu-overlays").append(
                html.div({
                    classname: "md-chartmenu-menu ui-widget ui-widget-content ui-corner-all",
                    id: "chartmenu-"+(buttons.length),
                    elements: [
                        html.div({
                            id: "md-options-downloads-list",
                            elements: groups
                        })
                    ]
                })
            );

        }

        // buttons
        jQuery("#md-chartmenu button").each(function(index) {
            jQuery(this).button(buttons[index]);
        });

        /*
         * width:           int    width or string "auto" [auto]
         * offsetTop:       int    y offset from opening element in pixels [22]
         * offsetLeft:      int    x offset from opening elements left corner, in pixels [0]
         * className:       string class name added to overlay element [overlay]
         * fadeInDuration:  int    duration for fade in animation, in millis [0]
         * fadeOutDuration: int    duration for fade out animation, in millis [250]
         */
        var options = {
            width: 300,
            offsetTop: 30,
            offsetLeft: 0,
            fadeInDuration: 0,
            fadeOutDuration: 500
        };

        jQuery("#md-chartmenu button").each(function(index) {

            var button = this;
            var overlay = document.getElementById("chartmenu-"+(index+1));

            jQuery(overlay).css("display", "none");
            jQuery(overlay).css("position", "absolute");

            jQuery(button).css("cursor", "pointer");

            if (options.width && (typeof options.width == "number")) {
                jQuery(overlay).css("width", options.width+"px");
            }
            if (options.className && (options.className !== "")) {
                jQuery(overlay).attr("class", options.className);
            }

            var offset = jQuery(button).position();
            if (typeof options.offsetTop == "number") {
                jQuery(overlay).css("top", (offset.top+options.offsetTop)+"px");
            }
            if (typeof options.offsetLeft == "number") {

                var viewportWidth = jQuery(document).width();
                var overlayWidth = jQuery(overlay).width();
                if ((offset.left + overlayWidth + 50) > viewportWidth) {
                    jQuery(overlay).css("right", 0);
                } else {
                    jQuery(overlay).css("left", (offset.left+options.offsetLeft)+"px");
                }
            }

            var menuButtonHover = function() {
                menuOverlayCloseAll();
                if (options.fadeInDuration > 0) {
                    jQuery(overlay).fadeIn(options.fadeInDuration);
                } else {
                    jQuery(overlay).css("display", "block");
                }
                jQuery(overlay).addClass("active-overlay");
            };

            var menuButtonLeave = function() {

            };

            var menuOverlayEnter = function() {

            };

            var menuOverlayLeave = function() {
                menuOverlayHide(overlay);
            };

            var menuOverlayCloseAll = function() {
                jQuery(".active-overlay").each(function() {
                    menuOverlayHide(this);
                });
            };

            var menuOverlayHide = function(overlayEl) {
                jQuery(overlayEl).removeClass("active-overlay");
                jQuery(overlayEl).css("display", "none");
            };

            jQuery(button).bind("click", menuButtonHover);
            jQuery(button).hoverIntent({
                sensitivity: 2,
                interval: 100,
                over: menuButtonHover,
                timeout: 400,
                out: menuButtonLeave
            });
            jQuery(overlay).bind("mouseenter", menuOverlayEnter).bind("mouseleave", menuOverlayLeave);

        });

    };

    /**
     * get technical analysis configuration by key (id)
     */
    this.getAnalysisConfig = function(key) {
        if (key) {
            for (var i = 0; i < this.config.ui.analyses.length; i++) {
                for (var j = 0; j < this.config.ui.analyses[i].items.length; j++) {
                    if (this.config.ui.analyses[i].items[j].id == key) {
                        return this.config.ui.analyses[i].items[j];
                    }
                }
            }
        }
        return null;
    };

    /**
     * add/show configured technical analysis, by key
     */
    this.addAnalysis = function(key) {
        return this.chart.addAnalysis(this.getAnalysisConfig(key));
    };

    /**
     * remove/hide configured technical analysis, by key
     */
    this.removeAnalysis = function(key) {
        return this.chart.removeAnalysis(this.getAnalysisConfig(key));
    };

    /**
     *
     */
    this.log = function(o) {
        if (debug > 0) {
            this.getMarketData().log(o);
        }
    };

    /**
     *
     */
    this.shortDateRangeToDateRange = function(range) {

        var firstDate = this.chart ? this.chart.getFirstDate() : new Date();
        var lastDate = this.chart ? this.chart.getLastDate() : new Date();

        var o = [
            null,
            lastDate
        ];

        if (range == "ytd") {
            o[0] = new Date(new Date().getFullYear(), 0, 1, 0, 0, 0, 0);
            return o;
        }
        if (range == "max" && this.chart) {
            o[0] = firstDate;
            return o;
        }

        var d = new Date(lastDate);
        var year = d.getFullYear();
        var month = d.getMonth();
        var day = d.getDate();
        var hour = d.getHours();
        var minute = d.getMinutes();
        var second = d.getSeconds();
        var milli = d.getMilliseconds();

        switch (range) {
            case "1h":   hour = hour - 1;    break;
            case "6h":   hour = hour - 6;    break;
            case "12h":  hour = hour - 12;   break;
            case "1d":   day = day - 1;      break;
            case "1w":   day = day - 7;      break;
            case "10d":  day = day - 10;     break;
            case "1m":   month = month - 1;  break;
            case "3m":   month = month - 3;  break;
            case "1y":   year = year - 1;    break;
            case "2y":   year = year - 2;    break;
            case "3y":   year = year - 3;    break;
            case "5y":   year = year - 5;    break;
            default:     year = year - 1;    break;
        }
        o[0] = new Date(year, month, day, hour, minute, second);
        return o;
    };

    /**
     * Track state, state changes, and state change job queue
     * TODO: refactor the need* entries into the chart class?
     */
    this.state = {
        current: {
            primary: this.getPrimaryId(), //this.config.ui.instruments[0].items[0].id,
            comparison: {},
            marker: {},
            analysis: {},
            seriesType: "line",
            scaleType: "values",
            showVolume: false,
            dateRange: this.shortDateRangeToDateRange("3y"),
            dateRangeMode: "interday"
        },
        reference: {
            primary: "",
            comparison: {},
            marker: {},
            analysis: {},
            seriesType: "line",
            scaleType: "values",
            showVolume: false,
            dateRange: [null,null],
            dateRangeMode: null
        },
        queue: {
            jobs: 0,
            allJobsStarted: false,
            needSettingsChanges: false,
            needConfigChanges: false,
            needEventMarkersChanges: false
        },
        timer: null
    };

    /**
     * Commit changed state using relevant chart component methods
     * TODO: refactor away direct dependency on chart component
     * CHECKME: refactor away this method into the chart class?
     */
    this.commitState = function(evt, o) {

        this.log("commitState called, num jobs: "+this.state.queue.jobs+", allJobsStarted: "+this.state.queue.allJobsStarted);

        if (this.state.queue.jobs === 0 && this.state.queue.allJobsStarted === true) {

            this.log(" READY");

            if (this.state.queue.needEventMarkersChanges === true) {
                this.state.queue.needEventMarkersChanges = false;
                this.log("  applyEventMarkersChanges");
                this.chart.applyEventMarkersChanges();
            }

            if (this.state.queue.needConfigChanges === true) {

                this.state.queue.needConfigChanges = false;
                this.log("  applyConfigChanges");
                this.chart.applyConfigChanges();

                this.chart.cache.del("marker:press");
                this.chart.cache.del("marker:reports");
                this.chart.cache.del("marker:insiders");
                this.state.current.marker = {};

            } else if (this.state.queue.needSettingsChanges === true) {
                this.state.queue.needSettingsChanges = false;
                this.log("  applySettingsChanges");
                this.chart.applySettingsChanges();
            }

            /*
             * clear timer and waiting state
             */
            clearTimeout(this.state.timer);
            this.chart.setChartWaitingState(false);

            /*
             * clear queue and copy current state
             */
            this.state.queue.allJobsStarted = false;
            this.state.reference = this.md.clone(this.state.current);

            for (var type in this.state.current) {
                jQuery(this.state).trigger("onStateCommit", {
                    type: type,
                    data: this.state.current[type]
                });
            }

        } else {
            this.log(" NOT READY");
        }
    };

    /**
     * Request change in chart state independently of chart component
     *
     * @param Object or an Array of Objects
     *  o.key String Name
     *  o.value Mixed Value
     *  o.action String Actions: add, remove
     *  o.applyChanges Boolean
     * @param boolean
     *
     * updateState('comparison', 'MDA:1235', 'add')
     * updateState('comparison', 'MDA:6789', 'remove')
     *
     * updateState('showVolume', true)
     *
     * TODO: add value validation to updateState
     */
    this.updateState = function(o, applyChanges) {

        applyChanges = applyChanges || true;

        var key, value, action;
        var args = jQuery.isArray(o) ? o : [o];

        for (var i = 0; i < args.length; i++) {

            key = args[i].key;
            value = args[i].value;
            action = args[i].action || null;

            switch (key) {
                case "primary":
                    var uri = document.location.href,
                        target;
                    if (uri.match(/afw_id=MDA/i)) {
                        target = uri.replace(/afw_id=MDA:.+?($|\&)/, "afw_id="+value+"$1");
                    } else {
                        target = uri+"&afw_id="+value;
                    }
                    document.location = target;
                    return;
                    //this.state.current.primary = value;
                break;
                case "comparison":
                    if (action == "add" && !this.state.current.comparison[value]) {
                        this.state.current.comparison[value] = true;
                    } else if (action == "remove" && this.state.current.comparison[value]) {
                        delete this.state.current.comparison[value];
                    }
                    if (this.md.count(this.state.current.comparison) > 0) {
                        this.state.current.scaleType = "percent";
                    } else {
                        this.state.current.scaleType = "values";
                    }
                break;
                case "marker":
                    if (action == "add" && !this.state.current.marker[value]) {
                        this.state.current.marker[value] = true;
                    } else if (action == "remove" && this.state.current.marker[value]) {
                        delete this.state.current.marker[value];
                    }
                break;
                case "analysis":
                    if (action == "add" && !this.state.current.analysis[value]) {
                        this.state.current.analysis[value] = true;
                    } else if (action == "remove" && this.state.current.analysis[value]) {
                        delete this.state.current.analysis[value];
                    }
                break;
                case "seriesType":
                    this.state.current.seriesType = value;
                break;
                case "scaleType":
                    this.state.current.scaleType = value;
                break;
                case "showVolume":
                    this.state.current.showVolume = value;
                break;
                case "dateRange":
                    this.state.current.dateRange = value;
                break;
                case "dateRangeMode":
                    this.state.current.dateRangeMode = value;
                break;
            }

        }

        if (applyChanges === true) {
            this.applyState();
        }
    }; // updateState

    /**
     * Apply requested state changes using chart component independent methods
     * TODO: refactor away direct dependency on chart component (need* stuff)
     * CHECKME: refactor away this method into the chart class?
     */
    this.applyState = function() {

        var self = this;

        this.state.queue.needConfigChanges = false;
        this.state.queue.needSettingsChanges = false;
        this.state.queue.allJobsStarted = false;

        this.state.timer = setTimeout(function() {
            self.chart.setChartWaitingState(true);
        }, 150);

        /*
         * Range Mode: intraday or interday
         */
        if (this.state.reference.dateRangeMode != this.state.current.dateRangeMode) {
            this.state.queue.needConfigChanges = true;
            this.log("adding job setRangeMode");
            this.state.queue.jobs++;
            this.state.current.comparison = {};
            this.state.current.scaleType = "values";
            this.setRangeMode(this.state.current.dateRangeMode);
        }

        /*
         * Comparison
         */
        var id;
        for (id in this.state.reference.comparison) {
            if (!this.state.current.comparison[id]) {
                this.state.queue.needSettingsChanges = true;
                this.log("adding job removeComparison");
                this.state.queue.jobs++;
                this.removeComparison(id);
            }
        }
        for (id in this.state.current.comparison) {
            if (!this.state.reference.comparison[id]) {
                this.state.queue.needConfigChanges = true;
                this.log("adding job addComparison");
                this.state.queue.jobs++;
                this.addComparison({
                    id: id,
                    title: this.getComparisonAttr(id, "name", id),
                    color: "#"+this.getComparisonAttr(id, "color", id),
                    intraday: (this.state.current.dateRangeMode == "intraday") ? true : false
                });
            }
        }

        /*
         * Markers
         */
        for (id in this.state.reference.marker) {
            if (!this.state.current.marker[id]) {
                this.state.queue.needEventMarkersChanges = true;
                this.log("adding job removeMarker ("+id+")");
                this.state.queue.jobs++;
                this.removeMarker(id);
            }
        }
        for (id in this.state.current.marker) {
            if (!this.state.reference.marker[id]) {
                this.state.queue.needEventMarkersChanges = true;
                this.log("adding job addMarker ("+id+")");
                this.state.queue.jobs++;
                this.addMarker(id);
            }
        }

        /*
         * Technical Analyses (including Volume!)
         */
        for (id in this.state.reference.analysis) {
            if (!this.state.current.analysis[id]) {
                this.state.queue.needSettingsChanges = true;
                this.log("adding job removeAnalysis ("+id+")");
                this.state.queue.jobs++;
                this.removeAnalysis(id);
            }
        }
        for (id in this.state.current.analysis) {
            if (!this.state.reference.analysis[id]) {
                this.state.queue.needSettingsChanges = true;
                this.log("adding job addAnalysis ("+id+")");
                this.state.queue.jobs++;
                this.addAnalysis(id);
            }
        }

        /*
         * Scale type
         */
        if (this.state.reference.scaleType != this.state.current.scaleType) {
            this.state.queue.needSettingsChanges = true;
            this.log("adding job setScaleType ("+this.state.current.setScaleType+")");
            this.state.queue.jobs++;
            this.setScaleType("idMainChart", this.state.current.scaleType, false);
        }

        /*
         * Series type; bar, line etc
         */
        if (this.state.reference.seriesType != this.state.current.seriesType) {
            this.state.queue.needSettingsChanges = true;
            this.log("adding job setSeriesType ("+this.state.current.seriesType+")");
            this.state.queue.jobs++;
            this.setSeriesType("idMainChart", this.state.current.seriesType);
        }

        /*
         * Date range
         * CHECKME: why is changing date range not handled as a job?
         */
        if (this.state.reference.dateRange.toString() != this.state.current.dateRange.toString()) {
            this.chart.zoomTo(this.state.current.dateRange[0], this.state.current.dateRange[1]);
        }

        this.state.queue.allJobsStarted = true;

        if (this.state.queue.jobs === 0) {
            this.commitState();
        }
    }; // applyState

    /*
     * Init
     */

    this.initUI();
    this.initDateRangePickerUI();

    if (this.getPrimaryId()) {

        this.module.quote     = new MarketData_UI_Module_Quote(this);
        this.module.shareinfo = new MarketData_UI_Module_Shareinfo(this);
        this.module.sharedata = new MarketData_UI_Module_Sharedata(this);
        this.module.orderbook = new MarketData_UI_Module_Orderbook(this);

        var onQuoteReady = function() {
            self.module.shareinfo.update(true);
        };

        var onShareinfoReady = function() {
            // add additional info to config.ui.instruments
            var instruments = self.module.shareinfo.cache.get("instruments");
            var i, j, c, id;
            c = 0;
            for (i = 0; i < self.config.ui.instruments.length; i++) {
                for (j = 0; j < self.config.ui.instruments[i].items.length; j++) {
                    id = self.config.ui.instruments[i].items[j].id;
                    self.config.ui.instruments[i].items[j].color = config.ui.colors.instruments[c];
                    self.config.ui.instruments[i].items[j].shortname = instruments[id].getValue("shortname");
                    if (!self.config.ui.instruments[i].items[j].name) {
                        self.config.ui.instruments[i].items[j].name = instruments[id].getValue("longname");
                    }
                    c++;
                }
            }
            c = 0;
            for (i = 0; i < self.config.ui.comparisons.length; i++) {
                for (j = 0; j < self.config.ui.comparisons[i].items.length; j++) {
                    id = self.config.ui.comparisons[i].items[j].id;
                    self.config.ui.comparisons[i].items[j].color = config.ui.colors.comparisons[c];
                    self.config.ui.comparisons[i].items[j].shortname = instruments[id].getValue("shortname");
                    if (!self.config.ui.comparisons[i].items[j].name) {
                        self.config.ui.comparisons[i].items[j].name = instruments[id].getValue("longname");
                    }
                    c++;
                }
            }
            self.module.quote.populate();
            self.module.sharedata.populate();
            self.module.orderbook.update(true);

            self.populateUI();
            self.chart = new MarketData_Chart(self, function() {
                self.applyState();
            });
        };

        // always load quote module
        this.module.quote.update(false);
        // run onQuoteReady when done
        this.module.quote.subscribe("update", onQuoteReady);
        // load chart
        this.module.shareinfo.subscribe("update", onShareinfoReady);

        /**
         *
         */
        jQuery(this.state).bind("jobDone", function(evt, o) {
            self.log("jobDone "+o.type+" (data="+o.data+") triggered, num jobs: "+self.state.queue.jobs);
            if (self.state.queue.jobs > 0) {
                self.state.queue.jobs--;
            }
            self.commitState();
        });

    } else {
        alert("Error");
    }
};

/**
 * @version 2011-07-05
 */
var MarketData_UI_Module = {
    ui: null,
    /*
     * Prototype auto refresh timer implementation
     *
     * Example usage: UI.module.orderbook.timer.start(UI.module.orderbook, 1000);
     */
    autoUpdate: {
        obj: null,
        interval: 60000,
        enabled: false,
        module: null,
        callback: function() {
            this.module.update();
            if (this.enabled === true) {
                this.start(this.module, this.interval);
            }
        },
        start: function(module, interval) {
            var self = this;
            if (!module || !module.update) {
                return;
            }
            this.interval = interval || this.interval;
            this.module = module;
            this.enabled = true;
            this.obj = setTimeout(function() {
                self.callback();
            }, this.interval);
        },
        stop: function() {
            this.enabled = false;
            clearTimeout(this.obj);
        }
    },
    cache: {
        data: {},
        get: function(name, default_value) {
            if (this.data[name]) {
                return this.data[name];
            } else {
                return default_value || null;
            }
        },
        put: function(name, data) {
            this.data[name] = data;
        },
        clear: function() {
            this.data = {};
        }
    },
    fireEvent: function(n, o) {
        if (this.events[n]) {
            var fn;
            for (var i = 0; i < this.events[n].length; i++) {
                fn = this.events[n][i];
                fn(n, o);
            }
        }
    },
    subscribe: function(n, fn) {
        if (this.events[n]) {
            this.events[n].push(fn);
        }
    }
};

/**
 * @version 2011-07-05
 */
var MarketData_UI_Module_Quote = function(ui) {

    jQuery.extend(this, MarketData_UI_Module);
    this.ui = ui;
    this.events = {
        update: [],
        populate: []
    };

    this.update = function(populate, callback) {
        var self = this, md = this.ui.getMarketData();
        var ids = this.getQuoteInstruments(true);
        md.getQuotes(ids, function(result) {
            if (result) {
                self.cache.put("quote", result);
                if (populate) {
                    self.populate();
                }
                if (callback) {
                    callback(this);
                }
                self.fireEvent("update", result);
            }
        });
    };

    this.populate = function() {
        var self = this,
            md = this.ui.getMarketData(),
            trend = "e",
            ids = this.getQuoteInstruments(),
            quote,
            id,
            val,
            oval,
            key,
            arg;

        if ((quote = this.cache.get("quote"))) {
            var keys = {
                listing:       function(o) { return o; },
                shortname:     function(o) { return "("+o+")"; },
                timestamp:     function(o) { return md.epochFormat(o, ui.dateformat.datetime); },
                diff:          function(o) { return md.nf(o, 2); },
                diffpercent:   function(o) { return "("+md.nf(o, 2)+"%)"; },
                last:          function(o) { return md.nf(o, 2); },
                sell:          function(o) { return md.nf(o, 2); },
                buy:           function(o) { return md.nf(o, 2); },
                high:          function(o) { return md.nf(o, 2); },
                low:           function(o) { return md.nf(o, 2); },
                volume:        function(o) { return md.nf(o, 0); }
            };
            for (var i = 0; i < ids.length; i++) {
                id = ids[i].id;
                if (this.ui.getPrimaryId() == id) {
                    for (key in keys) {
                        if (typeof quote[id] !== "undefined") {
                            val = quote[id].getValue(key);
                            oval = val;
                            if (typeof keys[key] == "function") {
                                if (key == "listing") {
                                    arg = ids[i].name;
                                } else if (key == "shortname") {
                                    arg = ids[i].shortname;
                                } else {
                                    arg = val;
                                }
                                val = keys[key](arg);
                            }
                            if (key == "timestamp") {
                                jQuery(".quote-"+key).html(val);
                            } else if (key == "diff") {
                                if (oval >= 1.1) {
                                    trend = "n";  // NORTH
                                } else if (oval >= 0.1 && oval < 1.1) {
                                    trend = "ne"; // NORTH EAST
                                } else if (Math.abs(oval) < 0.1) {
                                    trend = "e";  // EAST
                                } else if (oval <= -0.1 && oval > -1.1) {
                                    trend = "se"; // SOUTH EAST
                                } else if (oval <= -1.1) {
                                    trend = "s";  // SOUTH
                                }
                                jQuery(".quote-"+key).html(val);
                                jQuery(".quote-trend").addClass(trend);
                            } else {
                                jQuery(".quote-"+key).html(val);
                            }
                        }
                    }
                }
            }
        }
    };

    this.getQuoteInstruments = function(ids_only) {
        if (this.ui.config.ui.instruments.length > 0) {
            var ids = [], instr;
            for (var i = 0; i < this.ui.config.ui.instruments.length; i++) {
                for (var j = 0; j < this.ui.config.ui.instruments[i].items.length; j++) {
                    instr = this.ui.config.ui.instruments[i].items[j];
                    if (instr.ticker === true) {
                        if (ids_only && ids_only === true) {
                            ids.push(instr.id);
                        } else {
                            ids.push(instr);
                        }
                    }
                }
            }
            return ids;
        } else {
            return (ids_only && ids_only === true) ? this.ui.config.ui.instruments[0].items[0].id : this.ui.config.ui.instruments[i].items;
        }
    };

};

/**
 * @version 2011-07-05
 */
var MarketData_UI_Module_Sharedata = function(ui) {

    jQuery.extend(this, MarketData_UI_Module);
    this.ui = ui;
    this.events = {
        update: [],
        populate: []
    };

    this.update = function(populate, callback) {
        var self = this, md = this.ui.getMarketData();
        var primary_id = this.ui.getPrimaryId();
        md.getQuotes(primary_id, function(result) {
            if (result) {
                self.cache.put("quote", result);
                if (populate) {
                    self.populate();
                }
                if (callback) {
                    callback(this);
                }
            }
            self.fireEvent("update", result);
        });
    };

    this.populate = function() {
        var self = this,
            md = this.ui.getMarketData(),
            primary_id = this.ui.getPrimaryId(),
            quote,
            keys = [],
            formats = {},
            key,
            val,
            i;
        if ((quote = this.cache.get("quote")) && typeof quote[primary_id] !== "undefined") {
            var formats = {
                stockvalue:          function(o) { return md.nf((o/1000000), 0); },
                closeprice_yearend:  function(o) { return md.nf(o, 2); },
                diffpercent_yearend: function(o) { return md.nf(o, 2); },
                diffpercent_1y:      function(o) { return md.nf(o, 2); },
                high_selfyear:       function(o) { return md.nf(o, 2); },
                low_selfyear:        function(o) { return md.nf(o, 2); },
                low_thisyear:        function(o) { return md.nf(o, 2); },
                high_thisyear:       function(o) { return md.nf(o, 2); },
                high_history:        function(o) { return md.nf(o, 2); },
                high_history_date:   function(o) { return md.epochFormat(o, ui.dateformat.date); }
            };
            // get all keys from sharedata table classes
            jQuery("div#md div#md-sharedata table td").each(function(i) {
                if ((match = this.className.match(/sharedata-(.+?)$/)) && match[1] !== "undefined") {
                    keys.push(match[1]);
                }
            });
            for (i = 0; i < keys.length; i++) {
                key = keys[i];
                val = quote[primary_id].getValue(key);
                if (formats[key] && typeof formats[key] == "function") {
                    val = formats[key](val);
                }
                jQuery("div#md td.sharedata-"+key).html(val);
            }
        }
        self.fireEvent("populate", quote);
    };

};

/**
 * @version 2011-07-05
 */
var MarketData_UI_Module_Orderbook = function(ui) {

    jQuery.extend(this, MarketData_UI_Module);

    var self = this;

    this.ui = ui;
    this.events = {
        update: [],
        populate: []
    };

    this.update = function(populate) {
        var self = this, md = this.ui.getMarketData();
        var primary_id = this.ui.getPrimaryId();
        md.getOrderbooks(primary_id, function(result) {
            self.cache.put("orderbook", result);
            if (populate) {
                self.populate();
            }
        });
    };

    this.populate = function() {
        var self = this,
            datetime = 0,
            md = this.ui.getMarketData(),
            orderbook;
        if ((orderbook = this.cache.get("orderbook"))) {
            var primary_id = this.ui.getPrimaryId();
            var keys = {
                bid_quantity: function(o) { return md.nf(o, 0); },
                bid_price:    function(o) { return md.nf(o, 2); },
                ask_price:    function(o) { return md.nf(o, 2); },
                ask_quantity: function(o) { return md.nf(o, 0); }
            };
            var o = orderbook[primary_id];
            if (o) {
                var high = { ask: [], bid: [] };
                for (var i = 0; i < o.length; i++) {
                    high.ask.push(o[i].getValue("ask_quantity"));
                    high.bid.push(o[i].getValue("bid_quantity"));
                }
                var ask_quantity_high = Math.max.apply(Math, high.ask);
                var bid_quantity_high = Math.max.apply(Math, high.bid);
                var val, key, percent;
                for (i = 0; i < o.length; i++) {
                    for (key in keys) {
                        val = o[i].getValue(key);
                        if (key == "bid_quantity") {
                            percent = Math.round((val/bid_quantity_high)*100);
                            jQuery("#md tr.row-"+i+" td.orderbook-bid_bar div").css("width", percent+"%");
                        } else if (key == "ask_quantity") {
                            percent = Math.round((val/ask_quantity_high)*100);
                            jQuery("#md tr.row-"+i+" td.orderbook-ask_bar div").css("width", percent+"%");
                        }
                        if (typeof keys[key] == "function") {
                            val = keys[key](val);
                        }
                        if (o[i].getValue("timestamp") > datetime) {
                            datetime = o[i].getValue("timestamp");
                        }
                        jQuery("#md tr.row-"+i+" td.orderbook-"+key).html(val);
                    }
                }
                if (datetime > 0) {
                    jQuery("#md .orderbook-datetime").html(md.epochFormat(datetime));
                }
            }
        }
    };

};

/**
 * @version 2011-07-05
 */
var MarketData_UI_Module_Shareinfo = function(ui) {

    jQuery.extend(this, MarketData_UI_Module);
    this.ui = ui;
    this.events = {
        update: [],
        populate: []
    };

    this.update = function(populate, callback) {
        var self = this,
            md = this.ui.getMarketData(),
            all_instrument_ids = this.ui.getInstrumentIds(true),
            cache = this.cache;
        md.getInstruments(all_instrument_ids, function(result) {
            if (result) {
                cache.put("instruments", result);
                if (populate) {
                    self.populate();
                }
                if (callback) {
                    callback(this);
                }
            }
            self.fireEvent("update", result);
        });
    };

    this.populate = function() {
        var self = this,
            md = this.ui.getMarketData(),
            primary_id = this.ui.getPrimaryId(),
            cache = this.cache,
            instruments,
            val,
            key;
        var keys = {
            marketplace_name: null,
            listing_name:     null,
            issuedate:        null,
            shortname:        null,
            longname:         null,
            currency:         null,
            lot:              null,
            isin:             null
        };
        if ((instruments = cache.get("instruments"))) {
            for (key in keys) {
                if (typeof instruments[primary_id] !== "undefined") {
                    val = instruments[primary_id].getValue(key);
                    if (typeof keys[key] == "function") {
                        val = keys[key](val);
                    }
                    jQuery("div#md td.shareinfo-"+key).html(val);
                }
            }
        }
        self.fireEvent("populate", instruments);
    };

};

var MarketData_UI_Config = {


};

/**
 * @version 2011-08-17
 */
var MarketData_UI_HTMLHelper = {

    /*
     * @param object
     * - label: string label
     * - o.name: string name
     * - o.value: string value
     * - o.checked: boolean
     * - o.id: string id
     * - o.classname: string class name
     * - o.events:
     */
    checkbox: function(o) {
        var e = document.createElement("input"), el, html;
        e.type = "checkbox";
        e.name = o.name;
        e.value = o.value;
        if (o.checked === true) {
            e.checked = true;
        }
        if (o.disabled === true) {
            e.disabled = true;
        }
        if (o.id) {
            e.id = o.id;
        }
        if (o.events) {
            for (var i = 0; i < o.events.length; i++) {
                el = (o.events[i].el) ? o.events[i].el : e;
                jQuery(el).bind(o.events[i].type, { myself: e }, o.events[i].callback);
            }
        }
        if (o.label) {
            var l = document.createElement("label");
            if (o.classname) {
                l.className = o.classname;
            }
            l.appendChild(e);
            l.appendChild(document.createTextNode(o.label));
            html = l;
        } else {
            html = e;
        }
        return html;
    },

    /*
     * @param object
     * - label: string label
     * - o.name: string name
     * - o.value: string value
     * - o.checked: boolean
     * - o.id: string id
     * - o.classname: string class name
     * - o.events
     */
    radio: function(o) {
        var e = document.createElement("INPUT"), html;
        e.type = "radio";
        e.name = o.name;
        e.value = o.value;
        if (o.checked === true) {
            e.checked = true;
        }
        if (o.id) {
            e.id = o.id;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        if (o.label) {
            var l = document.createElement("label");
            if (o.classname) {
                l.className = o.classname;
            }
            l.appendChild(e);
            l.appendChild(document.createTextNode(o.label));
            html = l;
        } else {
            html = e;
        }
        return html;
    },

    /*
     * @param object
     * - o.id: string id
     * - o.classname: string class name
     * - o.lis: [ object li, object li, ... ]
     */
    ul: function(o) {
        var e = document.createElement("ul");
        if (o.id) {
            e.id = o.id;
        }
        if (o.classname) {
            e.className = o.classname;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        for (var i = 0; i < o.lis.length; i++) {
            e.appendChild(o.lis[i]);
        }
        return e;
    },

    /*
     * @param object
     * - o.text: string text
     * - o.id: string id
     * - o.classname: string class name
     * - o.click: callback function on click
     */
    li: function(o) {
        var e = document.createElement("li");
        if (o.id) {
            e.id = o.id;
        }
        if (o.classname) {
            e.className = o.classname;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        if (typeof o.text == "string") {
            e.innerHTML = o.text;
        } else {
            e.appendChild(o.text);
        }
        return e;
    },

    /*
     * - o.text: string text
     * - o.id: string id
     * - o.classname: string class name
     * - o.click: callback function on click
     */
    h2: function(o) {
        var e = document.createElement("h2");
        if (o.id) {
            e.id = o.id;
        }
        if (o.classname) {
            e.className = o.classname;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        if (typeof o.text == "string") {
            e.innerHTML = o.text;
        } else {
            e.appendChild(o.text);
        }
        return e;
    },

    /*
     * - o.elements: array with html elements
     * - o.id: string id
     * - o.classname: string class name
     * - o.click: callback function on click
     */
    div: function(o) {
        var e = document.createElement("div");
        if (o.id) {
            e.id = o.id;
        }
        if (o.classname) {
            e.className = o.classname;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        if (o.elements && o.elements.length > 0) {
            for (var i = 0; i < o.elements.length; i++) {
                if (typeof o.elements[i] == "string") {
                    e.innerHTML += o.elements[i];
                } else {
                    e.appendChild(o.elements[i]);
                }
            }
        }
        return e;
    },

    /*
     * - o.elements: array with html elements
     * - o.id: string id
     * - o.classname: string class name
     * - o.click: callback function on click
     */
    button: function(o) {
        var e = document.createElement("button");
        if (o.id) {
            e.id = o.id;
        }
        if (o.classname) {
            e.className = o.classname;
        }
        if (o.events) {
            MarketData_UI_HTMLHelper.bindEvents(o.events, e);
        }
        if (o.text) {
            e.innerHTML = o.text;
        }
        return e;
    },

    /*
     *
     */
    bindEvents: function(events, e) {
        var i, el;
        for (i = 0; i < events.length; i++) {
            el = (events[i].el) ? events[i].el : e;
            jQuery(el).bind(events[i].type, { myself: e }, events[i].callback);
        }
    }

};
// EOF

