const edoc_key_separator = "_";
const edoc_key_prefix = "___edoc:";
const edoc_no_token_message = "_no_token";

const modelKeyPrefix = edoc_key_prefix + "model/";
const modelKeyRefId = "refId/";
const modelKeyShortname = "shortName/";

function setupConditions(form) {
	if (form.conditions === undefined)
		return;

	//iterate all conditions
	form.conditions.forEach(function (condition) {
		if (condition.if === undefined || condition.show === undefined)
			return;

		let updateVisibility = function (condition, isVisible) {
			condition.show.forEach(function (showField) {
				let container = null; //TODO: løs problemet med container 
				let idWithContainer = getIdWithContainer(showField, container);

				let edocObject = $("#" + CSS.escape(idWithContainer)).closest("[data-edoc-object]");

				if (edocObject.length === 0)
					console.error("Unsupported condition show element: " + showField + " - if " + JSON.stringify(condition.if, null, 2));

				if (isVisible) {
					edocObject.slideDown(100);
				} else {
					edocObject.slideUp(100);
				}
			});
		}

		let isVisible = evaluateCondition(condition); //initial show/hide
		updateVisibility(condition, isVisible);

		condition.if.forEach(function (iff) {
			let ifElement = $("#" + CSS.escape(iff.field));

			ifElement.change(function () {
				console.log($(this));

				let isVisible = evaluateCondition(condition);
				updateVisibility(condition, isVisible);

			    return true;
			});
		});
	});
}

function evaluateVisible(form, objectId, container) {
	let conditions = [];

	if (!form.conditions)
		return true;

	//get conditions that the object is output in, if any
	form.conditions.forEach(function (condition) {
		if (condition.if === undefined || condition.show === undefined)
			return;

		condition.show.forEach(function (outputId) {
			if (outputId === objectId)
				conditions.push(condition);
		});
	});

	//evaluate the conditions
	if (conditions.length === 0)
		return true;

	var isVisible = true;

	conditions.forEach(function (condition) {
		if (!evaluateCondition(condition, container))
			isVisible = false;
	});

	return isVisible;
}

//TODO: recursive conditions. 
//TODO: tidy up the way fields are declared in handlebar templates. <div class="row"> and <div class="col-xs-12"> doesn't need to be duplicated everywhere
function evaluateCondition(condition, container) {
	//evaluate all conditions
	let conditionResults = [];

	condition.if.forEach(function (conditionInputField) {
		let fieldValue = null;

		let idWithContainer = getIdWithContainer(conditionInputField.field, container);

		if (model.fields[idWithContainer] !== undefined)
			fieldValue = model.fields[idWithContainer].value.v;

		let operator = conditionInputField.operator;
		let conditionValue = conditionInputField.condition;

		let hasInputValue = function () { return fieldValue !== undefined && fieldValue != null && fieldValue !== "" };
		let isOption = function () { return fieldValue.search(/\[[\s\S]*\]\~\[[\s\S]*\]/) !== -1 };

		if (hasInputValue() && isOption()) { //get option value
			let regexp = /\[([\s\S]*)\]\~\[(?:[\s\S]*)\]/;
			let match = regexp.exec(fieldValue);
			fieldValue = match[1];
		}

		let result;

		if (operator === "Equals") { //TODO: support more operators, use Eval()???
			result = fieldValue === conditionValue;
		} else
			result = null;

		conditionResults.push(result);
	});

	let all = condition.type === undefined || condition.type === "all";
	let visible = false;

	//find unsupported conditions
	let unsupportedConditionIndex = conditionResults.indexOf(null);

	if (unsupportedConditionIndex !== -1) {
		alert("Found unsupported condition operator: " + condition.if[unsupportedConditionIndex].operator);
		return false;
	}

	if (all) {
		let containsFailedCondition = conditionResults.find(function (v) { return v === false }) !== undefined;
		visible = !containsFailedCondition; //show if no conditions failed
	} else {
		let containsSuccessfulCondition = conditionResults.find(function (v) { return v === true }) !== undefined;
		visible = containsSuccessfulCondition; //show if any conditions succeeded
	}

	return visible;
}
function getFieldById(id) {
	let field = null;

	const fields = getObjects(template, "id", id);

	if (fields.length === 0)
		return null;

	field = fields[0];

	return field;
}

function getObjects(obj, key, val) {
	var objects = [];
	for (var propertyName in obj) {
		if (!obj.hasOwnProperty(propertyName))
			continue;

		if (typeof obj[propertyName] == "object" && propertyName !== "inputs" && propertyName !== "outputs") { //don't traverse executable's input/outputs
			objects = objects.concat(getObjects(obj[propertyName], key, val));
		} else if (propertyName === key && obj[key] === val) {
			objects.push(obj);
		}
	}
	return objects;
}
let localResources;

function initializeLocalResources() {
    //init resources
    localResources = i18next.createInstance({
        fallbackLng: "nb",
        lowerCaseLng: true,
        debug: true,
        defaultNS: "translation",
        useCookie: false,
        interpolation: {
            format: function (value, format, lng) {
                var m = moment(value);

                if(m.isValid())
                    value = m.format(format);

                return value;
            }
        }
    }, (err, t) => {
        if (err)
            return console.error('Failed to load local resources.', err);
    });

    moment.locale(localResources.language);

    //keep moment in sync with i18next
    localResources.on('languageChanged', function (lng) {
        moment.locale(lng);
        $("html").attr("lang", lng);
    });

    localResources.addResources("en", "translation", resourceBundle_en);
    localResources.addResources("nb", "translation", resourceBundle_nb_no);
    localResources.addResources("nn", "translation", resourceBundle_nn_no);

    //init jquery plugin - https://github.com/i18next/jquery-i18next
    jqueryI18next.init(localResources, $, {
        tName: 't', // --> appends $.t = i18next.t
        i18nName: 'i18n', // --> appends $.i18n = i18next
        handleName: 'localize', // --> appends $(selector).localize(opts);
        selectorAttr: 'data-t', // selector for translating elements
        targetAttr: 't-target', // data-() attribute to grab target element to translate (if diffrent then itself)
        optionsAttr: 't-options', // data-() attribute that contains options, will load/set if useOptionsAttr = true
        useOptionsAttr: true, // see optionsAttr
        parseDefaultValueFromContent: true // parses default values from content ele.val or ele.text
    });

    //register handlebars i18next helper
    Handlebars.registerHelper('t', function (i18n_key) {
        var result = localResources.t(i18n_key);

        if (!result)
            result = "t:" + localResources.language + ":" + i18n_key;

        return new Handlebars.SafeString(result);
    });

    //register handlebars moment helper
    Handlebars.registerHelper('moment', function (value, format) {
        const m = moment(value);

        if (!m.isValid())
            return value;

        const result = m.format(format);

        return new Handlebars.SafeString(result);
    });
}

const defaultLanguage = "nb";

function localize(language) {
    if (!language)
        language = getCurrentLanguageFromLocalStorage();

    if (!localResources) {
        return;
        console.warn("swithLanguage skipped, localResources not initialized.");
    }

    if (i18next) {
        i18next.changeLanguage(language, (err, t) => {
            if (err)
                return console.error("i18next - Failed to changeLanguage " + language, err);
            else {
                console.log("i18next - changeLanguage OK to " + language);
            }
        });
    }

    localResources.changeLanguage(language, (err, t) => {
        if (err)
            return console.error("localResources - Failed to changeLanguage " + language, err);
        else {
            console.log("localResources - changeLanguage OK to " + language);

            $("*[data-lang='" + getCurrentLanguageFromLocalStorage() + "']").removeClass("active"); //remove previous active
            $("*[data-lang='" + language + "']").addClass("active"); //set new active

            setLanguageToLocalStorage(language);
        }
    });

    $("html").localize();
}

const languageKey = "_____language"

function getCurrentLanguageFromLocalStorage() {
    var storage = window.localStorage;

    var currentLanguage = storage.getItem(languageKey);

    if (currentLanguage)
        return currentLanguage;

    return defaultLanguage;
}

function setLanguageToLocalStorage(language) {
    var storage = window.localStorage;

    storage.setItem(languageKey, language);
}

function initializeServerResources() {
	return new Promise((resolve, reject) => {
		i18next
			.use(i18nextXHRBackend)
			.use(i18nextLocalStorageCache)
			.init({
			    fallbackLng: 'nb',
			    lowerCaseLng: true,
			    //load: 'current',
			    useCookie: false,
				debug: true,
				ns: ['ValidationMessages'], // globalShortname],
				backend: {
					loadPath: apiUrl + globalConsumer + '/resources/{{ns}}?culture={{lng}}',
					crossDomain: true
				},
                cache: {
                    enabled: true,
                    expirationTime: 5 * 60 * 1000,
                }
			},
			function(err, t) {
				if (err) {
					console.error("error loading i18n: " + err);
					reject(err);
				} else {
					resolve("ok");
				}
			});
	});
}
function readFromFieldWriteToModel(input) {
    setHasConfirmedInformation(window.model, false);

	//get value of field that was changed
	let value = "";
	let id = $(input).attr("id");

	if ($(input).attr("type") === "text" || $(input).is("textarea")) {
		value = $(input).val().trim();
	} else if ($(input).attr("type") === "radio") {
		id = $(input).attr("name");

		if ($(input).is(":checked"))
			value = $(input).val();
		else
			value = "";
	} else if ($(input).attr("type") === "checkbox") {
		id = $(input).attr("name");

		//append value of all checked boxes
		$("input[name='" + id + "']:checked")
            .each(function () {
            	value += $(this).val() + "|";
            });

		if (value !== "") {
			value = value.substring(0, value.length - 1); //remove last pipe
		}
	} else if ($(input).is("select")) {
		value = $(input).val();
	}

	setValueIntoModel(model, id, value);

	let idWithoutContainer = getIdWithoutContainer(id);

	let fieldToValidate = getFieldById(idWithoutContainer);

	let isValidationError = false;

	let executableId = $("#" + CSS.escape(id)).data("exe");
	let hasExecutable = executableId !== undefined && executableId !== null;

	if (fieldToValidate !== undefined) {
		let performServersideValidation = !hasExecutable;
		let errorMessage = validate(fieldToValidate, id, value, model, performServersideValidation);
		isValidationError = setErrorMessage(errorMessage, id);
	}

	putModelIntoSessionStorage(model);

	if (hasExecutable) {
		let executable = getFieldById(executableId);

		//TODO: executable can have multiple inputs: validate all executable.inputs before executing

		let containerId = getContainerId(id);

		//if (!isValidationError) {
		executePrefill(executable, containerId, model); //NOTE: prefill must be executed, even for invalid input, so that property empty values can be set in the case of read-only or hidden values. 
		//} else {
		//$(executable.executableInfo.outputs).each(function () {
		//	let id = this.id;
		//	clearValue(id, containerId, model);
		//});
		//TODO: don't invoke prefill if validation fails? clear prefill if input is cleared? 
		//TODO: clear validation output
		//}
	}
}

function setValueIntoModel(model, id, value) {
	if (!model.fields.hasOwnProperty(id)) {
		model.fields[id] = {};
	}

	if (!model.fields[id].hasOwnProperty(value)) {
		model.fields[id]["value"] = {};
	}

	model.fields[id]["value"].v = value;
}

function setHasVisistedSummarypage(model) {
	if (!model)
		return;

	if (!model.meta)
		return;

	if (!model.meta.hasVisistedSummarypage) {
		model.meta.hasVisistedSummarypage = true;
		putModelIntoSessionStorage(model);
	}
}

function hasVisistedSummarypage(model) {
	return model.meta.hasVisistedSummarypage !== undefined && model.meta.hasVisistedSummarypage;
}

function hasConfirmedInformation(model) {
    return model.meta.hasConfirmedInformation && model.meta.hasConfirmedInformation.v === true; 
}

function setHasConfirmedInformation(model, hasConfirmed) {
    if (!model)
        return;

    if (!model.meta)
        return;

    model.meta.hasConfirmedInformation = { v: hasConfirmed };
    putModelIntoSessionStorage(model);
}

function getIdWithContainer(id, container) {
	if (container === undefined || container == null || container === "")
		return id;

	return container + "_" + id;
}

function getIdWithoutContainer(id) {
	let parts = id.split("_");

	if (parts.length === 1)
		return id;

	return parts[1];
}

function getContainerId(id) {
	let parts = id.split("_");

	if (parts.length === 1)
		return null;

	return parts[0];
}

function readFromModelWriteToFields(model, isValueChange) {
	console.log("readFromModelWriteToFields\n" + JSON.stringify(model, null, 2));
	let fields = Object.getOwnPropertyNames(model.fields);

	$(fields).each(function (index, id) {
		readFromModelWriteToField(id, model, isValueChange);
	});
}

function getFieldValueFromModel(id, model) {
	let field = model.fields[id];
	let value;
	let v;
	if(field)
		value = field.value;
	if(value)
		v = value.v;

	return v;
}

function readFromModelWriteToField(id, model, isValueChange) {
	const field = model.fields[id];
	const value = field.value;
	const input = $("#" + CSS.escape(id));
	let v;

	if (value)
		v = value.v;

	if (input !== undefined && input !== null) {
		if (input.attr("type") === "text" || input.is("textarea")) {
			input.val(v);
		}
		else if (input.data("edoc-field") === "radio") {
			//if no value, clear radio
			if (v === undefined || v === "") {
				$("input:radio[name='" + id + "']:checked").prop("checked", false);
			}
				//if value, unwrap the name, then use that name and value to find the radio to check
			else {
				$("input:radio[name='" + id + "'][value='" + v + "']").prop("checked", true);
			}
		}
		else if (input.data("edoc-field") === "check") {
			//if no value, clear check
			if (v === undefined || v === "") {
				$("input:checkbox[name='" + id + "']:checked").prop("checked", false);
			}
				//if value, iterate selected values, then use that name and value to find the checkboxes to check
			else {
				$(v.split("|")).each(function () {
					$("input:checkbox[name='" + id + "'][value='" + this + "']").prop("checked", true);
				});
			}
		}
		else if (input.is("select")) {
			input.val(v);
		}
		else {
			console.log("Unable to determine input type for id and value: " + id + " - " + v);
		}

		let field = getFieldById(id);
		let errorMessage = validate(field, id, v, model, isValueChange);
		setErrorMessage(errorMessage, id);
	}
}

function putModelIntoSessionStorage(model, key) {
    if (!key)
        key = window.modelKey;

    console.log("putModelIntoSessionStorage: " + key);

	if (!isLocalStorageSupported())
	    return;


	if (model) {
	    const modelJson = JSON.stringify(model);
	    sessionStorage.setItem(key, modelJson);
	} else
        sessionStorage.removeItem(key);
}

function getModelFromSessionStorage(key) {
    try {
        console.log("getModelFromSessionStorage: " + key);

		var storedModel = JSON.parse(sessionStorage.getItem(key));

		if (storedModel == undefined || !storedModel.hasOwnProperty("fields")) {
			return null;
		}

		return storedModel;
	} catch (e) {
		return null;
	}
}

function getRefIdKey(refId) {
    return modelKeyPrefix + modelKeyRefId + refId;
}

function getShortnameKey(consumer, shortname) {
    return modelKeyPrefix + modelKeyShortname + consumer + ":" + shortname;
}

function getModelByRefId(consumer, refId) {
    const key = getRefIdKey(refId);
    const model = getModelFromSessionStorage(key);

    if (model !== null) //if model already created in local storage
        return Promise.resolve(model);

    //no model in local storage for refid

    //invoke rest API to resume model
    return resumeModel(consumer, refId)
        .then(function(model) {
            console.log("================================");
            console.log("Model resumed:\n" + JSON.stringify(model, null, 2));
            console.log("================================");

            putModelIntoSessionStorage(model, key);

            return Promise.resolve(model);
        });
}

function getModelByShortname(consumer, shortname) {
    const key = getShortnameKey(consumer, shortname);
    const model = getModelFromSessionStorage(key);

    if (model !== null) //if model already created in local storage
        return Promise.resolve(model);

    //no model in local storage for shortname

    //invoke rest API to get a new model
    return newModel(consumer, shortname)
        .then(function(model) {
            console.log("================================");
            console.log("Model created:\n" + JSON.stringify(model, null, 2));
            console.log("================================");

            putModelIntoSessionStorage(model, key);

            return Promise.resolve(model);
        });
}

function removeModel() {
    putModelIntoSessionStorage(null);
    window.model = null;
}
function executePrefill(executable, containerId, model) {
	console.log("executing prefill " + executable.id);

	let id = getIdWithoutContainer(executable.id);

	//TODO: find all output for the executable and disable it (if it isn't already disabled)?

    getPrefillDataFromServer(model, id, containerId)
        .then(function(data) {
            console.log("================ PREFILL DATA FROM SERVER ====================");
            console.log(JSON.stringify(data, null, 2));
            console.log("=============================================");

            $(Object.getOwnPropertyNames(data.fields))
                .each(function() {
                    let isUpdated = isUpdatedValue(this, model, data);
                    model.fields[this] = data.fields[this];

                    readFromModelWriteToField(this.valueOf(), model, isUpdated);
                });

            //TODO: if output was locked in the todo above, unlock output if it was editable to begin with (i.e. don't unlock if disabled by default)

            putModelIntoSessionStorage(model);

            console.log("================ MODEL AFTER PREFILL ====================");
            console.log(JSON.stringify(model, null, 2));
            console.log("=============================================");

            return Promise.resolve("ok");
        })
        .catch(function (response) {
            renderError(response);
        });
}

function isUpdatedValue(id, destination, source) {
	//both are null
	if (destination.fields[id] == null && source.fields[id] == null)
		return false;

	//either is null but other isnt
	if ((destination.fields[id] == null && source.fields[id] != null) ||
        destination.fields[id] != null && source.fields[id] == null)
		return true;

	//both have no value
	if (destination.fields[id].value == null && source.fields[id].value == null)
		return false;

	//either has value but other doesn't 
	if ((destination.fields[id].value == null && source.fields[id].value != null) ||
        (destination.fields[id].value != null && source.fields[id].value == null))
		return true;

	//both have same value
	if (destination.fields[id].value.v === source.fields[id].value.v)
		return false;

	return true;
}

function registerExecutable(executable, container) {
	$(executable.executableInfo.inputs).each(function () {
		let id = this.id;
		if (container)
			id = getIdWithContainer(id, container);

		$("#" + CSS.escape(id)).data("exe", executable.id); //set id of executable on input-field as custom attr
	});
}
let executables = new Array();

(function registerPartials() {
    //iterate all templates in hb.fields and register it as a partial
    Object.keys(hb.fields).forEach(function(key,index) {
        Handlebars.registerPartial(key.toLowerCase(), hb.fields[key]);
    });
})();

(function registerHelpers() {
    Handlebars.registerHelper("isRenderable", function (field) {
        if (field.type === "FieldRelation")
            return false;

        //all fields that are not executable are renderable, plus zip lookup
        return !field.isExecutable || field.type === "PostalCodeBoxCityLookup"; 
    });

    Handlebars.registerHelper("isExecutable", function (field) {
        return field.isExecutable;
    });

    Handlebars.registerHelper("registerExecutable", function (executable, container) {
        executables.push({ executable, container });
    });

    Handlebars.registerHelper("getId", function (id, container) {
        if (!container)
            return id;

        return container + "_" + id;
    });

    Handlebars.registerHelper("getRows", function (field, model) {
        if (!model)
            return [];

        return model.fields[field.id].value.v.split("|"); //TODO: null handling
    });

    Handlebars.registerHelper("isFieldContainer", function(field) {
        return field.hasOwnProperty("fields");
    });

    Handlebars.registerHelper("getWidthClass", function (field) {
        let width;

        if (field.width !== undefined && field.width !== null && field.width !== "") {
           width = field.width;
        }

        let widthClass = Math.round(12 * width);

        if (Number.isNaN(widthClass))
            widthClass = 12;

        return widthClass;
    });

    Handlebars.registerHelper("getTemplatename", function (field) {
        const textVariants = [
            "TextBox",
            "NumberBox",
            "DateBox",
            "OrganisationalNumberBox",
            "SocialSecurityNumberBox",
            "eMailBox",
            "PostalCodeBoxCityLookup",
            "CurrencyBox",
            "IpAddressBox",
            "AccountNumberBox",
            "RegularExpressionBox",
            "TelephoneBox",
            "UrlBox"
        ];

        const dropdownlistVariants = [
            "XmlDropDownList"
        ];

        let templatename;

        if(field.isHidden || field.type === "HiddenBox")
            return "hidden";

        if (textVariants.includes(field.type)) {
            templatename = "text";
        }
        else if (dropdownlistVariants.includes(field.type)) {
            templatename = "dropdownlist";
        } else {
            templatename = field.type;
        }

        templatename = templatename.toLowerCase();

        //template doesn't exist
        if (!Handlebars.partials[templatename])
            templatename = "undefined"; //default to undefined

        return templatename;
    });
})();

function renderForm() {
    if (isSummary()) {
        summary();
    } else {
        renderTitle(template.caption);
        renderBody();
        renderPages(template, model);
        renderButtons();
        readFromModelWriteToFields(model, false);
        setupConditions(template);
        genericEvents();
        formSpecificEvents(globalShortname);
        registerExecutables();

        if (hasVisistedSummarypage(model)) {
            validateForm();
        }

        expandPage(window.location.hash);
    }

    localize(getCurrentLanguageFromLocalStorage());
}

function registerExecutables() {
    for (var i = 0; i < executables.length; i++) {
        let executable = executables[i];
        registerExecutable(executable.executable, executable.container);
    }
}

const HashParam_Page = "p";

function expandPage(hash) {
    let currentUrl;

    if (hash)
        currentUrl = URI(hash);
    else
        currentUrl = URI(window.location);

    currentUrl.fragmentPrefix("");

	let pageElement = null;
	let isFirstPage = false;

    const hashParams = currentUrl.fragment(true);

    if (HashParam_Page in hashParams) {
	    pageElement = $("#" + hashParams[HashParam_Page]);
	}

	if (pageElement == null) {
		pageElement = $(".form-part").first();
		isFirstPage = true;
	}

	if (pageElement != null) {
		//Ekspandere siden
		pageElement.addClass("active");

		if (!isFirstPage && pageElement.offset()) {
			//Scrolle til siden
			$('html, body').animate({ scrollTop: pageElement.offset().top }, 0);
		}
	}
}

function editPage(pageName) {
    $("#bodycontent").empty();
    window.location.hash = HashParam_Page + "=" + pageName;
	renderForm();
}

function renderCategory(category) {
	let categoryHtml = hb["category"](
        {
        	caption: category.Caption,
        	description: category.Description,
        	shortname: category.Code,
            id :category.Id
        }
    );

	$("#categoryContainer").append(categoryHtml);

	renderTemplatelinks(category);
}

function renderTemplatelinks(category) {
	console.log("category: " + JSON.stringify(category, null, 2));

	for(let template of category.templates) {

		let html = hb["templatelink"](
			    {
			    	globalSiteroot: globalSiteroot,
			    	name: template.Name,
			    	shortname: template.ShortName,
			    	consumer: globalConsumer
			    }
		    );

		$("#templateContainer_" + category.Id).append(html);
	}
}

function renderOverview() {
	let overview = hb["overview"](
        {
        	siteroot: globalSiteroot,
        	consumer: globalConsumer
        }
    );

	$("#bodycontent").html(overview);
}

function setErrorMessage(errorMessage, id) {
	const isValidationError = errorMessage !== null && errorMessage !== undefined && errorMessage !== "";
	const input = $("#" + CSS.escape(id));

	if (isValidationError) {
		$(input).addClass("error");
		$("#response-text_" + id).show();
		$("#errorText_" + id).text(" " + errorMessage);
	} else {
		$(input).removeClass("error");
		$("#response-text_" + id).hide();
		$("#errorText_" + id).text("");
	}

	return isValidationError;
}

function formSpecificEvents() {
	$("input,textarea,select")
        .change(
            function () {
            	console.log("field changed " + this.id + ", reading from field writing to model.");
            	readFromFieldWriteToModel(this);
                return true;
            }
		);

    $("input[data-edoc-calendar='true']").each(function() {
        $(this).datepicker({
            showWeek: true,
            changeMonth: true,
            changeYear: true
        });
    });
}

function renderTitle(title) {
	let formtitleHtml = hb["formtitle"](
        {
        	caption: title
        }
    );

	$("#bodycontent").append(formtitleHtml);
}

function renderBody() {
	let rowHtml = hb["row"](
        {
        	id: "bodyrow"
        });

	$("#bodycontent").append(rowHtml);
}

function renderPage(page, pageOrder, model) {
	const pageHtml = hb["formpage"](
	    {
	        name: page.name,
	        caption: page.caption,
	        order: pageOrder,
	        id: page.id,
	        helpClick: page.helpClick,
	        fields: page.fields,
            model: model
	    }
	);

	const pageElement = $.parseHTML(pageHtml);
	const pages = $("#bodyrow");
	pages.append(pageElement);
}

function renderPages(form, model) {
	let pageOrder = 1;

	for (let page of form.pages) {
		renderPage(page, pageOrder++, model);
	}
}

function renderButtons() {
	let buttonsHtml = hb["buttons"]();
	var buttonsElement = $.parseHTML(buttonsHtml);
	$("#bodyrow").append(buttonsElement);
}

function summary() {
	let summaryPageHtml = hb["summary"](
		{
		}
    );

	var isValidForm = true;
	$("#bodycontent").html(summaryPageHtml);
    let model = window.model;
	setHasVisistedSummarypage(model);
	putModelIntoSessionStorage(model);

	for (let page of template.pages) {
		if (evaluateVisible(template, page.id, null, model)) {
			let summaryPageResult = summaryFormPage(template, page, model);
			if (isValidForm === true)
				isValidForm = summaryPageResult.isValidFormPage;
		}
	}

	summaryConfirmationBox(hasConfirmedInformation(window.model));

	$("#chkConfirmInformation").click(function (e) {
	    setHasConfirmedInformation(window.model, $("#chkConfirmInformation").is(":checked"));
	});

	if (!isValidForm) {
		$("#btnSubmitForm").attr("disabled", "disabled");
		$("#chkConfirmInformation").attr("disabled", "disabled");
	}

	window.scrollTo(0, 0);

	localize(getCurrentLanguageFromLocalStorage());
}

function summaryFormPage(form, page, model) {
	let caption;
	let isValidFormPage = true;

	if (page.captionSummaryPage != undefined)
		caption = page.captionSummaryPage;
	else
		caption = page.caption;

	let summaryPage = hb["summaryPage"](
        {
        	pageHeader: caption,
        	name: page.name
        }
    );

	$("#summaryFormContainer").append(summaryPage);
	let summaryRenderFieldsResult = summaryRenderFields(form, page, page.fields, undefined, model);

	isValidFormPage = summaryRenderFieldsResult.isValidPage;

	return { isValidFormPage: isValidFormPage }
}

//TODO: mye av det samme som renderfields, slå sammen?
function summaryRenderFields(form, parentPage, fields, container, model) {
	if (fields === undefined || fields === null)
		return;

	let isValidPage = true;

	for(let field of fields) {
		if (!evaluateVisible(form, field.id, container, model))
			continue;

		let fieldElement;

		var textboxVariants = ["TextBox",
                                "NumberBox",
                                "DateBox",
                                "OrganisationalNumberBox",
                                "SocialSecurityNumberBox",
                                "eMailBox",
                                "PostalCodeBoxCityLookup",
                                "CurrencyBox",
                                "IpAddressBox",
                                "AccountNumberBox",
                                "RegularExpressionBox",
                                "TelephoneBox",
                                "UrlBox"];

		let fieldProperties = {
			caption: field.caption
		};

		let fullId;

		if (container === undefined)
			fullId = field.id;
		else
			fullId = container + "_" + field.id;

		fieldProperties.id = fullId;
	//console.error("fieldProperties.id: " + fieldProperties.id);

		if (textboxVariants.includes(field.type)) {
			fieldProperties = Object.assign(fieldProperties,
                {
                	maxchars: field.maxChars,
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	placeholder: field.placeholder,
                	required: field.required,
                	isHidden: field.isHidden,
                	disabledStatic: field.disabledStatic,
                	inputAlign: field.inputAlign
                }
            );
		}
		else if (field.type === "RadioButtonList") {
			fieldProperties = Object.assign(fieldProperties,
                {
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	placeholder: field.placeholder,
                	required: field.required,
                	disabledStatic: field.disabledStatic,
                	options: field.options,
                	isHidden: field.isHidden
                }
            );
		}
		else if (field.type === "DropDownList" || field.type === "XmlDropDownList") {
			fieldProperties = Object.assign(fieldProperties,
                {
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	placeholder: field.placeholder,
                	required: field.required,
                	disabledStatic: field.disabledStatic,
                	options: field.options,
                	insertBlankItem: field.insertBlankItem,
                	isHidden: field.isHidden
                }
            );
		}
		else if (field.type === "CheckBoxList") {
			fieldProperties = Object.assign(fieldProperties,
                {
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	placeholder: field.placeholder,
                	required: field.required,
                	disabledStatic: field.disabledStatic,
                	options: field.options,
                	isHidden: field.isHidden
                }
            );
		}
		else if (field.type === "MultilineBox") {
			fieldProperties = Object.assign(fieldProperties,
                {
                	maxchars: field.maxChars,
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	placeholder: field.placeholder,
                	required: field.required,
                	disabledStatic: field.disabledStatic,
                	rows: field.rows,
                	cols: field.columns,
                	isHidden: field.isHidden
                }
            );
		}
		else if (field.type === "DateCalendarBox") {
			fieldProperties = Object.assign(fieldProperties,
                {
                	maxchars: field.maxChars,
                	helpClick: field.helpClick,
                	helpFocus: field.helpFocus,
                	required: field.required,
                	isHidden: field.isHidden
                }
            );
		}
		else if (field.type === "DataGrid") {
			//TODO
		}
		else if (field.type === "Panel") {
			let summaryRenderFieldsResult = summaryRenderFields(form, parentPage, field.fields, field.id, model);

			if (isValidPage === true)
				isValidPage = summaryRenderFieldsResult.isValidPage;
		}

		if (hideOnSummaryPage(field))
			continue;

		if (field.summaryPageCaption != undefined)
			fieldProperties.caption = field.summaryPageCaption;
		else
			fieldProperties.caption = field.caption;

		let fieldHasValue = model.fields[fullId] != undefined &&
			model.fields[fullId].value != undefined &&
			model.fields[fullId].value.v != undefined;

		let value = null;

		if (fieldHasValue) { 
			value = model.fields[fullId].value.v;
			if (isMultipleChoice(field)) {
				// MultipleChoiceControl model value is [name]~[caption]|[name]~[caption]|...
				let selectedCaptions = [];
				let regexpName = /\[([\s\S]*)\]\~\[(?:[\s\S]*)\]/;
				let regexpValue = /\[(?:[\s\S]*)\]\~\[([\s\S]*)\]/;
				let selectedValues = value.split("|");

				$(selectedValues).each(function () {
					let matchValue = regexpValue.exec(this);
					let matchName = regexpName.exec(this);
					let summaryValue = "";

					if (matchName != null) {
						var matchingFieldOption = getMatchingFieldOptionByName(field.options, matchName[1]);

						if (matchingFieldOption && matchingFieldOption[0] && matchingFieldOption[0].summaryPageCaption)
							summaryValue = matchingFieldOption[0].summaryPageCaption;
					}

					if (!summaryValue && matchValue != null)
						summaryValue = matchValue[1];

					selectedCaptions.push(summaryValue);
				});

				fieldProperties.value = selectedCaptions.join(", ");
			}
			else {
				fieldProperties.value = value;
			}
		}

		let error = validate(field, fullId, value, model, false);

		if (error !== undefined && error != null && error !== "") {
			fieldProperties.error = error;
			isValidPage = false;
		}

		fieldElement = hb["summaryPageField"](fieldProperties);
		$("#summaryPageFieldContainer_" + parentPage.name).append(fieldElement);
	}

	return { isValidPage: isValidPage }
}

function getMatchingFieldOptionByName(options, name) {
	return options.filter(
		function (options) {
			return options.name === name;
		}
	);
}

function isMultipleChoice(field) {
	if (field.type === "RadioButtonList" || field.type === "CheckBoxList" || field.type === "DropDownList" || field.type === "XmlDropDownList")
		return true;
	else
		return false;
}

function isHiddenField(field) {
	var hiddenFieldTypes = ["HiddenBox", "FieldRelation"];
	if (hiddenFieldTypes.includes(field.type)) return true;
	if (field.isHidden === true) return true;
	//FieldRelation er en isExecutable, men kun når den har output
	if (field.isExecutable === true && field.type !== "PostalCodeBoxCityLookup") return true;
}

function hideOnSummaryPage(field) {
	//TODO: felt som ikke har betingelse oppfylt? 
	if (isHiddenField(field)) return true;
	if (field.type === "Label") return true;
	if (field.type === "ImageControl") return true;
	if (field.type === "Panel") return true;
	if (field.summaryPageHidden) return true;
}

function summaryConfirmationBox(hasConfirmedInformation) {
	let summaryConfirmationBox = hb["summaryConfirmationBox"](
        { hasConfirmedInformation: hasConfirmedInformation }
    );

	$("#summaryFormContainer").append(summaryConfirmationBox);
}

function genericEvents()
{
	// Open / close a form-part
	$(".form-header a").click(function () {
		var p = $(this).parent();
		p.next(".form-content").slideToggle(100);
		p.parent().toggleClass("active");
	});

	$(".head-link").click(function () {
		let slideDown = true;
		if($(this).parent().hasClass("active"))
			slideDown = false;

		$(".head-link").parent(".active").removeClass("active");
		$(".head-link").next().slideUp();

		if(slideDown)
		{
			$(this).next().slideDown(250);
			$(this).parent().addClass("active");
		}
	});

	// Show explanation
	$(".help").click(function (e) {
		e.preventDefault();
		$(".explain[id='" + $(this).attr("href").substr(1) + "']").slideToggle(100);
		return;
	});
	// Adding class to parent in case of check. For styling.
	$("[type=checkbox]").change(function ()	{
		var pc = $(this).parents(".checkbox");
		if ($(this).is(":checked"))	{
			pc.addClass("ischecked");
		}
		else if (pc.hasClass("ischecked")) {
			pc.removeClass("ischecked");
		}
	});
}

function x() {
    alert("x");
}

export {summary, x};


function getTemplate(consumer, shortname) {
	const templateKey = edoc_key_prefix + consumer + edoc_key_separator + shortname;
	const template = window.localStorage.getItem(templateKey);

    //TODO: IMPLEMENT EXPIRATION 
	//if (template) {
	//    console.log("Found template in localStorage: " + templateKey);
    //    return JSON.parse(template);
    //}

    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'GET',
                url: apiUrl + consumer + "/templates/" + shortname + "?isPreview=" + isPreview,
                dataType: "json",
                async: true
            })
            .done(function(response, textStatus, jqXHR) {
                const templateString = JSON.stringify(response, null, 2);
                window.localStorage.setItem(templateKey, templateString);
                console.log("================================");
                console.log("getTemplate():");
                console.log(templateString);
                console.log("================================");

                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function getCategories(consumer) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'GET',
                url: apiUrl + consumer + "/categories?fill=true",
                dataType: "json"
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function newModel(consumer, shortname) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: "GET",
                url: window.apiUrl + consumer + "/instances/new/" + shortname + "?login=" + window.isLoggedIn + "&isPreview=" + window.isPreview,
                dataType: "json",
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                const model = response;
                resolve(model);
            })
            .fail(function(response, textStatus, jqXHR) {
                if (!redirectForLogin(response, reject))
                    reject(response);
            });
    });
}

function resumeModel(consumer, refId) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: "GET",
                url: apiUrl + consumer + "/instances/resume/" + refId,
                dataType: "json",
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                const model = response;
                resolve(model);
            })
            .fail(function (response, textStatus, jqXHR) {
                if (!redirectForLogin(response, reject))
                    reject(response);
            });
    });
}

function logOutServer(singleLogOut) {
    return new Promise(function (resolve, reject) {
        $.ajax({
                type: "GET",
                url: window.apiUrl + window.globalConsumer + "/logout?singleLogOut=" + singleLogOut, //no return url
                //dataType: "json", //doesn't return anything
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function getInstances(consumer) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'GET',
                url: apiUrl + consumer + "/instances",
                dataType: "json",
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function (response, textStatus, jqXHR) {
                if (!redirectForLogin(response, reject))
                    reject(response);
            });
    });
}

function getCustomerContext(consumer) {
    const contextKey = edoc_key_prefix + consumer;
    const context = window.localStorage.getItem(contextKey);

    //TODO: IMPLEMENT EXPIRATION 
    //if (context) {
    //    console.log("Found context in localStorage: " + contextKey);
    //    return JSON.parse(context);
    //}

    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'GET',
                url: apiUrl + consumer + "/resources?culture=" + getCurrentLanguageFromLocalStorage(),
                dataType: "json"
            })
            .done(function(response, textStatus, jqXHR) {
                const contextString = JSON.stringify(response, null, 2);
                window.localStorage.setItem(contextKey, contextString);
                console.log("================================");
                console.log("getCustomerContext():");
                console.log(contextString);
                console.log("================================");

                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function getInstanceInfo(consumer, refId) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'GET',
                url: apiUrl + consumer + "/instances/info/" + refId,
                dataType: "json"
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function saveToServer(model) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: "POST",
                url: apiUrl + globalConsumer + "/instances/save/",
                dataType: "json",
                data: model,
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                removeModel();
                resolve(response);
            })
            .fail(function (response, textStatus, jqXHR) {
                if (!redirectForLogin(response, reject, operations.save)) 
                    reject(response);
            });
    });
}

function redirectForLogin(response, reject, operation, stateFunction) {
    const redirect = response.statusText === edoc_no_token_message ||
        (response.responseJSON && response.responseJSON.Message && response.responseJSON.Message === edoc_no_token_message);

    if (redirect) {
        document.location = getApiLoginUrl(operation, null, stateFunction); //TODO: securityLevel
        reject(null);
    }

    return redirect;
}

function getApiLoginUrl(operation, securityLevel, stateFunction) {
    const apiLoginUrl = URI(window.apiUrl + window.globalConsumer + "/login");

    const currentUrl = URI(document.location);
    currentUrl.fragmentPrefix("");

    if (operation) {
        currentUrl.setHash(RouteHash.Operation, operation);

        if (stateFunction)
            currentUrl.setHash(stateFunction());
    }

    let returnUrl;

    //if (!window.isLoggedIn) //if not logged in, redirect user to local login before hitting requested resource
        returnUrl = URI(window.getLocalLoginUrl(currentUrl)); //window.isLoggedIn could be true, even if user is actually not logged in... 
    //else
    //    returnUrl = currentUrl;

    apiLoginUrl.setQuery("returnUrl", returnUrl);

    if (securityLevel)
        apiLoginUrl.setQuery("securityLevel", securityLevel);

    return apiLoginUrl;
}

function submitToServer(model) {
    return new Promise(function(resolve, reject) {
        $.ajax({
                type: "POST",
                url: window.apiUrl + window.globalConsumer + "/instances/submit?login=" + window.isLoggedIn,
                dataType: "json",
                data: model,
                xhrFields: { withCredentials: true }
            })
            .done(function(response, textStatus, jqXHR) {
                removeModel();
                resolve(response);
            })
            .fail(function (response, textStatus, jqXHR) {
                if (!redirectForLogin(response, reject, operations.submit))
                    reject(response);
            });
    });
}

function getPrefillDataFromServer(model, id, containerId) {
    let url = window.apiUrl + window.globalConsumer + "/values/prefill/" + id + "?isPreview=" + window.isPreview;

    if (containerId != undefined && containerId != null) {
        url += "&containerId=" + containerId;
    }

    return new Promise(function(resolve, reject) {
        $.ajax({
                type: 'POST',
                url: url,
                dataType: "json",
                data: model, //TODO: optimization, just send necessary parts of model??
                async: true
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function validateServerSide(model, fieldId, containerId, isPreview) {
    let url = apiUrl + globalConsumer + "/values/validate/" + fieldId + "?isPreview=" + isPreview;

    if (containerId != null) {
        url += "&containerId=" + containerId;
    }

    return new Promise(function(resolve, reject) {
        $.ajax({
                url: url,
                dataType: "json",
                async: true,
                type: "POST",
                data: model //TODO: optimization, just send necessary parts of model??
            })
            .done(function(response, textStatus, jqXHR) {
                resolve(response);
            })
            .fail(function(response, textStatus, jqXHR) {
                reject(response);
            });
    });
}

function deleteCaseFromServer(refId, stateFunction) {
    return new Promise(function (resolve, reject) {
        $.ajax({
            type: "GET",
            url: apiUrl + window.globalConsumer + "/instances/delete/" + refId,
            xhrFields: { withCredentials: true }
        })
            .done(function () {
                resolve();
            })
            .fail(function (response) {
                if (!redirectForLogin(response, reject, operations.deleteCase, stateFunction))
                    reject(response);
            });
    });
}
function isLocalStorageSupported() {
    if (typeof(Storage) !== "undefined") {
        return true;
    }
    else {
        return false;
    }
}

function logOut(singleLogOut) {
    removeAllSessionStorageItems();
    return logOutServer(singleLogOut);
}

function removeAllSessionStorageItems() {
    const keys = Object.keys(sessionStorage);

    keys.forEach(function (key) {
        if (key.startsWith(edoc_key_prefix))
            sessionStorage.removeItem(key);
    });
}

function getReceiptKey(refId) {
    return edoc_key_prefix + "receipt/" + refId;
}
var detectIdleTimeout = function () {
    let idleTimer;
    let warningTimer;

    const idleTimeoutMinutes = window.idleTimeoutMinutes ? window.idleTimeoutMinutes : 20;
    const warningTimeoutMinutes = window.warningTimeoutMinutes ? window.warningTimeoutMinutes : 1;

    window.onload = startIdleTimer;

    function startIdleTimer() {
        console.log("startIdleTimer");

        registerIdleTimeoutEvents();
        window.clearTimeout(idleTimer);
        const idleTimeoutMilliseconds = idleTimeoutMinutes * 60 * 1000;
        idleTimer = window.setTimeout(showIdleTimeoutWarning, idleTimeoutMilliseconds);
    }

    function stopIdleTimer() {
        console.log("stopIdleTimer");

        unregisterIdleTimeoutEvents();
        window.clearTimeout(idleTimer);
    }

    function startWarningTimer() {
        console.log("startWarningTimer");

        clearTimeout(warningTimer);
        const warningTimeoutMilliseconds = warningTimeoutMinutes * 60 * 1000;
        warningTimer = window.setTimeout(showIdleTimeoutMessage, warningTimeoutMilliseconds);
    }

    function stopWarningTimer() {
        console.log("stopWarningTimer");

        window.clearTimeout(warningTimer);
    }

    /**
     * https://stackoverflow.com/a/24989958/2570021
     * @returns {} 
     */
    function registerIdleTimeoutEvents() {
        window.addEventListener("mousemove", startIdleTimer);
        window.addEventListener("mousedown", startIdleTimer);
        window.addEventListener("keypress", startIdleTimer);
        window.addEventListener("click", startIdleTimer);
        window.addEventListener("scroll", startIdleTimer, true);
    }

    function unregisterIdleTimeoutEvents() {
        window.removeEventListener("mousemove", startIdleTimer);
        window.removeEventListener("mousedown", startIdleTimer);
        window.removeEventListener("keypress", startIdleTimer);
        window.removeEventListener("click", startIdleTimer);
        window.removeEventListener("scroll", startIdleTimer);
    }

    function showIdleTimeoutWarning() {
        console.log("showIdleTimeoutWarning");

        stopIdleTimer();
        startWarningTimer();

        renderMessageWindow("timeoutWarning",
            {
                minutes: idleTimeoutMinutes,
                eta: warningTimeoutMinutes
            })
            .on("click",
                "#yes",
                function () {
                    stopWarningTimer();
                    startIdleTimer();
                    closeMessageWindow();
                })
            .on("click",
                "#no",
                function () {
                    logOut(false)
                        .finally(
                            function () {
                                stopWarningTimer();
                                document.location = Url_LogOut + "?returnUrl=" + encodeURIComponent(Url_Start);
                            });
                });
    }

    function showIdleTimeoutMessage() {
        console.log("showIdleTimeoutMessage");

        stopWarningTimer();

        logOut(false)
            .finally(
                function () {
                    const returnUrl = URI(Url_Start);
                    returnUrl.fragmentPrefix("");
                    returnUrl.addFragment(RouteHash.Operation, operations.showMessage);
                    returnUrl.addFragment("message", "idleTimeout");

                    document.location = Url_LogOut + "?returnUrl=" + returnUrl;
                });
    }

    return {
        stop: stopIdleTimer,
        start: startIdleTimer
    }
};

window.idleTimer = detectIdleTimeout();
/**
  * Validates the entire form and inserts error messages at appropriate places.
  * @returns {} 
  */
function validateForm() {
    $("[data-edoc-field-id]").each(function () {
        const id = $(this).attr("data-edoc-field-id");
        const modelContainsValue = model.fields[id] && model.fields[id].value;
        let value = "";

        if (modelContainsValue) {
            value = model.fields[id].value.v;
        }

        const field = getFieldById(getIdWithoutContainer(id));
        const errorMessage = validate(field, id, value, model, false);
        setErrorMessage(errorMessage, id);
    });
}

function validate(fieldToValidate, id, value, model, valueChanged) {
	if (!fieldToValidate)
		return null;

	console.log("Validating field " + id);

	let error;

	let hasValue = value !== undefined && value != null && value.trim() !== "";
	let performServerSideValidation = false;

	if (hasValue) { //if field has input, validate that input
		if (fieldToValidate.type === "TextBox")
			error = validateTextBox(fieldToValidate, value);
		else if (fieldToValidate.type === "NumberBox")
			error = validateNumberBox(fieldToValidate, value, false);
		else if (fieldToValidate.type === "CurrencyBox")
			error = validateCurrencyBox(fieldToValidate, value);
		else if (fieldToValidate.type === "OrganisationalNumberBox") {
			error = validateOrganisationalNumberBox(fieldToValidate, value);
			performServerSideValidation = true;
		} else if (fieldToValidate.type === "SocialSecurityNumberBox")
			error = validateSocialSecurityNumberBox(fieldToValidate, value);
		else if (fieldToValidate.type === "PostalCodeBoxCityLookup")
			error = validatePostalCodeBox(fieldToValidate, value);
		else if (fieldToValidate.type === "DateCalendarBox" || fieldToValidate.type === "DateBox")
			error = validateDateBox(fieldToValidate, value);
		else if (fieldToValidate.type === "MultilineBox")
			error = validateMultilineBox(fieldToValidate, value);
		else if (fieldToValidate.type === "eMailBox")
			error = validateeMailBox(fieldToValidate, value);
		else if (fieldToValidate.type === "TelephoneBox")
			error = validateTelephoneBox(fieldToValidate, value);
		else if (fieldToValidate.type === "CheckBoxList")
			error = validateCheckBoxList(fieldToValidate, value);
		//TODO: flere felttyper

		if (error === undefined && performServerSideValidation) {
			if (valueChanged) {
				let fieldId = getIdWithoutContainer(id);
				let containerId = getContainerId(id);

				//TODO: lock field while performing external validation?? indicate that validation is performed?? abort validation if field is changed.
			    validateServerSide(model, fieldId, containerId, isPreview)
			        .then(function(data) {
			            console.log("================ VALIDATE DATA FROM SERVER ====================");
			            console.log(JSON.stringify(data, null, 2));
			            console.log("=============================================");

			            if (data.fields[fieldId].hasOwnProperty("_error")) {
			                model.fields[fieldId]["_error"] = data.fields[fieldId]["_error"];
			                error = model.fields[fieldId]["_error"].v;
			            }

			            console.log("================ MODEL AFTER VALIDATE ====================");
			            console.log(JSON.stringify(model, null, 2));
			            console.log("=============================================");

			            return Promise.resolve("ok");
			        })
			        .catch(function (response) {
			            renderError(response);
			        });
			} else {
				if (model.fields[id].hasOwnProperty("_error")) {
					return model.fields[id]["_error"].v;
				}
			}
		}
	} else if (hasVisistedSummarypage(model)) { //field has no value, and we are in strict mode (i.e we are on summary page or have previously visited summary page
		if (fieldToValidate.required === true && !hasValue) {
			if (fieldToValidate.messageMissingMandatoryInput)
				error = fieldToValidate.messageMissingMandatoryInput;
			else {
				error = i18next.t('ValidationMessages:InputField_MissingMandatoryInput', { caption: fieldToValidate.caption });
			}
		}
	}

	return error;
}

function validateTextBox(fieldToValidate, value) {
	//TODO: mer validering?
	let error;

	if (!value)
		return error;

	if (fieldToValidate.maxChars) {
		if (parseInt(value.length) > parseInt(fieldToValidate.maxChars))
			if (field.messageIncorrectCharCount)
				error = field.messageIncorrectCharCount;
			else {
				error = i18next.t('ValidationMessages:MultilineBox_IncorrectCharCount',
				{
					caption: fieldToValidate.caption,
					maxChars: fieldToValidate.maxChars
				});
			}
	}

	return error;
}

function validateCheckBoxList(fieldToValidate, value) {
	let error;
	let selectedChoices = value.split("|");
	let isValid = true;

	if (fieldToValidate.minSelectedOptions) {
		if (selectedChoices.length < parseInt(fieldToValidate.minSelectedOptions))
			isValid = false;
	}

	if (fieldToValidate.maxSelectedOptions) {
		if (selectedChoices.length > parseInt(fieldToValidate.maxSelectedOptions))
			isValid = false;
	}

	if (!isValid)
		error = fieldToValidate.messageIncorrectInputRange ? 
			fieldToValidate.messageIncorrectInputRange : 
			i18next.t('ValidationMessages:CheckBoxList_IncorrectInputRange',
			{
				caption: fieldToValidate.caption,
				minSelectedOptions: fieldToValidate.minSelectedOptions,
				maxSelectedOptions: fieldToValidate.maxSelectedOptions
			});

	return error;
}

function validateeMailBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	let regex = /^[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*@([a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*?\.[a-zA-Z]{2,6}|(\d{1,3}\.){3}\d{1,3})(:\d{4})?$/;

	if (!regex.test(value))
		error = fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på epost-adressen."; //TODO: remove hardcoded, localized text

	return error;
}

function validateTelephoneBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	let regex = new RegExp("^\\+?[0-9 ]{" + fieldToValidate.minDigits + "," + fieldToValidate.maxDigits + "}$");

	if (!regex.test(value))
		error = fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på telefonnummer."; //TODO: remove hardcoded, localized text

	return error;
}

function validateNumberBox(fieldToValidate, value, allowDecimals) {
	let error;

	error = validateTextBox(fieldToValidate, value);

	if (error)
		return error;

	let regex;
	let errorMessage;

	if (allowDecimals) {
		regex = /^-?[0-9]\d*(\,\d+)?$/;
		errorMessage = "Ikke et gyldig desimaltall."; //TODO: remove hardcoded, localized text
	}
	else {
		regex = /^[+\-]?\d+$/;
		errorMessage = "Ikke et gyldig heltall."; //TODO: remove hardcoded, localized text
	}

	if (!regex.test(value))
		return fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : errorMessage;

	if (fieldToValidate.minValue) {
		if (parseInt(value) < fieldToValidate.minValue)
			return fieldToValidate.messageIncorrectInputRange ? fieldToValidate.messageIncorrectInputRange : "Feltet har en verdi som er lavere enn tillatt verdi."; //TODO: remove hardcoded, localized text
	}

	if (fieldToValidate.maxValue) {
		if (parseInt(value) > fieldToValidate.maxValue)
			return fieldToValidate.messageIncorrectInputRange ? fieldToValidate.messageIncorrectInputRange : "Feltet har en verdi som er høyere enn tillatt verdi."; //TODO: remove hardcoded, localized text
	}

	return error;
}

function validateCurrencyBox(fieldToValidate, value) {
	let error;

	error = validateNumberBox(fieldToValidate, value, true);

	return error;
}

function validateMultilineBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);

	return error;
}

function validateDateBox(fieldToValidate, value) {
	let error;
	let isValid;

	if (!value)
		return error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	let dateFormat = "DD.MM.YYYY";
	let dateInputMoment = moment(value, dateFormat);
	let nowMoment = moment(new Date(), dateFormat).startOf('date');

	isValid = moment(value, dateFormat, true).isValid();

	//console.error("isValid: " + isValid + " inputValue: " + value + " dateInputMoment: " + dateInputMoment + " nowMoment: " +nowMoment);

	if (!isValid) {
		error = fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på dato."; //TODO: remove hardcoded, localized text
	}

	if (isValid && fieldToValidate.minDate) {
		let minDateMoment = moment(fieldToValidate.minDate, dateFormat);

		if (dateInputMoment.isBefore(minDateMoment)) {
			isValid = false;
			error = fieldToValidate.messageIncorrectInputRange ? fieldToValidate.messageIncorrectInputRange : "Oppgitt dato er før tidligste gyldige dato."; //TODO: remove hardcoded, localized text
		}
	}

	if (isValid && fieldToValidate.maxDate) {
		let maxDateMoment = moment(fieldToValidate.maxDate, dateFormat);

		if (dateInputMoment.isAfter(maxDateMoment)) {
			isValid = false;
			error = fieldToValidate.messageIncorrectInputRange ? fieldToValidate.messageIncorrectInputRange : "Oppgitt dato er etter største gyldige dato."; //TODO: remove hardcoded, localized text
		}
	}

	if (isValid && fieldToValidate.validDateRange) {
		if (fieldToValidate.validDateRange === "Today") {
			if (!dateInputMoment.isSame(nowMoment))
				isValid = false;
		}
		else if (fieldToValidate.validDateRange === "NotToday") {
			if (dateInputMoment.isSame(nowMoment))
				isValid = false;
		}
		else if (fieldToValidate.validDateRange === "TodayAndPast") {
			if (dateInputMoment.isAfter(nowMoment))
				isValid = false;
		}
		else if (fieldToValidate.validDateRange === "Past") {
			if (dateInputMoment.isSameOrAfter(nowMoment))
				isValid = false;
		}
		else if (fieldToValidate.validDateRange === "TodayAndFuture") {
			if (dateInputMoment.isBefore(nowMoment))
				isValid = false;
		}
		else if (fieldToValidate.validDateRange === "Future") {
			if (!dateInputMoment.isAfter(nowMoment))
				isValid = false;
		}

		if (!isValid)
			error = "Feltet " + fieldToValidate.caption + " faller utenfor de gyldige datoene."; //TODO: remove hardcoded, localized text
	}

	//Relative date
	if(isValid)
	{
		let minRelativeDate = fieldToValidate.minRelativeDate;
		let maxRelativeDate = fieldToValidate.maxRelativeDate;
		let momentDateToCompare = moment(value, dateFormat);
		let minValidRelativeDate;
		let maxValidRelativeDate;

		if(minRelativeDate)
			minValidRelativeDate = findDateFromTimeSpanString(minRelativeDate);

		if(maxRelativeDate)
			maxValidRelativeDate = findDateFromTimeSpanString(maxRelativeDate);

		if(minValidRelativeDate && momentDateToCompare.isBefore(minValidRelativeDate))
		{
			isValid = false;
			error = "Feltet " + fieldToValidate.caption + " faller utenfor de gyldige datoene. Minste tillatte dato er " + minValidRelativeDate.format('DD.MM.YYYY').toString();
		}

		if(maxValidRelativeDate && momentDateToCompare.isAfter(maxValidRelativeDate))
		{
			isValid = false;
			error = "Feltet " + fieldToValidate.caption + " faller utenfor de gyldige datoene. Største tillatte dato er " + maxValidRelativeDate.format('DD.MM.YYYY').toString();
		}
	}

	return error;
}

//Input ex: -2y 2m 3w 1d
//Returns the input converted to a date relative to todays date
function findDateFromTimeSpanString(timeSpanString)
{
	let dateFormat = 'DD.MM.YYYY';
	let nowDate = moment(new Date(), dateFormat).startOf('day');
	let y, m, w, d;
	let future = true;

	if(timeSpanString.startsWith("-"))
	{
		future = false;
		timeSpanString = timeSpanString.substring(1);
	}

	let relativeDateArray = timeSpanString.split(" ");
	let index = -1;

	relativeDateArray.forEach(function (element)
	{
		index = element.indexOf("y");
		if(index > -1)
			y = element.substring(0, index);

		index = element.indexOf("m");
		if(index > -1)
			m = element.substring(0, index);

		index = element.indexOf("w");
		if(index > -1)
			w = element.substring(0, index);

		index = element.indexOf("d");
		if(index > -1)
			d = element.substring(0, index);
	});

	let relativeDateLimit = nowDate;

	//TODO: hvorfor endrer dette nowDate?? kjør (console.log(nowDate.toString()); før og etter)
	if(future === false)
	{
		if(y) relativeDateLimit = relativeDateLimit.subtract(y, 'years');
		if(m) relativeDateLimit = relativeDateLimit.subtract(m, 'months');
		if(w) relativeDateLimit = relativeDateLimit.subtract(w, 'weeks');
		if(d) relativeDateLimit = relativeDateLimit.subtract(d, 'days');
	}
	else
	{
		if(y) relativeDateLimit = relativeDateLimit.add('years', y);
		if(m) relativeDateLimit = relativeDateLimit.add('months', m);
		if(w) relativeDateLimit = relativeDateLimit.add('weeks', w);
		if(d) relativeDateLimit = relativeDateLimit.add('days', d);
	}

	return relativeDateLimit;
}

function validatePostalCodeBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	let regex = /^\d{4}$/;

	if (!regex.test(value))
		error = fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på postnummer."; //TODO: remove hardcoded, localized text

	return error;
}

function validateSocialSecurityNumberBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	const ssno = value;

	//https://miles.no/blogg/validering-av-norske-data
	function isValidBirthNumber(birthNumber) {
		birthNumber = birthNumber.toString();
		if (!birthNumber || birthNumber.length !== 11) {
			return false;
		}

		var _sum = function (birthNumber, factors) {
			var sum = 0;
			for (i = 0, l = factors.length; i < l; ++i) {
				sum += parseInt(birthNumber.charAt(i), 10) * factors[i];
			}
			return sum;
		};

		var checksum1 = 11 - (_sum(birthNumber, [3, 7, 6, 1, 8, 9, 4, 5, 2]) % 11);
		if (checksum1 === 11) checksum1 = 0;
		var checksum2 = 11 - (_sum(birthNumber, [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]) % 11);
		if (checksum2 === 11) checksum2 = 0;
		return checksum1 === parseInt(birthNumber.charAt(9), 10)
				&& checksum2 === parseInt(birthNumber.charAt(10), 10);
	};

	//Gyldig fødselsnummer
	let isValidSSNO = isValidBirthNumber(ssno);
	if (!isValidSSNO)
		return fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på fødselsnummer."; //TODO: remove hardcoded, localized text

	//D-nummer
	let dDigit = parseInt(value.substring(0, 1));

	if (fieldToValidate.blockDNumber && dDigit > 3)
		return fieldToValidate.messageDNumberBlocked ? fieldToValidate.messageDNumberBlocked : i18next.t("ValidationMessages:SocialSecurityNumberBox_DNumberBlocked",
		{
			caption: fieldToValidate.caption
		});

	//H-nummer
	let hDigit = parseInt(value.substring(2, 3));

	if (fieldToValidate.blockHNumber && hDigit > 3)
		return fieldToValidate.messageHNumberBlocked ? fieldToValidate.messageHNumberBlocked : i18next.t("ValidationMessages:SocialSecurityNumberBox_HNumberBlocked",
		{
			caption: fieldToValidate.caption
		});

	return error;
}

function validateOrganisationalNumberBox(fieldToValidate, value) {
	let error;

	error = validateTextBox(fieldToValidate, value);
	if (error)
		return error;

	let regex = /^\d{9}$/;

	//TODO: feilmelding fra api
	if (!regex.test(value))
		error = fieldToValidate.messageIncorrectInputFormat ? fieldToValidate.messageIncorrectInputFormat : "Feil format på organisasjonsnummer."; //TODO: remove hardcoded, localized text

	return error;
}
const RouteHash = {
    Summary: "summary",
    Operation: "operation"
}

const ScreenName = {
    Receipt: "receipt",
    Form: "form",
    MyCases: "mycases",
    Case: "resume",
    Categories: "categories"
}

const Url_Start = globalSiteroot + "/" + globalConsumer;
const Url_LogOut = Url_Start + "/logout";
const Url_Receipt = Url_Start + "/receipt/";
const Url_Cases = Url_Start + "/cases";
const Url_Case = Url_Cases + "/";

function gotoStart() {
    window.document.location = Url_Start;
}

function gotoCases() {
    window.document.location = Url_Cases;
}

function isReceipt() {
    return document.location.href.startsWith(Url_Receipt);
}

function isCases() {
    const casesUrl = Url_Cases.toUpperCase();

    let currentUrl = document.location.href.replace(document.location.hash, ""); //remove hash
    currentUrl = currentUrl.toUpperCase();
    const currentIsCases = currentUrl === casesUrl || currentUrl === casesUrl + "/";

    return currentIsCases;
}

function isCase() {
    if(!document.location.href.startsWith(Url_Case))
        return false;

    const refId = document.location.href.substring(Url_Case.length, Url_Case.length + 6);

    return refId.match(/^[A-Z]{6}$/i);
}

function isSummary() {
    const currentUrl = URI(window.location);
    currentUrl.fragmentPrefix("");

    const fragment = currentUrl.fragment(true);

    return RouteHash.Summary in fragment;
}

function isOperation() {
    const currentUrl = URI(window.location.hash);
    currentUrl.fragmentPrefix("");

    const fragment = currentUrl.fragment(true);

    return RouteHash.Operation in fragment;
}

function getScreenName() {
    if (!globalConsumer) {
        renderError("Customer not found in path");
        return; 
    }

    if (isReceipt()) {
        return ScreenName.Receipt;
    }

    if (isCase()) { //note: this check must come before isCases()
        return ScreenName.Case;
    }

    if (isCases()) { //note: this check must come after isCase()
        return ScreenName.MyCases;
    } 

    if (globalShortname) {
        return ScreenName.Form;
    }

    return ScreenName.Categories;
}

function renderError(response) {
    if (!response)
        return;

    let message = "unkown error";

    let isError = function (e) {
        return e && e.stack && e.message;
    }

    if (response.responseJSON) {
        if (response.responseJSON.message) {
            message = response.responseJSON.message;
        } else {
            message = response.responseJSON;
        }
    } else if (isError(response)) {
        message = response.message;
    } else if (typeof response === "string") {
        message = response;
    } else {
        message = JSON.stringify(response);
    }

    console.error(message, response.stack);

    renderMessageWindow("error", { message: message })
        .on("click",
            "#ok",
            function() {
                closeMessageWindow();
            });


    localize(getCurrentLanguageFromLocalStorage());
}

function renderCurrentScreen() {
    const screenName = getScreenName();

    if (!screenName)
        return;

    if (screenName === ScreenName.MyCases) {
        renderMyCases();
    } else if (screenName === ScreenName.Categories) {
        renderCategories();
    } else if (screenName === ScreenName.Receipt) {
        renderReceipt();
    } else if (screenName === ScreenName.Case) {
        const refId = document.location.href.substring(Url_Case.length, Url_Case.length + 6);

        window.modelKey = getRefIdKey(refId);

        getModelByRefId(globalConsumer, refId)
            .then(function (model) {
                window.model = model;

                globalShortname = model.meta.shortName.v;

                return getTemplate(globalConsumer, globalShortname);
            })
            .then(function(template) {
                window.template = template;

                if (isOperation())
                    performOperation();
                else
                    renderForm();
            })
            .catch(function (response) {
                window.modelKey = null; 
                renderError(response);
            });
    } else if (screenName === ScreenName.Form) {
        window.modelKey = getShortnameKey(globalConsumer, globalShortname);

        Promise.all([
                getTemplate(globalConsumer, globalShortname),
                getModelByShortname(globalConsumer, globalShortname)
            ])
            .then(function(values) {
                window.template = values[0];
                window.model = values[1];

                if (isOperation())
                    performOperation();
                else
                    renderForm();
            })
            .catch(function (response) {
                window.modelKey = null;
                renderError(response);
            });
    } else {
        renderError("Unknown screenName: " + screenName);
    }
}

const operations = {
    save: "save",
    submit: "submit",
    deleteCase: "deleteCase",
    showMessage: "message"
};

function performOperation() {
    const currentUrl = URI(document.location);
    currentUrl.fragmentPrefix("");

    const fragment = currentUrl.fragment(true);

    if (!fragment[RouteHash.Operation]) {
        renderError("No operation found in query.");
        return;
    }

    const operation = fragment[RouteHash.Operation];

    //remove operation from fragment
    currentUrl.removeFragment(RouteHash.Operation);

    if (operation === operations.save) {
        save();
    } else if (operation === operations.submit) {
        submit();
    } else if (operation === operations.showMessage) {
        if(window.idleTimer)
            window.idleTimer.stop();

        const message = fragment.message;
        currentUrl.removeFragment(operations.showMessage);
        const m = new moment();
        m.locale(getCurrentLanguageFromLocalStorage());
        renderMessageWindow(message, {now: m.format("LLL")})
            .on("click", "#ok", function () {
                closeMessageWindow();

                if (window.idleTimer)
                    window.idleTimer.start();
            });
    } else if (operation === operations.deleteCase) {
        if(fragment.caseType === "saved")
            deleteSavedCase(fragment.refId, fragment.title, fragment.updatedOn, false);
        else if (fragment.caseType === "submitted")
            deleteSubmittedCase(fragment.refId, fragment.title, fragment.updatedOn, false);

        currentUrl
            .removeFragment("caseType")
            .removeFragment("refId")
            .removeFragment("title")
            .removeFragment("updatedOn");
    } else {
        renderError("Unsupported operation: '" + operation + "'");
    }

    document.location.hash = currentUrl.fragment();
}

function renderMyCases() {
    var doRender = function(cases) {
        const mycasesHtml = hb["mycases"](
            {
                saved: cases.saved,
                submitted: cases.submitted,
                siteroot: window.globalSiteroot,
                consumer: window.globalConsumer
            }
        );

        $("#page").html(mycasesHtml);

        localize();

        if (isOperation())
            performOperation();
    }

    if (window.cases) {
        doRender(window.cases);
    } else {
        getInstances(window.globalConsumer)
            .then(function(response) {
                window.cases = response.instances; //store here to redraw table later

                doRender(window.cases);

                return Promise.resolve("ok");
            })
            .catch(function(response) {
                renderError(response);
            });
    }
}

function renderCategories() {
    getCategories(globalConsumer)
        .then(function(categories) {
            renderOverview();

            for (let category of categories.categories) {
                renderCategory(category);
            }

            genericEvents();

            if (isOperation())
                performOperation();

            localize();
        })
        .catch(function (response) {
            renderError(response);
        });
    ;
}

function renderReceipt() {
    const refId = document.location.href.substring(Url_Receipt.length);

    if (!refId)
        console.error("Unable to find refId in location " + window.location.hash);

    const receiptKey = getReceiptKey(refId);

    let receiptData = window.sessionStorage.getItem(receiptKey);

    const renderHandlebar = function () {
        let confirmHtml = hb["receipt"]({
            formCaption: receiptData.formCaption || receiptData.name,
            refId: receiptData.refId,
            submittedOn: receiptData.updatedOn,
            customer: window.customerContext.customer,
            isLoggedIn: window.isLoggedIn
        });

        $("#page").html(confirmHtml);
        window.scrollTo(0, 0);

        localize(getCurrentLanguageFromLocalStorage());
    };

    if (receiptData) {
        receiptData = JSON.parse(receiptData);
        renderHandlebar();
    }
    else {
        getInstanceInfo(globalConsumer, refId)
            .then(function(instanceInfo) {
                receiptData = instanceInfo;

                renderHandlebar();
            })
            .catch(function (response) {
                renderError(response);
            });
    }
}

function renderMessageWindow(message, context, showX) {
    closeMessageWindow();

    if (message in hb.messages) {
        const messageModalHtml = hb["message"]({
            messageType: message,
            showX: showX
        });
        $("#content").append(messageModalHtml);

        const messageContentHtml = hb.messages[message](context);
        const messageWindow = $("#messageContainer").append(messageContentHtml);
        localize(getCurrentLanguageFromLocalStorage());  //$("#messageContainer").localize won't read values from data-t-options..

        return messageWindow;
    }
    else {
        const errorMessage = renderMessageWindow("error", { message: `Unable to render message '${message}': not found in 'hb.messages'.` });

	    errorMessage.on("click", "#ok",
            function () {
                closeMessageWindow();
            });

	    return errorMessage;
	}
}

function renderFramework() {
    $("#content").append(hb["header"](
        {
            start: Url_Start,
            cases: Url_Cases,
            siteroot: window.globalSiteroot,
            consumer: window.globalConsumer,
            customer: window.customerContext.customer,
            isLoggedIn: window.isLoggedIn,
            currentUrl: encodeURIComponent(document.location),
            profileUrl: window.idPortenProfileUrl
        }
    ));

    $(".lang")
        .on("click",
            function () {
                localize($(this).data("lang"));
            });

    $("#content").append(hb["body"]());

    $("html title").text("Søknadskjema - Skjema.no"); //TODO: fjern hardkodet tekst
    $("#content").append(hb["footer"]());
    //selectify.init();

    $("body")
        .on("click",
            ".menu a",
            function () {
                $(".menu a.active").removeClass("active");
                $(this).addClass("active");
            });

    $("#privacyLink")
        .click(function() {
            renderMessageWindow("privacy",
                {
                    customer: window.customerContext.customer
                })
                .on("click",
                    "#closePrivacy",
                    function() {
                        $("#messagewindow").remove();
                    });
        });

    $("#showmenu")
        .click(function () {
            $(".drop-content").slideToggle(75);
        });

    $(".list-unstyled")
        .click(function () {
            $(".drop-content").slideToggle(75);
        });

    $("body")
        .on("click",
            "#loginLink",
            function () {
                $(this).attr("href", this.href + "?returnUrl=" + encodeURIComponent(document.location));
            });

    $("body")
        .on("click",
            "#logoutLink",
            function () {
                const currentScreen = getScreenName();
                const logoutLink = this;

                if (currentScreen === ScreenName.Case || currentScreen === ScreenName.Form) {
                    renderMessageWindow("confirmLogout")
                        .on("click",
                            "#yes",
                            function() {
                                doLogout();
                            })
                        .on("click",
                            "#no",
                            function() {
                                closeMessageWindow();
                            });
                } else {
                    doLogout();
                }

                function doLogout() {
                    logOut(false)
                        .then(function () {
                            const logoutConfirmationUrl = $(logoutLink).attr("data-logout-confirmation");
                            const returnUrl = URI(logoutConfirmationUrl);
                            returnUrl.fragmentPrefix("");
                            returnUrl.addFragment(RouteHash.Operation, "message");
                            returnUrl.addFragment("message", "logoutConfirmed");

                            const logoutUrl = $(logoutLink).attr("href") +
                                "?returnUrl=" +
                                encodeURIComponent(returnUrl);

                            document.location = logoutUrl;
                        })
                        .catch(function(response) {
                            renderError(response);
                        });
                }

                return false; 
            });

    //close modal when clicking outside it
    window.onclick = function (event) {
        if ($(event.target).hasClass("blackout")) {
            if($("#messagewindow").attr("data-closeOnOutsideClick") === "true")
                closeMessageWindow();
        }
    }
}

function closeMessageWindow() {
    $("#messagewindow").remove();
}
function submit() {
    if (!hasConfirmedInformation(window.model)) {
        renderMessageWindow("missingConfirmation")
            .on("click",
                "#ok",
                function() {
                    closeMessageWindow();
                });

        return false;
    }

    $("#btnSubmitForm").attr("disabled", "disabled");

    submitToServer(model)
        .then(function (submitResponse) {
            if (submitResponse.fields) {
                Object.keys(submitResponse.fields)
                    .forEach(function (fieldId) {
                        const fieldToValidate = getFieldById(fieldId);
                        let fieldValue = undefined;

                        if (submitResponse.fields[fieldId] && submitResponse.fields[fieldId].value)
                            fieldValue = submitResponse.fields[fieldId].value.v;

                        validate(fieldToValidate, fieldId, fieldValue, model, true);
                        //perform any server side validation (will also cause client side validation)
                    });

                //render summary again, will cause client-side validation and re-read of server-validation performed above
                summary();
            } else {
                removeModel(); //TODO: belongs in .done for submitToServer() xhr (see saveToServer())

                if (!submitResponse.formCaption)
                    submitResponse.formCaption = template.caption;

                const receiptKey = getReceiptKey(submitResponse.refId);

                //store data in sessionScope
                window.sessionStorage.setItem(receiptKey, JSON.stringify(submitResponse));

                //update url (simulate browser click to url)
                window.location.href = Url_Receipt + submitResponse.refId;
            }
        })
        .catch(function (response) {
            renderError(response);
            $("#btnSubmitForm").removeAttr("disabled");
        });
}

function save() {
    saveToServer(model)
        .then(function (saveResponse) {
            console.log("Saved OK " + JSON.stringify(saveResponse));

            gotoCases();
        })
        .catch(function (response) {
            renderError(response);
        });
}

var deleteCase = (refId, title, updatedOn, caseType) => {
    deleteCaseFromServer(refId,
            function () {
                return { refId: refId, title: title, updatedOn: updatedOn, caseType: caseType };
            })
        .then(function () {
            if (window.cases) {
                const cases = caseType === "saved" ? window.cases.saved : window.cases.submitted;
                const indexOfCase = cases.findIndex(function (item) { return item.refId === refId; });

                if (indexOfCase !== -1)
                    cases.splice(indexOfCase, 1);

                renderMyCases();
            }
        })
        .catch(function (response) {
            renderError(response);

            //TODO: enable link, if still there.
        });
}

function deleteSavedCase(refId, title, updatedOn, showPrompt) {
    if (!showPrompt) {
        deleteCase(refId, title, updatedOn, "saved");
        return;
    }

    renderMessageWindow("confirmDeleteSavedCase", { title: title, updatedOn: updatedOn })
        .on("click",
            "#yes",
            function() {
                closeMessageWindow();

                //TODO: disable link

                deleteCase(refId, title, updatedOn, "saved");
            })
        .on("click",
            "#no",
            function() {
                closeMessageWindow();
            });
}

function deleteSubmittedCase(refId, title, updatedOn, showPrompt) {
    if (!showPrompt) {
        deleteCase(refId, title, updatedOn, "submitted");
        return;
    }

    renderMessageWindow("confirmDeleteSubmittedCase", { title: title, updatedOn: updatedOn, refId: refId })
        .on("click",
            "#yes",
            function() {
                closeMessageWindow();

                //TODO: disable link

                deleteCase(refId, title, updatedOn, "submitted");
            })
        .on("click",
            "#no",
            function() {
                closeMessageWindow();
            });
}

function abort() {
    renderMessageWindow("abort", null, true)
        .on("click",
            "#yes",
            function() {
                closeMessageWindow();
                save();
            })
        .on("click",
            "#no",
            function() {
                removeModel();
                closeMessageWindow();
                gotoStart();
            });
}

const resourceBundle_en = {
    "save": "Save",
    "abort": "Abort",
    "ok": "Ok",
    "close": "Close",
    "yes": "Yes",
    "no": "No",

    "footer-developed-by": "Kommune24:7, developed by",
    "cases-updatedOn-data": "{{savedOn, MM/DD/YYYY}}"
};
const resourceBundle_nb_no = {
    "save": "Lagre",
    "abort": "Avbryt",
    "edit": "Rediger",
    "summary-action": "Kontroller og send inn",
    "ok": "Ok",
    "close": "Lukk",
    "yes": "Ja",
    "no": "Nei",

    "catgories-form-overview": "SKJEMAOVERSIKT",

    "footer-developed-by": "Kommune24:7 er utviklet av",
    "footer-developed-by-link-title": "Link til leverandør",
    "footer-privacy": "Personvern",
    "footer-privacy-link-title": "Personvernerklæring",

    "header-switch-language-to-bokmål": "Bytt språk til norsk bokmål",
    "header-switch-language-to-nynorsk": "Bytt språk til nynorsk",
    "header-switch-language-to-english": "Switch language to English",
    "header-language-norsk-bokmål": "Norsk bokmål",
    "header-language-nynorsk": "Nynorsk",
    "header-language-english": "English",

    "header-formoverview": "Skjemaoversikt",
    "header-go-to-my-page": "Gå til min side",
    "header-my-profile": "Min profil",
    "header-my-cases": "Mine saker",
    "header-menu": "Meny",
    "header-log-out": "Logg ut",
    "header-log-in": "Logg inn",
    "header-image-alt": "Kommunevåpen",

    "field-multiline-placeholder": "Skriv inn ytterligere opplysninger",
    
    "receipt-title": "Skjemaet er sendt inn",
    "receipt-1": "Du har sendt inn <strong>{{caption}}</strong> til {{customerName}}. Skjemaet ble sendt inn {{submittedOn, L}} kl. {{submittedOn, LT}}.",
    "receipt-2": "Referanse-ID for innsendingen er <strong>{{refId}}</strong>. Oppgi denne dersom du trenger å kontakte {{customerName}} om saken. Var du innlogget da du sendte inn skjemaet, finner du referanse-ID-en under oversikt over innsendte skjema.",
    "receipt-go-to-cases": "Gå til 'Mine saker' »",
    "receipt-go-to-start": "Gå til start-side »",

    "summary-heading": "Oppsummering",
    "summary-confirmation-content": "Jeg bekrefter at opplysningene ovenfor er korrekte",
    "summary-confirmation-title": "Bekreft",
    "summary-confirmation-submit": "Send inn »",
    "summary-confirmation-abort": "Avbryt",
    "summary-missing-confirmation": "Du må bekrefte at opplysningene er korrekte.",

    "cases-header": "MINE SAKER",
    "cases-saved-header": "LAGREDE UTKAST",
    "cases-saved-info": "Oversikten viser skjema du har påbegynt og ikke sendt inn. Du gjenopptar utfylling ved å klikke på lenken. Lagrede utkast som ikke gjenopptas, vil automatisk bli slettet etter 3 måneder.",
    "cases-form-name": "Navn på skjema",
    "cases-customer": "Mottakende instans",
    "cases-savedOn": "Lagret dato",
    "cases-updatedOn-data": "{{date, DD.MM.YYYY}}",

    "cases-submitted-header": "INNSENDTE SKJEMA",
    "cases-submitted-info": "Oversikten viser hvilke skjema du tidligere har sendt inn. Lenken til skjema blir inaktiv kort tid etter at opplysningene er sendt videre til mottakende instans. Dette gjøres for å ivareta ditt personvern. Ønsker du å se de innsendte opplysningene, må du kontakte mottakende instans.",
    "cases-submittedOn": "Innsendt dato",
    "cases-refId": "Referanse",
	
    "privacy": "Personvern",
    "privacy-body": "<p>Denne erklæringen følger bestemmelsene i <a href=\"https://lovdata.no/lov/2000-04-14-31\" target=\"_blank\">personopplysningsloven</a> og redegjør for hvordan personopplysningene dine blir ivaretatt av Sem & Stenersen Prokom AS og {{customerName}} i denne...</p>",

    "message-save-changes": "Vil du lagre endringene du har gjort i skjemaet?",

    "message-confirm-delete-saved-case": "Ønsker du å slette utkast \"{{title}}\", sist endret \"{{updatedOn, L}}\"?",
    "message-confirm-delete-submitted-case": "Ønsker du å slette innsendt skjema \"{{title}}\", med referanseID \"{{refId}}\", sist endret \"{{updatedOn, L}}\"?",
    "message-confirm-logout": "Ønsker du å logge ut? <br/><br/>Du har startet utfylling av et skjema. Hvis du fortsetter utlogging, vil du miste arbeidet.",
    "message-confirm-logout-yes": "Ja, logg meg ut!",
    "message-confirm-logout-no": "Nei, avbryt utlogging!",
    "message-timeout-warning": "Er du der fortsatt?<br/><br>Du har vært inaktiv i løsningen i mer enn {{minutes}} minutter. For å ivareta din sikkerhet, vil sesjonen om kort tid bli avsluttet. Du kan forlenge sesjonen ved å velge 'Ja' nedenfor.",
    "message-timeout-warning-yes-extend-session": "Ja, forleng sesjonen min!",
    "message-timeout-warning-no-log-out": "Nei, logg meg ut!",
    "message-idle-timeout": "Din sesjon gikk ut på tid {{now, L}}.",
    "message-logout-confirmed": "Du er nå logget ut!",
    "no-submitted-cases": "Ingen innsendte skjema.",
    "no-saved-cases": "Ingen lagrede utkast."
};
const resourceBundle_nn_no = {
    "footer-developed-by": "Kommune24:7 er utvikla av"
};
/* global $ hb */

//TODO: solve global vars problem
//this.customerContext;
//this.template;
//this.model;

let customContext;

import {summary, x} from "../resources/viewer-render";

x();
summary();

//asdasd.flksdfjsldkfj

$(document).ready(function() {
    Promise
        .all([
            getCustomerContext(window.globalConsumer), //TODO: implement caching to avoid fetching consumerContext every time
            initializeServerResources()
        ])
        .then(function (values) {
            window.customerContext = values[0];
            initializeLocalResources();
            renderFramework();
            renderCurrentScreen();
            return Promise.resolve("ok");
        })
        .catch(response => {
            renderError(response);
        });
});