var mariaForm_clearField = function (fieldId,fieldData,formData) {
    fieldData.instance.setValue('');
}

var mariaForm_clearFieldIfDefault = function (fieldId,fieldData,formData) {
    if (fieldData.instance.getValue() == fieldData.defaultValue)
        mariaForm_clearField(fieldId,fieldData,formData);
}

var mariaForm_setDefaultValueIfEmpty = function (fieldId,fieldData,formData) {
    if (fieldData.instance.getValue() == '') {
        fieldData.instance.setValue(fieldData.defaultValue);
    }
}

var mariaForm_submit = function (fieldId,fieldData,formData) {
    mariaForm_forms[formData.id].submit();
}

var mariaForm_submitOnEnter = function (fieldId,fieldData,formData,e) {
    if (e.keyCode == 13)
        mariaForm_submit(fieldId,fieldData,formData);
}

var mariaForm_updatePropertyOnChange_serverRequestSuccess = function (formId,fieldId,field,prop,serverData,isError,statusMsg) {
    log('updating property '+prop+' for field '+fieldId+" set"+prop.substring(0,1).toUpperCase()+prop.substring(1)+"("+serverData+")");
    field["set"+prop.substring(0,1).toUpperCase()+prop.substring(1)](serverData);
}

var mariaForm_updatePropertyOnChange = function (altFormStateIndex,trigger,updatedProperties,fieldId,fieldData,formData) {
    var fire = true;
    for (var field in trigger) {
        if (trigger[field] !== null && formData.fields[field].instance.getValue() != trigger[field]) {
            fire = false;
        }
    }
    
    for (var field in updatedProperties) {
        var props = updatedProperties[field];
        for (var prop in props) {
            if (fire) {
                //the trigger is active!
                if (props[prop].dynamicValue) {
                    //dynamic property-value.. ask the server!
                    var submitData = mariaForm_forms[formData.id].getAdditionalValues();
                    for (var fieldI in updatedProperties) {
                        submitData[fieldI] = formData.fields[fieldI].instance.getValue();
                    }
                    for (var fieldI in trigger) {
                        submitData[fieldI] = formData.fields[fieldI].instance.getValue();
                    }
                    submitData['mariaForm_altFormStateIndex'] = altFormStateIndex;
                    submitData['mariaForm_field'] = field;
                    submitData['mariaForm_prop'] = prop;
                    var apiClient = new restlikeApi(formData.id,formData.formServer,formData.connectionErrMsg);
                    apiClient.call('getAlternateFormStateDynamicValue',submitData,
                        partial(mariaForm_updatePropertyOnChange_serverRequestSuccess,formData.id,field,formData.fields[field].instance,prop)
                    );
                } else {
                    //no dynamic property-value, just set it..
                    log('updating property '+prop+' for field '+field+" set"+prop.substring(0,1).toUpperCase()+prop.substring(1)+"("+props[prop].value+")");
                    formData.fields[field].instance["set"+prop.substring(0,1).toUpperCase()+prop.substring(1)](props[prop].value);
                }
            } else {
                //no matter if the value is dynamic or not we take the default value if there's no trigger for this case
                var defVal = formData.defaultFieldProps[field][prop];
                log('updating property '+prop+' for field '+field+" set"+prop.substring(0,1).toUpperCase()+prop.substring(1)+"("+defVal+")");
                formData.fields[field].instance["set"+prop.substring(0,1).toUpperCase()+prop.substring(1)](defVal);
            }
        }
    }
}

/**
    this function is called on document ready - it prepares the mariaForm with id 'id' for use with 
    javascript and ajax
*/
var mariaForm = function (newData,newId) {
    
    //import the data
    var id = newId;
    var data = newData;
    
    var lastErrorField = '';
    var lastCallWasError = false;
    
    var updateHiddenInputs = function (serverData) {
        if ($(serverData.REQUESTarray).length == 0) return;
        
        $("form#"+id+" input[type=hidden].mariaForm_autoFillAfterSubmit").each(function(i,id) {
            if (typeof(serverData.REQUESTarray[$(this).attr('id')]) == 'undefined') {
                $(this).val();
            } else {
                $(this).val(serverData.REQUESTarray[$(this).attr('id')]);
            }
        });
    }
    
    var resetValidation = function () {
        if (lastErrorField != '') {
            $("#"+lastErrorField).removeClass(data.erroneousClass);
            lastErrorField = '';
        }
        if (lastCallWasError) {
            customResetOnError();
            lastCallWasError = false;
        }
    }
    
    var submitSuccess = function (serverData,isError,statusMsg) {
        updateHiddenInputs(serverData);
        customLoaderFinish();
        customOnSuccess(statusMsg,serverData);
    }
    
    var submitError = function (serverData,isError,statusMsg) {
        if ($(serverData).length > 0) {
            updateHiddenInputs(serverData);
        }
        customLoaderFinish();
        if (serverData.errorField != '') {
            $("#"+serverData.errorField).addClass(data.erroneousClass);
            lastErrorField = serverData.errorField;
        }
        lastCallWasError = true;
        customOnError(statusMsg);
    }
    
    var reset = function () {
        resetValidation();
        customResetOnSuccess();
        resetOnUserAction();
        
        for (var fieldId in data.fields) {
            //hide all hidden fields
            if (data.fields[fieldId].hidden) {
                $("#"+fieldId).hide();
                $("#mariaForm_labelFor"+fieldId).hide();
            }
            //reset value and properties for anything that is no submit and not hidden
            if (data.fields[fieldId].fieldType != "mfSubmit" && $("#"+fieldId).attr('type') != 'hidden') {
                for (var prop in data.defaultFieldProps[fieldId]) {
                    if (prop == 'defaultValue') continue;
                    data.fields[fieldId].instance['set'+prop.substring(0,1).toUpperCase()+prop.substring(1)](data.defaultFieldProps[fieldId][prop]);
                }
                data.fields[fieldId].instance.setValue(data.defaultFieldProps[fieldId].defaultValue);
            }
            //reset the value for submits
            if (data.fields[fieldId].fieldType == "mfSubmit") {
                $("#"+fieldId).val(data.fields[fieldId].label);
            }
        }
    }
    
    var submit = function () {
        //clean up 
        resetValidation();
        customResetOnSuccess();
        
        //soft validation and data gathering
        var submitData = {};
        var v = new inputvalidator();
        for (var fieldId in data.fields) {
            //skip submit-fields
            if (data.fields[fieldId].fieldType == "mfSubmit")
                continue;
            
            var val = data.fields[fieldId].instance.getValue();
            //clear default value (if required)
            if ((data.fields[fieldId].clearDefaultOnEnter || data.fields[fieldId].clearDefaultOnFormSubmit) && val == data.fields[fieldId].defaultValue) {
                val = '';
            }
            
            //perform all input-validation checks for which a javascript implementation exists
            for (var i in data.fields[fieldId].validation) {
                if (typeof (v[i]) == "function" && !v[i](val,false)) {
                    $("#"+fieldId).addClass(data.erroneousClass);
                    lastErrorField = fieldId;
                    lastCallWasError = true
                    customOnError(data.fields[fieldId].validation[i]);
                    return;
                }
            }
            
            //save the value for submitting
            if (fieldId == 'setClass' || fieldId == "call" || fieldId == "callback") {
                log("mariaForm ERROR: setClass, call and callback are reserved and cannot be used as field-names");
                return;
            }
            submitData[fieldId] = val;
        }
        
        //add the auto-submit field
        submitData["mariaForm_autoSubmit_"+data.id] = 1;
        
        //merge with custom userdata and hidden inputs
        submitData = objectMerge(submitData,getAdditionalValues());
        
        //submit to the server
        customLoaderStart();
        var apiClient = new restlikeApi(data.id,data.formServer,data.connectionErrMsg);
        apiClient.call('process',submitData,submitSuccess,submitError);
    }    
    
    $(document).ready (function () {
        for (var fieldId in data.fields) {
            //hide all hidden fields
            if (data.fields[fieldId].hidden) {
                $("#"+fieldId).hide();
                $("#mariaForm_labelFor"+fieldId).hide();
            }
            //instanciate the modifier-class
            if ($("#"+fieldId).length == 1) {
                if (eval("typeof " + data.fields[fieldId].fieldType + " == 'function'")) {
                    var fieldClass = eval(data.fields[fieldId].fieldType);
                    data.fields[fieldId].instance = new fieldClass(fieldId,data);
                }
            } else {
                log("ERROR: the element for field '"+id+"' either doesn't exist or exists multiple times! Remove it or print its html exactly one time..");
            }
            //register the event-handlers
            if (data.fields[fieldId].eventHandlers.length !== 0) {
                for (var i in data.fields[fieldId].eventHandlers) {
                    for (var j in data.fields[fieldId].eventHandlers[i]) {
                        $("#"+fieldId)[i](partial(eval(data.fields[fieldId].eventHandlers[i][j]),fieldId,data.fields[fieldId],data));
                    }
                }
            }
        }
        
        //replace the auto-submit buttons  
        var tmp = {}
        tmp["mariaForm_autoSubmit_"+data['id']] = ''; tmp["mariaForm_autoReset_"+data['id']] = '';
        for (var val in tmp) {
            var attrs = {};
            for (var j in {"class":'', "value": ''}) {
                attrs['my'+j] = $("#"+val).attr(j);
            };
            $("#"+val).replaceWith("<input type='button' id='"+val+"' name='"+val+"' value='"+attrs.myvalue+"' class='"+attrs.myclass+"' />");
        }
        $("#mariaForm_autoSubmit_"+data['id']).click(submit);
        $("#mariaForm_autoReset_"+data['id']).click(reset);
        
        $(mariaForm_forms[data.id]).trigger("mariaReady", mariaForm_forms[data.id]);
    });

    //dummy user-funcs
    var customOnSuccess = function (msg,data) {
        $(document.body).append("<div class='successOverlay'>"+msg+"</div>");
    }
    
    var customOnError = function (errorMsg) {
        $(document.body).append("<div class='errorOverlay'>"+errorMsg+"</div>");
    }
    
    var resetOnUserAction = function(){
    	$(".successOverlay").remove();
    	$(".errorOverlay").remove();
    }
    
    var customResetOnSuccess = function () {
        $(".successOverlay").remove();
    }
    
    var customResetOnError = function () {
        $(".errorOverlay").remove();
    }
    
    var customUserDataSubmit = function () {
        return {}
    }
    
    var customLoaderStart = function () {
        $(document.body).append("<div class='loaderOverlay'>loading..</div>");
    }
    
    var customLoaderFinish = function () {
        $(".loaderOverlay").remove();
    }
    
    /**
        set a new js-function to be called after successful processing this form
    */
    this.onSuccess = function (func) {
        if (typeof(func) == "function") {
            customOnSuccess = func;
        }
    }
    
    /**
        set a new js-function to be called after validation or other errors when processing this form
    */
    this.onError = function  (func) {
        if (typeof(func) == "function") {
            customOnError = func;
        }
    }
    
    /**
        set a new js-function to be called when resetting the state-changes for a successful validation
    */
    this.resetOnSuccess = function  (func) {
        if (typeof(func) == "function") {
            customResetOnSuccess = func;
        }
    }
    
    /**
        set a new js-function to be called when resetting the state-changes for an unsuccessful validation
    */
    this.resetOnError = function  (func) {
        if (typeof(func) == "function") {
            customResetOnError = func;
        }
    }
    
    /**
        set a new js-function to initiate a loader
    */
    this.loaderStart = function (func) {
        if (typeof(func) == "function") {
            customLoaderStart = func;
        }
    }
    
    /**
        set a new js-function to end a loader
    */
    this.loaderFinish = function (func) {
        if (typeof(func) == "function") {
            customLoaderFinish = func;
        }
    }
    
    this.userDataSubmit = function (func) {
        if (typeof(func) == "function") {
            var tmpRes = func();
            if (typeof(tmpRes) != 'object' || typeof(tmpRes.length) != 'undefined') {
                log('mariaForm - userDataSubmit: parameter does not return an object. Exiting.');
                return;
            }
            customUserDataSubmit = func;
        }
    }
    
    /**
        public fields array
    */
    this.fields = data.fields;
    
    /**
        submit and reset are public
    */
    this.submit = function () {
        submit();
    }
    
    this.reset = function () {
        reset();
    }
    
    /**
        populate is public
        @PARAM params an object ('key' -> 'value') with parameters for the request
        @PARAM errorCb a callback to be called if an error occures, param: msg (human error-message)
        @PARAM successCb a callback which is called after the form is populated, params: none; may be ommitted
    */
    this.populate = function (params,errorCb,successCb) {
       
        if (typeof(errorCb) != 'function') {
            errorCb = customOnError;
        }
        
        if (typeof(successCb) != 'function') {
            successCb = noop;
        }
        
        var apiClient = new restlikeApi(data.className,data.formServer,data.connectionErrMsg);
        apiClient.call('populate',params,partial(populateSuccessHandler,successCb),partial(populateErrorHandler,errorCb));
       
    }
    
    //internal handlers for populate
    var populateErrorHandler = function (errorCb,serverData,isError,statusMsg) {
        errorCb(statusMsg);
    }
    
    var populateSuccessHandler = function (successCb,serverData,isError,statusMsg) {
        var newFields = serverData.fields;
        for (var fieldId in newFields) {
            if (newFields[fieldId]['fieldType'] != "mfSubmit") {
                data.fields[fieldId].instance.setValue(newFields[fieldId]['getPrivValue']);
                if (data.fields[fieldId].clearDefaultOnEnter) {
                    mariaForm_setDefaultValueIfEmpty(fieldId,data.fields[fieldId],serverData);
                }
            }
        }
        
        successCb();
    }
    
    /**
        anybody should be able to call the hooks..
    */
    this.customOnSuccess = function (msg,data) {
        customOnSuccess(msg,data);
    }
    
    /**
        set a new js-function to be called after validation or other errors when processing this form
    */
    this.customOnError = function  (msg) {
        customOnError(msg);
    }
    
    /**
        set a new js-function to be called when resetting the state-changes for a successful validation
    */
    this.customResetOnSuccess = function  () {
        customResetOnSuccess();
    }
    
    /**
        set a new js-function to be called when resetting the state-changes for an unsuccessful validation
    */
    this.customResetOnError = function  () {
        customResetOnError();
    }
    
    /**
        set a new js-function to initiate a loader
    */
    this.customLoaderStart = function () {
        customLoaderStart();
    }
    
    /**
        set a new js-function to end a loader
    */
    this.customLoaderFinish = function () {
        customLoaderFinish();
    }
    
    /**
     *  set a new js-function to user reset
     */
    this.onUserReset = function(func) {
    	if (typeof(func) == "function") {
            resetOnUserAction = func;
        }
    };
    
    /**
        return additional values (hidden inputs and customUserDataSubmit)
    */
    var getAdditionalValues = function () {
        submitData = {};
        
        //add all hidden fields this form contains
        $("#"+data.id+" input[type=hidden]").each(function (key,elem){
            submitData[$(elem).attr('id')] = $(elem).val();
        });
        
        //merge with custom userdata
        submitData = objectMerge(submitData,customUserDataSubmit());
        
        return submitData;
    }
    
    this.getAdditionalValues = function () {
        return getAdditionalValues();
    }
    
    /**
        set the formserver for the next calls
    */
    this.setFormServer = function (newServer) {
        data.formServer = newServer;
    };
    
    /**
        get the formserver-URL
    */
    this.getFormServer = function () {
        return data.formServer;
    };
    
}

//declare the array for the instances
var mariaForm_forms = {};

