// Types (kind) of nodes
const APPL = 1, COMP = 2, BU = 3, ORG = 4, AGGR = 5; GROUP = 6;
const QUEUE = 100, TOPIC = 101, FILE = 102, DB = 103, OTHER = 104;
const PRODUCER = 1, CONSUMER = 2, PUBLISHER = 3, SUBSCRIBER = 4, CONNECTED = 5, SOURCE= 6, SINK= 7, SOURCESINK = 8, SERVICE = 1000;

// Global variables 
var modelList;
var modelIndex;
var model;
var canvas;
var engine;
var xhr;
var scene;
var camera;
var advancedTexture;
var measureFactor;
//
var foreColor;
var backColorApps;
var backColorAppsExt;
var backColorSvcs;
//
var helper1;
var helper2;
var _selected; // selected mesh
var _selNodeMaterial; // material to be assigned to the selected node mesh
var _selSrvMaterial; // material to be assigned to the selected service mesh
var _prevMaterial; // material of the newly selected mesh, a save location
var _material1, _material2, _material3;
var _materialExt;
var _materialOrg;
var _materialOrgS;
var _materialBU;
var _materialBUS;
var _materialFile;
var _materialFileS;
var _materialGroup;
// viewStyle: "O" "S" "A" "N" or "M" (soscape original, soascape, archimate, node, mixed)
var CONFIG = { showServices: true, viewStyle: "O", enforceNodeLabels: false, enforceServiceLabels: false, planesVisible: true, embedded: false};
var SELECTED2;
var BACK_COLOR; 


function showmodel(index) {
    var hasScene;
    if (!(modelList || model)) {
        return;
    }
    if (scene) hasScene = true; else hasScene = false;
    modelIndex = index;
    if (index >= 0) {
        model = modelList.Models[modelIndex]; // Otherwise assume that the vaiable model is already set
    }
    var sceneToShow = createScene(); // if it already exists, will return the existing scene.
    // Somewhat awkward programming but need to ensure easy moving to the playground (only createScene goes there).
    if (hasScene) return;
    engine.runRenderLoop(function () {
        sceneToShow.render();
    });
}

window.addEventListener('DOMContentLoaded', function () {
    // get the canvas DOM element
    canvas = document.getElementById('renderCanvas');
    //alert("loaded " + canvas.id);
    // load the 3D engine
    engine = new BABYLON.Engine(canvas, true);
    canvas.addEventListener("dblclick", function () {
        event.preventDefault();
        event.stopPropagation();
        ZoomAll(90.0);
    });
});

window.addEventListener('resize', function () {
    engine.resize();
    //alert("engine.resize complete");
});

function ZoomAll(factor) {
    if (!camera) return;
    camera.zoomFactor = factor;
    camera.zoomOn(scene.meshes, true);
    canvas.focus();
};
function ConfigEmbedded(newValue) {
	//alert("embedded=" + newValue);
	CONFIG.embedded = newValue;
}

function setAfterSelectFunct(functName) {
	SELECTED2 = functName;
}

function ConfigShowPlanes(newValue) {
	//alert(newValue);
	CONFIG.planesVisible = newValue;
    var newAlpha;
    if (CONFIG.planesVisible) newAlpha = 0.2; else newAlpha = 0.0;
    if (helper1) helper1.material.alpha = newAlpha;
    if (helper2) helper2.material.alpha = newAlpha;
}

function ConfigNodeLabels(newValue) {
	CONFIG.enforceNodeLabels = newValue;
	showHideLabels()
}

function ConfigServiceLabels(newValue) {
	CONFIG.enforceServiceLabels = newValue;
	showHideLabels()
}

function showServices(newValue) {
    CONFIG.showServices = newValue;
    if (model) {
        showmodel(-1);
        ZoomAll(90.0);
    }
}

function changeStyle(newValue) {
	//alert(newValue);
	CONFIG.viewStyle = newValue;
    if (model) {
        showmodel(-1);
        ZoomAll(90.0);
    }       
}

function changeBackground(newColorHex) {
    scene.clearColor = BABYLON.Color3.FromHexString(newColorHex);
	BACK_COLOR = scene.clearColor; 
}

function replaceModelList(newList) {
    //alert("olala");
	modelList = JSON.parse(newList); 
    showmodel(0);
}

function getConfig() {
    //alert("olala");
    retValue = JSON.stringify(CONFIG);
    //alert(retValue);
    return retValue;
}

function showHideLabels() {
    var setValueN = CONFIG.enforceNodeLabels;
    var setValueS = CONFIG.enforceServiceLabels;
    var allMeshes = scene.meshes;
    for (var key in allMeshes) {
        if (allMeshes.hasOwnProperty(key)) {
            mesh = allMeshes[key];
            if ((mesh._label) && (mesh != _selected)) {
                if (mesh._type == "N") {
					if (mesh._subtype < 100) {
						if (mesh._label.isVisible != setValueN) mesh._label.isVisible = setValueN;
					} else {
						mesh._label.isVisible = false;
						//if (mesh._label.isVisible != setValueN) mesh._label.isVisible = setValueN;
					}
                } else {
                    if (mesh._label.isVisible != setValueS) mesh._label.isVisible = setValueS;
                }
            }
        }
    }
}

function selectEntity(name, entityType, id) {
	//alert(entityType + " " + name + " " + id);
	var allMeshes = scene.meshes;		
	for (var key in allMeshes) {
        if (allMeshes.hasOwnProperty(key)) {
            mesh = allMeshes[key];
            if ((mesh._id2 == id) && (mesh._type == entityType)) {
                if (mesh != _selected) selectMesh(mesh, false); 
            }
        }
    }
}

function selectMesh(mesh, propagate) {
	if (mesh._label) mesh._label.isVisible = true;
	var previous = _selected;
	_selected = mesh;
	//alert("selectMesh");
	//alert("CONFIG.embedded=" + CONFIG.embedded);
	//alert("selected: " + _selected._type + " " + _selected.position); // + " " + _selected.position.Y + " " + _selected.position.Z);
	if (previous) {
		previous.material = _prevMaterial;
		previous.actionManager.processTrigger(BABYLON.ActionManager.OnPointerOutTrigger, BABYLON.ActionEvent.CreateNew(previous));
	}
	_prevMaterial = _selected.material;
	if (_selected._type == "N") {
		switch (_selected._subtype) {
			case BU:
				_selected.material = _materialBUS;
				break;
			case ORG:
				_selected.material = _materialOrgS;
				break;
			case FILE:
				_selected.material = _materialFileS;
				break;
			default:
				_selected.material = _selNodeMaterial;
		}
	} else {
		_selected.material = _selSrvMaterial;
	}
	if (CONFIG.embedded && propagate) { 	
		var jsonObj = "{ \"id\": " + mesh._id2 + ", \"type\":\"" + mesh._type + "\"}"; 	
		//alert("select back: " + jsonObj);
		window.chrome.webview.hostObjects.sync.dotnet.SelectedinGraphic(jsonObj);
	} else {
		if (SELECTED2) window[SELECTED2](mesh._id2);
	}
}

// *************************************************************************************************************************************
function createScene() {
	_selected = null;
    if (scene) {
        measureFactor = 1.0; //0.745;
        var meshes = scene.meshes;
        while (meshes.length) {
            if (meshes[0]._label) {
                meshes[0]._label.isVisible = false;
                //if (meshes[0]._label.advancedTexture) meshes[0]._label.advancedTexture.dispose();
                meshes[0]._label = null; // Would garbage collection pick this (BABYLON.GUI.Rectangle) up??
            }
            if (meshes[0].material) {
                if (meshes[0].material.emissiveTexture) meshes[0].material.emissiveTexture.dispose();
                meshes[0].material.dispose();
            }
            meshes[0].dispose(true);           
        }
        if (advancedTexture) {
            // advancedTexture.dispose();
        }
    }  else {
        //testAlert("billboard.js loaded");
        measureFactor = 1.0; //1.342;
        scene = new BABYLON.Scene(engine);
        //camera = new BABYLON.ArcRotateCamera("Camera", 1, 0.8, 600, new BABYLON.Vector3(400, 80, 500), scene);
        camera = new BABYLON.ArcRotateCamera("Camera", 1, 0.8, 600, new BABYLON.Vector3(50, 240, 50), scene);
        camera.attachControl(canvas, true);
        camera.allowUpsideDown = false;
        camera.zoomFactor = 90.0;
        //var light = new BABYLON.HemisphericLight("hemi", new BABYLON.Vector3(0, 1, 0), scene);
        var light = new BABYLON.PointLight("omni", new BABYLON.Vector3(0, 50, 0), scene);
        //light.position = camera.position;
        light.parent = camera;
        light.specular = BABYLON.Color3.Black();
        advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");
    }

    var linesColor1;
    var linesColor2;
    var linesAlpha1;
    var linesAlpha2;
    var specialNodeColor = new BABYLON.Color3(0.02, 0.9, 0.46);  // Not used at the moment (0.8, 1.00, 1.0);
	var selectedNodeColor = new BABYLON.Color3(1.0, 0.66, 0.08);
	var selectedSrvColor = new BABYLON.Color3(0.3, 1.0, 1.0);
    switch(CONFIG.viewStyle) {
        case "A": // ArchiMate
            if (!BACK_COLOR) scene.clearColor = new BABYLON.Color3(1.0, 1.0, 1.0);
            foreColor = "black";
            backColorApps = "#AFFFFF";
			backColorAppsExt = "#AFFFFF";     // 14BABA    // #FFACA5 #AFFFFF
            backColorSvcs = "#AFFFFF";
            linesColor1 = new BABYLON.Color3(0, 0, 0);
            linesColor2 = new BABYLON.Color3(0.6, 0.1, 0.0);
            linesAlpha1 = 0.3;
            linesAlpha2 = 0.9;
            break;
        case "M":
		case "O":		    
        case "S":
            if (CONFIG.viewStyle == "O") 
				if (!BACK_COLOR) scene.clearColor = new BABYLON.Color3(1.0, 1.0, 1.0);
			else
				if (!BACK_COLOR) scene.clearColor = new BABYLON.Color3(1.0, 1.0, 1.0);
            foreColor = "white";
            backColorApps = "#E70000";
			backColorAppsExt = "#FF9390";     // 14BABA FF6161 FF9390 
            backColorSvcs = "#1387F5";
            linesColor1 = new BABYLON.Color3(0.5, 0.5, 0.5);  // (0.5, 0.5, 1.0);  
            linesColor2 = new BABYLON.Color3(0.4, 0.1, 0.4);  // (1, 0.2, 0.1);  
            linesAlpha1 = 0.2; // = 0.2;
            linesAlpha2 = 0.6; // = 0.4;
            break;
        default:  // "N"
            if (!BACK_COLOR) scene.clearColor = new BABYLON.Color3(1.0, 1.0, 1.0);
            foreColor = "white";
            backColorApps = "#E70000";
            backColorAppsExt = "#66CCCC";     // 14BABA       
			backColorSvcs = "#1387F5";
            linesColor1 = new BABYLON.Color3(0.5, 0.5, 1.0);
            linesColor2 = new BABYLON.Color3(1, 0.2, 0.1);
            linesAlpha1 = 0.2;
            linesAlpha2 = 0.4;
    }

    // if (CONFIG.viewStyle == "A") scene.clearColor = new BABYLON.Color3(1.0, 1.0, 1.0); else scene.clearColor = new BABYLON.Color4(0.2, 0.2, 0.3);
	// Diffuse - the basic color or texture of the material as viewed under a light;
	// Specular - the highlight given to the material by a light;
	// Emissive - the color or texture of the material as if self lit;
	// Ambient - the color or texture of the material lit by the environmental background lighting.
    _material1 = new BABYLON.StandardMaterial("appl", scene);
    _material1.diffuseColor = new BABYLON.Color3(0.6, 0, 0);
    //_material1.specularColor = new BABYLON.Color3(0.91, 0, 0);
    _material1.emissiveColor = new BABYLON.Color3(0.91, 0, 0);
    _material1.ambientColor = new BABYLON.Color3(0.91, 0, 0);
	//
	_material2 = new BABYLON.StandardMaterial("srv", scene);
    _material2.diffuseColor = new BABYLON.Color3(0.055, 0.35, 1.0); // new BABYLON.Color3(0.075, 0.55, 1.0);
	//_material2.specularColor = new BABYLON.Color3(0.075, 0.55, 1.0);
	_material2.emissiveColor = new BABYLON.Color3(0.055, 0.35, 1.0);
    _material2.ambientColor = new BABYLON.Color3(0.075, 0.55, 1.0); // (0.085, 0.65, 1.0);
	//
	_material3 = new BABYLON.StandardMaterial("srv", scene);
    _material3.diffuseColor = new BABYLON.Color3(0.94, 0.76, 0.0); // new BABYLON.Color3(0.075, 0.55, 1.0);
	_material3.emissiveColor = new BABYLON.Color3(0.94, 0.76, 0.0);
    _material3.ambientColor = new BABYLON.Color3(0.94, 0.76, 0.0); // (0.085, 0.65, 1.0);
	//
	_materialExt = new BABYLON.StandardMaterial("ext", scene); // External applications & services
    _materialExt.diffuseColor = new BABYLON.Color3(0.8, 0.37, 0.36);
    //_materialExt.specularColor = new BABYLON.Color3(1.0, 0.58, 0.56);
    _materialExt.emissiveColor = new BABYLON.Color3(1.0, 0.58, 0.56);
    _materialExt.ambientColor = new BABYLON.Color3(1.0, 0.58, 0.56);
	//
    var materialX = new BABYLON.StandardMaterial("x", scene); // Organizations & Business Units
	materialX.diffuseColor = new BABYLON.Color3(0.11, 0.63, 0.63);  
	//materialX.diffuseColor = new BABYLON.Color3(0.11, 0.63, 0.38);  
    // materialX.specularColor = new BABYLON.Color3(0.02, 0.9, 0.46);
	materialX.emissiveColor = new BABYLON.Color3(0.11, 0.50, 0.50);  
	//materialX.emissiveColor = new BABYLON.Color3(0.0, 0.5, 0);
	// materialX.ambientColor = new BABYLON.Color3(0.00, 0.5, 0.0);
	//
	_selNodeMaterial = new BABYLON.StandardMaterial("sel1", scene);
	_selNodeMaterial.diffuseColor = selectedNodeColor;
	_selNodeMaterial.ambientColor = selectedNodeColor;
	//
	_selSrvMaterial = new BABYLON.StandardMaterial("sel2", scene);
	_selSrvMaterial.diffuseColor = selectedSrvColor;
	_selSrvMaterial.ambientColor = selectedSrvColor;
	// ********************************************************************************************************************
	_materialGroup = new BABYLON.StandardMaterial("textureGroup", scene);
    _materialGroup.useAlphaFromDiffuseTexture = true;
    _materialGroup.diffuseTexture = new BABYLON.Texture("images/ring.png", scene); // "Application Group"
    _materialGroup.diffuseTexture.hasAlpha = true;
	_materialOrg = new BABYLON.StandardMaterial("textureORG", scene);
    _materialOrg.useAlphaFromDiffuseTexture = true;
    _materialOrg.diffuseTexture = new BABYLON.Texture("images/Organization.png", scene); // "Organization"
    _materialOrg.diffuseTexture.hasAlpha = true;
    _materialBU = new BABYLON.StandardMaterial("textureBU", scene);
    _materialBU.useAlphaFromDiffuseTexture = true;
    _materialBU.diffuseTexture = new BABYLON.Texture("images/BusinessUnit.png", scene); // "BusinessUnit.svg"
    _materialBU.diffuseTexture.hasAlpha = true;
    _materialOtherC = new BABYLON.StandardMaterial("textureOtherC", scene);
    _materialOtherC.useAlphaFromDiffuseTexture = true;
    _materialOtherC.diffuseTexture = new BABYLON.Texture("images/otherConn.png", scene); // "otherConn.pdn"
    _materialOtherC.diffuseTexture.hasAlpha = true;
    _materialFile = new BABYLON.StandardMaterial("textureBU", scene);
    _materialFile.useAlphaFromDiffuseTexture = true;
    _materialFile.diffuseTexture = new BABYLON.Texture("images/folder2.png", scene); // "BusinessUnit.svg"
    _materialFile.diffuseTexture.hasAlpha = true;
	_materialOrgS = new BABYLON.StandardMaterial("textureORG", scene);
    _materialOrgS.useAlphaFromDiffuseTexture = true;
    _materialOrgS.diffuseTexture = new BABYLON.Texture("images/Organization-sel.png", scene); // "Organization"
    _materialOrgS.diffuseTexture.hasAlpha = true;
    _materialBUS = new BABYLON.StandardMaterial("textureBU", scene);
    _materialBUS.useAlphaFromDiffuseTexture = true;
    _materialBUS.diffuseTexture = new BABYLON.Texture("images/BusinessUnit-sel.png", scene); // "BusinessUnit.svg"
    _materialBUS.diffuseTexture.hasAlpha = true;
    _materialFileS = new BABYLON.StandardMaterial("textureBU", scene);
    _materialFileS.useAlphaFromDiffuseTexture = true;
    _materialFileS.diffuseTexture = new BABYLON.Texture("images/folder2-sel.png", scene); // "BusinessUnit.svg"
    _materialFileS.diffuseTexture.hasAlpha = true;
	//
    var appMesh;
	var bbViewStyle = (CONFIG.viewStyle == "S") || (CONFIG.viewStyle == "A") || (CONFIG.viewStyle == "O")
    for (var key in model.Nodes) {
        var aObj;
        if (model.Nodes.hasOwnProperty(key)) {
			var node = model.Nodes[key];
            var isAnchor = (node.Attr & 1) > 0;
            var isAppl = node.Kind == APPL;
            var isExternal = (node.Attr & 2) > 0;
            var isBillBoard = isAnchor || bbViewStyle;
            if (appMesh && isAppl && (!isAnchor)) { // && isAppl && (!isAnchor)
				aObj = cloneApplMesh(node.Name, appMesh, isBillBoard, isExternal);
            } else {
				aObj = createNodeMesh(node, isBillBoard, isAnchor, isExternal);
				if ((!appMesh) && isAppl & (!isAnchor)) appMesh = aObj;
            }
			node.V3 = new BABYLON.Vector3(model.Nodes[key].X, model.Nodes[key].Y, model.Nodes[key].Z);
			if ((node.Kind != QUEUE) && (node.Kind != TOPIC)) {
				aObj.position = node.V3;
			}
            createLabel(aObj, advancedTexture, CONFIG.enforceNodeLabels, 0.6, "yellow", "black");
			aObj._id2 = node.Id2;
			addActions(aObj);
        }
    }

    if (CONFIG.showServices) {
        var srvMesh;
        var svcBillBoard = (CONFIG.viewStyle == "M") || (CONFIG.viewStyle == "A") || (CONFIG.viewStyle == "S");
		var svcLargeBlue = false; //(CONFIG.viewStyle == "O");
        for (var key in model.Subnodes) {
            var sObj;
            if (model.Subnodes.hasOwnProperty(key)) {
				var subnode = model.Subnodes[key];
				var bbMesh = svcBillBoard && (subnode.Kind == SERVICE)
                if (srvMesh && (subnode.Kind == SERVICE)) {
                    sObj = srvMesh.clone(subnode.Name);
					if (bbMesh) {					
						sObj.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;
						showArchiSvc(sObj, scene, backColorSvcs, foreColor);
					}
                } else {
					var isSpecial = (subnode.Kind == PRODUCER) || (subnode.Kind == PUBLISHER);
					sObj = createSubnodeMesh(subnode, bbMesh, svcLargeBlue, isExternal, isSpecial);
                    if (subnode.Kind == SERVICE) {
						srvMesh = sObj;
						if (bbMesh) {					
							sObj.billboardMode = BABYLON.Mesh.BILLBOARDMODE_ALL;
							showArchiSvc(sObj, scene, backColorSvcs, foreColor);
						}
					}
                }
                model.Subnodes[key].V3 = new BABYLON.Vector3(model.Subnodes[key].X, model.Subnodes[key].Y, model.Subnodes[key].Z);
                sObj.position = model.Subnodes[key].V3;
                createLabel(sObj, advancedTexture, CONFIG.enforceServiceLabels, 0.6, "yellow", "black");
                sObj._type = "S";
				sObj._id2 = model.Subnodes[key].Id2;
				addActions(sObj);

                // Draw line to the provider
                var pId = model.Subnodes[key].ProviderId;
                var line = BABYLON.Mesh.CreateLines("a", [sObj.position, model.Nodes[pId].V3], scene);
				line.color = linesColor2;
                line.alpha = linesAlpha2;
            }
        }
    }
	//
	//
    for (var key in model.ConsumingRelations) {
        if (model.ConsumingRelations.hasOwnProperty(key)) {
            var idA = model.ConsumingRelations[key].ConsumerId;
			var idS = model.ConsumingRelations[key].ServiceId;
			var subnode = model.Subnodes[idS];
            var line1;
            var line2;
            if (CONFIG.showServices || (subnode.Kind != SERVICE)) {                
				var isSpecial = (subnode.Kind == PRODUCER) || (subnode.Kind == PUBLISHER);
                line1 = BABYLON.Mesh.CreateLines("a", [subnode.V3, model.Nodes[idA].V3], scene);
				line1.color = linesColor1;
				if (isSpecial) line1.alpha = 1.0; else line1.alpha = linesAlpha1;
            } else {
                var idP = model.ConsumingRelations[key].ProviderId;
                var pos = new BABYLON.Vector3(
                    (model.Nodes[idA].V3.x - model.Nodes[idP].V3.x) / 12.0 + model.Nodes[idP].V3.x,
                    (model.Nodes[idA].V3.y - model.Nodes[idP].V3.y) / 12.0 + model.Nodes[idP].V3.y,
                    (model.Nodes[idA].V3.z - model.Nodes[idP].V3.z) / 12.0 + model.Nodes[idP].V3.z);
                line1 = BABYLON.Mesh.CreateLines("a", [pos, model.Nodes[idA].V3], scene);
				line1.color = linesColor1;
				line1.alpha = linesAlpha1;
                line2 = BABYLON.Mesh.CreateLines("a", [pos, model.Nodes[idP].V3], scene);
                line2.color = linesColor2;
                line2.alpha = linesAlpha2;
            }
        }
    }
    if (model.Mode == 1) createHelpers();
	
   
	scene.executeWhenReady(function() {
		setTimeout(function () { ZoomAll(90.0); }, 10);
		
	})

    return scene;
}
