'use strict'; 
var sedAngularApp = angular.module('sedAngularApp', [ 'ui.bootstrap', 'datePicker1', 'isteven-multi-select',
	'ngSanitize', 'ui.select', 'checklist-model', 'smart-table', 'ui.checkbox.met', , 'ui.IR', 'ui.TR', 'ui.DH']);

sedAngularApp.controller("SedController", [ "$scope", "$element", "$timeout", "$window", "anchorSmoothScroll", "dialogsSvc", SedController ]);
sedAngularApp.controller("SectionController", [ "$scope", "$timeout", "$window", "anchorSmoothScroll", "dialogsSvc",
	SectionController ]);
sedAngularApp.controller(
	"FooterController",
	[ "$scope", "$element", "$timeout", "$window", "anchorSmoothScroll", "dialogsSvc", FooterController ]);

// This is necessary to be able to interpolate on ng-repeat for ng-model and
// ng-form directives
// For example, to be able to have fields with name="name_{{$index}}"
sedAngularApp.config([
	'$provide',
	function($provide)
	{
		$provide.decorator('ngModelDirective', [
			'$delegate',
			function($delegate)
			{
				var ngModel = $delegate[0], controller = ngModel.controller;
				ngModel.controller = [ '$scope', '$element', '$attrs', '$injector',
					function($scope, $element, $attrs, $injector)
					{
						var $interpolate = $injector.get('$interpolate');
						$attrs.$set('name', $interpolate($attrs.name || '')($scope));
						$injector.invoke(controller, this,
						{
							'$scope' : $scope,
							'$element' : $element,
							'$attrs' : $attrs
						});
					} ];
				return $delegate;
			} ]);
		$provide.decorator('formDirective', [
			'$delegate',
			function($delegate)
			{
				var form = $delegate[0], controller = form.controller;
				form.controller = [ '$scope', '$element', '$attrs', '$injector',
					function($scope, $element, $attrs, $injector)
					{
						var $interpolate = $injector.get('$interpolate');
						$attrs.$set('name', $interpolate($attrs.name || $attrs.ngForm || '')($scope));
						$injector.invoke(controller, this,
						{
							'$scope' : $scope,
							'$element' : $element,
							'$attrs' : $attrs
						});
					} ];
				return $delegate;
			} ]);
	} ]);

angular.module('sedAngularApp').factory('sedVocabulariesCacheSvc', [ function()
{
	var cache =
	{};

	return cache;
} ]);

// Config necessary for tooltips custom behaviour
sedAngularApp.config([ '$tooltipProvider', function($tooltipProvider)
{
	$tooltipProvider.setTriggers(
	{
		'mouseenter' : 'mouseleave',
		'click' : 'mouseleave',
		'focus' : 'blur'
	});
	
} ]);

function SectionController($scope, $timeout, $window, anchorSmoothScroll, dialogsSvc)
{
	$scope.getNextUUID = Utils.getNextUUID;
	$scope.getCurrentUUID = Utils.getCurrentUUID;
	
	// TEMP - deactivate lazyload
	//$scope.shouldLoadSection = true;
    //$scope.metaSection.$$shouldLoadSection = true;
	
	// RINA team code - added by ionut
	sectionNo++;

	
	if( $scope.metaSection.$$shouldLoadSection == true) {
		$scope.shouldLoadSection = true;
		return;
	}
	
	// If adding a section through the Add button ... load it immediately
	if($scope.$parent && $scope.$parent.metaSection)
		if($scope.$parent.metaSection.$$shouldLoadSection) 
		{
			$scope.shouldLoadSection = true;
		    $scope.metaSection.$$shouldLoadSection = true;
			return;
		}
		
	// RINA team code
	// Ionut - to avoid some nasty error where the error list ends empty even if there are errors (loading a section triggers a $watch and the errors are cleared)
	var delay = getDelay(sectionNo);
	if (delay == 0) 
	{
		//console.log("1.Section no: "+sectionNo+" with delay: "+delay);
	    $scope.shouldLoadSection = true;
	    //$scope.metaSection.$$shouldLoadSection = true;
	}
	else
	{
		//console.log("2.Section no: "+sectionNo+" with delay: "+delay);
		$timeout(function () {
		  $scope.shouldLoadSection = true;
		  $scope.metaSection.$$shouldLoadSection = true;
		}, delay);
	}
	
	// End RINA team code
}

function FooterController($scope, $element, $timeout, $window, anchorSmoothScroll, dialogsSvc)
{
	$scope.itemsByPage = 5;

	$scope.showError = function(errorObj)
	{
		var elementId = errorObj.id;
		var elementIdToFocus = errorObj.id_focus;
		
		// RINA team code 
		if (errorObj.source == 'server') { //server errors navigation
			var id = getElementIdFromErrorPath(errorObj);
		    // MET adaptation to align with specs
			elementId = id;
			elementIdToFocus = id;
			// End MET adaptation
		}
		// End RINA team code
		  
		if (elementId == '-')
			return;

		var contentElement = $scope.VM.contentElement;

		var content = contentElement[0].querySelector('#' + $scope.contentDiv);

		var elm = contentElement[0].querySelector('#' + elementId);

		if (elm == null)
			alert('Error tagged element ' + elementId + ' does not exist!!! ');
		else
		{
			anchorSmoothScroll.scrollTo(elm, content, $scope);

			// RINA team code - adaugat $timeout de ionut
			$timeout(function(){
				var elmToFocus = contentElement[0].querySelector('#' + elementIdToFocus);
	        
				focusElement(elm, elmToFocus);
	        
			}, 200);
			// End RINA team code
		}
	}
	
	// RINA team code - functie adaugata de mine care transforma erorare,din forma XPath, in forma utilizata de voi pentru navigar sifocus.
	function getElementIdFromErrorPath(errorObj) {
		// /sed:P8000[1]/InsuredPerson[1]/PersonIdentification[1]/PersonalDetails[1]/PINPersonInEachInstitution[2]/Country[1]
		var node = $scope.VM.metaModel;
		var path = errorObj.path.split('/');
		path.shift();
		path[0] = path[0].replace('sed:', '');
		path[0] = path[0].replace('sed:', '');
		angular.forEach(path, function (el, elIndex) {
	    if (!el) return;

	    el = el.replace('[1]', '');
	    if (angular.isArray(node))
	      node = node[0];
	    if (node[el])         //prop
	      node = node[el];
	    else {                //prop[2]
	      var prop = el.split('[')[0];
	      var indexPart = el.split('[')[1];
	      var index = indexPart.substring(0, indexPart.length - 1);

	      node = node[prop][index - 1];
	    }
	  });
	  if (angular.isArray(node))
	    node = node[0];
	  var type = (node.$$type == 'section') ? 'section' : 'field';
	  var id = type + "_" + node.$$uuid;
	  return id;
	}
}


/*
 *  Method to focus an element from the form.
 *  Need to process separately the following custom widgets that have different focus methods:
 *  - ui-select widget (identified by the type attribute with the value "enum" in the DOM element (e.g. type="enum")
 *  - isteven multi-select widget (identified by the presence of the isteven-multi-select directive in the DOM element)
 *  - custom RINA style checkbox (identified by the type attribute with the value "checkbox-met" in the DOM element (e.g. type="checkbox-met")
 */
function focusElement(elm, elmToFocus) 
{
    if (elmToFocus != null) {
    	var customWidget = null;
    	var useSetFocus = false;
 
    	// Angular ui-select widget
    	if(elmToFocus.getAttribute("type")=="enum") {
    		customWidget = angular.element(elmToFocus).controller('uiSelect');
    		useSetFocus = true;
    	}
    	//isteven multi-select widget
    	if(elmToFocus.getAttribute("isteven-multi-select")!=null)
    		customWidget = angular.element(elmToFocus).children().children()[0];
    	//checkbox-met (RINA style)
    	/* TODO */
    	if(elmToFocus.getAttribute("type")=="checkbox-met")
    		customWidget = angular.element(elmToFocus).children()[0];
    	//*/
    	
        if(customWidget) {
        	if (useSetFocus)
        		customWidget.setFocus();
        	else
        		customWidget.focus();
        }
         else	
        	elmToFocus.focus();
    }
    else {
    	var customWidget = null;
    	var useSetFocus = false;
    	// Angular ui-select widget
    	if(elm.getAttribute("type")=="enum") {
    		customWidget = angular.element(elm).controller('uiSelect');
    		useSetFocus = true;
    	}
    	//isteven multi-select widget
    	if(elm.getAttribute("isteven-multi-select")!=null)
    		customWidget = angular.element(elm).children().children()[0];
    	//checkbox-met (RINA style)
    	/* TODO */
    	if(elm.getAttribute("type")=="checkbox-met")
    		customWidget = angular.element(elm).children()[0];
    	//*/
    	
        if(customWidget) {
        	if (useSetFocus)
        		customWidget.setFocus();
        	else
        		customWidget.focus();
        }
        else	
        	elm.focus();
    }
}

//RINA team code
var sectionNo;

function SedController($scope, $element, $timeout, $window, anchorSmoothScroll, dialogsSvc)
{
	// RINA team code
	sectionNo = 0;
	$scope.VM.contentElement = $element;
	// End RINA team code

	$scope.model = angular.copy($scope.MODEL);
	$scope.metaModel = angular.copy($scope.META_MODEL);
	
	// RINA team code - adaugat demine acum, ca sa am meta-modelul in controlerul de footer
	$scope.VM.metaModel = $scope.metaModel;

	MetaModel.updateStructure($scope, true);
	$scope.input = INPUT;
	$scope.locale = LOCALE;
		
	$scope.validateForm = false;
	$scope.validateAsDraft = false;
	// RINA team code
	$scope.validateNonDraftAsWarnings = false;
	
	SedController.dialogsSvc = dialogsSvc;
	
	// RINA adjustment for "pretty" section display
	SedController.anchorSmoothScroll = anchorSmoothScroll;
	SedController.$timeout = $timeout;
	
	$scope.addSection = SedController.addSection;
	$scope.removeSection = SedController.removeSection;

	$scope.getNextUUID = Utils.getNextUUID;
	$scope.getCurrentUUID = Utils.getCurrentUUID;

	$scope.UUID = Utils.UUID;
	
	MetaModel.updateElemNo($scope, true);
	
	//MetaModel.updateRepHeadersId($scope);

	// RINA team code
	$scope.handleSplitter = function () 
	{
		$timeout(function () 
		{
			// RINA - sa nu mai apara lista cand am numai warninguri, nu si erori
		    // var errors = $scope.errors.filter(function (el) {
		    //   return el.severity == "Error"
		    // });
			var errors = $scope.errors;
			if (errors.length == 0)
				$scope.VM.splitterPosition = 1;
			else 
			{
				if ($scope.VM.splitterPosition < 10)
					$scope.VM.splitterPosition = 150;
			}
	    }, 500);
	 };
	 // End RINA team code
	  
	//console.log('Version of angular is :'+angular.version.full);

	/* Old version
	$scope.$watch('section', function(newValue, oldValue)
	{
		if (!angular.equals(newValue, oldValue)) {
			$scope.sedForm.$setValidity('error', true);
			$scope.errors.length = 0;
			MetaModel.validate($scope);
			
			console.log('executed');
		}
		  
		// RINA team code - ionut adaugat la iteratia curenta, pentru dirty/reset logic
		if ($scope.VM.dataIsLoaded)
			if (!angular.equals(newValue, oldValue))
		{
		      $scope.VM.dataIsDirty = true;
		      
		      //$scope.sedForm.$setValidity('error', true);
			  //$scope.errors.length = 0;
			  //MetaModel.validate($scope);
		      
		      $scope.handleSplitter();
		}
		//ionut
		//$timeout(function () {
		//if ($scope.errors.length == 0)
		//		$scope.VM.splitterPosition = 1;
		// 	else {
		// 		if($scope.VM.splitterPosition < 10)
		// 			$scope.VM.splitterPosition = 150;
		// 	}
		//}, 100);
		// End RINA team code
	}, true);
	*/
	
	$scope.$watch('section', function (newValue, oldValue) {
		if (angular.equals(newValue, oldValue)) return;

		$scope.sedForm.$setValidity('error', true);
		$scope.errors.length = 0;
		MetaModel.validate($scope);

		// RINA team code - ionut adaugat la iteratia curenta, pentru dirty/reset logic
		if (!$scope.VM.dataIsLoaded) return;

		$scope.VM.dataIsDirty = true;
		// $scope.sedForm.$setValidity('error', true);
		// $scope.errors.length = 0;
		// MetaModel.validate($scope);
		$scope.handleSplitter();

		// End RINA team code
		}, true);

	
	$scope.test = function(val)
	{
		console.log(val);
	};
	
	$scope.validateFromChoice = function()
	{
		this.sedForm.$setValidity('error', true);
		this.errors.length = 0;
		MetaModel.validate(this);
		MetaModel.updateElemNo(this, true);
	};
	
	$scope.validate = function()
	{
		this.sedForm.$setValidity('error', true);
		this.errors.length = 0;
		MetaModel.validate(this);
	};
	
	$scope.getFormData = function(craftEnum)
	{
		if (typeof craftEnum === "undefined" || craftEnum === null) 
			craftEnum = true;
		
		//console.log($scope.metaModel);
		var data = MetaModel.readData($scope, false);
		// TODO - craftEnum
		/*
		if(craftEnum)
			data = "";
		*/
		return data;
		/*	    
		var data = MetaModel.readDataAsString(this, false);
	    // Changed the method to return an Object
	    return angular.fromJson(data);
	    */
	};

	// Needed internally by MET application 
	$scope.getFormDataAsString = function(craftEnum)
	{
		if (typeof craftEnum === "undefined" || craftEnum === null) 
			craftEnum = true;
		
		var data = MetaModel.readDataAsString(this, false);
		// TODO - craftEnum
		/*
		if(craftEnum)
			data = "";
		*/
		return data;
	};
	
	$scope.getFormDataFull = function(craftEnum)
	{
		if (typeof craftEnum === "undefined" || craftEnum === null) 
			craftEnum = true;
		
		var data = MetaModel.readDataAsString(this, true);
		// TODO - craftEnum
		/*
		if(craftEnum)
			data = "";
		*/
		return data;
	};

	$scope.setFormData = function(inputModel, craftEnum)
	{
		if (typeof craftEnum === "undefined" || craftEnum === null) 
			craftEnum = true;
		// TODO - craftEnum
		/*
		if(craftEnum)
			----
		*/
		
		// RINA team code - adaugat la iteratia curenta, pentru dirty/reset logic
		$scope.VM.dataIsDirty = false;
		$scope.VM.dataIsLoaded = false;
		// End RINA team code

		if (inputModel == null)
			Utils.assertFailed("Parameter sedAsJsonString should not be null");

		// when data is written, disable the validation process
		// RINA team code
		//this.validateForm = false; //In RINA context "this" is a scope object, the child of "$scope" object
		$scope.validateForm = false;
		// End RINA team code
		
		// whole form starts as valid
		this.sedForm.$setValidity('error', true);
		this.errors.length = 0;

		SedController.copyX(this, this.model, inputModel);
		MetaModel.updateStructure(this, true);
		SedController.updateFixedValueElem(this, this.model, $scope.metaModel);
		MetaModel.validate(this);
		MetaModel.updateElemNo(this, true);

		// RINA team code
		$scope.VM.splitterPosition = 1;

		// adaugat la iteratia curenta, pentru dirty/reset logic
		$timeout(function () {
			$scope.VM.dataIsLoaded = true;
		}, 300);
		// End RINA team code
	};

	$scope.clearFormData = function()
	{
		// set to pristine the whole form and disable the validation process
		this.sedForm.$setPristine();
		//this.validateForm = false;
		//adaugat de ionut pt ca $scope si this sunt diferite in contextul RINA fata de MET
		$scope.validateForm = false;

		// whole form starts as valid
		this.sedForm.$setValidity('error', true);
		this.errors.length = 0;

		Model.clearJsonObject(this, this.model);
		MetaModel.updateStructure(this, true);
		MetaModel.validate(this);
		MetaModel.updateElemNo(this, true);

		// RINA team code
		$scope.VM.splitterPosition = 1;
	};
	
	$scope.validateFormDataAsDraft = function()
	{
		this.validateAsDraft = true;
		// RINA team code
		$scope.validateAsDraft = true;
		//this.validateForm = false;
		// RINA team code
		$scope.validateForm = false;
		
		// RINA team code
		this.validateNonDraftAsWarnings = false;
	    $scope.validateNonDraftAsWarnings = false;
	    // End RINA team code
	    
		this.validateFormData();

	};
	
	$scope.validateFormDataRegular = function()
	{
		this.validateAsDraft = false;
		// RINA team code
		$scope.validateAsDraft = false;
		//this.validateForm = false;
		// RINA team code
		//$scope.validateForm = false;
		
		// RINA team code
		this.validateNonDraftAsWarnings = false;
	    $scope.validateNonDraftAsWarnings = false;
	    // End RINA team code
	    
		this.validateFormData();
	};
	
	// RINA team code
	$scope.validateFormDataAsDraftWarnings = function()
	{
		this.validateAsDraft = true;
	    $scope.validateAsDraft = true;
	    
	    this.validateNonDraftAsWarnings = true;
	    $scope.validateNonDraftAsWarnings = true;
	    
	    this.validateForm = false;
	    $scope.validateForm = false;
	    
	    this.validateFormData();
	}

	// RINA team code
	$scope.showErrors = function (showMode) 
	{
		if(showMode == 'DRAFT')
			$scope.validateFormDataAsDraft();
		else if(showMode == 'FULL')
			$scope.validateFormDataRegular();
		else if(showMode == 'NON_DRAFT_AS_WARNING') 
			$scope.validateFormDataAsDraftWarnings();
		else if(showMode == 'None') 
			$scope.clearFormData();
	};
	
	// Method that will navigate to a section - TODO according to the planning in September 2016 (SED Navigation task)
	$scope.showSection = function(sectionInfo)
	{
		return;
	}
	
	// Method that will return the list of sections - TODO according to the planning in September 2016 (SED Navigation task)
	$scope.getSections = function()
	{
		return;
	}

	$scope.validateFormData = function()
	{
		// enable the validation process and trigger the form update
		this.validateForm = true;

		//RINA team code
		//adaugat de ionut pt ca $scope si this sunt diferite in contextul RINA fata de MET
	    //rezolvare rapida si murdara
	    $scope.validateForm = true;

		// whole form starts as valid
		this.sedForm.$setValidity('error', true);
		this.errors.length = 0;

		MetaModel.updateStructure(this, false);
		MetaModel.validate(this);
		MetaModel.updateElemNo(this, true);

		// RINA team code
	    $scope.handleSplitter();
	    // End RINA team code

		// Scroll to the first element with errors
	    // RINA team code
	    //filter out warnings
	    $timeout(function () { // Ionut - temporary, dirty solution for avoiding error because the sections are not loaded
	        var errors = $scope.errors.filter(function (el) {
	          return el.severity == "Error"
	        });
	    
		    if (errors.length == 0)
		    	errors = $scope.errors;
		    
		    if (errors.length == 0) return;
		    // End RINA team code
		    
			//if ($scope.errors.length > 0) {
			var contentElement = $scope.VM.contentElement;
	
			var content = contentElement[0].querySelector('#' + $scope.contentDiv);
	
			// RINA team code
		    var elementId = errors[0].id;
		    var elementIdToFocus = errors[0].id_focus;
		    // End RINA team code
	
			var elm = contentElement[0].querySelector('#' + elementId);
	
			if (elm == null)
				alert('Error tagged element ' + elementId + ' does not exist!!! ');
			else
			{
				anchorSmoothScroll.scrollTo(elm, content, $scope);
	
		        // RINA team code - adaugat $timeout de ionut
		        $timeout(function(){
		          var elmToFocus = contentElement[0].querySelector('#' + elementIdToFocus);
		          
		          focusElement(elm, elmToFocus);
		        }, 200);
		        // End RINA team code
			}
	    }, 1000);
	}
	
	$scope.getMetaModel = function()
	{
		return $scope.metaModel;
	};
	
	// Print the Form
	$scope.printFormMET = function(elem, title)
	{
		//console.log(printSection);
		//console.log(elem);
		//console.log(title);
		
		$timeout(function () {
            //printElementMET($element[0]);
			printElementMET(elem, title);
          }, 1000);
	};
	
	function printElementMET(elem, title) 
	{
		// clones the element you want to print
		var domTitleClone = title.cloneNode(true);
	    var domClone = elem.cloneNode(true);
	    printSection.innerHTML = '';
	    
	    printSection.appendChild(domTitleClone);
	    printSection.appendChild(document.createElement("br"));  
	    printSection.appendChild(document.createElement("br"));
	    printSection.appendChild(domClone);
	   
	    window.print();
	};

	$scope.radioClearSelection = function($section,param,event) {
		// this does the trick, but is weired :)
		if ($section[param] == event.target.value)
			$section[param] = null;
	};
}

//RINA team code - added by ionut
function getDelay(sectionNo) { //we will calibrate this better in time
	if (sectionNo <= 3)
	    return 0;
	return (sectionNo - 3) * 1000;
}

// This is needed it because Internet Explorer lack of support
function adinfoPathCutSlashesFromEnd(path, offset) {
	var pathArray = path.split( '/' );

	var newPath = ""; 
	for (var i = 1; i < pathArray.length - offset; i++) {
		newPath += pathArray[i];
		newPath += "/";
	}

	return newPath;
}

/* AdInfo: on-click display the following information in a new window:
 * o	dataTypeTechName - ?
 * o	DictionaryEntryName - ?
 * o	pattern - meta
 * o	minLength - meta
 * o	maxLength - meta
 * o	minInclusive - meta
 * o	maxInclusive - meta
 */
function adinfoDetailsFunction(adInfoData) {
	// Construct the URL for adinfo html page.
	var parrent_folder = adinfoPathCutSlashesFromEnd(window.location.pathname, 1);

	var adinfo_location_html;
	if ( window.location.protocol == "file:" )
		// local
		adinfo_location_html = window.location.protocol + "///" + parrent_folder + "_forms/adinfo/";

	else {
		// remote
		var port = window.location.port;
		var host = window.location.host;
		adinfo_location_html = window.location.protocol + "//" + host + ":" + port + "/static/" + parrent_folder + "_forms/adinfo/";
	}

	var adinfo_html = "adinfo.html";
	var adinfo_url = adinfo_location_html + adinfo_html + "?" + adInfoData;
	var adinfo_name = 'AdditionalInfo';
	var adinfo_windowFeatures = 'width=640,height=240';
	var adinfoDetailsWindow = window.open( adinfo_url, adinfo_name, adinfo_windowFeatures );  
}

/**
 * Copies source into destination field by field; navigating the object internal
 * structure. Thus it preserves ordering of fields. Typically the destination
 * would be a copy of an ever-empty sed, formated to reflect the proper ordering
 * of the section/attributes, so that it does not collide with XSD sequences
 *
 * @param destination
 * @param source
 */
SedController.copy = function(scope, destinationParent, sDestinationItemName, source)
{
	if (!destinationParent.hasOwnProperty(sDestinationItemName))
	{
		Utils.assertFailed("Structure not ready to receive value");

		// shallow-copy in case the source did not have a structure; possibly
		// we may need a deep copy here; we may need to guard that source is not
		// null
		destinationParent[sDestinationItemName] = source;

		return;
	}
	var destination = destinationParent[sDestinationItemName];
	
	SedController.copyX(scope, destination, source);
}

/**
 * deep-copies data destination <- source without removing structural elements
 * that exist in the destination. This is needed because of two way binding of
 * Angular and because we need to bind each section-div (that is nested
 * arbitrarily deep) directly on the corresponding SED data-sections within the
 * SED structure, that is because we have to use relative-paths; absolute paths
 * do not work with ng-repeat
 */
SedController.copyX = function(scope, destination, source)
{
	if (destination == null)
	{
		Utils.assertFailed("Destination cannot be null. "
			+ "Use SedController.copy() instead if you expect thet destination could be null");
	}

	if (source == null)
		return;

	for ( var sItemName in source)
	{
		if (SedController.isInvisibleItem(sItemName))
			continue;

		if (!destination.hasOwnProperty(sItemName))
		{
			destination[sItemName] = source[sItemName];
			continue;
		}
		var elem = source[sItemName];

		if (elem == null)
		{
			destination[sItemName] = null;
		} 
		else if (typeof elem !== "object" || Utils.getClassName(elem) == 'Date')
		{
			destination[sItemName] = elem;
//			if (Utils.getClassName(elem) == 'Date')
//			{
//				// UTC issue; commenting out the below element; date must pass as is otherwise we loose data neutrality
//				//destination[sItemName] = elem.toISOString();
//				destination[sItemName] = elem;
//			}
//			else
//				destination[sItemName] = source[sItemName];
			
		} 
		else if (elem instanceof Array)
			SedController.copyArray(scope, destination, sItemName, source[sItemName]);
		else
		{
			// Enums are stored internally as a concatenated value (each individual value is delimitated from the next one with a special marker:$)
			// When performing the data transfer from the forms, the read operation needs to transform these values into value arrays, and the write
			// operation needs to transform the value arrays into a single concatenated value
			// Ex: 
			//	- "nationality": "AT$BE$" will be transformed at read into "nationality": { "value": ["AT","BE"] }
			//  - "nationality": { "value": ["AT","BE"] } will be transformed at write into "nationality": "AT$BE$" 
			
			// Identify if the source is an enum with value as array (and only that property)
			if(elem.hasOwnProperty("value") && Object.keys(elem).length == 1)
			{
				var value = elem["value"];
				
				if(value instanceof Array)
				{
					// Check if there is an empty value array possible from an InitialJson
					// in the form of "element": { "value": [] } - NA for the moment
					//if(value.length == 0)
					//	continue;
					
					// Check if it is not a regular array (repeatable section)
					if(value.length>0)
						if(typeof value[0] != "object")
						{
							// Transform the value array into a concatenated value
							var sVal = "";
							for (var i = 0; i < value.length; ++i)
								sVal += value[i]+"$";
							
							destination[sItemName] = sVal;

							continue;
						}
				}
				// Enum with single element (Ex:  { "value": "AT" })
				else /*if(!destination[sItemName].hasOwnProperty("value"))*/
				{
					/*
					console.log("SINGLE ENUM");
					*/
					destination[sItemName] = value+"$";
					continue;
				}
			}
			
			SedController.copy(scope, destination, sItemName, elem);
		}
	}
	// cleaning up the properties that are not in the source
	for ( var sItem in destination)
	{
		if (SedController.isInvisibleItem(sItem))
			continue;

		if (!source.hasOwnProperty(sItem))
			Model.clearJsonItem(scope, destination, sItem);
	}
}

/**
 * some items within JSON objects are meant for state tracking and they should
 * not participate in evaluation of having-data, or copying
 */
SedController.isInvisibleItem = function(sItemName)
{
	if (sItemName === "$$hashKey")
		return true;

	return false;
}

SedController.copyArray = function(scope, destinationParent, sDestItemName, arrSource)
{
	if (!destinationParent.hasOwnProperty(sDestItemName))
		Utils.assertFailed("Structural mismatch - Array property <" + sDestItemName + "> missing")

	var arrDest = destinationParent[sDestItemName];

	if (!(arrDest instanceof Array) || arrDest.length === 0)
		Utils.assertFailed("Structural mismatch - Array property <" + sDestItemName
			+ "> is present but is not an Array")

		// we obtain the first element to use as structural template
	var templateArrayItem = arrDest[0];

	// we clear the array
	arrDest.length = 0;

	for (var i = 0; i < arrSource.length; ++i)
	{
		var newElement = angular.copy(templateArrayItem);
		SedController.copyX(scope, newElement, arrSource[i]);
		arrDest.push(newElement);
	}
}

/**
 * Update elements with fixedValue from source model based on meta_model.
 */
function MMNavUpdateElemFixedValue(scope)
{
	MMNavigator.call(this, scope);

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		//nothing to do: an Object cannot have a fixedValue
	}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		if (metaModelParent[sItem].$$fixedValue && Utils.isEmptyString(jsonParent[sItem]))
			jsonParent[sItem] = metaModelParent[sItem].$$fixedValue;
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		//nothing to do!
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		//nothing to do: an Object cannot have a fixedValue
	}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		if (metaModelArray[nItem].$$fixedValue)
			jsonParent[nItem] = metaModelParent[nItem].$$fixedValue;
	}
}
MMNavUpdateElemFixedValue.prototype = Object.create(MMNavigator.prototype);
MMNavUpdateElemFixedValue.prototype.constructor = MMNavUpdateElemFixedValue;

SedController.updateFixedValueElem = function(scope, source, meta_model)
{
	var nav = new MMNavUpdateElemFixedValue(scope);
	MetaModel.navigateContent(scope.model, meta_model, nav, 0);
}

/**
 * NOTE: this method is a static of SedController but it is added to scope and
 * it is used as a ng-click event handler; it will be called as a scope method,
 * meaning that it will have a "this" set on the local scope. As local scopes
 * inherit prototypically from outer scopes and eventually from the global
 * scope, this gives access to the most broad representation of the "scope"
 *
 * @param arrayToCloneInto
 * @param arrayToCloneIntoMeta
 * @param $index
 */
SedController.addSection = function(arrayToCloneInto, arrayToCloneIntoMeta, $index)
{
	if (!arrayToCloneInto[$index]) {
		//Utils.assertFailed("Cannot find the given section in the array to clone");
		SedController.dialogsSvc.OKDialog("Cannot find the given section in the array to clone");
		return;
	}

	var obj = angular.copy(arrayToCloneInto[$index]);
	var meta = angular.copy(arrayToCloneIntoMeta[$index]);
	// Adaptation for the newly added section to be displayed immediately
	meta.$$shouldLoadSection = true;
	Model.clearJsonObject(this, obj);
	arrayToCloneInto.splice($index + 1, 0, obj);
	arrayToCloneIntoMeta.splice($index + 1, 0, meta);

	// inserting a corresponding metaSection
	MetaModel.updateStructure(this, false);
	MetaModel.updateElemNo(this, true);
	
	// auto scroll down to display the new section - RINA only scroll
	var that = this;
    SedController.$timeout(function()
    {
    	var position = iterate(obj, 0);
    	var newElementNo = parseInt(arrayToCloneIntoMeta[$index].$$uuid) + position;
    	var newElement = { id: "field_" + newElementNo };
    	SedController.anchorSmoothScroll.scrollTo(newElement, null, that);
    }, 200);
}

function iterate(obj, size) 
{
	var size = size;
	for (var property in obj) 
	{
		if (obj.hasOwnProperty(property)) 
		{
			if (typeof obj[property] == "object") 
			{
				size++;
				size = iterate(obj[property], size);
			} 
			else 
			{
				size++;
			}
		}
	}
	return size;
}

/**
 * NOTE: see the note from addSection(); it applies also here
 */
SedController.removeSection = function(arrayToRemoveFrom, arrayToRemoveFromMeta, $index)
{
	if (!arrayToRemoveFrom[$index]) {
		//Utils.assertFailed("Cannot find the given section in the array to remove");
		SedController.dialogsSvc.OKDialog("Cannot find the given section in the array to remove");
		return;
	}

	if (arrayToRemoveFrom.length == 1) {
		//Utils.assertFailed("You cannot have an empty section. There needs to be at least one entry in it");
		SedController.dialogsSvc.OKDialog("You cannot have an empty section. There needs to be at least one entry in it");
		return;
	}

	arrayToRemoveFrom.splice($index, 1);
	arrayToRemoveFromMeta.splice($index, 1);

	// removing the corresponding metaSection
	MetaModel.updateStructure(this, false);
	MetaModel.updateElemNo(this, true);
}


function Model()
{
	this._DUMMY = null;
}

/**
 * Cleans a json object. If the parameter is not an object (with inner
 * attributes) but a primitive, it will not clean it. In such case use
 * cleaJsonItem() instead
 */
Model.clearJsonObject = function(scope, json)
{
	var nav = new MNavClean(scope);
	Model.navigateContent(json, nav, 0);
}

/**
 * Cleans a json "item" (either an object, array or a primitive child of the
 * parent).
 */
Model.clearJsonItem = function(scope, jsonParent, sItem)
{
	var nav = new MNavClean(scope);
	Model.navigateItem(jsonParent, sItem, nav, 0);
}

Model.hasData = function(json)
{
	var nav = MNavHasData();
	if (typeof json != "object" || Utils.getClassName(json) == "Date")
		nav.onValue(json);
	else
		Model.navigateContent(json, nav, 0);
	return nav.m_bHasData;
}

Model.navigateContent = function(jsonParent, navigator, nLevel)
{
	for ( var sItem in jsonParent)
	{
		Model.navigateItem(jsonParent, sItem, navigator, nLevel);
	}
}

Model.navigateItem = function(jsonParent, sItem, navigator, nLevel)
{
	if (sItem === "sedPackage" || sItem === "sedGVer" || sItem === "sedVer" || sItem.indexOf("$$") == 0)
		return;

	var json = jsonParent[sItem];

	/*
	 * if (json == null) return;
	 */
	if (json == null)
		navigator.onNull(jsonParent, sItem, json, nLevel);

	if (typeof json === "object")
	{
		if (Utils.getClassName(json) === "Date")
		{
			navigator.onValue(jsonParent, sItem, json, nLevel);
			return;
		}
		if (json instanceof Array)
		{
			navigator.onArray(jsonParent, sItem, json, nLevel);
			Model.navigateArray(json, navigator, nLevel);
			return;
		}
		navigator.onObject(jsonParent, sItem, json, nLevel);
		Model.navigateContent(json, navigator, nLevel + 1);
		return;
	}
	navigator.onValue(jsonParent, sItem, json, nLevel);
}

Model.navigateArray = function(jsonArray, navigator, nLevel)
{
	for ( var i in jsonArray)
	{
		Model.navigateArrayItem(jsonArray, i, navigator, nLevel);
	}
}

Model.navigateArrayItem = function(jsonArray, nItem, navigator, nLevel)
{
	var json = jsonArray[nItem];

	if (json == null)
		return;

	if (typeof json === "object")
	{
		if (Utils.getClassName(json) === "Date")
		{
			navigator.onArrayValue(jsonArray, nItem, json, nLevel);
			return;
		}
		if (json instanceof Array)
			Utils.asserFailed("Array on array not supported");

		navigator.onArrayObject(jsonArray, nItem, json, nLevel);
		Model.navigateContent(json, navigator, nLevel + 1);
		return;
	}
	navigator.onArrayValue(jsonArray, nItem, json, nLevel);
}

function MNavigator(scope)
{
	this._SCOPE = scope;
	if (this._SCOPE == null)
		Utils.assertFailed("All navigators should specify the scope variable");

	this.onValue = function(jsonParent, sItem, json, nLevel)
	{}

	this.onNull = function(jsonParent, sItem, json, nLevel)
	{}

	this.onArray = function(jsonParent, sItem, jsonArray, nLevel)
	{}

	this.onObject = function(jsonParent, sItem, json, nLevel)
	{}

	this.onArrayValue = function(jsonArray, nItem, json, nLevel)
	{}

	this.onArrayObject = function(jsonArray, nItem, json, nLevel)
	{}
}

function MNavClean(scope)
{
	MNavigator.call(this, scope);

	this.onNull = function(jsonParent, sItem, json, nLevel)
	{
		jsonParent[sItem] = null;
	}

	this.onValue = function(jsonParent, sItem, json, nLevel)
	{
		jsonParent[sItem] = null;
	}

	this.onArray = function(jsonParent, sItem, jsonArray, nLevel)
	{
		// keeping one and only one element in the array
		jsonArray.length = 1;
	}

	this.onArrayValue = function(jsonArray, nItem, json, nLevel)
	{
		jsonArray[nItem] = null;
	}
}
MNavClean.prototype = Object.create(MNavigator.prototype);
MNavClean.prototype.constructor = MNavClean;

function MNavHasData(scope)
{
	MNavigator.call(this, scope);

	this.m_bHasData = false;

	this.onValue = function(jsonParent, sItem, json, nLevel)
	{
		if (!Utils.isEmptyString(json.toString()))
			this.m_bHasData = true;
	}

	this.onArrayValue = function(jsonArray, nItem, json, nLevel)
	{
		if (!Utils.isEmptyString(json.toString()))
			this.m_bHasData = true;
	}

}
MNavHasData.prototype = Object.create(MNavigator.prototype);
MNavHasData.prototype.constructor = MNavHasData;


/**
 * This is used when obtaining the the content of the model as a JSON string. At this point we do not want angular to 
 * convert the dates; the angular.toJson() converts the dates via Date.toISOString() which may result in a "yesterday" date for timezones
 * at the right of the UTC (UTC + N with N > 0). This is because the java script Date includes time info; when the datePicker1 reads the 
 * date, the time-part is automatically supplied as 00:00:00.000, which in turn when converted to ISO string will become the UTC time, 
 * decrementing the date with the local UTC offset, enter the "yesterday" date. What we do, we supply our own conversion and replace the 
 * Date with the string <yyyy-MM-dd>. A later serialization via angular.toJson() will preserve the date as a string.
 */
function MNavBeforeToString(scope)
{
	MNavigator.call(this, scope);

	this.onValue = function(jsonParent, sItem, json, nLevel)
	{
		if (json != null && Utils.getClassName(json) === "Date")
		{
			jsonParent[sItem] = Utils.toSimpleLocalDateString(json); 
		}
	}
}
MNavBeforeToString.prototype = Object.create(MNavigator.prototype);
MNavBeforeToString.prototype.constructor = MNavBeforeToString;


/**
 * paralel navigation on a json object and its meta-model
 */
function MetaModel()
{
	this._DUMMY = null;
}

MetaModel.updateStructure = function(scope, bResetChoices)
{
	var nav = new MMNavUpdateStructure(scope, bResetChoices);
	MetaModel.navigateContent(scope.model, scope.metaModel, nav, 0);
	
	// When the structure is updated, reset the whole UUIDs
	Utils.UUID = 0;
	var navReset = new MMNavResetUUIDs(scope);
	navReset.resetLocalUUIDs(scope.META_MODEL);
	MetaModel.navigateContent(scope.model, scope.metaModel, navReset, 0);
	
	// To force the $$visible flags to be set for choices
	MetaModel.updateFlags(scope);
}

MetaModel.updateElemNo = function(scope, bResetChoices)
{
	// To force the $$visible flags to be set for choices
	//MetaModel.updateFlags(scope);
	
	var nav = new MMNavUpdateElemNo(scope, bResetChoices);
	MetaModel.navigateContent(scope.model, scope.metaModel, nav, 0);
	
	var sed = null;
	for ( var sItem in scope.metaModel)
	{
		var json = scope.metaModel[sItem];
		// Get the SED
		if (typeof json === "object")
			if(json.$$type == "sed") {
				sed = json;
				break;
			}
	}
	if(sed != null)
		sed.$$elementsNo = nav.m_elementsNo;
	//scope.metaModel.$$elementsNo = nav.m_elementsNo;
	
}

MetaModel.updateFlags = function(scope)
{
	var navRead = new MMNavUpdateFlags(scope);
	MetaModel.navigateContent(scope.model, scope.metaModel, navRead, 0);
}

MetaModel.validate = function(scope)
{
	MetaModel.updateFlags(scope);
	
	var navValidate = new MMNavValidate(scope);
	MetaModel.navigateContent(scope.model, scope.metaModel, navValidate, 0);
	// Uncomment if using pagination for the error list to insert padding
	// elements
	// Utils.padErrorList();
}

MetaModel.readData = function(scope, bReadFullData)
{
	MetaModel.validate(scope);
	var model = angular.copy(scope.model);
	var navRead = new MMNavRead(scope, bReadFullData);
	MetaModel.navigateContent(model, scope.metaModel, navRead, 0);
	return model;
}

MetaModel.readDataAsString = function(scope, bReadFullData)
{
	var json = MetaModel.readData(scope, bReadFullData);
	var navBeforeToString = new MNavBeforeToString(scope);
	Model.navigateContent(json, navBeforeToString, 0);
	var sJson = angular.toJson(json, true);
	return sJson;
}

MetaModel.navigateContent = function(json, metaModel, navigator, nLevel)
{

	for ( var sItem in json)
	{
		MetaModel.navigateItem(json, sItem, metaModel, navigator, nLevel);
	}
}

MetaModel.navigateArray = function(jsonArray, metaModelParent, metaModelArray, navigator, nLevel)
{
	for (var i = 0; i < jsonArray.length; ++i)
	{
		MetaModel.navigateArrayItem(jsonArray, i, metaModelParent, metaModelArray, navigator, nLevel);
	}
}

MetaModel.navigateItem = function(jsonParent, sItem, metaModel, navigator, nLevel)
{
	if (sItem === "sedPackage" || sItem === "sedGVer" || sItem === "sedVer" || sItem.indexOf("$$") == 0)
		return;

	var json = jsonParent[sItem];

	if (typeof json === "object")
	{
		// null is an object (not a value)
		if (json == null)
		{
			navigator.onValue(jsonParent, sItem, null, metaModel, nLevel);
			return;
		}
		if (Utils.getClassName(json) === "Date")
		{
			navigator.onValue(jsonParent, sItem, json, metaModel, nLevel);
			return;
		}
		if (json instanceof Array)
		{
			navigator.onArray(jsonParent, sItem, json, metaModel, nLevel);
			MetaModel.navigateArray(json, metaModel, metaModel[sItem], navigator, nLevel);
			return;
		}
		navigator.onObject(jsonParent, sItem, json, metaModel, nLevel);

		MetaModel.navigateContent(json, metaModel[sItem], navigator, nLevel + 1);

		navigator.onObjectEnd(jsonParent, sItem, json, metaModel, nLevel);

		return;
	}
	navigator.onValue(jsonParent, sItem, json, metaModel, nLevel);
}

MetaModel.navigateArrayItem = function(jsonArray, nItem, metaModelParent, metaModelArray, navigator, nLevel)
{
	var json = jsonArray[nItem];

	if (typeof json === "object")
	{
		if (Utils.getClassName(json) === "Date")
		{
			navigator.onArrayValue(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
			return;
		}
		if (json instanceof Array)
			Utils.asserFailed("Array on array not supported");

		navigator.onArrayObject(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);

		MetaModel.navigateContent(json, metaModelArray[nItem], navigator, nLevel + 1);

		navigator.onArrayObjectEnd(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);

		return;
	}
	navigator.onArrayValue(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
}

MetaModel.toString = function(scope)
{
	var navPrint = new MMNavPrint(scope);
	MetaModel.navigateContent(scope.model, scope.metaModel, navPrint, 0);
	return navPrint.m_sText;
}

MetaModel.hasData = function(json)
{
	if (json == null)
		return false;

	if (typeof json === "string")
		return !Utils.isEmptyString(json);

	if (typeof json === "object" && Utils.getClassName(json) === "Date")
		return !Utils.isEmptyString(json.toString());

	if (typeof json === "boolean")
	{
		if (json === false || json === true)
			return true;

		if (json === null)
			return false;

		return false;
	}
	if (typeof json === "object")
	{
		for ( var sItem in json)
		{
			if (SedController.isInvisibleItem(sItem))
				continue;

			var item = json[sItem];
			if (MetaModel.hasData(item))
				return true;
		}
		return false;
	}
	Utils.assertFailed("Do not know how to handle this case");
	return false;
}

function MMNavigator()
{
	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}

	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}
}

/** Get heading No - used in construction of indexed header. */
function getHeaderNoFromHeading(metaModelParent, metaModel) {
	if ( !metaModelParent.$$heading ) {
			if ( metaModel.$$heading )
				return metaModel.$$heading + ".";
			else
				return "";
	}

	var pHeaderLen = metaModelParent.$$heading.length;
	var header = metaModel.$$heading;
	var headerNo = header.substr(pHeaderLen+1);

	return header.substr(pHeaderLen+1) + ".";
}

/** Set indexed header according to array (addSection). */
function updateHeaderIndexed( metaModelParent, metaModel, nItem) {
	// sentinel: if there is no headingIndexed, just ignore the item (this should happen only for root)
	if (!metaModel.$$headingIndexed)
		return;

	var headingIndexedParent = metaModelParent.$$headingIndexed ? metaModelParent.$$headingIndexed : "";
	var headerNo = getHeaderNoFromHeading (metaModelParent, metaModel);
	var counter = "";

	if (metaModel.$$headingIndexed) {
		if ( metaModel.$$repetition ) {
			metaModel.$$headingIndex = nItem + 1;
			counter = "[" + metaModel.$$headingIndex + "].";
		}

		metaModel.$$headingIndexed = headingIndexedParent + headerNo + counter;
		metaModel.$$headingIndexSet = true;

		// Print the index
		//console.log(metaModel.$$headingIndexed + " " + metaModel.$$name);
	}
}

function MMNavUpdateStructure(scope, bResetChoices)
{
	MMNavigator.call(this, scope);

	this.m_bResetChoices = bResetChoices;
	if (this.m_bResetChoices == null)
		this.m_bResetChoices = false;

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		// parent initialization
		metaModel.$$parent = metaModelParent;
		
		// RINA lazy loading propagation
		if(metaModel.$$shouldLoadSection)
			metaModel.$$parent.$$shouldLoadSection = true;
		
		// clearing flags
		metaModel.$$hasData = false;
		metaModel.$$error = null;

		// clear also the value
		metaModel.$$value = null;
		
		//console.log(metaModel.$$name);
		
		// choice initialization
		this._checkUpdateChoice(metaModel, json);
		
		// path as name
		if(metaModel.$$parent && metaModel.$$parent.$$path)
			metaModel.$$path = metaModel.$$parent.$$path + "/" + metaModel.$$name;
		else
			metaModel.$$path = metaModel.$$name;

		// we see that injecting a JS OBJECT that has been deserialized, produces strings instead of dates. That means that when we serialize
		// deserialize we loose data neutrality. But the deserialization is the task of RINA. We receive string because they pass it as such. 
		// Thus we are in doubt if to touch these transformations or not. For the time being we keep this commented out
//		if (metaModel.$$type === 'date' && json != null && typeof json === "string")
//		{
//			jsonParent[sItem] = new Date(json);
//		}

		updateHeaderIndexed(metaModelParent, metaModel, 0);
	}

	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		metaModelParent[sItem].$$value = null;

		this.onObject(jsonParent, sItem, json, metaModelParent, nLevel);
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		// part 1 of updating the array - setting it at equal lengt with the
		// json array
		var metaModelArray = metaModelParent[sItem];
		// console.log("OnArray Entry: Old length is:"+metaModelArray.length);

		if (metaModelArray == null)
			Utils.assertFailed("Cannot find in the original meta-model structure the array called '" + sItem + "'");

		metaModelArray.length = json.length;
		
		// console.log("New length is:"+metaModelArray.length);
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		// part 2 of updating the array
		if (metaModelArray[nItem] == null)
		{
			var newMetaModel = angular.copy(metaModelArray[nItem - 1]);
			this._resetChoices(json, newMetaModel);
			this._resetUUIDs(json, newMetaModel);
			metaModelArray[nItem] = newMetaModel;
		}

		this.onArrayValue(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);

	}

	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		if (!metaModel)
			return;

		// parent initialization
		metaModel.$$parent = metaModelParent;
		
		// clearing flags
		metaModel.$$hasData = false;
		metaModel.$$error = null;

		// choice initialization
		this._checkUpdateChoice(metaModel, json);
		
		// path as [] for array entries
		if(metaModel.$$parent && metaModel.$$parent.$$path)
			metaModel.$$path = metaModel.$$parent.$$path + "/" + metaModel.$$name + "[" + nItem + "]";
		else
			metaModel.$$path = metaModel.$$name + "[" + nItem + "]";

		updateHeaderIndexed(metaModelParent, metaModel, nItem);
	}

	this._checkUpdateChoice = function(metaModel, json)
	{
		// choice initialization
		if (metaModel.hasOwnProperty("$$choice"))
		{
			// the metaModel.$$parent must be equipped with a $$choice_<N>
			// variable
			// where N = metaModel.$$choice (the index of the choice-group)
			var metaModelParent = metaModel.$$parent;
			var sChoice = "$$choice_" + metaModel.$$choice;
			var sChoiceHasRealData = "$$choice_" + metaModel.$$choice+"_realData";
			// observe the logic of things: first ensure that there is
			// $$choice_<N> var
			// then set it to reflect this-field choice-index if the json has
			// data
			// but if it doesn't and yet is set then reset it.
			
			// V1
			/* 
			if (!metaModelParent[sChoice])
				metaModelParent[sChoice] = 0;
			//if (MetaModel.hasData(json))
			if (MetaModel.hasData(json) && !metaModelParent[sChoice])
				metaModelParent[sChoice] = metaModel.$$choiceIndex;
			else if (this.m_bResetChoices && metaModelParent[sChoice] == metaModel.$$choiceIndex)
				metaModelParent[sChoice] = 0;
			*/
			
			// V2 - rewrite to see more clearly the logic
			var hasData = MetaModel.hasData(json);
			var isUndefined = (metaModelParent[sChoice] === undefined);
			if(metaModelParent[sChoiceHasRealData] === undefined)
				metaModelParent[sChoiceHasRealData] = false;
			
			if(isUndefined)
				metaModelParent[sChoice] = 0;
			
			// If first choice, reset the choiceHasRealData flag (only LOCAL, in RINA the JSON with data is used at very edit/update)
			if(typeof LOCAL_MET_COMMON_JS != 'undefined')
				if (metaModel.$$choiceIndex == 0)
					metaModelParent[sChoiceHasRealData] = false;
				
			// Leave this commented to solve a choice selection issue in RINA; for local form will use the above fix
			//if (metaModel.$$choiceIndex == 0)
			//	metaModelParent[sChoiceHasRealData] = false;
			
			// If there is data in this choice depth
			if(hasData)
			{
				// If there was not a previous choice with data, set it to point to this one 
				if(!metaModelParent[sChoiceHasRealData])
				{
					metaModelParent[sChoice] = metaModel.$$choiceIndex;
					metaModelParent[sChoiceHasRealData] = true;
				}
			}
			//If there is NOT data in this choice depth
			else
			{
				if (this.m_bResetChoices && metaModelParent[sChoice] == metaModel.$$choiceIndex)
					metaModelParent[sChoice] = 0;
			}
		}
	}

	this._resetChoices = function(json, metaModel)
	{
		var navReset = new MMNavResetChoices(scope);
		navReset.resetLocalChoices(metaModel);
		MetaModel.navigateContent(json, metaModel, navReset, 0);
	}

	this._resetUUIDs = function(json, metaModel)
	{
		var navReset = new MMNavResetUUIDs(scope);
		navReset.resetLocalUUIDs(metaModel);
		MetaModel.navigateContent(json, metaModel, navReset, 0);
	}

}
MMNavUpdateStructure.prototype = Object.create(MMNavigator.prototype);
MMNavUpdateStructure.prototype.constructor = MMNavUpdateStructure;

function MMNavUpdateElemNo(scope, bResetChoices)
{
	this.m_elementsNo = 0;
	
	MMNavigator.call(this, scope);

	this.m_bResetChoices = bResetChoices;
	if (this.m_bResetChoices == null)
		this.m_bResetChoices = false;

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
				
		if(metaModel.$$type != "sed" && metaModel.$$visible)
		{
			//console.log("Incrementing for object:");
			//console.log(metaModel);
			this.m_elementsNo++;
			
			metaModel.$$elemNo = this.m_elementsNo; 
		}
	}

	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		this.onObject(jsonParent, sItem, json, metaModelParent, nLevel);
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		// part 1 of updating the array - setting it at equal lengt with the
		// json array
		var metaModelArray = metaModelParent[sItem];
		// console.log("OnArray Entry: Old length is:"+metaModelArray.length);

		if (metaModelArray == null)
			Utils.assertFailed("Cannot find in the original meta-model structure the array called '" + sItem + "'");
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		this.onArrayValue(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
	}

	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		if (!metaModel)
			return;
	}
}
MMNavUpdateElemNo.prototype = Object.create(MMNavigator.prototype);
MMNavUpdateElemNo.prototype.constructor = MMNavUpdateElemNo;

function MMNavUpdateFlags(scope)
{
	MMNavigator.call(this, scope);

	this.m_invisibleChoiceSection = null;

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		metaModel.$$error = null;
		metaModel.$$efectivelyRequired = false;
		if (metaModel.$$type == "sed")
		{
			metaModel.$$hasData = true;
			metaModel.$$visible = true;
		} else
		{
			metaModel.$$hasData = false;
			metaModel.$$visible = metaModelParent.$$visible && !MMNavUpdateFlags.isUnselectedChoice(metaModel);
		}
	}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		this.updateFieldFlags(json, metaModelParent[sItem]);
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem]
		metaModel.$$error = null;
		metaModel.$$efectivelyRequired = false;
		metaModel.$$hasData = false;
		metaModel.$$visible = metaModelParent.$$visible && !MMNavUpdateFlags.isUnselectedChoice(metaModel);
	}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		this.updateFieldFlags(json, metaModelArray[nItem]);
	}

	/**
	 * called on fields (= primitive attributes)
	 */
	this.updateFieldFlags = function(json, metaModel)
	{
		// reset flags
		metaModel.$$error = null;
		metaModel.$$value = null;
		metaModel.$$hasData = false;
		metaModel.$$visible = metaModel.$$parent.$$visible && !MMNavUpdateFlags.isUnselectedChoice(metaModel);
		//metaModel.$$visible = true;

		if (!metaModel.$$visible)
			return;

		// below it infers if the json (a field) has data.
		if (typeof json === "string")
		{
			metaModel.$$hasData = !Utils.isEmptyString(json);
		} else if (typeof json === "object" && Utils.getClassName(json) === "Date")
		{
			metaModel.$$hasData = (json == null) ? false : !Utils.isEmptyString(json.toString());
		} else if (typeof json === "boolean")
		{
			metaModel.$$hasData = true;
		} else if (json == null)
		{
			//console.log(json);
			// this could happen when a control such as date-picker contains
			// invalid-formatted data
			metaModel.$$hasData = false;
			if (metaModel.$$type == 'date' && json === undefined)
				metaModel.$$value = "00-00-0000";
			return;
		}
		if (metaModel.$$hasData)
		{
			MMNavUpdateFlags._propagateHasData(metaModel.$$parent);
			metaModel.$$value = json;
			return;
		}
	}
}
MMNavUpdateFlags.prototype = Object.create(MMNavigator.prototype);
MMNavUpdateFlags.prototype.constructor = MMNavUpdateFlags;

MMNavUpdateFlags.isUnselectedChoice = function(metaModel)
{
	if (!metaModel.hasOwnProperty("$$choice"))
		return false;
	var metaModelParent = metaModel.$$parent;
	var sChoice = "$$choice_" + metaModel.$$choice;
	var bUnselectedChoice = metaModelParent[sChoice] != metaModel.$$choiceIndex;
	return bUnselectedChoice;
}

/**
 * goes from a primitive field upwards on $$parent relationship and sets the
 * flag $$hasData on true. This is because if the child has data then the parent
 * has data too. The reverse is not true (the parent may still have data even if
 * certain children do NOT)
 */
MMNavUpdateFlags._propagateHasData = function(metaModel)
{
	if (metaModel == null)
		return;
	metaModel.$$hasData = true;
	this._propagateHasData(metaModel.$$parent);
}

function MMNavPrint(scope)
{
	MMNavigator.call(this, scope);

	this.m_sText = "";

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		if (metaModel.$$parent !== metaModelParent)
			Utils.assertFailed("parent variable not properly set");
		this._print(metaModel, nLevel);
	}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		this.onObject(jsonParent, sItem, json, metaModelParent, nLevel);
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
	// this.onObject(jsonParent, sItem, json, metaModelParent, nLevel);
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		this._print(metaModel, nLevel);
	}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		this.onArrayObject(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
	}

	this._print = function(metaModel, nLevel)
	{
		this.m_sText += Utils.alignString(20, metaModel.$$heading);
		this.m_sText += Utils.alignString(5, nLevel);
		this.m_sText += metaModel.$$repetition ? " R" : "  ";
		this.m_sText += metaModel.hasOwnProperty("$$choice") ? Utils.alignString(8, this._getChoicePrint(metaModel))
			: "        ";
		this.m_sText += metaModel.$$mandatory ? " M" : "  ";
		this.m_sText += metaModel.$$effectivelyRequired ? "* " : "  ";
		this.m_sText += metaModel.$$hasData ? " D" : "  ";
		this.m_sText += !Utils.isEmptyString(metaModel.$$error) ? " <!>" : "    ";
		this.m_sText += metaModel.$$visible ? " V " : " I  ";
		this.m_sText += Utils.alignString(50, Utils.ms_wsTabPads[nLevel] + metaModel.$$name);
		if (metaModel.hasOwnProperty("$$value"))
			this.m_sText += metaModel.$$value;
		this.m_sText += '\r\n';
	}

	this._getChoicePrint = function(metaModel)
	{
		return " X" + metaModel.$$choice + "_" + metaModel.$$choiceIndex + "="
			+ metaModel.$$parent["$$choice_" + metaModel.$$choice];
	}

}
MMNavPrint.prototype = Object.create(MMNavigator.prototype);
MMNavPrint.prototype.constructor = MMNavPrint;

function MMNavValidate(scope)
{
	MMNavigator.call(this, scope);

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		if (metaModelParent[sItem].$$visible)
		{
			if (metaModelParent[sItem].$$type == 'section' || metaModelParent[sItem].$$type == 'sed')
				MMNavValidate.validateSection(scope, metaModelParent[sItem]);
			else
				MMNavValidate.validateField(scope, metaModelParent[sItem]);
		}
	}

	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		if (metaModelParent[sItem].$$visible)
			MMNavValidate.validateField(scope, metaModelParent[sItem]);
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		if (metaModelArray[nItem].$$visible)
			MMNavValidate.validateSection(scope, metaModelArray[nItem]);
	}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		if (metaModelArray[nItem].$$visible)
			MMNavValidate.validateField(scope, metaModelArray[nItem]);
	}
}
MMNavValidate.prototype = Object.create(MMNavigator.prototype);
MMNavValidate.prototype.constructor = MMNavValidate;

MMNavValidate.getFirstNonSectionElementId = function(jsonParent, sItem)
{
	var json = jsonParent[sItem];

	if (json == null)
		json = sItem;

	if (typeof json === "object")
	{

		if (!(json instanceof Array) && json.$$type != "section")
			return json.$$uuid;
		else
			for (var item in json)
			{
				// Avoid backwards loop
				if(item == '$$parent')
					continue;

				if (typeof json[item] === "object")
				{
					//console.log(json);
					//console.log(item);
					var id = MMNavValidate.getFirstNonSectionElementId(json, item);
					if (id)
					{
						return id;
						break;
					}
				}
			}
	}

	return;
}

MMNavValidate.validateSection = function(scope, metaModel)
{
	var bParentEffectivelyRequired = true;
	var metaModelParent = metaModel.$$parent;
	if (metaModelParent != null && metaModelParent.hasOwnProperty("$$effectivelyRequired"))
		bParentEffectivelyRequired = metaModelParent.$$effectivelyRequired;

	//metaModel.$$effectivelyRequired = MMNavValidate.isMandatory(metaModel, scope.validateAsDraft) && bParentEffectivelyRequired || metaModel.$$hasData

	// RINA team code added
	metaModel.$$effectivelyRequired = (MMNavValidate.isMandatory(metaModel, scope.validateAsDraft)
	    || MMNavValidate.isWarning(metaModel, scope.validateNonDraftAsWarnings))
	    && bParentEffectivelyRequired || metaModel.$$hasData

	metaModel.$$error = null;
	metaModel.$$warning = null;
	if (metaModel.$$effectivelyRequired && !metaModel.$$hasData)
		metaModel.$$error = ERROR_MESSAGES['section'] + " <" + metaModel.$$heading + "> "
			+ ERROR_MESSAGES['requiresdata'];

	if (metaModel.$$error && scope.validateForm)
	{
		scope.sedForm.$setValidity('error', false);
		// var errId = "section_"+metaModel.$$heading;
		var idToFocus = errId;

		var level = 0;
		// For now disable the focus on the first element of the section
		/*
		var id = MMNavValidate.getFirstNonSectionElementId(metaModelParent, metaModel);
		if (id)
			idToFocus = "field_" + id;
		*/
		
		var errId = "section_" + metaModel.$$uuid;
		
		// RINA team code
		var severity = MMNavValidate.isWarning(metaModel, scope.validateNonDraftAsWarnings) ? "Warning" : "Error";
		var level = metaModel.$$draftCritical ? 'draft':'full';
		var severityMessage;
		// MET Adaptation: if there is incorrect data inputed, even if the field is non critical, treat it as an error
	    if(severity == 'Error') {
	    	severityMessage = ERROR_MESSAGES['error'];
	    	level = 'draft';
		} else {
			severityMessage = ERROR_MESSAGES['warning'];
		}
		
		var errorInfo =
	    {
			id: errId,
			id_focus: idToFocus, 		// MET adaptation: Additional parameter needed for MET in order to implement the scrolling
										// to the first element field of an offending section
			message: metaModel.$$error,
			severity : severity,
			severityMessage : severityMessage,
			path: metaModel.$$path,
			level: level,
			source: 'client'
	    };
	    
		
		// End RINA team code
		
		/*
		var errorItem =
		{
			value : metaModel.$$error,
			id : errId,
			id_focus : idToFocus
		};
		*/

		scope.errors.push(errorInfo);
	}
}

MMNavValidate.validateField = function(scope, metaModel)
{
	var bParentEffectivelyRequired = true;
	var metaModelParent = metaModel.$$parent;
	if (metaModelParent != null)
		bParentEffectivelyRequired = metaModelParent.$$effectivelyRequired;

	metaModel.$$error = null;
	metaModel.$$warning = null;

	// First check for data presence
	if (bParentEffectivelyRequired && !metaModel.$$hasData && MMNavValidate.isMandatory(metaModel, scope.validateAsDraft) && !metaModel.$$isHidden)
	{
		metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
			+ ERROR_MESSAGES['requiresdata'];
	}
	// RINA team code
	else if (bParentEffectivelyRequired && !metaModel.$$hasData && MMNavValidate.isWarning(metaModel, scope.validateNonDraftAsWarnings) && !metaModel.$$isHidden)
	{
		metaModel.$$warning = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
	      + ERROR_MESSAGES['requiresdata'];
	}
	// End RINA team code
	
		
	// Then check for field format
	if (metaModel.$$value)
	{
		if (metaModel.$$type == 'int' && !Utils.isInt(metaModel.$$value))
			metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
				+ ERROR_MESSAGES['invalidformat'];

		if (metaModel.$$type == 'double' && !Utils.isDouble(metaModel.$$value))
			metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
				+ ERROR_MESSAGES['invalidformat'];

		// Check for date validity
		if (metaModel.$$type == 'date')
		{
			var date = new Date(metaModel.$$value);
			var day = date.getDate();
			var month = date.getMonth();
			var year = date.getFullYear();

			// This means that the DatePicker has invalidated the date
			if (metaModel.$$value == "00-00-0000")
				metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
					+ ERROR_MESSAGES['invalidformat'];
			else if (!Utils.isCorrectDate(day + "/" + month + "/" + year))
				metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
					+ ERROR_MESSAGES['invalidformat'];
		}
		
		// Check for latin characters restriction only after the other format restrictions have been checked 
		if(!metaModel.$$error)
		{
			if(!Utils.respectsLatin(metaModel.$$value))
				metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
				+ ERROR_MESSAGES['invalidformat'];
		}
		
		// No simple type error detected - check custom field restrictions
		// The following restrictions are supported:
		// - pattern (using Regular Expression)
		// - maxInclusive, minInclusive
		// - maxLength, minLength
		if(!metaModel.$$error && metaModel.$$hasRestrictions) 
		{
			//console.log(metaModel.$$name+" has the following restrictions ");
			//console.log(metaModel.$$restrictions);
			for ( var sItem in metaModel.$$restrictions)
			{
				if(!metaModel.$$restrictions[sItem])
					continue;
				
				// pattern
				if(sItem == "pattern")
				{
					var pattern = metaModel.$$restrictions[sItem];
						
					if(!Utils.respectsPattern(metaModel.$$value, pattern))
						metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
						+ ERROR_MESSAGES['norestriction'] + " \""+sItem+":" + pattern + "\"";
				}
				
				// maxLength/minLength
				if(sItem == "maxLength" || sItem == "minLength")
				{
					var minmaxLength = metaModel.$$restrictions[sItem];
										
					// Signal if the restriction is in wrong format
					if(!Utils.isInt(minmaxLength))
					{
						metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
						+ "has invalid non number format defined restriction" + " \""+sItem+":" + minmaxLength + "\"";
					}
					else
					{
						if(!Utils.respectsLength(metaModel.$$value, minmaxLength, sItem ))
							metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
							+ ERROR_MESSAGES['norestriction'] + " \""+sItem+":" + minmaxLength + "\"";
					}
				}
				
				// maxInclusive/minInclusive
				if(sItem == "maxInclusive" || sItem == "minInclusive")
				{
					var minmaxValue = metaModel.$$restrictions[sItem];
										
					// Signal if the restriction is in wrong format OR if the data is in wrong non-number format (should be catched by
					// the type verification, but just in case)
					var dataFormatOK = Utils.isInt(metaModel.$$value);
					var restrictionFormatOK = Utils.isInt(minmaxValue);
					if(!dataFormatOK || !restrictionFormatOK)
					{
						if(!dataFormatOK)
							metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
							+ ERROR_MESSAGES['invalidformat'];
						else
							metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
							+ "has invalid non number format defined restriction" + " \""+sItem+":" + minmaxValue + "\"";
					}
					else
					{
						if(!Utils.respectsInclusive(metaModel.$$value, minmaxValue, sItem ))
							metaModel.$$error = ERROR_MESSAGES['field'] + " <" + metaModel.$$heading + "> "
							+ ERROR_MESSAGES['norestriction'] + " \""+sItem+":" + minmaxValue + "\"";
					}
				}
				
			}
		}
			
	}

	// Added RINA team code for warning
	if ((metaModel.$$error || metaModel.$$warning) && scope.validateForm)
	{
		scope.sedForm.$setValidity('error', false);
		// var errId = "field_"+metaModel.$$heading;
		var errId = "field_" + metaModel.$$uuid;
		
		// RINA team code
		var severity = metaModel.$$error ? "Error" : "Warning";
	    var message = metaModel.$$error ? metaModel.$$error : metaModel.$$warning;
	    var level = metaModel.$$draftCritical ? 'draft':'full';
	    var severityMessage;
	    // MET Adaptation: if there is incorrect data inputed, even if the field is non critical, treat it as an error
	    if(severity == 'Error') {
	    	severityMessage = ERROR_MESSAGES['error'];
			level = 'draft';
		} else {
			severityMessage = ERROR_MESSAGES['warning'];
		}

	    var errorInfo =
	    {
			id: errId,
			id_focus: errId, 		// MET adaptation: Additional parameter needed for MET in order to implement the scrolling
									// to the first element field of an offending section
			message: message,
			severity : severity,
			severityMessage : severityMessage,
			path: metaModel.$$path,					
			level: level,
			source: 'client'
	    };
	    
		// End RINA team code
	    /*
		var errorItem =
		{
			value : metaModel.$$error,
			id : errId,
			id_focus : errId
		};
		*/

		scope.errors.push(errorInfo);
	}
}

// RINA team code
MMNavValidate.isWarning = function (metaModel, bValidateNonDraftAsWarning) 
{
	return metaModel.$$mandatory && !metaModel.$$draftCritical && bValidateNonDraftAsWarning;
}

MMNavValidate.isMandatory = function(metaModel, bValidateAsDraft)
{
	return metaModel.$$mandatory && !bValidateAsDraft || bValidateAsDraft && metaModel.$$draftCritical;
}

/**
 * copies the JSON from the "official" model and applies on it certain
 * opperations such as removing invisible sections, removing empty elements,
 * replacing nulls with "" (when reading full data)
 */
function MMNavRead(scope, bReadFullData)
{
	MMNavigator.call(this, scope);

	this.m_bReadFullData = bReadFullData;

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		if (!metaModel.$$visible)
		{
			delete jsonParent[sItem];
			return;
		}
		if (!this.m_bReadFullData && !metaModel.$$hasData)
		{
			delete jsonParent[sItem];
			return;
		}
	}

	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{}

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		if (!metaModel.$$visible)
		{
			delete jsonParent[sItem];
			return;
		}
		if (!this.m_bReadFullData && !metaModel.$$hasData)
		{
			delete jsonParent[sItem];
			return;
		}
		if (Utils.getClassName(json) === "Date")
		{
			//jsonParent[sItem] = Utils.toUTCNoonDate(json);
			// RINA team code - Reverted to the old version of date as a String and not as a Date object
			jsonParent[sItem] = Utils.toSimpleLocalDateString(json);
		}
		
		if (json == null)
			jsonParent[sItem] = "";
		
		// Enums are stored internally as a concatenated value (each individual value is delimitated from the next one with a special marker:$)
		// When performing the data transfer from the forms, the read operation needs to transform these values into value arrays, and the write
		// operation needs to transform the value arrays into a single concatenated value
		// Ex: 
		//	- "nationality": "AT$BE$" will be transformed at read into "nationality": { "value": ["AT","BE"] }
		//  - "nationality": { "value": ["AT","BE"] } will be transformed at write into "nationality": "AT$BE$" 
		if(metaModel.$$type == 'enum' && json != null && jsonParent[sItem] != "")
		{
			//console.log("enum: "+metaModel.$$name);
			//console.log("enum data: "+json);
			
			var parts = json.split("$");
			//console.log("Number of values:"+parts.length);
						
			var value = [];
			for (var i = 0; i < parts.length; ++i)
			{
				var elem = parts[i];
				
				if(elem != "" && elem != undefined)
					value.push(elem);
			}
						
			//console.log(value);
			
			//if(value.length>1)
				jsonParent[sItem] = { "value":value };
			//else
			//	jsonParent[sItem] = { "value":value[0] };
		}
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModelArray = metaModelParent[sItem];
		if (MMNavUpdateFlags.isUnselectedChoice(metaModelArray[0]))
		{
			delete jsonParent[sItem];
			return;
		}
		if (!this.m_bReadFullData && !MetaModel.hasData(json))
		{
			delete jsonParent[sItem];
			return;
		}

	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		if (!metaModel.$$visible)
			jsonArray.splice(nItem, 1);
	}

	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		this.onArrayObject(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
	}
}
MMNavRead.prototype = Object.create(MMNavigator.prototype);
MMNavRead.prototype.constructor = MMNavRead;


//function MMNavRemoveInvisibles(scope)
//{
//	MMNavigator.call(this, scope);
//
//	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		var metaModel = metaModelParent[sItem];
//		if (!metaModel.$$visible)
//		{
//			delete jsonParent[sItem];
//			return;
//		}
//	}
//
//	this.onObjectEnd = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{}
//
//	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		this.onObject(jsonParent, sItem, json, metaModelParent, nLevel);
//	}
//
//	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		var metaModelArray = metaModelParent[sItem];
//		if (MMNavUpdateFlags.isUnselectedChoice(metaModelArray[0]))
//			delete jsonParent[sItem];
//	}
//
//	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{
//		var metaModel = metaModelArray[nItem];
//		if (!metaModel.$$visible)
//			jsonArray.splice(nItem, 1);
//	}
//
//	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{}
//
//	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{
//		this.onArrayObject(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel);
//	}
//}
//
//MMNavRemoveInvisibles.prototype = Object.create(MMNavigator.prototype);
//MMNavRemoveInvisibles.prototype.constructor = MMNavRemoveInvisibles;

//function MMNavClearEmptyElems(scope)
//{
//	MMNavigator.call(this, scope);
//
//	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		if (!metaModelParent[sItem].$$hasData)
//			delete jsonParent[sItem];
//	}
//
//	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		if (metaModelParent[sItem].$$hasData)
//			return;
//		delete jsonParent[sItem];
//	}
//
//	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
//	{
//		var metaModelArray = metaModelParent[sItem];
//		if (MMNavClearEmptyElems.isEmptyArray(metaModelArray))
//			delete jsonParent[sItem];
//	}
//
//	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{
//		if (!metaModelArray[nItem].$$hasData)
//			jsonArray.splice(nItem, 1);
//	}
//
//	this.onArrayObjectEnd = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{}
//
//	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
//	{
//		if (!metaModelArray[nItem].$$hasData)
//			jsonArray.splice(nItem, 1);
//	}
//
//}
//
//MMNavClearEmptyElems.prototype = Object.create(MMNavigator.prototype);
//MMNavClearEmptyElems.prototype.constructor = MMNavRemoveInvisibles;
//
//MMNavClearEmptyElems.isEmptyArray = function(metaModelArray)
//{
//	for (var i = 0; i < metaModelArray.length; ++i)
//		if (metaModelArray[i].$$hasData)
//			return false;
//	return true;
//}

function MMNavResetChoices(scope)
{
	MMNavigator.call(this, scope);

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];
		this.resetLocalChoices(metaModel);
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		this.resetLocalChoices(metaModel);
	}

	this.resetLocalChoices = function(metaModel)
	{
		for ( var sElem in metaModel)
		{
			if (sElem.indexOf("$$choice_") == 0)
				metaModel[sElem] = 0;
		}
	}
}
MMNavResetChoices.prototype = Object.create(MMNavigator.prototype);
MMNavResetChoices.prototype.constructor = MMNavResetChoices;

function MMNavResetUUIDs(scope)
{
	MMNavigator.call(this, scope);

	this.onValue = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];

		if (!metaModel)
			Utils.assertFailed("Cannot find in the original meta-model structure the value called <'" + sItem + "'");

		this.resetLocalUUIDs(metaModel);
	}

	this.onObject = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModel = metaModelParent[sItem];

		if (!metaModel)
			Utils.assertFailed("Cannot find in the original meta-model structure the object called <'" + sItem + "'");

		this.resetLocalUUIDs(metaModel);
	}

	this.onArray = function(jsonParent, sItem, json, metaModelParent, nLevel)
	{
		var metaModelArray = metaModelParent[sItem];

		if (!metaModelArray)
			Utils.assertFailed("Cannot find in the original meta-model structure the array called <'" + sItem + "'");

		this.resetLocalUUIDs(metaModelArray);
	}

	this.onArrayObject = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];

		if (!metaModel)
			Utils.assertFailed("Cannot find in the original meta-model structure the array object called <'" + nItem
				+ "'");

		this.resetLocalUUIDs(metaModel);
	}

	this.onArrayValue = function(jsonArray, nItem, json, metaModelParent, metaModelArray, nLevel)
	{
		var metaModel = metaModelArray[nItem];
		if (!metaModel)
		{
			Utils.assertFailed("Cannot find in the original meta-model structure the array value called '" + nItem
				+ "'");
		}

		this.resetLocalUUIDs(metaModel);
	}

	this.resetLocalUUIDs = function(metaModel)
	{
		for ( var sElem in metaModel)
		{
			if (sElem.indexOf("$$uuid") == 0)
			{
				metaModel[sElem] = 0;
				Utils.getNextUUID(metaModel);
			}
		}
	}
}
MMNavResetUUIDs.prototype = Object.create(MMNavigator.prototype);
MMNavResetUUIDs.prototype.constructor = MMNavResetUUIDs;

function Utils()
{
	this._dummy = null;
}

Utils.assertFailed = function(sMsg)
{
	alert(sMsg);
	//dialogsSvc.OKDialog(sMsg);
	throw sMsg;
}

// UUID system that just returns consecutive numbers
// Could be switched to a more fancy UUID system
Utils.UUID = 0;

// Get and also set the element UUID
Utils.getNextUUID = function(element)
{
	if (element.$$uuid != 0)
		return element.$$uuid;

	element.$$uuid = ++Utils.UUID;
	return Utils.UUID;
}

// Get the current UUID
Utils.getCurrentUUID = function()
{
	return Utils.UUID;
}

Utils.noNull = function(sValue)
{
	if (sValue == null)
		return "";
	else
		return sValue;
}

Utils.TAB = "\t";

Utils.getClassName = function(object)
{
	if (object != null)
	{
		var funcNameRegex = /function (.{1,})\(/;
		return (funcNameRegex).exec(object.constructor.toString())[1];
	}
	return "null";
}

Utils.isEmptyString = function(sValue)
{
	if (sValue == null)
		return true;
	if (sValue.length == 0)
		return true;
	return false;
}

Utils.isInt = function(value)
{
	return !isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10));
}

Utils.isDouble = function(value)
{
	return !isNaN(value) && parseFloat(Number(value)) == value && !isNaN(parseFloat(value));
}

// validates if the date is in the format dd/MM/YYYY
Utils.isCorrectDate = function(date)
{
	// console.log("Checking date:"+date);
	// Check for the pattern dd/MM/YYYY
	var pattern = new RegExp("/^\d{1,2}\/\d{1,2}\/\d{4}$/");
	if (pattern.test(date))
		return false;
	// Parse the date parts as integers
	var parts = date.split("/");

	var day = parseInt(parts[0], 10);
	var month = parseInt(parts[1], 10);
	month += 1;
	var year = parseInt(parts[2], 10);

	// Check for ranges of month and year using for year sensible limits
	if (year < 1800 || year > 2500 || month == 0 || month > 12)
		return false;

	var monthsLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];

	// Take leap years into account
	if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
		monthsLength[1] = 29;

	// Check the range of the day
	return day > 0 && day <= monthsLength[month - 1];
}

// validates if the pattern is correct
Utils.respectsPattern = function(data, patternString)
{
	// Process the pattern for Unicode Character Block
	patternString = Utils.processUnicodeCharacterBlocks(patternString); 
	var pattern = new RegExp("^"+patternString+"$");
	//console.log("Validating: "+pattern);
	if(pattern.test(data))
		return true;
	return false;
}

Utils.processUnicodeCharacterBlocks = function(patternString)
{
	patternString = patternString.replace("\\p{IsBasicLatin}"		,"\\u0000-\\u007F");
	patternString = patternString.replace("\\p{IsLatin-1Supplement}","\\u0080-\\u00FF");
	patternString = patternString.replace("\\p{IsLatinExtended-A}"	,"\\u0100-\\u017F");
	patternString = patternString.replace("\\p{IsLatinExtended-B}"	,"\\u0180-\\u024F");
		
	return patternString;
}

// validates if the text contains only latin characters
Utils.respectsLatin = function(data)
{
	// TODO - activate after a decision to enfoce latin has been reached
	return true;
	
	var latinString = "[\x00-\x7F]*";
	var pattern = new RegExp("^"+latinString+"$");
	if(pattern.test(data))
		return true;
	return false;
}

//validates if maxLength/minLength is respected
Utils.respectsLength = function(data, length, minmax)
{
	var str = data.toString();
		
	if(minmax == "maxLength")
		if(str.length > Utils.parseInt(length))
			return false;
	
	if(minmax == "minLength")
		if(str.length < Utils.parseInt(length))
			return false;
		
	return true;
}

//validates if maxInclusive/minInclusive is respected
Utils.respectsInclusive = function(data, threshold, minmax)
{
	if(minmax == "maxInclusive")
		if(Utils.parseInt(data) > Utils.parseInt(threshold))
			return false;
	
	if(minmax == "minInclusive")
		if(Utils.parseInt(data) < Utils.parseInt(threshold))
			return false;
		
	return true;
}

Utils.toSimpleLocalDateString = function(date) 
{
    var local = new Date(date);
    local.setMinutes(date.getMinutes() - date.getTimezoneOffset());
    return local.toJSON().slice(0, 10);
}

Utils.toUTCNoonString = function(date) 
{
	return Utils.toUTCNoonDate(date).toISOString();
}

Utils.toUTCNoonDate = function(date) 
{
    var utcDate = new Date(date);
    utcDate.setHours(12);
    utcDate.setMinutes(date.getMinutes() - date.getTimezoneOffset());
    return utcDate;
}



Utils.getWatchers = function(root) 
{
	root = angular.element(root || document.documentElement);
	var watcherCount = 0;
	 
	function getElemWatchers(element) 
	{
		var isolateWatchers = getWatchersFromScope(element.data().$isolateScope);
	    var scopeWatchers = getWatchersFromScope(element.data().$scope);
	    var watchers = scopeWatchers.concat(isolateWatchers);
	    angular.forEach(element.children(), function (childElement) {
	      watchers = watchers.concat(getElemWatchers(angular.element(childElement)));
	    });
	    return watchers;
	  }
	  
	function getWatchersFromScope(scope) 
	{
		if (scope) 
		{
	      return scope.$$watchers || [];
	    } 
		else 
		{
	      return [];
	    }
	}
	 
	return getElemWatchers(root);
	// usage to get the total count: Utils.getWatchers().length
}
	

/**
 * pads the string with white-spaces (WS) so it achieves the length specified in
 * nRightPos, which thus become the right-alignment pos
 *
 * @param nRightPos -
 *            right alignment position (the length of the string after WS
 *            padding
 * @param sValue -
 *            the string that is to be padded
 */
Utils.alignString = function(nRightPos, sValue)
{
	var nCurrentLen = 0;
	if (sValue != null)
		nCurrentLen = sValue.toString().length;
	var nPadSize = nRightPos - nCurrentLen;
	if (nPadSize <= 0)
		Utils.assertFailed("Cannot pad-align string to pos = " + nRightPos
			+ " because string is longer already (length = " + sValue.length + "; text = '" + sValue + "')");

	var nMaxPad = Utils.ms_wsPadsStringArray.length - 1;
	var sPad = "";
	if (nPadSize >= nMaxPad)
	{
		var LOOPS = Math.floor(nPadSize / nMaxPad);
		for (var i = 0; i < LOOPS; ++i)
		{
			sPad += Utils.ms_wsPadsStringArray[nMaxPad]
			nPadSize -= nMaxPad;
		}
	}
	sValue = Utils.noNull(sValue) + sPad + Utils.ms_wsPadsStringArray[nPadSize];
	return sValue;
}

Utils.parseInt = function(sNumber)
{
	var n = parseInt(sNumber);
	if (n === NaN)
		Utils.assertFailed("Cannot interpret '" + sNumber + "' as a valid integer");
	return n;
}

/**
 * this is used to pad strings in order to achieve righ alignment The approach
 * is favoring speed (one string concatenation) but it gives limmited padding
 * capacity that can align right only N more char-positions Here N is 10 =
 * number of pads in the array (there are 11 with the 0 pos which is not used
 */
Utils.ms_wsPadsStringArray = [ "", " ", "  ", "   ", "    ", "     ", "      ", "       ", "        ", "         ",
	"          " ];

Utils.ms_wsTabPads = [ "", "	", "		", "			", "				", "					", "						", "							", "								", "									",
	"										", "											", "												", "													", "														", "															",
	"																", "																	", "																		", "																			", "																				",
	"																					", "																						", "																							", "																								",
	"																									", "																										", "																											" ];
