function GeotabDatasourceObject(successFunction, errorFunction) {
    var self = this;
    this.loginDialogButton = null;
    this.loginDialog = null;
    this.successFunction = successFunction;
    this.errorFunction = errorFunction;
    this.unhandledErrorFunction = function(err) {
        alert("Unhandled Error:\n" + err);
    };
    this.loginDialogData = {
        database: "",
        userName: "",
        password: ""
    };
    this.unknownUser = "InvalidUser";
    this.unknownDatabase = "DbUnavailable";
    this.thisServer = "ThisServer";
    this.blankScreen = "BlankScreen";
    this.redirect = "Redirect";
    this.dataSourceService = null;



    /***************************
    *
    * Some basic functions (RPC)
    *
    ****************************/
    this.onError = function(err) {
        if (!err)
            return;
        if (err.errors) {
            //jsonrpc errors
            var jsoneror = err.errors[0];
            err = jsoneror.name + ": " + jsoneror.message;
        }
        if (self.onLoginError) {
            self.onLoginError(err);
            return;
        }
        if (self.errorFunction) {
            self.errorFunction(err);
            return;
        }
        if (err.indexOf(self.unknownUser) > -1 || err.indexOf(self.unknownDatabase) > -1) {
            self.init();
            return;
        }
        if (err == "timeout") {
            err = GeotabConfig.login.serverTimeout;
        }
        self.unhandledErrorFunction(err);
    };
    
    //Return a correctly bridged (JQ to JAYROCK) DataStoreService.
    this.getDataStoreService = function(timeoutMilliseconds) {
        var s = new DataStoreService();
        s.channel.rpc = function(call) {
            var params = [];
            $.each(call.request.params, function(k, v) {
                if (v) params.push(k + '=' + encodeURIComponent(v));
            });
            var ajax_REQ = $.ajax({
                url: call.url,
                type: 'POST',
                cache: true,
                data: "JSON-RPC=" + encodeURIComponent($.toJSON(call.request)),
                dataType: 'json',
                timeout: timeoutMilliseconds,
                error: function(arg1, arg2, arg3) {
                    if (typeof arg1 == "function") return;
                    var err;
                    if (typeof arg1 == "string")
                        err = arg1;
                    else if (typeof arg2 == "string")
                        err = arg2;
                    else if (typeof arg3 == "string")
                        err = arg3;
                    else
                        try {
                        err = $.toJSON(arg1);
                    } catch (e) { err = "Undefined Error " + (typeof arg1); }
                    self.onError(err);
                },
                success: function(data) {
                    if (ajax_REQ) { ajax_REQ.abort(); }
                    if (data.error) {
                        self.onError(data.error);
                    }
                    else {
                        call.callback(data);
                    }
                }
            });
        };
        return s;
    };


    /***************************
    *
    * Login functionality
    *
    ****************************/
    this.init = function() {
        self.dataSourceService = self.getDataStoreService(90000);
        var login = self.getLoginFromURL();
        var securityToken = sessvars.securityToken;
        if (!Environment.supported) {
            return;
        }
        if (!login || typeof (login) != "object") {
            if (!securityToken) {
                return;
            } else {
                self.loginDialogData = {
                    database: securityToken.database,
                    userName: securityToken.userName,
                    password: ""
                };
                self.tryLogin(self.loginDialogData.database, self.loginDialogData.userName, "");
                return;
            }
        } else {
            self.loginDialogData = {
                database: login.database,
                userName: login.userName,
                password: ""
            };
            if (self.successFunction) {
                self.successFunction(self.blankScreen);
            }
            if (!securityToken || !securityToken.sessionId || securityToken.database != login.database || securityToken.userName != login.userName) {
                self.tryLogin(self.loginDialogData.database, self.loginDialogData.userName, "");
                return;
            }
            self.loginToHost(securityToken, document.location.hostname);
        }
    };
    
    this.getLoginFromURL = function() {
        var login;
        try {
            eval("login=" + decodeURIComponent(document.location.search).replace(/(^[^{]*)|([^}]*$)/ig, ''));
        } catch (e) { login = null; }
        return login;
    };
    
    this.displayLoginScreen = function(title) {
        var div = document.body.appendChild(document.createElement("DIV"));
        var loginData = this.loginDialogData;
        if (!loginData) {
            this.loginDialogData = {
                database: "",
                userName: "",
                password: ""
            };
            loginData = this.loginDialogData;
        }
        loginData.database = loginData.database || "";
        loginData.userName = loginData.userName || "";
        loginData.password = loginData.password || "";
        div.className = "dialogWindow";
		var tbl =  document.createElement("TABLE");
		//row1
		tbl.insertRow(-1);
		tbl.rows[0].insertCell(-1);
		tbl.rows[0].insertCell(-1);
		tbl.rows[0].cells[0].appendChild(document.createTextNode("Database"));
		var ipt = document.createElement("INPUT");
		ipt.className="ipt";
		ipt.value=loginData.database;
		tbl.rows[0].cells[1].appendChild(ipt);
		//row2
		tbl.insertRow(-1);
		tbl.rows[1].insertCell(-1);
		tbl.rows[1].insertCell(-1);
		tbl.rows[1].cells[0].appendChild(document.createTextNode("User (Email)"));
		ipt = document.createElement("INPUT");
		ipt.className="ipt";
		ipt.value=loginData.userName;
		tbl.rows[1].cells[1].appendChild(ipt);
		//row3
		tbl.insertRow(-1);
		tbl.rows[2].insertCell(-1);
		tbl.rows[2].insertCell(-1);
		tbl.rows[2].cells[0].appendChild(document.createTextNode("Password"));
		ipt = document.createElement("INPUT");
		ipt.className="ipt";
		ipt.type="password";
		ipt.value=loginData.password;
		tbl.rows[2].cells[1].appendChild(ipt);
		//row4
		tbl.insertRow(-1);
		tbl.rows[3].insertCell(-1);
		tbl.rows[3].cells[0].colSpan=2;
		tbl.rows[3].cells[0].align="right";
		ipt = document.createElement("INPUT");
		ipt.type="button";
		ipt.value="Login";
		tbl.rows[3].cells[0].appendChild(ipt);
		
		div.appendChild(tbl);
		/*
        div.innerHTML = '' +
		'<table>' +
			'<tr><td>Database</td><td><input class="ipt" value="' + loginData.database + '"/></td></tr>' +
			'<tr><td>User (Email)</td><td><input class="ipt" value="' + loginData.userName + '"/></td></tr>' +
			'<tr><td>Password</td><td><input type="password" class="ipt" value="' + loginData.password + '"/></td></tr>' +
			'<tr><td colspan="2" align="right"><input type="button" value="Login"></td></tr>' +
		'</table>';
		*/
        this.setupControls(div, self.tryLogin);
        this.loginDialogButton.disabled = false;
        this.loginDialog = $(div).dialog(GeotabConfig.login.dialog);
        if (!title) {
            title = GeotabConfig.login.dialog.title_login;
        }
        this.loginDialog.data('title.dialog', title);
        this.loginDialog.dialog("open");

    };
    
    this.setupControls = function(div, action) {
        var d = div.getElementsByTagName("INPUT");
        self.loginDialogButton = d[3];
        for (var i = 0; i < 3; i++)
            d[i].onkeypress = function(e) { e = e || window.event; if (e && e.keyCode && e.keyCode == 13) self.loginDialogButton.onclick(); };

        self.loginDialogButton.onclick = function() {
            var dv = div.getElementsByTagName("INPUT");
            action(dv[0].value, dv[1].value, dv[2].value);
        };
    };

    this.loginToHost = function(securityTokenValue, hostname) {
        //custom error handler. Call the funciton if any error occured
        self.onLoginError = function(err) {
            if (self.loginDialogButton) {
                self.loginDialogButton.disabled = false;
            }
            var loginDialogTitle;
            if (err == "timeout") {
                err = GeotabConfig.login.serverTimeout;
                if (self.loginDialog) {
                    loginDialogTitle = err;
                }
            }
            else if (err.indexOf(self.unknownUser) > -1) {
                if (self.loginDialog) {
                    loginDialogTitle = GeotabConfig.login.authorizationFailed;
                }
                else {
                    loginDialogTitle = GeotabConfig.login.dialog.title_login;
                }
            }
            else if (err.indexOf(self.unknownDatabase) > -1) {
                loginDialogTitle = GeotabConfig.login.unknownDatabase;
            }
            if (loginDialogTitle) {
                if (self.loginDialog) {
                    self.loginDialog.data('title.dialog', loginDialogTitle);
                    return;
                }
                else if (err) {
                    self.displayLoginScreen(loginDialogTitle);
                    return;
                }
            }
            if (self.errorFunction) {
                self.errorFunction(err);
                return;
            }
            self.unhandledErrorFunction(err);
        };
        var pathname = document.location.pathname;
        self.dataSourceService.LoginGlobally(securityTokenValue, hostname, function(data) {
            if (!data.id || data.error) {
                document.location.href = pathname;
                self.onError(data.error);
            }
            else {
                var result = data.result;
                if (result.indexOf("http") > -1 || result == self.thisServer) {
                    sessvars.securityToken = securityTokenValue;
                    if (self.loginDialog) {
                        self.loginDialog.dialog("close");
                        self.loginDialogButton = null;
                        self.onLoginError = null;
                    }
                    if (result == self.thisServer) {
                        var login = self.getLoginFromURL();
                        if (!(!login || securityTokenValue.database != login.database || securityTokenValue.userName != login.userName)) {
                            self.successFunction(self.thisServer);
                            return;
                        }
                    }
                    else {
                        pathname = result;
                    }
                    self.successFunction(self.redirect);
                    document.location.href = new SecurityToken(securityTokenValue).getRedirectHref(pathname);
                }
                else {
                    sessvars.securityToken = null;
                    self.onError(result);
                }
            }
        });
    };
    
    this.tryLogin = function(database, user, pass) {
        //disable button
        if (self.loginDialog) {
            self.loginDialog.data('title.dialog', GeotabConfig.login.dialog.title_wait);
            self.loginDialogButton.disabled = true;
        }
        var securityToken;
        if (database || user || pass) {
            self.loginDialogData = {
                database: database,
                userName: user,
                password: pass
            };
        }
        var loginDialogData = self.loginDialogData;
        if (loginDialogData) {
            securityToken = new SecurityToken(loginDialogData.database, loginDialogData.userName, loginDialogData.password);
        }
        else {
            securityToken = new SecurityToken();
        }
        self.loginToHost(securityToken.getLogin(), document.location.hostname);
    };


    /***************************
    *
    * Info functionality
    *
    ****************************/
    this.getDataStore = function() {
        return new GeotabDataStore(sessvars.securityToken, this.dataSourceService);
    };
    
    this.logOff = function() {
        document.location.href = window.location.href.replace(window.location.search, '');
    };
    
    this.reLogin = function() {
        this.displayLoginScreen();
    };
    
    this.checkCompanyExists = function(callback, companyName) {
        this.dataStoreService.CheckCompanyExists(companyName, callback);
    };

    /*
    --------------------------------------------------------
    --------------------------------------------------------
    */
    //NODES
    this.setNodeName = function(id, pId, name, callback) {
        var newNode = { "name": name, "EntityIdentifier": { "Id": id }, "parent": { "EntityIdentifier": { "Id": pId}} };
        this.dataSourceService.SetNode(sessvars.securityToken, newNode, function(node) {
            node = node.result;
            callback(node.id, value);
        });
    };
    
    this.addNode = function(id, pId, name, selector, callback) {
        var newNode = { "name": name, "parent": { "EntityIdentifier": { "Id": pId}} };
        this.dataSourceService.AddNode(sessvars.securityToken, newNode, function(node) {
            node = node.result;
            callback(node.EntityIdentifier.Id, node.Name, selector);
        });
    };
    
    this.deleteNode = function(id, pId, callback) {
        var node = { "EntityIdentifier": { "Id": id} };
        this.dataSourceService.RemoveNode(sessvars.securityToken, node, function(node) {
            node = node.result;
            callback(node.id);
        });
    };
    
    /**
     * Builds nodes tree from raw server-side data
     * @param nodes - raw collection of nodes to build tree from
     * @param filter - string to filter nodes by
     * @return array of top-level nodes
     */
    this._buildNodesTree = function(nodes, filter) {
        var result = [];
        if (!nodes) return result;

        for (var i = 0; i < nodes.length; i++) {
            var id = (nodes[i].EntityIdentifier ? nodes[i].EntityIdentifier.Id : nodes[i].entityIdentifier.Id);
            var name = (nodes[i].EntityIdentifier ? nodes[i].Name : nodes[i].name);
            var children = this._buildNodesTree(nodes[i].Children, filter);
            if (typeof filter == "undefined" || !filter || filter == "" || filter == "%" || name.indexOf("Organization Node") != -1 || name.toLowerCase().indexOf(filter.toLowerCase()) != -1 /*|| children.length > 0*/)
	            result[result.length] = {
	                "id": id,
	                "name": name,
	                "children": children
	            };
        }
        return result;
    };
    
    this._getSubTreesInProcess = false;
    this.getNodes = function(id, filter, selector, callback) {
        var parentNode = { "EntityIdentifier": { "Id": id } };
        self._getSubTreesInProcess = true;
        this.dataSourceService.GetSubTrees(sessvars.securityToken, parentNode, function(nodes) {
            if (nodes.error != null) {
                throw (nodes.error);
            }
            nodes = nodes.result;
            var data = self._buildNodesTree(nodes, filter);
            callback(id, data);
            self._getSubTreesInProcess = false;
        });
        // second part of the grid
        var serverFilter = (filter=='%'?'%':'%' + filter + '%');
        if (selector == "driver") {
            this.dataSourceService.GetDriversSearch(sessvars.securityToken, [parentNode], false, serverFilter, function(value) {
                function f(value) {
                    // make the records to appear below the groups.
                    if (self._getSubTreesInProcess) {
                        setTimeout(function() { f(value); }, 100);
                        return;
                    }
                    if (value.error != null) {
                        throw (value.error);
                    }
                    var nodes = value.result;
                    var data = {
                        "id": 0,
                        "name": "",
                        "children": self._buildNodesTree(nodes)
                    };
                    callback(id, data, selector);
                }
                f(value);
            });
        }
        if (selector == "device") {
            this.dataSourceService.GetDevicesSearch(sessvars.securityToken, [parentNode], false, serverFilter, function(value) {
                function f(value) {
                    // make the records to appear below the groups.
                    if (self._getSubTreesInProcess) {
                        setTimeout(function() { f(value); }, 100);
                        return;
                    }
                    if (value.error != null) {
                        throw (value.error);
                    }
                    var nodes = value.result;
                    var data = {
                        "id": 0,
                        "name": "",
                        "children": self._buildNodesTree(nodes)
                    };
                    
                    callback(id, data, selector);
                }
                f(value);
            });
        }
    };

    this._createCollectionForRequest = function(ar) {
        var res = [];
        for (var i = 0; i < ar.length; i++) {
            res[i] = { entityIdentifier: { Id: ar[i]} };
        }
        return res;
    };
    
    /////////////REPORTS /////////////////
    this.loadReport = function(type, fromDate, toDate, nodes, drivers, devices, minCustomerStopMinutes, expandNodesIntoAssets, includeAuxiliary, callback) {
        if (nodes) nodes = this._createCollectionForRequest(nodes);
        if (drivers) drivers = this._createCollectionForRequest(drivers);
        if (devices) devices = this._createCollectionForRequest(devices);
        switch (type) {
            case "details_device":
                //device details
                this.dataSourceService.GetActivityDetailByDevice(sessvars.securityToken, fromDate, toDate, nodes, devices, includeAuxiliary, function(data) {
                    if (data.error != null) {
                        self.onError(data.error);
                    } else {
                        callback(data.result);
                    }
                });
                break;
            case "details_driver":
                //driver details
                this.dataSourceService.GetActivityDetailByDriver(sessvars.securityToken, fromDate, toDate, nodes, drivers, includeAuxiliary, function(data) {
                    if (data.error != null) {
                        self.onError(data.error);
                    } else {
                        callback(data.result);
                    }
                });
                break;
            case "summary_device":
                //driver summary
                this.dataSourceService.GetActivitySummaryByDevice(sessvars.securityToken, fromDate, toDate, nodes, drivers, minCustomerStopMinutes, expandNodesIntoAssets, function(data) {
                    if (data.error != null) {
                        self.onError(data.error);
                    } else {
                        callback(data.result);
                    }
                });
                break;
            case "summary_driver":
                //device summary
                this.dataSourceService.GetActivitySummaryByDriver(sessvars.securityToken, fromDate, toDate, nodes, devices, minCustomerStopMinutes, expandNodesIntoAssets, function(data) {
                    if (data.error != null) {
                        self.onError(data.error);
                    } else {
                        callback(data.result);
                    }
                });
                break;
        }
    };

    //this.init();
}
window.dataSource = null;
function initGeotabDatasource(successFunction) {
    var ds = new GeotabDatasourceObject(successFunction);
    window.dataSource = ds;
    ds.init();
};