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

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

/**
 * Client-side Market Data API
 *
 * Example how to populate a html bullet list with quotes
 *
 * md = new MarketData();
 * md.getQuotes(["MDA:1234","MDA:5678"], function(quotes) {
 *     if (quotes) {
 *         for (var i = 0; i < quotes.length; i++) {
 *             // append price for MDA:1234 and MDA:5678 to page
 *             jQuery("document").append(quotes[i].getValue("price"));
 *         }
 *     }
 * });
 */
var MarketData = function(options) {

    options = options || {};

    this.lang = options.lang ? options.lang : "en";
    this.baseuri = options.baseuri ? options.baseuri : "/marketdata/MarketDataServer.php";

    /**
     * Get Instruments
     * @param mixed A string ID or an array with IDs
     * @param function A callback function to call when data is ready
     *        To the callback function, an object will be passed in:
     *        {id: [MarketData_Instrument, ...], id: [MarketData_Instrument, ...]}
     */
    this.getInstruments = function(id, callback) {
        return this.getByType("instrument", id, callback);
    };

    /**
     * Get Quotes
     * @param mixed A string ID or an array with IDs
     * @param function A callback function to call when data is ready
     *        To the callback function, an object will be passed in:
     *        { id1: MarketData_Quote, id2: MarketData_Quote, ... }
     */
    this.getQuotes = function(id, callback) {
        return this.getByType("quote", id, callback);
    };

    /**
     * Get History
     * @param mixed A string ID or an array with IDs
     * @param function A callback function to call when data is ready
     *        To the callback function, an object will be passed in:
     *        { id1: MarketData_History, id2: MarketData_History, ... }
     * @param object Options: order, limit, resolution, from, to
     */
    this.getHistory = function(id, callback, options) {
        return this.getByType("history", id, callback, options);
    };

    /**
     * Get Orderbooks
     * @param mixed A string ID or an array with IDs
     * @param function A callback function to call when data is ready
     *        To the callback function, an object will be passed in:
     *        {id: [MarketData_Orderbook, ...], id: [MarketData_Orderbook, ...]}
     */
    this.getOrderbooks = function(id, callback) {
        return this.getByType("orderbook", id, callback);
    };

    /**
     * Get Trades
     * @param mixed A string ID or an array with IDs
     * @param function A callback function to call when data is ready
     *        To the callback function, an object will be passed in:
     *        {id: [MarketData_Trades, ...], id: [MarketData_Trades, ...]}
     * @param object Options: order, limit, from, to
     */
    this.getTrades = function(id, callback, options) {
        options = options || null;
        return this.getByType("trades", id, callback, options);
    };

    /**
     * Get Press events
     */
    this.getPressEvents = function(id, callback, options) {
        options = options || null;
        return this.getByType("press_events", id, callback, options);
    };

    /**
     * Get Insider events
     */
    this.getInsiderEvents = function(id, callback, options) {
        options = options || null;
        return this.getByType("insider_events", id, callback, options);
    };

    /**
     * Get Insider events
     */
    this.getInsidersHoldings = function(orgid, personid, callback, options) {
        options = options || {};
        options.person = personid;
        return this.getByType("insiders_holdings", orgid, callback, options);
    };

    /**
     * Get Corporate Action events
     */
    this.getCorporateActionEvents = function(id, callback, options) {
        options = options || null;
        return this.getByType("corporateaction_events", id, callback, options);
    };

    /**
     * Get data by type - used by the other get methods
     */
    this.getByType = function(type, id, callback, options) {

        if (typeof(type) === "undefined") {
            throw "Type is required";
        }
        if (typeof(id) === "undefined") {
            throw "ID is required";
        }
        if (typeof(callback) === "undefined") {
            throw "Callback is required";
        }
        if (typeof(id) === "string") {
            id = [id];
        }
        options = options || {};
        var self = this;
        var uri = this.baseuri+"?type="+type+"&id="+id.join("|");
        var id = id.join("|");
        var format = "json_compact";

        if (format)             { uri += "&format="+format; }
        if (options.limit > -1) { uri += "&limit="+options.limit; }
        if (options.order)      { uri += "&order="+options.order; }
        if (options.from)       { uri += "&from="+options.from; }
        if (options.to)         { uri += "&to="+options.to; }
        if (options.resolution) { uri += "&resolution="+options.resolution; }
        if (options.namespace)  { uri += "&namespace="+options.namespace; }
        if (options.category)   { uri += "&category="+options.category; }
        if (options.lang)       { uri += "&lang="+options.lang; }
        if (options.person)     { uri += "&person="+options.person; }

        try {
            this.ajaxJSON(uri, function(json) {
                if (json && json.type == type) {
                    var o = {};
                    var s = [];
                    for (key in json.data) {
                        if (json.data[key].error) {
                            self.error(json.data[key].error);
                        } else if (json.data[key].compact_series) {
                            if (json.data[key].compact_series.keys && json.data[key].compact_series.vals) {
                                for (var i = 0; i < json.data[key].compact_series.vals.length; i++) {
                                    s.push(self.getInstanceByType(type, {
                                        keys: json.data[key].compact_series.keys,
                                        vals: json.data[key].compact_series.vals[i]
                                    }));
                                }
                            }
                            o[key] = s;
                        } else if (json.data[key].series) {
                            s = [];
                            for (var i = 0; i < json.data[key].series.length; i++) {
                                s.push(self.getInstanceByType(type, json.data[key].series[i]));
                            }
                            o[key] = s;
                        } else if (json.data[key].values) {
                            o[key] = self.getInstanceByType(type, json.data[key].values);
                        } else {
                            // wut?
                        }
                    }
                    callback(o);
                }
            });
        } catch (e) {
            throw e;
        }
    };

    /**
     * Get a new instance of varoius MarketData objects
     */
    this.getInstanceByType = function(type, arg) {
        arg = arg || null;
        var o = null;
        switch (type) {
            case "instrument":             o = new MarketData_Instrument(arg);            break;
            case "quote":                  o = new MarketData_Quote(arg);                 break;
            case "history":                o = new MarketData_History(arg);               break;
            case "orderbook":              o = new MarketData_Orderbook(arg);             break;
            case "trades":                 o = new MarketData_Trades(arg);                break;
            case "press_events":           o = new MarketData_PressEvents(arg);           break;
            case "insider_events":         o = new MarketData_InsiderEvents(arg);         break;
            case "insiders_holdings":      o = new MarketData_InsidersHoldings(arg);      break;
            case "corporateaction_events": o = new MarketData_CorporateActionEvents(arg); break;
        }
        return o;
    };

    /**
     * Perform an asynchronous HTTP request and send the
     * result to a callback function. Log on error.
     */
    this.ajaxJSON = function(url, callback) {
        if (typeof(callback) === "undefined") {
            throw "Callback is required";
        }
        var self = this;
        jQuery.ajax({
            url: url,
            dataType: "json",
            data: null,
            success: callback,
            //cache: false,
            error: function(jqXHR) {
                self.logError(jqXHR.statusText);
                callback(null);
            }
        });
    };

    /**
     * Epoch formatter - returns "yyyy-MM-dd HH:mm" or specified format
     */
    this.epochFormat = function(epoch, format) {
        format = format || "yyyy-MM-dd HH:mm";
        return jQuery.format.date(new Date(epoch*1000), format);
    }

    /**
     * Date formatter - returns "yyyy-MM-dd", e.g. 2010-01-01
     */
    this.fd = function(date) {
        var m = (date.getUTCMonth()+1).toString();
        var d = date.getUTCDate().toString();
        return date.getUTCFullYear()+"-"
                +(m.length == 1 ? "0" : "") + m+"-"
                +(d.length == 1 ? "0" : "") + d;
    }

    /**
     * Number formatter
     */
    this.nf = function(number, decimals, lang, dec_point, thousands_sep) {
        var n = number;
        var l = lang == undefined ? this.lang : lang;
        var c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
        var d = dec_point == undefined ? (l == "sv" ? "," : ".") : dec_point;
        var t = thousands_sep == undefined ? (l == "sv" ? " " : ",") : thousands_sep;
        var s = n < 0 ? "-" : "";
        var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
        return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
    };

    /**
     * Clone an object
     */
    this.clone = function(obj) {
        // Handle the 3 simple types, and null or undefined
        if (null == obj || "object" != typeof obj) return obj;
        // Handle Date
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }
        // Handle Array
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; ++i) {
                copy[i] = this.clone(obj[i]);
            }
            return copy;
        }
        // Handle Object
        if (obj instanceof Object) {
            var copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = this.clone(obj[attr]);
            }
            return copy;
        }
        throw new Error("Unable to copy object.");
    };


    /**
     * Get number of properties in an object
     */
    this.count = function(obj) {
        var i = 0;
        for (key in obj) {
            if (obj.hasOwnProperty(key)) {
                i++;
            }
        }
        return i;
    };

    /**
     * Log message
     */
    this.log = function(msg) {
        if (console && console.log) {
            console.log(msg);
        }
    };

    /**
     * Log error message
     */
    this.logError = function(msg) {
        if (console && console.error) {
            console.error(msg);
        }
    };

    /**
     * Log error message, shorthand version
     */
    this.error = function(msg) {
        /*if (console && console.error) {
            console.error(msg);
        }*/
        this.logError(msg);
    };

};

/**
 * A single instrument
 */
var MarketData_Instrument = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A single quote
 */
var MarketData_Quote = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A single set of history
 */
var MarketData_History = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A single orderbook
 */
var MarketData_Orderbook = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A single set of trades
 */
var MarketData_Trades = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A set of news
 */
var MarketData_PressEvents = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A set of insider events
 */
var MarketData_InsiderEvents = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A set of insiders holdings
 */
var MarketData_InsidersHoldings = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof data !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * A set of corporate actions
 */
var MarketData_CorporateActionEvents = function(data) {

    jQuery.extend(this, MarketData_ValueHolder);

    if (typeof(data) !== "undefined") {
        if (data.keys && data.vals) {
            this.keys = data.keys;
            this.values = data.vals;
        } else {
            this.values = data;
        }
    }
};

/**
 * ValueHolder abstracts common basic functionality for
 * several of the other classes in the MarketData API
 */
var MarketData_ValueHolder = {

    /**
     * The keys. Overriden by subclasses.
     */
    keys: [],

    /**
     * The values. Overriden by subclasses.
     */
    values: {},

    /**
     * Get a value by name
     */
    getValue: function(name, default_value) {
        var i = -1;
        if (this.keys.length && ((i = this.keys.indexOf(name)) > -1) && this.values[i] && this.values[i] !== null) {
            return this.values[i];
        } else if (name != "" && this.values[name] && this.values[name] !== null) {
            return this.values[name];
        } else {
            return default_value || null;
        }
    },

    /**
     * Get an array of supported value names.
     */
    getValueNames: function() {
        if (this.keys.length > 0) {
            return this.keys;
        } else {
            var names = [];
            for (key in this.values) {
                names.push(key);
            }
            return names;
        }
    }
};

/* for IE */
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(obj, start) {
        for (var i = (start || 0), j = this.length; i < j; i++) {
            if (this[i] === obj) { return i; }
        }
        return -1;
    }
}

// EOF



