////////////////////////////////////////
///          DbField class           ///
////////////////////////////////////////

RB.DbFields = RB.DbFields || {};

RB.DbFields.DbField = RB.LWClass();
_p = RB.DbFields.DbField.prototype;

_p.initialize = function(args) {
    this.hidden = false;
    this._null = false;
    this.nullval = null;
    this.blank = false;
    this.choices = null;
    this.defaultVal = null;
    this.help_text = "";
    this.max_length = null;
    this.center = false;
    this.verbose_name = null;
    this.validationFunction = null;
    this.tableWidth = "%";
    this.dirty = false;
    this.fieldName = "unnamed";

    this.$("init")(args);

    for (var fld in args) {
        this[fld] = args[fld];
    }
    this.value = this.defaultVal;
}

_p.isDbField = function() { return true; }

_p.init = function() { };

_p.setName = function(name) { this.fieldName = name; };

_p.isNull = function() { return this.value === null; };

_p.isDirty = function() { return this.dirty; };

_p.toString = function() {
    if (this.value !== null && this.value !== undefined) {
        return this.value.toString();
    } else {
        return "";
    }
};

_p.toDatabase = function() {
    if (this.value === null) {
        return this.nullval;
    }
    return this.$("toString")();
};

_p.set = function(value) {
    var oldVal = this.value;
    this.value = value;
    if (oldVal != this.value) {
        this.dirty = true;
    }
};

_p.makeInput = function(attribs) {
    var defAttrs = {
        type: "text",
        name: "fld_%s".format(this.fieldName),
        class: "db_field",
        value: this.$("toString")(),
    };
    if (this.max_length !== null) {
        defAttrs["max_length"] = this.max_length;
    }
    var attr = jQuery.extend(defAttrs, attribs);

    var elem = $("<input />");
    for (var fld in attr) {
        elem.attr(fld, attr[fld]);
    }
    if (elem.attr('disabled')) {
        elem.attr('readonly', true);
        elem.addClass('disabled');
    }
    return elem;
};

_p.makeSelect = function(attribs, opts) {
    var self = this;
    var attr = jQuery.extend({
        name: "fld_%s".format(this.fieldName),
        class: "db_field",
    }, attribs);

    var elem = $("<select />");
    for (var fld in attr) {
        elem.attr(fld, attr[fld]);
    }

    for (var val in opts) {
        elem.append('<option value="%s">%s</option>'.format(val, opts[val]));
    }
    elem.find('option').each(function(i, el){el.selected = el.value == self.$("toString")() ? true : false;});

    return elem;
};

_p.makeHtml = function(attribs) {
    if (!attribs) {
        attribs = {};
    }
    if (this.choices !== null) {
        return this.$("makeSelect")(attribs, this.choices);
    } else {
        return this.$("makeInput")(attribs);
    }
};

_p.fromForm = function(dialog) {
    return dialog.find("[name=fld_%s]".format(this.fieldName)).val();
}

////////////////////////////////////////
///     ForeignKeyField Class        ///
////////////////////////////////////////

RB.DbFields.ForeignKeyField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.ForeignKeyField.prototype;
_p.init = function(args) {
    this.fk_model = null;
    this.fk_modelName = "";
    this.fk_selectField = "uuid";
    this.fetchFunction = null;
}
_p.makeHtml = function(attribs) {
    var choices = {};
    choices[this.value] = "Current";
    var select = this.$("makeSelect")(attribs, choices);
    return select;
}
_p.isForeignKey = function() { return true; }
_p.set = function(value) {
    var oldVal = this.value;
    if (value == "**NULL**") {
        this.value = null;
    } else {
        this.value = value;
    }
    if (oldVal != this.value) {
        this.dirty = true;
    }
};

////////////////////////////////////////
///        IntegerField class        ///
////////////////////////////////////////

RB.DbFields.IntegerField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.IntegerField.prototype;
_p.initialize = function(args) {
}
_p.set = function(value) {
    var oldVal = this.value;
    this.value = parseInt(value);
    if (oldVal != this.value) {
        this.dirty = true;
    }
};
_p.toDatabase = function(value) {
    if (this.value === null || isNaN(this.value)) {
        return this.nullval;
    }
    return this.value;
};
_p.makeHtml = function() {
    var elem = this.super.$("makeHtml")();
    elem.inputField({
        allowNumeric: true,
        allowAlpha: false,
        disallowChars: ".",
    });
    return elem;
};
_p.fromForm = function(dialog) {
    return parseInt(dialog.find("[name=fld_%s]".format(this.fieldName)).val());
}
_p.toString = function() {
    if (isNaN(this.value)) {
        return "";
    } else {
        return this.value.toString();
    }
}

////////////////////////////////////////
///        DecimalField class        ///
////////////////////////////////////////

RB.DbFields.DecimalField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.DecimalField.prototype;
_p.initialize = function(args) {
}
_p.set = function(value) {
    var oldVal = this.value;
    this.value = parseFloat(value);
    if (oldVal != this.value) {
        this.dirty = true;
    }
}
_p.toDatabase = function(value) {
    if (this.value === null || isNaN(this.value)) {
        return this.nullval;
    }
    return this.value;
}
_p.makeHtml = function() {
    var elem = this.super.$("makeHtml")();
    elem.inputField({
        allowNumeric: true,
        allowAlpha: false,
    });
    return elem;
};
_p.fromForm = function(dialog) {
    return parseFloat(dialog.find("[name=fld_%s]".format(this.fieldName)).val());
}
_p.toString = function() {
    if (this.value === null || this.value === undefined || isNaN(this.value)) {
        return "";
    } else {
        return this.value.toString();
    }
}

////////////////////////////////////////
///          CharField class         ///
////////////////////////////////////////

RB.DbFields.CharField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.CharField.prototype;
_p.initialize = function(args) {
}
_p.init = function(args) {
    this.mask = null;
}

////////////////////////////////////////
///          UuidField class         ///
////////////////////////////////////////

RB.DbFields.UuidField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.UuidField.prototype;
_p.init = function(args) {
    this.mask = null;
    this.max_length = 36;
    this.defaultVal = null;
}

////////////////////////////////////////
///          DateField class         ///
////////////////////////////////////////

RB.DbFields.DateField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.DateField.prototype;
_p.initialize = function(args) {
}
_p.init = function(args) {
    this.dateFormat = "MMM d, yyyy";
    this.auto_now = false;
    this.auto_now_add = false;
}
_p.toString = function(format) {
    if (!format) format = this.dateFormat;
    if (this.value === null) {
        return "";
    }
    return this.value.toString(format);
}

_p.toDateString = _p.toString;

_p.set = function(value) {
    var oldVal = this.$("toDatabase")();
    if (typeof(value) == "string") {
        this.value = Date.parse(value);
    } else {
        this.value = value;
    }
    if (oldVal != this.$("toDatabase")()) {
        this.dirty = true;
    }
}
_p.toDatabase = function() {
    if (this.value == null)
        return null;
    else
        return this.value.toString("yyyy-MM-dd");
}
_p.makeHtml = function(attrs) {
    var lattrs = $.extend({
        max_length: 10,
        class:"dateField",
        value:this.$("toString")(this.dateFormat),
    }, attrs);
    var elem = this.super.$("makeInput")(lattrs);
    /*elem.inputField({
        allowNumeric: true,
        allowAlpha: false,
        allowChars: "-",
        disallowChars: ".",
    });*/
    elem.datepicker({dateFormat: this.dateFormat});
    return elem;
};
_p.fromForm = function(dialog) {
    var date = Date.parse(dialog.find("[name=fld_%s].dateField".format(this.fieldName)).val());
    if (date == null && !this.blank) {
        RB.alert("Invalid Entry", "Invalid Date.  Please enter a date in the format 'Mon, DD YYYY'.");
        return undefined;
    } else  {
        return date;
    }
}

////////////////////////////////////////
///          TimeField class         ///
////////////////////////////////////////

RB.DbFields.TimeField = RB.LWClass(RB.DbFields.DbField);
_p = RB.DbFields.TimeField.prototype;
_p.initialize = function(args) {
}
_p.init = function(args) {
    this.timeFormat = "HH:mm:ss";
    this.auto_now = false;
    this.auto_now_add = false;
}
_p.toString = function(format) {
    if (!format) format = this.timeFormat;
    if (this.value == null) {
        return "";
    }
    return this.value.toString(format);
}
_p.toTimeString = _p.toString;

_p.set = function(value) {
    var oldVal = this.$("toDatabase")();
    this.value = Date.parse(value);
    if (oldVal != this.$("toDatabase")()) {
        this.dirty = true;
    }
}
_p.toDatabase = function() {
    if (this.value == null)
        return null;
    else
        return this.value.toString("HH:mm:ss");
}
_p.fromForm = function(dialog) {
    var time = Date.parse(dialog.find("[name=fld_%s].timeField".format(this.fieldName)).val());
    if (time == null && !this.blank) {
        RB.alert("Invalid Entry", "Invalid Time.  Please enter a time in the format 'HH:mm:ss'.");
        return undefined;
    } else {
        return time;
    }
}
_p.makeHtml = function(attrs) {
    var ourAttrs = $.extend({
        max_length: 8,
        class:"timeField",
        value:this.$("toString")(this.timeFormat),
    }, attrs);
    var elem = this.super.$("makeInput")(ourAttrs);
    var getTime = function(str, addMinute) {
        var date = Date.parse(str);
        if (date === null) {
            return null;
        }
        date.setTime(date.getTime() + (addMinute * 60 * 1000));
        return date;
    }

    var populate = function(evt) {
        var char = evt.data;
        var curTime = new Date();
        switch(char) {
            case "t":
                $(this).val(curTime.toString("HH:mm"));
                break;
            case "shift+t":
                $(this).val(curTime.toString("HH:mm:ss"));
                break;
            case "a":
                $(this).select();
                break;
            case "m":
                $(this).selectText(3,5);
                break;
            case "h":
                $(this).selectText(0,2);
                break;
            case "s":
                $(this).selectText(6, 2);
                break;
            case "+":
            case "shift++":
                var tmp = $(this).val();
                var cnt = tmp.split(":");
                var newTime = getTime(tmp, 1);
                if (cnt.length == 3) {
                    $(this).val(newTime.toString("HH:mm:ss")).selectText(3,5);
                } else {
                    $(this).val(newTime.toString("HH:mm")).selectText(3,2);
                }
                break;
            case "-":
                var tmp = $(this).val();
                var cnt = tmp.split(":");
                var newTime = getTime(tmp, -1);
                if (cnt.length == 3) {
                    $(this).val(newTime.toString("HH:mm:ss")).selectText(3,5);
                } else {
                    $(this).val(newTime.toString("HH:mm")).selectText(3,2);
                }
                break;
            case "r":
            case "/":
                if (RB.repeatTime) {
                    $(this).val(RB.repeatTime.toString("HH:mm:ss"));
                    $(this).selectText(3,5);
                }
                break;
            case "del":
                $(this).val("");
                break;
            default:
        }
        evt.preventDefault();
    }

    elem.inputField({
        allowNumeric: true,
        allowAlpha: false,
        transform: {".": ":"},
        keyTriggers: {"a": populate,
                     "m": populate,
                     "t": populate,
                     "/": populate,
                     "+": populate,
                     "-": populate,
                     "r": populate,
                     "h": populate,
                     "s": populate,
                     "shift+t": populate,
                     "shift++": populate,
                     "del": populate},
    });
    elem.blur(function(evt){
        var self = this;
        var val = $(this).val();
        if (RB.getNumericChars(val).length < 1) {
            $(this).val("");
            return;
        }
        var tmp = null;
        if (val.indexOf(":") != -1)
            var tmp = Date.parse(val);

        if (tmp == null && val.length > 0) {
            val = RB.getNumericChars(val);
            if (val.length == 3) {
                val = "%s:%s".format(val.substring(0, 1), val.substring(1,3));
            } else if (val.length == 4) {
                val = "%s:%s".format(val.substring(0, 2), val.substring(2,4));
            } else if (val.length == 5) {
                val = "%s:%s:%s".format(val.substring(0,1), val.substring(1,3), val.substring(3,5));
            } else if (val.length == 6) {
                val = "%s:%s:%s".format(val.substring(0,2), val.substring(2,4), val.substring(4,6));
            } else {
                val = "";
            }
            tmp = Date.parse(val);
        }
        if (tmp != null && val.length > 0) {
            RB.repeatTime = tmp;
            $(this).val(tmp.toString("HH:mm:ss"));
        } else {
            RB.alert("Invalid Time", "This is not a valid time", function() { $(self).select().focus(); });
        }
    });

    return elem;
}


////////////////////////////////////////
///       DateTimeField class        ///
////////////////////////////////////////

RB.DbFields.DateTimeField = RB.LWClass(RB.DbFields.DbField);

RB.DbFields.DateTimeField.defaultDate = Date.today();
RB.DbFields.DateTimeField.setDefaultDate = function(date) {
    RB.DbFields.DateTimeField.defaultDate = date;
}

_p = RB.DbFields.DateTimeField.prototype;
_p.initialize = function(args) {
}
_p.init = function(args) {
    this.textFormat = "MMM d, yyyy HH:mm:ss";
    this.timeFormat = "HH:mm:ss";
    this.dateFormat = "MMM d, yyyy"
    this.auto_now = false;
    this.auto_now_add = false;
}
_p.toString = function(format) {
    if (!format) format = this.textFormat;
    if (this.value === null) {
        return "";
    }
    return this.value.toString(format);
}
_p.toTimeString = RB.DbFields.TimeField.prototype.toTimeString;
_p.toDateString = RB.DbFields.DateField.prototype.toDateString;
_p.set = function(value) {
    var oldVal = this.$("toDatabase")();
    if (typeof(value) == "string") {
        this.value = Date.parse(value);
    } else {
        this.value = value;
    }
    if (oldVal != this.$("toDatabase")()) {
        this.dirty = true;
    }
}
_p.toDatabase = function() {
    if (this.value == null)
        return null;
    else
        return this.value.toString("yyyy-MM-dd HH:mm:ss");
}
_p.makeHtml = function(attrs) {
    if (!attrs) {
        attrs = {};
    }
    var elem = $('<div class="db_datetime"></div>');
    var showTime = true;
    var showDate = true;
    if (attrs.time === false) {
        delete attrs.time;
        showTime = false;
    }
    if (attrs.date === false) {
        delete attrs.date;
        showDate = false;
    }
    if (showTime)
        elem.append(RB.DbFields.TimeField.prototype.makeHtml.apply(this, [ $.extend({max_length: 10}, attrs) ]));
    if (showDate) {
        elem.append(RB.DbFields.DateField.prototype.makeHtml.apply(this, [ $.extend({max_length: 10}, attrs) ]));
        if (elem.find(".hasDatepicker").val() == "") {
            elem.find(".hasDatepicker").val(RB.DbFields.DateTimeField.defaultDate.toString(this.dateFormat));
        }
    }
    return elem;
};
_p.fromForm = function(dialog) {
    var dateVal = dialog.find("[name=fld_%s].dateField".format(this.fieldName)).val();
    var timeVal = dialog.find("[name=fld_%s].timeField".format(this.fieldName)).val();
    var date = Date.parse(dateVal);
    var time = Date.parse(timeVal);

    if (( dateVal && date == null )) {
        RB.alert("Invalid Entry", "Invalid Date.  Please enter a date in the format '%s'.".format(this.dateFormat));
        return undefined;
    } else if (( timeVal && time == null ) || (!timeVal && !this.blank)) {
        RB.alert("Invalid Entry", "Invalid Time.  Please enter a time in the format '%s'.".format(this.timeFormat));
        return undefined;
    }
    if (dateVal == undefined && time) {
        date = RB.DbFields.DateTimeField.defaultDate;
    } else if (timeVal == undefined && date) {
        time = Date.parse("00:00:00");
    }
    if (date && time) {
        var dt = "%sT%s".format(date.toString("yyyy-MM-dd"), time.toString("HH:mm:ss"));
        return Date.parse(dt);
    }
    return null;
}


////////////////////////////////////////
///          DbModel class           ///
////////////////////////////////////////

/** RB.DbModel -- Base class for all javascript models **/
RB.DbModel = RB.LWClass();
_p = RB.DbModel.prototype;

_p.initialize = function(system, data) {
    this.system = system;
    this.fieldList = [];
    this.urls = {
            "deleteUrl": base_url + "/delete",
            "saveUrl": base_url + "/update"
        };
    this.$("addField")("uuid", new RB.DbFields.UuidField({hidden: true, blank: false, defaultVal:null}));
    this.$("initFields")();
    this.$("loadArray")(data);
}

_p.loadArray = function(data) {
    for(var field in data) {
        if (this[field] && this[field].isDbField) {
            this[field].$("set")(data[field]);
        } else {
            this[field] = data[field];
        }
    }
}

_p.toString = function() {
    arr = [];
    for (var i in this.fieldList) {
        var field = this[this.fieldList[i]];
        if (!field.hidden) {
            arr.push(field.$("toString")());
        }
    }
    return arr.join(", ");
};

_p.countItems = function(includeHidden) {
    var n = 0;
    for (var i in this.fieldList) {
        var field = this[this.fieldList[i]];
        if (includeHidden || !field.hidden) {
            n++;
        }
    }
    return n;
}

_p.open = function() {
    // Overload this to go to the page that opens this modal
};

_p.getLine = function(outer, inner, opts) {
    if (opts.outerClass) {
        output = '<%s class="%s" id="%s">'.format(outer, opts.outerClass, this.uuid.value);
    } else {
        output = "<%s>".format(outer);
    }
    defSize = "%s%".format( parseInt(100 / this.$("countItems")()) );
    for (var i in this.fieldList) {
        var field = this[this.fieldList[i]];
        if (!field.hidden) {
            var width = field.tableWidth;
            if (width == "%") {
                width = defSize;
            }
            var style = "width: %s;".format(width);
            if (field.center)
                style = style + "text-align: center;";
            output += '<%s style="%s">%s</%s>'.format(inner, style, field.$("toString")(), inner);
        }
    }
    output += "</%s>".format(outer);
    return output;
}

_p.initFields = function() {
    // Nothing to do here
};

_p.getConfirmDeleteMessage = function() {
    return "Are you sure you want to delete this?";
}

_p.addField = function(name, obj) {
    this.fieldList.push(name);
    obj.$("setName")(name);
    this[name] = obj;
}

_p.serializeToObject = function() {
    output = {};
    for (var i in this.fieldList) {
        output[this.fieldList[i]] = this[this.fieldList[i]].$("toDatabase")();
    }
    return output;
}

_p._deleteRightNow = function(callback) {
    var self = this;
    $.ajax({
        url : this.urls.deleteUrl,
        ifModified : false,
        data: {uuid: self.uuid.value},
        dataType: "json",
        type: "GET",
        error : RB.genericAjaxError,
        success : function(data, status) {
            if (data.status == "failed") {
                if (data.message == "Unauthenticated") {
                    self.system.$("LoginForm")(true, function() {
                        self.$("_deleteRightNow")(callback);
                    }, function() {
                        callback(false, "Unauthorized");
                    });
                } else {
                    callback(false, data.message);
                }
            } else {
                callback(true);
            }
        },
    });
};

_p.deleteRecord = function(callback) {
    var self = this;

    RB.confirm("Are you sure?", this.$("getConfirmDeleteMessage")(),
        function() { self.$("_deleteRightNow")(callback); });
};

_p.save = function(callback) {
    var self = this;
    data = $.toJSON([ this.$("serializeToObject")() ]);
    $.ajax({
        url : this.urls.saveUrl,
        ifModified : false,
        data: data,
        dataType: "json",
        type: "POST",
        error : RB.genericAjaxError,
        contentType: "application/json",
        processData: false,
        success : function(data, status) {
            if (data.status == "failed") {
                if (data.message == "Unauthenticated") {
                    self.system.$("LoginForm")(true, function() {
                        self.$("save")(callback);
                    }, function() {
                        if (callback) {
                            callback(false, "Unauthorized");
                        }
                    });
                } else {
                    if (callback) {
                        callback(false, data.message);
                    }
                }
            } else {
                if (callback) {
                    self.$("loadArray")(data[0]);
                    callback(true, self);
                }
            }
        },
    });
}

_p.getEditFields = function() {
    var fields = [];
    var fkFields = {};
    for (var i in this.fieldList) {
        var fld = this.fieldList[i];
        if (this[fld].hidden) {
            continue;
        }

        var labelText = this[fld].verbose_name;
        if (!labelText) labelText = fld;
        var fieldHtml = this[fld].$("makeHtml")();
        if (this[fld].isForeignKey) {
            var fName = this[fld].fk_modelName;
            if (!fkFields[fName]) {
                fkFields[fName] = {obj: this[fld].fk_model, selectField: this[fld].fk_selectField, list: []};
            }
            fkFields[fName].list.push([ fieldHtml, this[fld].value ]);
        }
        fields.push($("<li><label>%s:</label></li>".format(labelText)).append(fieldHtml));
    }

    for (var name in fkFields) {
        if (fkFields[name].obj.getList) {
            var cur = fkFields[name];
            var selectVal = this[cur.selectField].value;
            cur.obj.getList(selectVal, function(list) {
                for (var i in cur.list) {
                    var fld = cur.list[i][0];
                    var value = cur.list[i][1];
                    fld.find("option").remove();
                    fld.append('<option value="**NULL**"></option>');
                    for (var val in list) {
                        fld.append('<option value="%s">%s</option>'.format(val, list[val]));
                    }
                    fld.find('option').each(function(i, el){el.selected = el.value == value ? true : false;});
                }
            });
        }
    }
    return fields;
}

_p.edit = function(callbackSaved, callbackCanceled) {
    var self = this;
    var dialog = $("<div></div>");

    var editTable = $( '<ul class="DbModelEdit"></ul>' );
    var fields = this.$("getEditFields")();
    for (var i in fields) {
        editTable.append(fields[i]);
    }

    dialog.append(editTable);

    var saveFunc = function() {
        for (var i in self.fieldList) {
            var fld = self.fieldList[i];
            if (self[fld].hidden) {
                continue;
            }
            var val = self[fld].$("fromForm")(dialog);
            if (val === undefined) {
                return false;
            }
            else {
                self[fld].set(val);
            }
        }
        self.$("save")(function(success, data) {
            if (!success) {
                RB.alert('Could not save', data);
                return;
            } else if (callbackSaved) {
                callbackSaved(success, data);
            }
            dialog.dialog("close");
            dialog.remove();
        });
    };
    var cancelFunc = function() {
        dialog.dialog("close");
        dialog.remove();
        if (callbackCanceled) {
            callbackCanceled();
        }
    }
    dialog.dialog({
        title: "Edit Entry",
        autoOpen : true,
        modal: true,
        dialogClass: 'dbEdit',
        resizable: true,
        closeOnEscape: false,
        buttons: {
            "Save": saveFunc,
            "Cancel": cancelFunc,
        },
        close: function() { dialog.remove(); },
        open: function() {
            var dlg = dialog.dialog("widget");
            dlg.find(".ui-dialog-titlebar-close").hide();
            dlg.find('button:contains("Save")').buttonize("disk");
            dlg.find('button:contains("Cancel")').buttonize("closethick");
            dlg.find(":input:visible:enabled:first").select().focus();
            dlg.find(":input:visible:enabled:not(button):not(:last)").bind("keydown", "return", function(evt) {
                $(this).parents("li").next().find(":input").select().focus();
            });
            dlg.find(":input:visible:enabled:not(button):last").bind("keydown", "return", function(evt) {
                saveFunc();
            });
            dlg.find(":input").bind("keydown", "esc", cancelFunc);
        },
    });
};



