define([
  'app',
],
function (app) {

    var Main = {
            Models: {},
            Collections: {},
        };
    var classRef;

    classRef =
    Main.Models.Base = Backbone.Epoxy.Model.extend({
        connectionString: app.ConnectionStrings.app,
        procedures: {
            get: null,
            create: null,
            update: null,
            remove: null,
        },
        transaction_timestamp: null,

        create: function (options) {
            var that = this,
                qp = new QueryParameters(),
                opt = _.extend(
                    {
                        params: {},
                        async: true,
                    },
                    options
                );

            for (var pname in opt.params)
                qp.Add(pname, 'VARCHAR', opt.params[pname]);

            Core.Json.CallProcedure(
                that.procedures.create,
                qp,
                {
                    onSuccess: function (resp) {
                        if (that.parseResponse(resp, opt) == true) {
                            that.trigger('fetch', that, resp, opt);

                            opt.success(that, resp, opt);
                        }
                        else {
                            var msg = (resp.Message) ? resp.Message : 'SERVER_RESPONSE_NOT_VALID';

                            opt.error(that, msg);
                            console.error(new Error(msg).stack);
                        }
                    },
                    onError: function (msg) {
                        try {
                            opt.error(msg);
                            console.error(new Error(msg).stack);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    Async: opt.async,
                    Secured: true,
                },
                that.connectionString
            );
        },
        fetch: function (options) {
            var that = this,
                opt = _.extend(
                    { error: function () { }, success: function () { }, params: {}, },
                    options
                ),
                qp = new QueryParameters(),
                ttimestamp = this.transaction_timestamp = new Date().getTime();

            for (var pname in opt.params)
                qp.Add(pname, 'VARCHAR', opt.params[pname]);

            Core.Json.CallProcedure(
                that.procedures.get,
                qp,
                {
                    onSuccess: function (resp) {
                        try {
                            //checking transaction timestamp
                            if (ttimestamp != that.transaction_timestamp)
                                return;

                            if (that.parseResponse(resp, options) == true) {
                                that.trigger('fetch', that, resp, options);

                                opt.success(that, resp, options);
                            }
                            else {
                                var msg = (resp.Message) ? resp.Message : 'SERVER_RESPONSE_NOT_VALID';

                                opt.error(that, msg);
                                console.error(new Error(msg).stack);
                            }
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    onError: function (msg) {
                        try {
                            opt.error(msg);
                            console.error(new Error(msg).stack);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    Async: opt.async,
                    Secured: true,
                },
                that.connectionString
            );

            return this;
        },
        parseResponse: function (resp, opt) {
            if ((resp) && (resp.Table)) {
                this.set(
                    this.parse(resp.Table[0]),
                    _.extend({}, opt, { from: 'fetch' })
                );

                return true;
            }

            return false;
        },
        remove: function (options) {
            var that = this,
                qp = new QueryParameters(),
                opt = _.extend(
                    {
                        params: {},
                        async: true,
                    },
                    options
                );

            qp.Add('id', 'INT', this.get('id'));

            for (var pname in opt.params)
                qp.Add(pname, 'VARCHAR', opt.params[pname]);

            Core.Json.CallProcedure(
                that.procedures.remove,
                qp,
                {
                    onSuccess: function (resp) {
                        if ((resp) && (!resp.Message)) {

                            opt.success(that, resp, opt);
                        }
                        else {
                            var msg = (resp.Message) ? resp.Message : 'SERVER_RESPONSE_NOT_VALID';

                            opt.error(that, msg);
                            console.error(new Error(msg).stack);
                        }
                    },
                    onError: function (msg) {
                        try {
                            opt.error(msg);
                            console.error(new Error(msg).stack);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    Async: opt.async,
                    Secured: true,
                },
                that.connectionString
            );

            return this;
        },
        setRaw: function (/*obj [, obj2, ..., objn]*/) {
            this.set(this.parse.apply(this, arguments), { from: 'fetch', });
        },
        update: function (options) {
            var that = this,
                qp = new QueryParameters(),
                opt = _.extend(
                    { async: true, error: function () { }, success: function () { }, params: {}, },
                    options
                );

            qp.Add('id', 'INT', this.get('id'));

            for (var pname in opt.params)
                qp.Add(pname, 'VARCHAR', opt.params[pname]);

            Core.Json.CallProcedure(
                that.procedures.update,
                qp,
                {
                    onSuccess: function (resp) {
                        if (that.parseResponse(resp, opt) == true) {
                            that.trigger('fetch', that, resp, options);

                            opt.success(that, resp, options);
                        }
                        else {
                            var msg = (resp.Message) ? resp.Message : 'SERVER_RESPONSE_NOT_VALID';

                            opt.error(that, msg);
                            console.error(new Error(msg).stack);
                        }
                    },
                    onError: function (msg) {
                        try {
                            opt.error(msg);
                            console.error(new Error(msg).stack);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    Async: opt.async,
                    Secured: true,
                },
                that.connectionString
            );
        },
    });

    classRef =
    Main.Collections.Base = Backbone.Collection.extend({
        currentPage: 1,
        fixedParameters: [],
        isFetching: false,
        pageSize: -1,
        transaction_timestamp: null,

        getData: function (options) {
            var opt = _.extend(
                    { async: true, parse: false, },
                    options
                ),
                qp = null;

            if (opt.params) {
                //Check if params is a query parameters instance or not.
                if (opt.params.GetQueryParameter) {
                    qp = opt.params;
                }
                else {
                    qp = new QueryParameters();

                    for (var pname in opt.params)
                        qp.Add(pname, 'VARCHAR', opt.params[pname]);
                }
            }
            

            Core.Json.CallProcedure(
                this.model.prototype.procedures.get,
                qp,
                {
                    onSuccess: function (resp) {
                        try {
                            opt.success(resp);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    onError: function (msg) {
                        try {
                            opt.error(msg);
                            console.error(new Error(msg).stack);
                        }
                        catch (error) { console.error((error.stack) ? error.stack : new Error(error).stack); }
                    },
                    Async: opt.async,
                    Secured: true,
                },
                this.model.prototype.connectionString
            );
        },
        fetch: function (options) {
            var that = this,
                opt = _.extend(
                    { error: function () { }, success: function () { }, },
                    options
                ),
                qp = new QueryParameters(),
                ttimestamp = this.transaction_timestamp = new Date().getTime();

            this.isFetching = true;

            if (opt.reset)
                this.resetPagination(true);

            if ((opt.refresh) || (opt.reset))
                this.updateFixedParameters(opt.params);


            _.each(this.fixedParameters, function (qpParams) {
                qp.Add(qpParams.name, qpParams.type, qpParams.value);
            });

            if (opt.refresh) {
                qp.Add('@fromRow', 'INT', 0);
                qp.Add('@rowsToFetch', 'INT', this.currentPage * this.pageSize);
            }
            else if (opt.reset) {
                qp.Add('@fromRow', 'INT', 0);
                qp.Add('@rowsToFetch', 'INT', this.pageSize);
            }
            else {
                qp.Add('@fromRow', 'INT', (this.currentPage - 1) * this.pageSize);
                qp.Add('@rowsToFetch', 'INT', this.pageSize);
            }


            this.getData(_.extend(
                {},
                opt,
                {
                    params: qp,
                    success: function (resp) {
                        //checking transaction timestamp
                        if (ttimestamp != that.transaction_timestamp)
                            return;

                        if (that.parseResponse(resp, opt) == true) {
                            that.trigger('fetch', that, resp, options);

                            opt.success(that, resp);

                            ////decreasing page since we couldn't get any data on this page.
                            //if (newColl.length == 0 && method == 'add')
                            //    that.currentPage--;

                            that.isFetching = false;
                        }
                        else {
                            var msg = (resp.Message) ? resp.Message : 'SERVER_RESPONSE_NOT_VALID';

                            opt.error(that, msg);
                            console.error(new Error(msg).stack);
                        }
                    },
                }
            ));

            return this;
        },
        getNextPage: function (options) {
            if (!this.isFetching) {
                this.currentPage++;
                options = (_.isObject(options)) ? options : {};
                this.fetch(options);
            }
        },
        parseResponse: function (resp, opt) {
            if ((resp) && (resp.Table)) {
                var records = resp.Table,
                    newColl;

                newColl = _.map(records, this.model.prototype.parse);

                var method = ((opt.refresh) || ((opt.reset))) ? 'set' : 'add';

                var editingItems = _.where(this.toJSON(), { isNew: true });
                [].push.apply(newColl, editingItems);

                this[method](newColl, { from: 'fetch' });

                return true;
            }

            return false;
        },
        resetPagination: function (force) {
            if (!this.isFetching || force == true) {
                this.currentPage = 1;
            } else {
                _.delay(this.resetPagination, 100);
            }
        },
        updateFixedParameters: function (params) {
            this.fixedParameters = [];

            for (var pName in params)
                this.fixedParameters.push({ name: pName, value: params[pName], type: 'VARCHAR', });
        },
    });

    // Required, return the module for AMD compliance.
    return Main;

});