
if (typeof AcDocuments === "undefined") {

	function AcDocuments(step, config) {
		var defaultConfig = {
			rootSelector: '[data-ov-widget="file-upload"]',
			msg: {
			},
			urls: {
				addFile: null,
				deleteFile: null
			},
			validate: {
				minSizes: {
					'image/jpeg': 300,
					'image/png': 80,
					'application/pdf': 5120,
					'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 10000,
					'application/msword': 20000
				}
			},
			animate: {
				duration: {
					show: 400,
					hide: 900
				},
				timeout: {
					hide: 4000
				}
			},
			reloadOnAuthError: false
		};

		// AcTramiteStep instance
		this.step = step;
		this.config = jQuery.extend({}, defaultConfig, config || {});
		this.pendingOperations = 0;

		if (this.findElements().length != 1) {
			throw new Error('Invalid root selector: ' + this.config.rootSelector);
		}
	}

	AcDocuments.prototype.reloadIfAuthError = function(timeout) {
		if (this.config.reloadOnAuthError === true) {
			// fixme: Reload should not be done on last page
			// setTimeout(window.location.reload, timeout ? timeout : 500);
		}
	}

	AcDocuments.prototype.add = function(id, fileName, mimeType) {
		this.documents[id] = {
			file: fileName,
			mime: mimeType
		};
	}

	AcDocuments.prototype.init = function() {
		if (typeof this.config.init === "function") {
			this.config.init(this);
		}
	}

	AcDocuments.prototype.findElements = function(groupId, selector) {
		var result = jQuery(this.config.rootSelector).first();

		if (typeof groupId === "string" && groupId.trim()) {
			result = result.find('[data-ov-file-upload-group-id="' + groupId + '"]').first();
		}

		if (typeof selector === "string" && selector.trim()) {
			result = result.find(selector.trim());
		}

		return result;
	}

	AcDocuments.prototype.findElement = function(groupId, selector) {
		return this.findElements(groupId, selector).first();
	}

	AcDocuments.prototype.createFileElement = function(groupId, fileId, file) {
		var fileElem = jQuery('<span class="fileImage"></span>');
  		fileElem.attr("data-ov-file-upload-document-id", fileId);
  		var icon = jQuery('<span class="document file-type-icon"></span>');

  		if (this.isImage(file)) {
  			icon.addClass('file-type-image');
  			var img = document.createElement('img');
  			img.src = URL.createObjectURL(file);
  			img.alt = "";
  			img.onLoad = function() {
  				URL.revokeObjectURL(this.src);
  			};
  			icon.append(img);
  		} else if (this.isPdf(file)) {
  			icon.addClass('file-type-pdf');
  		} else if (file && file.name.indexOf('.') >= 0) {
  			var ext = file.name.toLowerCase().split('.').slice(-1);
  			icon.addClass('file-type-' + ext);
  		} else {
  			icon.addClass('file-type-any');
  		}

  		fileElem.append(icon);
  		fileElem.append(jQuery('<span class="name">' + file.name + '</span>'));
  		fileElem.append(jQuery('<span class="progress"><span class="progress-bar"></span></span>'));

  		this.findElement(groupId, ".fileList").append(fileElem);
  		this.findElement(groupId, '.placeholder').addClass('hide');

  		return fileElem;
	}

	AcDocuments.prototype.addFileElementDeleteLink = function(groupId, fileId, replaceJElem) {
		var deleteButton = jQuery('<span class="delete action-button" aria-role="button">' + this.config.msg.eliminarFichero + '</span>');

		if (typeof replaceJElem === "object") {
			replaceJElem.replaceWith(deleteButton);
		} else {
			var fileElem = this.findElement(groupId, '[data-ov-file-upload-document-id="' + fileId + '"]');

			if (fileElem.find('.delete').length > 0) {
				return;
			}

			fileElem.append(deleteButton);
		}

		this.attachEventHandlers(deleteButton);
	}

	AcDocuments.prototype.deleteFileElement = function(groupId, fileId) {
		var document = this.findElement(groupId, '[data-ov-file-upload-document-id="' + fileId + '"]');
		document.find('.delete').off('click.dnd');
		document.remove();

		if (this.findElement(groupId, '.fileList .fileImage').length == 0) {
			this.findElement(groupId, '.placeholder').removeClass('hide');
		}
	}

	/**
	 * Add a file details to the list after being processed in backend
	 *
	 * @param groupId Group Id
	 * @param tmpFileId Unique file Id used to create the progress
	 * @param fileData File details obtained from the call: fileData.id, fileData.simpleType, fileData.fileName,
	 * fileData.mimeType, fileData.base64Thumbnail
	 *
	 */
	AcDocuments.prototype.updateFileInList = function(groupId, tmpFileId, fileData) {

		var fileElem = this.findElement(groupId, '[data-ov-file-upload-document-id="' + tmpFileId + '"]');

		if (fileElem.length == 0) {
			fileElem = this.createFileElement(groupId, fileData.id);
		} else {
			fileElem.attr('data-ov-file-upload-document-id', fileData.id);
		}

  		fileElem.find('.name').text(fileData.fileName);

  		var icon = fileElem.find('.document.file-type-icon').addClass('.file-type-' + fileData.simpleType)
  			.removeClass('.file-type-any');

  		if (fileData.mimeType && fileData.base64Thumbnail) {
  			var img = icon.find('img');
  			var dataUrl = 'data:' + fileData.mimeType + ";base64," + fileData.base64Thumbnail;

  			if (img.length === 0) {
  				img = jQuery('<img src="' + dataUrl + '" alt="">');
  				icon.append(img);
  			} else {
  				img.attr('src', dataUrl).removeClass('hide');
  			}
  		}

  		return fileElem;
	}

	AcDocuments.prototype.attachEventHandlers = function(itemRootElem) {
		var $this = this;
		var targetElem = itemRootElem.find('.delete');

		if (itemRootElem.hasClass('delete') && itemRootElem.hasClass('action-button')) {
			targetElem = itemRootElem.first();
		}

		targetElem.off('click.dnd').on('click.dnd', function(ev){
			$this.handleDeleteEvent(this, ev);
		});
	}

	AcDocuments.prototype.handleDeleteEvent = function(domElem, ev) {
		ev.stopPropagation();
		ev.preventDefault();
		var fileElem = jQuery(domElem).closest('[data-ov-file-upload-document-id]');
		var fileId = fileElem.attr('data-ov-file-upload-document-id');

		if (fileId) {
			var groupId = this.getGroupId(domElem);
			this.ajaxDeleteFile(groupId, fileId, fileElem);
		}
	}

	AcDocuments.prototype.ajaxDeleteFile = function(groupId, fileId, fileElem) {
		var $this = this;

		var data = {};
		data[this.step.config.nmsp + "groupId"] = groupId;
		data[this.step.config.nmsp + "fileId"] = fileId;

		$this.onBackgroundOperationStart();

		jQuery.ajax({
			url: this.config.url.deleteFile,
			method: 'POST',
			data: data,
			cache: false,
			dataType: 'json'
		}).done(function(result, textStatus, jqXHR){
			$this.internalFileDelete(groupId, fileElem, result.success, result.status);
		}).fail(function(jqXHR, textStatus, errorThrown){
			$this.internalFileDelete(groupId, null, false, textStatus);
		}).always(function(){
			$this.onBackgroundOperationEnd();
		});
	}

	AcDocuments.prototype.internalFileDelete = function (groupId, fileElem, success, statusOrMsg){
		if (success) {
			fileElem.remove();
			var fileList = this.findElements(groupId, '.fileList');
			if (fileList.children().length == 0) {
				fileList.parent().find('.placeholder').removeClass('hide').show();
			}
		} else {
			if (statusOrMsg && statusOrMsg.code === "401") {
				// Session token expired, must reload page
				this.reloadIfAuthError();
			}

			this.showError(groupId, statusOrMsg);
		}
	}

	AcDocuments.prototype.normalizeToFileArray = function (files) {
		var result = [];

		if (files instanceof Array || files instanceof FileList || files instanceof DataTransferItemList) {
			for (var index = 0; index < files.length; index++) {
				var file = files[index];

				if (file instanceof DataTransferItem) {
					file = file.getAsFile();
				}

				if (file instanceof File) {
					result.push(file);
				} else {
					result = result.concat(this.normalizeToFileArray(file));
				}
			}
		} else if (files instanceof DataTransferItem) {
			result.push(files.getAsFile());
		} else if (files instanceof File) {
			result.push(files);
		} else if (file) {
			throw new Error("Cannot normalize to File[]: " + files);
		}

		return result;
	}

	AcDocuments.prototype.ajaxAddFiles = function(groupId, fileKey, fileValues, fileAddedCallback, finalCallback) {
		var files = this.normalizeToFileArray(fileValues);
		var count = files.length;
		var order = 0;

		for (var index = 0; index < files.length; index++) {
			var file = files[index];
			order++;

			this.ajaxAddFile(groupId, fileKey, file, function(finalGroupId, finalFileId){
				count--;

				if (typeof fileAddedCallback === 'function') {
					fileAddedCallback(finalGroupId, finalFileId, order, activeUploads);
				}

				if (count == 0 && typeof finalCallback === 'function') {
					finalCallback(finalGroupId, finalFileId);
				}
			})
		}
	}

	AcDocuments.prototype.generateFileId = function(groupId, file) {
		return (new Date()).getTime() + "" + parseInt(Math.random() * 1000000);
	}

	AcDocuments.prototype.ajaxAddFile = function(groupId, fileKey, fileValue, finalCallback) {
		var tmpFileId = this.generateFileId(groupId, fileValue);

		var fileElem = this.createFileElement(groupId, tmpFileId, fileValue);
		this.startProgressAnimation(fileElem);

		var formData = new FormData();
		formData.append(fileKey, fileValue)
		formData.append(this.step.config.nmsp + "groupId", groupId);
		formData.append(this.step.config.nmsp + "tmpFileId", tmpFileId);

		var $this = this;

		$this.onBackgroundOperationStart();

		jQuery.ajax({
			url: this.config.url.addFile,
			method: 'POST',
			data: formData,
	        cache: false,
	        contentType: false,
	        processData: false,
	        dataType: 'json',
	        timeout: 50000,
	        beforeSend: function() {

	        }
		}).done(function(result, textStatus, jqXHR) {
			var data = result && result.success && result.data ? result.data : null;
			$this.handleAjaxFileUploadResult(result.success && data, result.status, fileElem, groupId, tmpFileId, data, finalCallback);
		}).fail(function(jqXHR, textStatus, errorThrown){
			$this.handleAjaxFileUploadResult(false, textStatus, fileElem, groupId, tmpFileId, null, finalCallback);
		}).always(function(){
			$this.onBackgroundOperationEnd();
		});
	}

	AcDocuments.prototype.handleAjaxFileUploadResult = function (success, statusOrMsg, fileElem, groupId, tmpFileId, data, finalCallback){
		var $this = this;

		this.stopProgressAnimation(fileElem, function(progress){
			if (success) {
				$this.updateFileInList(groupId, tmpFileId, data);
				$this.addFileElementDeleteLink(groupId, data.id, progress);

				if (typeof finalCallback === "function") {
					finalCallback(groupId, data.id);
				}
			} else {
				$this.deleteFileElement(groupId, tmpFileId);

				if (statusOrMsg && statusOrMsg.code === "401") {
					// Session token expired, must reload page
					$this.reloadIfAuthError();
				}

				$this.showError(groupId, statusOrMsg);

				if (typeof finalCallback === "function") {
					finalCallback(groupId, undefined);
				}
			}
		});
	}

	AcDocuments.prototype.onBackgroundOperationStart = function() {
		this.pendingOperations++;
		this.step.disableUserInteraction();
	}

	AcDocuments.prototype.onBackgroundOperationEnd = function() {
		this.pendingOperations--;

		if (this.pendingOperations === 0) {
			this.step.enableUserInteraction();
		}
	}

	AcDocuments.prototype.filterMsgToKey = function(msg) {
		var safeMsg = "";

		if (typeof msg === "object") {
			if ("status" in msg && typeof msg.status.message === "string") {
				safeMsg = msg.status.message;
			} else if (typeof msg === "object" && "message" in msg) {
				safeMsg = msg.message;
			}

			safeMsg = safeMsg.replace(" ", "_");
		} else if (typeof msg === "string") {
			safeMsg = msg;
		}

		var diacriticsRegex = new RegExp("\\p{Diacritic}", "gu");
		safeMsg = safeMsg.trim().normalize("NFD").replace(diacriticsRegex, "");
		safeMsg = safeMsg.toLowerCase().replace(/[ \t\r\n\-]+/, "-");

		return safeMsg;
	}

	AcDocuments.prototype.getMessageObj = function(msgKey, msg) {
		var result = {
				title: this.config.msg.genericErrorTitle,
				description: this.config.msg.genericErrorDescription
		};

		var unhandledErrors = ["timeout", "parsererror", "abort", "error", "authorization_required"];

		if (msgKey === "invalid-size" || msgKey == "error-file-upload-max-size") {
			result.title = this.config.msg.invalidSizeTitle;
			result.description = this.config.msg.invalidSizeDescription;
		} else if (msgKey === "invalid-format" || msgKey == "error-file-upload-format") {
			result.title = this.config.msg.fileCorruptedTitle;
			result.description = this.config.msg.fileCorruptedDescription;
		} else if (msgKey === "invalid-file" || msgKey == "error-file-corrupted" || msgKey == "error-file-truncated") {
			result.title = this.config.msg.fileCorruptedTitle;
			result.description = this.config.msg.fileCorruptedDescription;
		} else if (unhandledErrors.indexOf(msgKey) >= 0) {
			var errorKey = "ajaxError";

			if (msgKey === "Error") {
				errorKey += "Generic";
			} else {
				// convert msgKey to camelCase and append to errorKey
				var parts = msgKey.split("-");
				for (var index = 0; index < parts.length; index++) {
					var part = parts[index];
					errorKey += part[0].toUpperCase() + part.substring(1);
				}
			}

			if (errorKey in this.config.msg) {
				result.description = this.config.msg[errorKey];
			}

		} else if (msgKey.length > 0) {
			result.description = msgKey.trim().replace(/[\-_]+/, " ");
			result.description = result.description[0].toUpperCase() + result.description.substr(1);
		}

		return result;
	}

	AcDocuments.prototype.showError = function(groupId, msg) {
		var msgKey = this.filterMsgToKey(msg);
		var msgObj = this.getMessageObj(msgKey, msg);

		var errorContainer = this.findElement(groupId, '.file-upload-error-message');
		errorContainer.find('.title').text(msgObj.title);
		errorContainer.find('.description').text(msgObj.description);
		var $this = this;
		var showTime = this.config.animate.duration.show || 'slow';
		var hideTimeout = this.config.animate.timeout.hide || 4000;

		errorContainer
			.attr("aria-role", "alert")
			.addClass("current-error")
			.show(showTime, function(){
				setTimeout(function(){
					$this.hideError(groupId);
				}, hideTimeout);
			});
	}

	AcDocuments.prototype.hideError = function(groupId) {
		var errorContainer = this.findElement(groupId, '.file-upload-error-message');
		var hideTime = this.config.animate.duration.hide || 'fast';
		errorContainer
			.removeAttr("aria-role")
			.removeClass("current-error")
			.hide(hideTime);
	}

	AcDocuments.prototype.initDnD = function() {
		var rootElem = this.findElement();
		this.initValidation(rootElem);
		this.init();

		// Ensure there is a file input and that it contains all the required stuff
		rootElem.find('[data-ov-file-upload-group-id] input[type="file"]')
			.attr('accept', this.config.validate.accepts.join(','))
			// Ignore file elements in built-in validation as they are handled by this pseudo-class directly
			.attr('data-ov-validation-ignore', true)
			.attr('data-ov-validation', 'valid')
			.prop('multiple', true)
			.show()
			.addClass('ov-hidden-accesible-block');

		this.handleEvents(rootElem);
	}

	AcDocuments.prototype.handleEvents = function(rootElem) {
		var $this = this;
		rootElem.find('[data-ov-file-upload-group-id] [data-ov-file-upload-document-id]').each(function(pos, elem){
			var jElem = jQuery(elem);
			$this.attachEventHandlers(jElem);
		});

		rootElem.find('[data-ov-file-upload-group-id] .drop_zone')
			.on('dragover.dnd dragleave.dnd', function(ev) {
				ev.stopPropagation();
				ev.preventDefault();
				$this.hideError($this.getGroupId(this));
			}).on('drop.dnd', function(ev) {
				$this.onDrop(this, ev);
			});

		rootElem.find('[data-ov-file-upload-group-id] input[type="file"]')
			.on('change.dnd', function(ev){
				$this.onChange(this, ev);
			});
	}

	AcDocuments.prototype.initValidation = function(rootElem) {
		var accepts = rootElem.attr("data-ov-upload-file-accepts");
		accepts = accepts ? accepts.trim().toLowerCase().split(/[,\ ]+/) : [];

		var maxSize = rootElem.attr("data-ov-upload-file-max-size");
		maxSize = maxSize ? parseInt(maxSize.trim()) : 0;

		if (isNaN(maxSize) || maxSize < 0) {
			maxSize = 0;
		}

		this.config.validate.maxSize = maxSize;
		this.config.validate.accepts = accepts;
	}

	AcDocuments.prototype.getGroupId = function(elem) {
		var jElem = jQuery(elem);
		var groupId = jElem.attr('data-ov-file-upload-group-id');

		if (!groupId) {
			jElem = jElem.closest('[data-ov-file-upload-group-id]');

			if (jElem.length >= 0) {
				groupId = jElem.attr('data-ov-file-upload-group-id');
			}
		}

		return groupId ? groupId : undefined;
	}

	AcDocuments.prototype.getFileInputByElem = function(domElement) {
		var groupId = this.getGroupId(domElement);

		if (!groupId) {
			throw new Error('No groupId found by DOM element', domElement);
		}

		var fileInput = this.findElement(groupId, 'input[type="file"]');

		return fileInput && fileInput.length > 0 ? fileInput : undefined;
	}

	AcDocuments.prototype.onDrop = function(domElement, ev) {
		ev.stopPropagation();
		ev.preventDefault();

		// Normalize to a File array.

		var dataTransferItems = (ev.dataTransfer  && ev.dataTransfer.items)
			|| (ev.originalEvent.dataTransfer && ev.originalEvent.dataTransfer.items)
			|| [];
		var dataFiles = ev.target.files || ev.originalEvent.target.files || [];

		var files = this.normalizeToFileArray([].concat(dataTransferItems).concat(dataFiles));

		var fileInput = this.getFileInputByElem(domElement);

		if (!fileInput) {
			throw new Error('No file input found by group ' + groupId);
		}

		this.handleFilesForUpload(domElement, fileInput.attr("name"), files);
	}

	AcDocuments.prototype.onChange = function(targetElem, ev) {
		ev.preventDefault();
		ev.stopPropagation();

		var fileUploadRow = jQuery(targetElem).closest('.file-upload-row');

		if (fileUploadRow.length === 0) {
			throw new Error('Invalid target element is not inside the upload row');
		}

		var files = this.normalizeToFileArray(ev.target.files);
		this.handleFilesForUpload(fileUploadRow[0], ev.target.name, files);
	}

	AcDocuments.prototype.handleFilesForUpload = function(targetElem, inputName, files) {
		var groupId = this.getGroupId(targetElem);

		if (!groupId) {
			throw new Error("No group found for target element");
		}

		for (var index = 0; index < files.length; index++) {
			var candidate = files[index];
			var validation = this.validateFile(candidate);

			if (validation !== "") {
				console && console.error("Invalid file " + candidate.name + ". Validation: " + validation);
				this.showError(groupId, validation);
				return;
			}
		}

		var $this = this;

		this.ajaxAddFiles(groupId, inputName, files, null, function(finalGroupId, finalFileId){
			var input = $this.findElement(finalGroupId, 'input[type="file"]');

			if (input.length === 1) {
				$this.clearFileInput(input);
			} else if (input.length > 1) {
				throw new Error("Found multiple inputs in group " + finalGroupId);
			} else if (input.length === 0) {
				throw new Error("No file input found in group " + finalGroupId);
			}
		});
	}

	AcDocuments.prototype.clearFileInput = function(input) {
		// clear validation state
		input.val('').attr("data-ov-validation", "valid")[0].removeAttribute("aria-invalid");
		// Clear the file input replacing with a deep clone while keeping all event handlers
		var newInput = input.clone(true);
		input.replaceWith(newInput);
		// Do validation over the new empty one
		newInput.change();
	}

	AcDocuments.prototype.validateFileNotCorrupted = function(file) {
		var key = (file && file.type.toLowerCase()) || "none";
		var minSize = 0;

		if (key in this.config.validate.minSizes) {
			minSize = this.config.validate.minSizes[key];
		}

		return AcValidations.validateFileNotEmptyAndMime(file, minSize);
	}

	AcDocuments.prototype.validateFileSize = function(file) {
		return AcValidations.validateFileSize(file, this.config.validate.maxSize);
	}

	AcDocuments.prototype.validateFileAccept = function(file) {
		return AcValidations.validateFileAccept(file, this.config.validate.accepts);
	}

	AcDocuments.prototype.validateFile = function(file) {

		if (!this.validateFileNotCorrupted(file)) {
			return "invalid-file";
		} else if (!this.validateFileSize(file)) {
			return "invalid-size";
		} else if (!this.validateFileAccept(file)) {
			return "invalid-format";
		}

		return '';
	}

	AcDocuments.prototype.isImage = function(file) {
		return file && file.type && file.type.startsWith('image/');
	}

	AcDocuments.prototype.isPdf = function(file) {
		return file && file.type && file.type === 'application/pdf';
	}

	AcDocuments.prototype.startProgressAnimation = function(fileElem, onEnd) {
		var progress = fileElem.find('.progress');
		var domElem = progress.get(0);
		var totalWidth = parseInt(progress.css('width'));
		var maxTimeUnits = 120;
		var stepTime = 400;

		var fill = progress.find('.progress-bar');
		var currentWidth = parseInt(fill.css('width'));

		var stepWidth = (totalWidth - currentWidth)/maxTimeUnits;
		var $this = this;

		var updateFill = function() {
			clearTimeout(domElem.progressTimer);

			if (currentWidth >= totalWidth) {
				$this.showError(fileElem.attr('data-ov-file-upload-group-id'));
				$this.stopProgressAnimation(fileElem, onEnd);
			} else {
				currentWidth += stepWidth;
				fill.css('width', currentWidth + 'px');

				if (currentWidth > (totalWidth / 4)) {
					stepTime = 300;
					stepWidth = stepWidth / 2;
				} else if (currentWidth > (totalWidth / 2)) {
					stepTime = 200;
					stepWidth = stepWidth / 2;
				}

				domElem.progressTimer = setTimeout(updateFill, stepTime);
			}
		};

		updateFill();
	}

	AcDocuments.prototype.stopProgressAnimation = function(fileElem, onEnd) {
		var progress = fileElem.find('.progress');

		if (progress.length > 0) {
			var domElem = progress.get(0);
			clearTimeout(domElem.progressTimer);
			progress.find('.progress-bar').css('width', '100%');
		}

		if (typeof onEnd === 'function') {
			setTimeout(function(){
				onEnd(progress);
			}, 100);
		}
	}
}

/* Definition of class AcTramiteStep */

if (typeof AcTramiteStep === 'undefined') {
	function AcTramiteStep(config)  {

		var baseSelector = ".ac-tramite.tramite-cambio-titular";
		var sectionSelector = baseSelector + " .tramite-wrapper";

		var defaultConfig = {
			nmsp: "",
			validation: {},
			selectors: {
				base: baseSelector,
				section: sectionSelector,
				form: sectionSelector + " form.tramite-form"
			},
		};

		this.config = jQuery.extend({}, defaultConfig, config || {});

		this.name = "tramite-step";
		var parts = this.getCurrentStep().split("-");

		for (var index = 0; index < parts.length; index++) {
			var part = parts[index];
			if (part.length > 0) {
				this.name += part[0].toUpperCase() + part.substr(1);
			}
		}

		// jQuery validate validator object
		this.validator = null;
		// Flag to avoid recursion between checks inside enableSubmitButton and handlers that call it
		this.enableButtonPending = false;
		this.debug = false;
	}

	/**
	 * Returns the portvar namespace
	 */
	AcTramiteStep.prototype.getNmsp = function() {
		return this.config.nmsp;
	}

	AcTramiteStep.prototype.getName = function () {
		return this.name;
	}

	/**
	 * Returns the validator object returned by jQuery.validate
	 *
	 * @see https://jqueryvalidation.org/documentation/
	 * @example
     * Validator.form() – Validates the form.
     * Validator.element() – Validates a single element.
     * Validator.resetForm() – Resets the controlled form.
     * Validator.showErrors() – Show the specified messages.
     * Validator.numberOfInvalids() – Returns the number of invalid fields.
     * Validator.destroy() – Destroys this instance of validator.
     *
	 */
	AcTramiteStep.prototype.getValidator = function() {
		return this.validator;
	}

	/**
	 *
	 */
	AcTramiteStep.prototype._getElement = function(type) {
		var elem = null;

		if (type in this.config.selectors) {
			elem = jQuery(this.config.selectors[type]);

			if (typeof elem === "undefined" || !elem.length) {
				typeof window.console === "object"
					&& console.error("No element found by selector type " + type + ": " + this.config.selectors[type]);
			}
		} else {
			typeof window.console === "object"
				&& console.error("Invalid selector type " + type);
		}

		return elem;
	}

	AcTramiteStep.prototype.getSectionElement = function() {
		return this._getElement("section");
	}

	/**
	 * Gets the tramite form jQuery object
	 */
	AcTramiteStep.prototype.getForm = function() {
		return this._getElement("form");
	}

	/**
	 * Gets the DOM element of a form input by name
	 */
	AcTramiteStep.prototype.getFormElement = function (elemName) {
		if (typeof elemName !== "string" || !elemName) {
			return null;
		}

		return this.getForm().find('[name="' + elemName + '"]');
	}

	/**
	 * Already valid elements
	 */
	AcTramiteStep.prototype.getValidElements = function () {
		return this._getValidationElements("valid");
	}

	/**
	 * Already invalid elements
	 */
	AcTramiteStep.prototype.getInvalidElements = function () {
		return this._getValidationElements("invalid");
	}

	/**
	 * Elements marked as required but not validated
	 */
	AcTramiteStep.prototype.getMissingElements = function () {
		return this._getValidationElements("missing");
	}

	/**
	 * Elements that haven't been validated whether they are required or not
	 */
	AcTramiteStep.prototype.getAllElements = function () {
		return this._getValidationElements("all");
	}

	AcTramiteStep.prototype._getValidationElements = function (validFlag) {

		var filterType = "";

		if (validFlag === true || validFlag === "true") {
			// Valid if not invalid
			filterType = "valid";
		} else if (validFlag === false || validFlag === "false") {
			// Invalid if not valid
			filterType = "invalid";
		} else if (validFlag === "missing") {
			// Elements required but not already filled but they are also not marked as invalid
			filterType = "missing";
		} else if (validFlag === undefined || validFlag === "not-validated") {
			// Elements not validated whether already invalid or not
			filterType = "not-validated";
		} else if (validFlag === "validated") {
			// Elements not validated whether already invalid or not
			filterType = "not-validated";
		} else {
			// all, no selector
			filterType = "all";
		}

		var result = this.getValidator().elements();
		var $this = this;

		if (filterType !== "all") {
			result = result.filter(function(pos, elem){
				return $this._is(jQuery(elem), filterType);
			});
		}

		return result;
	}

	AcTramiteStep.prototype.getElementsSummary = function() {
		var summary = {
				total: 0,
				required: 0,
				missing: 0,
				validated: 0,
				notValidated: 0,
				valid: 0,
				invalid: 0
		};

		var $this = this;

		this.getValidator().elements().each(function(pos, elem){
			var jElem = jQuery(elem);
			summary.total++;
			summary.required += jElem.is(':required') ? 1: 0;
			summary.missing += $this._is(jElem, "missing") ? 1 : 0;
			summary.validated += $this._is(jElem, "validated") ? 1 : 0;
			summary.notValidated += $this._is(jElem, "not-validated") ? 1 : 0;
			summary.valid += $this._is(jElem, "valid") ? 1 : 0;
			summary.invalid += $this._is(jElem, "invalid") ? 1 : 0;
		});

		return summary;
	}

	AcTramiteStep.prototype._is = function(jElem, filterType) {
		if (jElem.is(":hidden") || jElem.prop("readonly")) {
			return false;
		}

		switch (filterType) {
			case "valid":
				return jElem.is('[data-ov-validation="valid"]');
			case "invalid":
				return jElem.is('[data-ov-validation="invalid"]');
			case "missing":
				return this.validator.isMissing(jElem[0]);
			case "not-validated":
				return jElem.is('[data-ov-validation="pending"]');
			case "validated":
				return jElem.is(':not([data-ov-validation="pending"])');
			default:
				return true;
		}
	}

	/**
	 * Gets if the current form is valid and can be submitted
	 *
	 * Checks if all required fields are filled and valid and that no invalid fields have been found.
	 */
	AcTramiteStep.prototype.isValid = function() {
		var summary = this.getElementsSummary();

		var formIsValid = summary.missing === 0 && summary.invalid === 0;

		if (this.debug) {
			typeof window.console === "object"
				&& console.debug("isValid::" + formIsValid, summary)
				&& console.debug(this.getForm().find(':focus').first());
		}

		return formIsValid;
	}

	/**
	 * Gets the tramite submit button jQuery object or null
	 */
	AcTramiteStep.prototype.getSubmitButton = function(form) {
		form = form || this.getForm();
		return form.find('.btn-tramite[type="submit"]');
	}

	AcTramiteStep.prototype._delayedEnableSubmitButton = function(enable, form) {
		var $this = this;

		if (this._delayedEnableSubmitButtonTimeout !== null) {
			clearTimeout(this._delayedEnableSubmitButtonTimeout);
			this._delayedEnableSubmitButtonTimeout = null;
		}

		this._delayedEnableSubmitButtonTimeout = setTimeout(function(){
			this._delayedEnableSubmitButtonTimeout = null;
			$this.enableSubmitButton(enable, form);
		}, 50);
	}

	AcTramiteStep.prototype.enableSubmitButton = function(enable, form) {
		if (typeof enable === "undefined" || enable === null) {
			enable = this.isValid();
		}

		var propValue = typeof enable === "boolean" && enable;
		this.getSubmitButton(form).prop("disabled", !propValue);
	}

	AcTramiteStep.prototype.hideErrorsBlock = function(time) {
		if (this.getInvalidElements().length === 0
				&& jQuery('#errores-formulario').is(":visible")) {
			// No invalids, errors shown, then clear them.
			jQuery('#errores-formulario').hide(time || 800);
		}
	}

	/**
	 * Gets the current step key
	 */
	AcTramiteStep.prototype.getCurrentStep = function() {
		var section = this.getSectionElement();
		var data = section.attr("data-tramite-step");

		if (typeof data === "string" && data.trim()) {
			return data.trim();
		}

		return "";
	}

	/**
	 * Gets the name of the current step
	 */
	AcTramiteStep.prototype.getCurrentStepName = function() {
		var step = this.getCurrentStep();
		var name = "";

		if (step) {
			var parts = step.split("-");
			for (var index = 0; index < parts.length; index++) {
				if (index == 0) {
					name += parts[index];
				} else {
					name += parts[index][0].toUpperCase()
						+ parts[index].substr(1);
				}
			}
		}

		return name;
	}

	/**
	 * Returns default validation object to configure jQuery validate plugin.
	 */
	AcTramiteStep.prototype.getDefaultValidation = function() {
		var $this = this;

		return {
			errorClass: "has-error",
			validClass: "is-valid",
			success: "is-valid",
			errorElement: "span",
			errorPlacement: function(error, element) {
				jQuery(element)
					.closest(".ov-form-field")
					.find(".field-error")
					.empty()
					.append(error);
		  	},
		    highlight: function(element, errorClass, validClass) {
		        jQuery(element)
		        	.closest(".ov-form-field")
		        	.addClass(errorClass)
		        	.removeClass(validClass);
		    },
		    unhighlight: function(element, errorClass, validClass) {
		    	jQuery(element)
		    		.closest(".ov-form-field")
		    		.removeClass(errorClass)
		    		.addClass(validClass);
		    },
		    invalidHandler: function(event, validator) {
		        // 'this' refers to the form
		        $this.enableSubmitButton(false);
		    },
		    afterElementValidated: function (element, valid) {
		    	$this._delayedEnableSubmitButton();
		    }
		}
	}

	AcTramiteStep.prototype.destroyValidator = function() {
		if (this.validator != null) {
			var validator = this.validator;
			this.validator = null;
			validator.destroy();
		}
	}

	AcTramiteStep.prototype.getOrCreateWaitLoad = function() {
		var section = this.getSectionElement();
		var wait = section.find("#wait-load");

		if (wait && wait.length === 0) {
			wait = section.find(".spinner.wait-load");
		}

		if (!wait || wait.length === 0) {
			wait = jQuery('<div class="spinner wait-load" style="display: none;"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>');
			section.append(wait);
		}

		return wait;
	}

	AcTramiteStep.prototype.disableUserInteraction = function(focus, disableAllInputs) {
		var wait = this.getOrCreateWaitLoad();

		if (wait && wait.length === 1 && !wait.is(':visible')) {
			wait.show();
			if (focus) {
				wait.attr("tabindex", 0)[0].scrollIntoView()
			}
		} else {
			return;
		}

		var form = this.getForm();
		form.attr("data-ov-disabled", "true");

		if (disableAllInputs) {
			form.find("input:not(:hidden)").each(function(pos, elem){
				var jElem = jQuery(elem);
				if (!jElem.prop("disabled")) {
					jElem.attr("data-ov-disabled", 'true');
					jElem.prop("disabled", true);
				}
			});
		}

		form.find('button[type="submit"]').each(function(pos, elem){
			var jElem = jQuery(elem);
			var disabledPrev = jElem.prop('disabled') === true;
			jElem.attr("data-ov-disabled-prev", disabledPrev);
			jElem.prop('disabled', true);
		});
	}

	AcTramiteStep.prototype.enableUserInteraction = function() {
		var section = this.getSectionElement();
		var wait = section.find("#wait-load");

		if (wait && wait.length === 0) {
			wait = section.find(".spinner.wait-load");
		}

		if (wait && wait.length === 1 && wait.is(':visible')) {
			wait.hide();
		}

		var form = this.getForm();
		form.removeAttr("data-ov-disabled");

		form.find('[data-ov-disabled="true"]').each(function(pos, elem){
			jQuery(elem).removeAttr("data-ov-disabled").prop('disabled', false);
		});

		form.find('button[type="submit"]').each(function(pos, elem){
			var jElem = jQuery(elem);
			var disabledPrev = jElem.attr('data-ov-disabled-prev') === 'true';
			jElem.removeAttr('data-ov-disabled-prev');

			if (!disabledPrev) {
				jElem.prop('disabled', false);
			}
		});
	}

	/**
	 * Initializes generic validation with jQuery validate plugin
	 *
	 * Merges the customValidation argument over the default validation and uses it
	 *
	 */
	AcTramiteStep.prototype.initValidation = function(customValidation) {

		if (this.validator != null) {
			return;
		}

		customValidation = customValidation || {};
		var defaultValidation = this.getDefaultValidation();
		var validation = jQuery.extend({}, defaultValidation, this.config.validation, customValidation);

		var form = this.getForm();
		this.validator = form.validate(validation);
		var $this = this;

		// Handle change on elements that toggle parts of the form and revalidate those parts
		form.find('input[data-ov-toggle-visibility], select[data-ov-toggle-visibility], textarea[data-ov-toggle-visibility]').change(function(ev){
			$this.toggleVisibility(this);
		});

		// Force a change to set initial state
		jQuery("[data-ov-toggle-visibility]").change();

		// After initializing validate the form if some data is present and hide/show the button
		if (this.validator.elementsFilled().length > 0) {
			var valid = form.valid();

			this.validator.showErrors();

			this.enableSubmitButton(valid, form);
		} else {
			this.validator.initElements();
		}
	}

	AcTramiteStep.prototype.toggleVisibility = function(elem) {
		var jElem = jQuery(elem);
		var toggleSource = jElem.closest(".row--panel-toggle-source");
		var visible = jElem.is(":checked");

		if (visible) {
			toggleSource.addClass("visible");
		} else {
			toggleSource.removeClass("visible");
		}

		var targets = jElem.attr('data-ov-toggle-visibility').split(' ');

		for (var index = 0; index < targets.length; index++) {
			var targetId = targets[index];
			// Search only under the form element
			var target = this.getForm().find('[id="' + targetId + '"]').first();

			if (!visible) {
				this.hideToggleTarget(target);
			} else {
				this.showToggleTarget(target);
			}
		}
	}

	AcTramiteStep.prototype.getTargetElements = function(target) {
		return jQuery(target)
			.find('input, select, textarea')
			.not(':submit, :reset, :image, :disabled, [type="hidden"], [readonly]')
			;
	}

	AcTramiteStep.prototype.hideToggleTarget = function(target) {
		var $this = this;

		target.hide("fast", "swing", function() {
			$this.validator.resetElements($this.getTargetElements(target));
			$this._delayedEnableSubmitButton();
		});
	}

	AcTramiteStep.prototype.showToggleTarget = function(target) {
		var $this = this;

		target.show(400, "swing", function() {
			// Validate shown elements only if at least one is filled
			var enableValidation = false;

			var elements = $this.getTargetElements(target);

			for (var index = 0; index < elements.length; index++) {
				var jElem = jQuery(elements[index]);

				if (jElem.is(":visible") && !jElem.is(":blank")) {
					enableValidation = true;
					break;
				}
			}

			if (enableValidation) {
				elements.each(function(pos, targetElement){
					// Validate on show only those visible inputs with content or marked as invalid
					$this.validator.element(targetElement);
				});
			} else {
				$this.validator.resetElements(elements);
			}

			$this._delayedEnableSubmitButton();
		});
	}
}


if (typeof AcTramite === 'undefined') {
    function AcTramite(config) {
        this.steps = {};
        this.msg = {};
        this.funcs = {};

        var defaultConfig = {
            nmsp: "",
            name: "tramite-generic",
            defaultStep: "default-step"
        };

        this.config = jQuery.extend({}, defaultConfig, config || {});
    }

    AcTramite.prototype.getCurrentStep = function() {
        var currentStep = jQuery('.ac-tramite.tramite-' + this.config.name +  ' .tramite-wrapper').attr('data-tramite-step');

        if (!currentStep) {
            currentStep = this.config.defaultStep;
        }

        return currentStep;
    };

    AcTramite.prototype.createStep = function(stepConfig) {
        var config = stepConfig || {};

        var defaultConfig = {
            nmsp: this.config.nmsp,
        };

        var finalConfig = jQuery.extend(defaultConfig, config);
        var step = new AcTramiteStep(finalConfig);

        return step;
    }

    AcTramite.prototype.init = function () {
        var currentStep = this.getCurrentStep();

        if (typeof this.funcs['before-init'] === "function") {
            this.funcs['before-init'](currentStep);
        }

        if (typeof this.funcs['init-' + currentStep]) {
            this.funcs['init-' + currentStep]();
        }

        if (typeof this.funcs['after-init']) {
            this.funcs['after-init'](currentStep);
        }
    }

    AcTramite.prototype.initStep = function (stepName) {

    };

}


if (typeof AcValidations === "undefined") {
	function AcValidations() {

	}

	AcValidations.validateFileNotEmptyAndMime = function (file, minSize) {
		if (!file) {
			return true;
		}

		var safeMinSize = parseInt(minSize);
		if (isNaN(safeMinSize) || safeMinSize < 0) {
			safeMinSize = 0;
		}

		return file.size > safeMinSize
			&& file.type !== ""
			&& file.name.indexOf(".") > 0;
	}

	AcValidations.validateFileSize = function (file, maximumSize) {
		var fileSizeLimit = parseInt(maximumSize);

		if (!file || isNaN(fileSizeLimit) || fileSizeLimit <= 0) {
			return true;
		}

		return file.size <= fileSizeLimit;
	}

	AcValidations.validateFileAccept = function (file, accept) {
		if (!file || (typeof accept === "string" && accept.trim() === "")
				|| (accept instanceof Array && accept.length === 0)) {
			return true;
		}

		var expressions = null;

		if (typeof accept === "string") {
			expressions = accept.split(",");
		} else if (accept instanceof Array) {
			expressions = accept;
		} else {
			return true;
		}

		var fileExt = file.name.toLowerCase().substring(file.name.lastIndexOf("."));
		var fileType = file.type.toLowerCase();

		for (var index = 0; index < expressions.length; index++) {
			var expression = expressions[index].trim().toLowerCase();

			if (expression.length === 0) {
				continue;
			}

			var checkTypeEnds = false;

			if (expression.endsWith("*")) {
				expression = expression.substring(0, expression.length - 1);
				checkTypeEnds = true;
			}

			if (expression === fileExt
				|| (!checkTypeEnds && fileType === expression)
				|| (checkTypeEnds && fileType.startsWith(expression))) {
				return true;
			}
		}

		return false;
	}
}

