var csInterface = new CSInterface();

var osIsWindows = function(){
    //return false
    return process.platform == "win32";
}

var activateToolTipsOnMacIfPrefsSet = function(){
    if(preferences.showTooltips){
        activateTooltipsOnMac()
    }
}

var activateTooltipsOnMac = function(){
    if(!osIsWindows()){
        $(function () {
          $('[data-toggle="tooltip"]').tooltip({delay: { "show": 800, "hide": 0 }, trigger: "hover", animation: false})
        })
    }
}

var deactivateTooltipsOnMac = function(){
    if(!osIsWindows()){
        $(function () {
          $('[data-toggle="tooltip"]').tooltip("dispose");
        })
    }
}

var openURLInDefaultBrowser = function(urlToOpen){
    window.cep.util.openURLInDefaultBrowser(urlToOpen);
}

var mkdirSync = function (path) {
  try {
    fs.mkdirSync(path);
  } catch(e) {
    if ( e.code != 'EEXIST' ) console.log(e);
  }
}



var mkUserDataDirectory = function(){
    try{
    fs.mkdirsSync(userPath);
    fs.mkdirsSync(userPath + "temp" + path.sep);
    }catch(e){
        console.log(e)
    }
    try{fs.statSync(userPath + "temp" + path.sep + "favorites.geojson")}catch(err){
        copyFilePromise(basePath + "temp" + path.sep + "favorites.geojson", userPath + "temp" + path.sep + "favorites.geojson");
    }
    try{fs.statSync(userPath + "temp" + path.sep + "busy.png")}catch(err){
        copyFilePromise(basePath + "temp" + path.sep + "busy.png", userPath + "temp" + path.sep + "busy.png");
    }
    try{fs.statSync(userPath + "temp" + path.sep + "512.png")}catch(err){
        copyFilePromise(basePath + "temp" + path.sep + "512.png", userPath + "temp" + path.sep + "512.png");
    }
}

var validateRecentProjects = function(){
    var curProj, allRecentFiles, snapshotPaths, noNeededSnapshots
    for(var p = 0; p < preferences.recentProjects.length; p++){
        curProj = preferences.recentProjects[p]
        if(!fs.existsSync(curProj.projectPath)){
            preferences.recentProjects.splice(p, 1)
            p--
        }
    }
    allRecentSnapshots = getFilesInDirectory(preferences.tempPath,/recent.*.png$/)
    snapshotPaths = preferences.recentProjects.map(function(recentProject){
       return recentProject.snapshotPath
    })
    noNeededSnapshots = allRecentSnapshots.filter(function(recentSnapshot){
        return !snapshotPaths.contains(recentSnapshot)
    })
    noNeededSnapshots.forEach(function(noNeededSnapshot){
        try{fs.unlinkSync(noNeededSnapshot)}catch(err){}
    })
}

var getFileInDirectory = function(startPath, filter){
    var fileNameFound
    function fromDir(startPath,filter,callback){
        if (!fs.existsSync(startPath)){
            console.log("no dir ",startPath);
            return;
        }
        var files=fs.readdirSync(startPath);
        for(var i=0;i<files.length;i++){
            var filename=path.join(startPath,files[i]);
            var stat = fs.lstatSync(filename);
            if (stat.isDirectory()){
                fromDir(filename,filter,callback); //recurse
            }
            else if (filter.test(filename)) callback(filename);
        };
    };

    fromDir(startPath,filter,function(filename){
        fileNameFound = filename
    });
    return fileNameFound
}
var getFilesInDirectory = function(startPath, filter){
    var fileNamesFound = []
    function fromDir(startPath,filter,callback){
        if (!fs.existsSync(startPath)){
            console.log("no dir ",startPath);
            return;
        }
        var files=fs.readdirSync(startPath);
        for(var i=0;i<files.length;i++){
            var filename=path.join(startPath,files[i]);
            var stat = fs.lstatSync(filename);
            if (stat.isDirectory()){
                fromDir(filename,filter,callback); //recurse
            }
            else if (filter.test(filename)) callback(filename);
        };
    };

    fromDir(startPath,filter,function(filename){
        fileNamesFound.push(filename)
    });
    return fileNamesFound
}

var fileExists = function(pathToFile){
    try{
        fs.statSync(pathToFile);
        return true
    }catch(err){
        return false
    }
}

var getPrefsFileName = function(){
    return "settings" + gl_manifestVersion.substring(0,gl_manifestVersion.lastIndexOf(".")).replace(".", "_") + ".json"
}

var loadPreferences = function(loadDeafaults) {
    var settingsStr = null;
    var loadedPrefs = {};
    var pathToPrefsFile = preferences.tempPath + getPrefsFileName()
    if(loadDeafaults){
        pathToPrefsFile = basePath + "temp" + path.sep + "defaultSettings.json"
    }
    try{settingsStr = fs.readFileSync(pathToPrefsFile, "utf8");}catch(err){console.log(err)}
    if(settingsStr){
        try{
           loadedPrefs = JSON.parse(settingsStr);
            if(loadDeafaults){
                loadedPrefs.tilesPath = userPath + "tiles" + path.sep
                loadedPrefs.standardShapeFilePaths = []
                loadedPrefs.missingTileFootagePath = userPath + "temp" + path.sep + "512.png"
                loadedPrefs.busyFootagePath = userPath + "temp" + path.sep + "busy.png"
                loadedPrefs.sampleProjectsPath = userPath + "sample projects" + path.sep
                delete loadedPrefs.lastPathToSaveFile
                delete loadedPrefs.lastPathToPickFile
                delete loadedPrefs.lastPathToPickShapefile
            }
            delete loadedPrefs.tempPath
            delete loadedPrefs.missingTileFootagePath
            delete loadedPrefs.busyFootagePath
            delete loadedPrefs.defaultLabelsPath
            delete loadedPrefs.templateProjectsPath
            delete loadedPrefs.expressionsPath
            angular.extend(preferences, loadedPrefs);
            applyChangePrefsPromise();
        }catch(err){console.log("no settings parsed");console.log(err);console.log(settingsStr)}

    }
}



var getAEprefsPromise = function(name){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getAEprefs(' + JSON.stringify(name) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var setAEprefsPromise = function(name, data){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setAEprefs(' + JSON.stringify(name) + ',' + JSON.stringify(data) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var newProjectPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.newProject();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}


var loadDefaultPrefs = function(){
    loadPreferences(true);
}

var savePreferences = function() {
    fs.outputFile(preferences.tempPath + getPrefsFileName(), angular.toJson(preferences), "utf8" ,function(err){
        if(err){
            console.log("error writing prefs")
            console.log(err)
        }
    });
}

var saveTrialStatus = function(){
    setAEprefsPromise("trialStatus", gl.trialStatus);
}

var getTrialStatus = function(){
    getAEprefsPromise("trialStatus").then(function(trialStatus){
        angular.extend(gl.trialStatus, trialStatus);
    }).catch(function(reason){
        console.log(reason)
    })
}

var writeTileCacheFile = function(serverprofile){
    var tileCache = getTileCache(serverprofile);
    fs.writeFile(preferences.tilesPath + serverprofile.tileCacheID, JSON.stringify(tileCache));
}

var removeTileCacheFile = function(serverprofile){
    try{fs.unlinkSync(preferences.tilesPath + serverprofile.tileCacheID)}catch(err){};
}

var purgeAllTileCaches = function(){
    gl.tileCaches = {};
}

var purgeTileCache = function(serverprofile){
    if(!serverprofile){serverprofile = interfaceData.selectedMapcomp.mapcompprofile.serverprofile}
    setTileCache(serverprofile, {})
    removeTileCacheFile(serverprofile);
}

var loadTileCacheFilePromise = function(serverprofile){
    return new Promise(function(resolve, reject){
        fs.readFile(preferences.tilesPath + serverprofile.tileCacheID, "utf8", function(err, data){
            if(err){
                //console.log(err)
                setTileCache(serverprofile, {})
            }else{
                var loadedCache
                try{loadedCache = JSON.parse(data)}catch(err){console.log(err)}
                if(loadedCache){
                    var newTileCache = angular.extend(loadedCache, getTileCache(serverprofile))
                    setTileCache(serverprofile, newTileCache);
                }
            }
            resolve(true)//loaded or not
        });
    });
}


var getTileCache = function(serverprofile){
    if(serverprofile.enableTileDownload){
        if(!serverprofile.tileCacheID){serverprofile.tileCacheID = getTileCacheID(serverprofile)}
        if(gl.tileCaches[serverprofile.tileCacheID]){
            return gl.tileCaches[serverprofile.tileCacheID]
        }else{
            gl.tileCaches[serverprofile.tileCacheID] = {};
            loadTileCacheFilePromise(serverprofile)//async ... extends cache
            return gl.tileCaches[serverprofile.tileCacheID]
        }
    }else{
        return {}
    }

}

var setTileCache = function(serverprofile, tileCache){
    gl.tileCaches[serverprofile.tileCacheID] = tileCache;
}

var isTileInCache = function(serverprofile, quadcode){
    return getTileCache(serverprofile)[quadcode]
}

var getCachedTilePath = function(serverprofile, quadcode){
    if(isTileInCache(serverprofile, quadcode)){
        var pathToFile = preferences.tilesPath + getTileFileName(serverprofile.filenameAppendix, quadcode) + "." + serverprofile.fileExtension;
        if(fileExists(pathToFile)){
            return pathToFile
        }else{
            removeTileFromCache(serverprofile, quadcode);
            return false
        }
    }else{
        return null
    }
}

var addTileToCache = function(serverprofile, quadcode){
    getTileCache(serverprofile)[quadcode] = true;
}

var removeTileFromCache = function(serverprofile, quadcode){
    delete getTileCache(serverprofile)[quadcode];
}

var getTileCacheID = function(serverprofile){
    var fullT = serverprofile.filenameAppendix +"_"+ encodeURI(Base64.encode(multiSplit(serverprofile.tileUriMask, ["//", "\\", "/", ".", ":", "?"]).concat(["gl"]).reduce(function(prev, cur){
        var curWthoutBrackets = cur.replace(/[\])}[{(]/g, '');
        return prev + curWthoutBrackets.substr(0,Math.min(curWthoutBrackets.length, 1))+curWthoutBrackets.substr(curWthoutBrackets.length-1,Math.min(curWthoutBrackets.length, 1));
    }))+String(serverprofile.tileSize));
    return fullT.substr(0, Math.min(fullT.length, 50));
}

var muteHostInterface = function(){
    csInterface.evalScript(
    'mb_GEOlayers2HostInterface.muteHostInterface();',
    function (answer) {

    });
}
var enableHostInterface = function(){
    csInterface.evalScript(
    'mb_GEOlayers2HostInterface.enableHostInterface();',
    function (answer) {

    });
}

var getVersionStringFromManifest = function(pathToManifest){
    var manifestStr
    try{manifestStr = fs.readFileSync(pathToManifest, "utf8");}catch(err){console.log(err)}
    if(manifestStr){
        return getStrBetween(manifestStr, 'ExtensionBundleVersion="', '"');
    }
}

var compareVersionStr = function(a, b){
    var i, diff;
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

var multiSplit = function(str,delimeters){
    var result = [str];
    if (typeof(delimeters) == 'string')
        delimeters = [delimeters];
    while(delimeters.length>0){
        for(var i = 0;i<result.length;i++){
            var tempSplit = result[i] && result[i].split(delimeters[0]) || [];
            result = result.slice(0,i).concat(tempSplit).concat(result.slice(i+1));
        }
        delimeters.shift();
    }
    return result;
}

saveFavoriteFeatureCollection = function(geoJSON) {
    getFeaturesAryFromGeoJsonObj(geoJSON, false, undefined, undefined, true).forEach(function(feature){
        /*delete feature.isSelected = false;
        delete feature.GEOlayersDepthIndex;*/
        if(feature.properties){
            feature.properties.GEOlayersFavorit = true;
        }
    })
    geoJSON.GEOlayersBrowserIcon = "favorites";
    geoJSON.properties = {
        "name":"Favorites",
        "GEOlayersDisplayName":"Favorites"
    }
    fs.writeFile(gl.baseBrowserItems[0], angular.toJson(geoJSON));
}

applyChangePrefsPromise = function(){
    return new Promise(function(resolve, reject){
        var applyPrefProp = function(keyname, value){
            preferences[keyname] = value;
            console.log("pref changed: " + keyname)
        }
        autoChangePrefs.forEach(function(autoChangePref){
            switch(autoChangePref.changeType){
                case "once":
                    getAEprefsPromise("autoChangePrefsLogAry").then(function(autoChangePrefsLogAry){
                        if(autoChangePrefsLogAry instanceof Array){
                            if(!autoChangePrefsLogAry.contains(autoChangePref.id)){
                                applyPrefProp(autoChangePref.keyname, autoChangePref.value)
                                autoChangePrefsLogAry.push(autoChangePref.id)
                            }
                        }else{
                            applyPrefProp(autoChangePref.keyname, autoChangePref.value)
                            autoChangePrefsLogAry = [autoChangePref.id];
                        }
                        setAEprefsPromise("autoChangePrefsLogAry", autoChangePrefsLogAry)
                        resolve(preferences);
                    }).catch(function(err){
                        applyPrefProp(autoChangePref.keyname, autoChangePref.value)
                        autoChangePrefsLogAry = [autoChangePref.id];
                        setAEprefsPromise("autoChangePrefsLogAry", autoChangePrefsLogAry)
                        resolve(preferences);
                    })
                    break;
                case "allways":
                    applyPrefProp(autoChangePref.keyname, autoChangePref.value)
                    resolve(preferences);
                    break;
                default :
                    applyPrefProp(autoChangePref.keyname, autoChangePref.value)
                    resolve(preferences);
                    break;
            }
        })
    })

}

applyPreferencesPromise = function() {
    setPrefsInHostEnginePromise()
    .then(function(){

    })
    .catch(function(reason){
        console.log(reason)
    })
}

var flattenObject = function(ob) {
	var toReturn = {};

	for (var i in ob) {
		if (!ob.hasOwnProperty(i)) continue;

		if ((typeof ob[i]) == 'object') {
			var flatObject = flattenObject(ob[i]);
			for (var x in flatObject) {
				if (!flatObject.hasOwnProperty(x)) continue;
				toReturn[i + '_' + x] = flatObject[x];
			}
		} else {
			toReturn[i] = ob[i];
		}
	}
	return toReturn;
};

var isCtrlOrMetaKeypressed = function($event){
    return $event.ctrlKey || $event.metaKey;
}

var factorizeValueKeypressed = function(val, $event){
    if(!val){return 0}
    if($event.shiftKey){val=val*5; return val}
    if($event.ctrlKey){val=val/10; return val}
    if($event.metaKey){val=val/10; return val}
    return val
}

var getActiveServerprofile = function(){
    return interfaceData.selectedMapcomp.mapcompprofile.serverprofile;
}

var getActiveLayerprofile = function(){
    return interfaceData.selectedMapcomp.mapcompprofile.layerprofile;
}

var isMapcompNameUnique = function(mapcomps, mapcompName){
    var uniqueName = true;
    mapcomps.forEach(function(mapcomp){
        if (mapcomp.name === mapcompName) {uniqueName = false; }
    })
    return uniqueName
}

var isMapcomp = function(item){//onlyMapcomps
    var isMapcomp = false;
    if(item && item.mapcompprofile && item.id != null){isMapcomp = true}
    return isMapcomp;
}

var getLinkedMapcomps = function(mapcomps, linkedToMapcomp){
    var linkedMapcomps = [linkedToMapcomp];
    mapcomps.forEach(function(mapcomp){
        if (mapcomp.mapcompprofile.layerprofile.linkMapcomp.name === linkedToMapcomp.name) {linkedMapcomps.push(mapcomp)}
    })
    return linkedMapcomps
}

var setMapcompStatusNotFinalised = function(mapcomp){
    if(mapcomp.mapcompprofile.status.id != -1){
        mapcomp.mapcompprofile.status = gl.emptySelectedMapcompObj.mapcompprofile.status;
        updateMapPivotPromise(mapcomp, mapcomp.mapcompprofile).catch(function(err){console.log(err)})
    }
    return mapcomp
}

var setMapcompsStatiNotFinalised = function(mapcomps){
    return mapcomps.map(function(mapcomp){
        return setMapcompStatusNotFinalised(mapcomp);
    })
}

/*var copyFilePromise = function(source, target) {
    return new Promise(function(resolve, reject) {
        var rd = fs.createReadStream(source);
        rd.on('error', reject("error copying file"));
        var wr = fs.createWriteStream(target);
        wr.on('error', reject("error copying file"));
        wr.on('finish', resolve(target));
        rd.pipe(wr);
    });
}*/
var copyFilePromise = function(source, target) {
    return new Promise(function(resolve, reject) {
        try{
            fs.writeFileSync(target, fs.readFileSync(source));
        }catch(err){
            reject(err);
        }
        resolve(target);
    });
}

var getNewIdForArray = function(ary){
    var id = 2;
    ary.forEach(function(listItem){
        id = Math.max(id, listItem.id + 1);
    })
    return id
}

var addServerProfileToPrefs = function(serverprofile){
    var newId = getNewIdForArray(preferences.serverprofiles);
    serverprofile.id = newId;
    serverprofile.filenameAppendix = "c"+String(serverprofile.id)
    serverprofile.tileCacheID = getTileCacheID(serverprofile);
    preferences.serverprofiles.push(serverprofile);
}

var downloadFilePromise = function (sourceUrl, pathAndFilenameWithoutExt, conOptions, auth, forceFiletype, progressCallback) {
    if(conOptions == undefined){
        conOptions = JSON.parse(JSON.stringify(preferences.conOptions));
    }else{
        conOptions = JSON.parse(JSON.stringify(conOptions));
    }
    if(auth && auth.username.length > 0 && auth.password.length > 0){
        conOptions = angular.extend(conOptions, {auth: auth.username + ":" + auth.password});
    }
    conOptions = angular.extend(conOptions, {getFileExtension: true});
    var parsedUrl = url.parse(sourceUrl);
    return new Promise(function(resolve, reject){
        mkdirSync(path.normalize(path.dirname(pathAndFilenameWithoutExt)));
        if(parsedUrl && parsedUrl.protocol.indexOf("http") > -1){
            httpreq.download(sourceUrl, pathAndFilenameWithoutExt, conOptions,
                function(err, progress){
                    if (err) {
                        console.log(err)
                    }else{
                        if(progressCallback){progressCallback(progress)}
                    }
                },
                function(err, res){
                    if (err) {
                        console.log(err)
                    }
                    if(res && res.statusCode == 200){
                        if(!forceFiletype){
                            var fileExtensionFromContentType = res.fileExtension;
                            if(!fileExtensionFromContentType && res.headers["content-type"].lastIndexOf("/")>-1){
                                fileExtensionFromContentType = res.headers["content-type"].substring(res.headers["content-type"].lastIndexOf("/")+1,res.headers["content-type"].length);
                                if(fileExtensionFromContentType.toUpperCase() === "JPEG"){fileExtensionFromContentType = "jpg"}
                            }
                            fileExtensionFromContentType = "." + fileExtensionFromContentType;
                        }else{
                            var fileExtensionFromContentType = "";
                        }
                        var newFileName = res.downloadlocation.replace(fileExtensionFromContentType, "") + fileExtensionFromContentType;
                        fs.rename(res.downloadlocation, newFileName, function(err){
                            if(err) {
                                console.log(err);
                                reject("error downloading")
                            }else{
                                resolve(newFileName);
                            }
                        })

                    }else{
                        reject("error downloading")
                    }
                }

            )
        }else{
            copyFilePromise(sourceUrl, pathAndFilenameWithoutExt + "." + getFileExtensionOfFileName(sourceUrl))
            .then(function(path){
                resolve(path);
            })
            .catch(function(err){
                console.log(err)
                reject(err);
            })
        }
    })


}

var downloadFile = function (sourceUrl, pathAndFilenameWithoutExt, callback, conOptions) {

    mkdirSync(path.normalize(path.dirname(pathAndFilenameWithoutExt)));
    httpreq.download(sourceUrl, pathAndFilenameWithoutExt,
        function(err, progress){
            if (err) {
                console.log(err)
            }
        },
        function(err, res){
            if (err) {
                console.log(err)
            }
            if(res && res.statusCode == 200){
                var fileExtensionFromContentType = "";
                    if(res.headers["content-type"].lastIndexOf("/")>-1){
                        fileExtensionFromContentType = "."+
                            res.headers["content-type"]
                            .substring(res.headers["content-type"].lastIndexOf("/")+1,res.headers["content-type"].length);
                    }
                    fs.rename(res.downloadlocation, res.downloadlocation+fileExtensionFromContentType, function(err){
                        if(err){
                            console.log(err)
                        }else{
                            callback(res.downloadlocation+fileExtensionFromContentType);
                        }

                    })

            }else{
                //reject("error downloading")
            }
        },
        conOptions
    )


}

var getTilesWithIndividualQuads = function(tiles){
    var quadsAllreadyInAry = [];
    var individualQuads = tiles.filter(function(tile){
        var diffQuads = quadsAllreadyInAry.filter(function(quadcode){
            return (tile.quadcode === quadcode)
        })

        if(diffQuads.length === 0){
            quadsAllreadyInAry.push(tile.quadcode)
            return true
        }else{
            return false
        }
    })
    return individualQuads;
}

var addProviderLogoToContainingComp = function(mapcomp){
    var pathToLogo = mapcomp.mapcompprofile.serverprofile.pathToLogo;
    if(mapcomp.mapcompprofile.serverprofile.providerApi == "bingmapsApi"){
        pathToLogo = bingmapsApi.pathToLogo;
    }
    if(pathToLogo && pathToLogo.length > 0){
        var fileName = mapcomp.mapcompprofile.serverprofile.filenameAppendix + "Logo";
        downloadFilePromise(pathToLogo, preferences.tempPath + fileName)
        .then(function(pathToLogo){
           return createLogoLayerPromise(mapcomp, pathToLogo);
        }).catch(function(err){console.log(err)})
    }
}

var downloadAndAddTilePromise = function(mapcomp, tile){
    return new Promise(function(resolve, reject){
        if(interfaceData.isLicenseTypeTrial){
            if(gl.trialStatus.tilesDownloaded > gl.trialRestrictions.maxTilesToDownload){
                if(!gl.trialRestrictions.isMaxTilesAlertHasBeenShown){gl.trialRestrictions.isMaxTilesAlertHasBeenShown = true;dlg_simpleAlert("Trial limitation", "You reached the maximum number of tiles to be downloaded with the trial version. GEOlayers 2 won't work as expected.")}
                reject("You downloaded more than "+ String(gl.trialRestrictions.maxTilesToDownload) + " Tiles. That's enough for the trial version.")
            }
        }
        if(interfaceData.isLicenseTypeTrial){gl.trialStatus.tilesDownloaded++}
        getTileserverApi(mapcomp.mapcompprofile.serverprofile).downloadTilePromise(tile.quadcode)
        .then(function(pathReturn){
            addTileToMapcompPromise(mapcomp, tile, pathReturn)
            .then(function(tileReturn){
                countUpProgressbarJob("downloadTiles", "progress", "placing Tile ")
                //updateProgressbar(forEachindex/forEachLength*100, "progress", "Placing Tile "+String(forEachindex)+"/"+String(forEachLength))
                resolve(tileReturn);
            })
            .catch(function(tileReturn){
                resolve(null);//promises in all need to be resolved
            })

        })
        .catch(function(reason){
            resolve(null)//promises in all need to be resolved
        })
    })
}
var downloadAndBatchAddTilesPromise = function(mapcomp, inputTiles){
    return new Promise(function(resolve, reject){
        if(gl.isFinalizingStatus < 0){reject("finalization canceled")}
        if(interfaceData.isLicenseTypeTrial){
            if(gl.trialStatus.tilesDownloaded > gl.trialRestrictions.maxTilesToDownload){
                if(!gl.trialRestrictions.isMaxTilesAlertHasBeenShown){gl.trialRestrictions.isMaxTilesAlertHasBeenShown = true;dlg_simpleAlert("Trial limitation", "You reached the maximum number of tiles to be downloaded with the trial version. GEOlayers 2 won't work as expected.")}
                reject("You downloaded more than "+ String(gl.trialRestrictions.maxTilesToDownload) + " Tiles. That's enough for the trial version.")
            }
        }
        if(interfaceData.isLicenseTypeTrial){gl.trialStatus.tilesDownloaded = gl.trialStatus.tilesDownloaded + inputTiles.length}
        Promise.all(inputTiles.map(function(tile){
            return new Promise(function(resolve, reject){
                getTileserverApi(mapcomp.mapcompprofile.serverprofile).downloadTilePromise(tile.quadcode).then(function(tile){
                    resolve(tile);
                }).catch(function(tile){
                    resolve(null);
                })
            })
        })).then(function(tiles){
            addTilesToMapcompPromise(mapcomp, inputTiles.filter(function(tile, index){
                if(tiles[index]){
                    return true
                }else{
                    return false
                }
            })).then(function(tiles){
                resolve(tiles)
                tiles.forEach(function(){
                    countUpProgressbarJob("downloadTiles", "progress", "placing Tile ")
                })
            }).catch(function(tiles){
                console.log("batch tile placement rejected")
                reject(tiles)
            })
        }).catch(function(error){
            console.log(error)
        })

    })
}

var downloadAndAddTilesInPackagesPromise = function(mapcomp, tiles){
    return downloadAndBatchAddTilesInPackagesPromise(mapcomp, tiles)
    //return downloadAndIndividualAddTilesInPackagesPromise(mapcomp, tiles)
}
var downloadAndIndividualAddTilesInPackagesPromise = function(mapcomp, tiles){
    return new Promise(function(resolve, reject){
        initProgressbarJob("downloadTiles", tiles.length, "downloading tiles...");
        var packageSize = preferences.tilePlacementPackageSize;
        if(mapcomp.mapcompprofile.serverprofile.tileSizeIn && mapcomp.mapcompprofile.serverprofile.tileSizeIn < mapcomp.mapcompprofile.serverprofile.tileSize){
           packageSize = Math.max(1, Math.floor(Math.pow(mapcomp.mapcompprofile.serverprofile.tileSizeIn,2)*preferences.tilePlacementPackageSize/Math.pow(mapcomp.mapcompprofile.serverprofile.tileSize,2)))
        }
        var packagedTiles = splitAryToPackages(tiles, packageSize);
        var promisesAry = [];
        var promiseInputsAry = [];
        packagedTiles.forEach(function(tilePackage){
            if(tilePackage instanceof Array){
                promisesAry.push(downloadAndAddTilesPromise);
                promiseInputsAry.push(tilePackage);
            }

        })
        var answerAry = [];
        var chainedPromise = promisesAry.reduce(function(previous, item, index){
            return previous.then(function(previousValue){
                if(previousValue instanceof Array){
                    answerAry = answerAry.concat(previousValue);
                }
                return item(mapcomp, promiseInputsAry[index])
            })
        }, Promise.resolve());

        chainedPromise
        .then(function(answer){
            answerAry = answerAry.concat(answer);
            resolve(answerAry)
            finishProgressbarJob("downloadTiles");
        })
        .catch(function(answer){
            answerAry = answerAry.concat(answer);//can't happen
            reject(answer)
            finishProgressbarJob("downloadTiles");
        })
    })
}
var downloadAndBatchAddTilesInPackagesPromise = function(mapcomp, tiles){
    return new Promise(function(resolve, reject){
        initProgressbarJob("downloadTiles", tiles.length, "downloading tiles...");
        var packageSize = preferences.tilePlacementPackageSize;
        if(mapcomp.mapcompprofile.serverprofile.tileSizeIn && mapcomp.mapcompprofile.serverprofile.tileSizeIn < mapcomp.mapcompprofile.serverprofile.tileSize){
           packageSize = Math.max(1, Math.floor(Math.pow(mapcomp.mapcompprofile.serverprofile.tileSizeIn,2)*preferences.tilePlacementPackageSize/Math.pow(mapcomp.mapcompprofile.serverprofile.tileSize,2)))
        }
        var packagedTiles = splitAryToPackages(tiles, packageSize);
        var promisesAry = [];
        var promiseInputsAry = [];
        packagedTiles.forEach(function(tilePackage){
            if(tilePackage instanceof Array){
                promisesAry.push(downloadAndBatchAddTilesPromise);
                promiseInputsAry.push(tilePackage);
            }

        })
        var answerAry = [];
        promisesAry.reduce(function(previous, item, index){
            return previous.then(function(previousValue){
                if(previousValue instanceof Array){
                    answerAry = answerAry.concat(previousValue);
                }
                return item(mapcomp, promiseInputsAry[index])
            })
        }, Promise.resolve()).then(function(answer){
            answerAry = answerAry.concat(answer);
            resolve(answerAry)
            finishProgressbarJob("downloadTiles");
        })
        .catch(function(answer){
            if(gl.isFinalizingStatus < 0){reject("finalization canceled")}
            answerAry = answerAry.concat(answer);//can't happen
            resolve(answer)
            finishProgressbarJob("downloadTiles");
        })
    })
}


var downloadAndAddTilesPromise = function(mapcomp, tiles){
    return new Promise(function(resolve, reject){
        if(tiles.length < 1){resolve(tiles)}
        var downloadCount = 0;
        var addTilePromises = [];
        if(gl.isFinalizingStatus < 0){reject("finalization canceled")}
        Promise.all(tiles.map(function(tile){
            return downloadAndAddTilePromise(mapcomp, tile)
        }))
        .then(function(tilesAdded){
            tilesAdded = tilesAdded.filter(function(tile){
                return (tile != null)//from failed tileimport/download
            })
            //updateProgressbar(100);
            //alert("then" + JSON.stringify(tilesAdded))
            resolve(tilesAdded);

        })
        .catch(function(reason){
            reject(reason);//cancel fnalisation
            //cant happen each tile is resolved in order to not cancel .all
        })

    })
}

var downloadAndAddTiles = function(mapcomp, tiles, callback){
    if(tiles.length < 1){callback(tiles)}
    var downloadCount = 0;
    var addTilePromises = [];
    //initProgressbarJob("downloadTiles", tiles.length, "downloading tiles...");
    tiles.forEach(function(tile, index){
    //alert(JSON.stringify(getActiveServerprofile().tileUriMask))
        addTilePromises.push(downloadAndAddTilePromise(mapcomp, tile));
    })

    Promise.all(addTilePromises)
    .then(function(tilesAdded){
        //finishProgressbarJob("downloadTiles");
        tilesAdded = tilesAdded.filter(function(tile){
            return (tile != null)//from failed tileimport/download
        })
        //updateProgressbar(100);
        //alert("then" + JSON.stringify(tilesAdded))
        callback(tilesAdded);

    })
    .catch(function(reason){
        //cant happen each tile is resolved in order to not cancel .all
    })

}

var updateCurrentViewPromise = function(mapcomp, updateLinked){
    if(updateLinked == undefined){updateLinked = preferences.updateLinkedMapcompViews};
    return new Promise(function(resolve, reject){
        var mapcompsToUpdate = [mapcomp];
        if(updateLinked){
           mapcompsToUpdate = getLinkedMapcomps(interfaceData.mapcomps, mapcomp);
        }
        addTilesForCurrentViewsPromise(mapcompsToUpdate).then(function(m){
            resolve(m);
        }).catch(function(e){
            reject(e);
        })
    })
}

var addTilesForCurrentViewsPromise = function(mapcomps){
    return new Promise(function(resolve, reject){
        Promise.all(mapcomps.map(function(mapcomp){
            return addTilesForCurrentViewPromise(mapcomp)
        })).then(function(missingTilesArray){
            resolve(missingTilesArray)
        }).catch(function(missingTilesArray){
            reject(missingTilesArray)
        })
    })
}

var addTilesForCurrentViewPromise = function(mapcomp){
    return new Promise (function(resolve, reject){
        if(mapcomp.mapcompprofile.serverprofile.enableTileDownload){
            disablePanelButtons();
            if(mapcomp.mapcompprofile.serverprofile.enableTileDownload){
                var missingTiles = [];
                getTileRequirement(mapcomp, undefined, undefined, function(tilesInMapcomp){
                    var tilesToDownload = getTilesWithIndividualQuads(tilesInMapcomp.needed);
                    //check cache
                    downloadAndAddTilesInPackagesPromise(mapcomp, tilesToDownload)
                    .then(function(addedTiles){
                        var tilesToAddWithAdditionalFullRots = getTilesInAThatAreNotInB(tilesInMapcomp.needed, tilesToDownload);
                        if(tilesInMapcomp.inComp.length > preferences.maxTilesForPreview){
                            var tilesNotNeededInMapcomp = getTilesInAThatAreNotInB(tilesInMapcomp.inComp, tilesInMapcomp.forView);
                            removeTilesPromise(mapcomp, tilesNotNeededInMapcomp).catch(function(reason){console.log(reason)});
                        }
                        addTilesToMapcompPromise(mapcomp, tilesToAddWithAdditionalFullRots)
                        .then(function(val){
                            missingTiles = getTilesInAThatAreNotInB(tilesInMapcomp.needed, val.concat(addedTiles));
                            fixMissingTilesByLowerZoomlevels(mapcomp, missingTiles, preferences.lowerZoomlevelDepth, function(tilesStillMissing, addedLowerZoomlevelTiles, zoomleveldepth){
                                deleteTileLayerBlendFKsPromise(mapcomp);
                                enablePanelButtons();
                                resolve(missingTiles);
                            })
                        })
                        .catch(function(val){
                            missingTiles = missingTiles.concat(val);
                            enablePanelButtons();
                            reject(missingTiles);
                        });
                    })
                    .catch(function(reason){
                        dlg_simpleAlert("Error downloading Tiles", reason)
                        enablePanelButtons();
                        reject(reason);
                    })
                });
            }else{
                enablePanelButtons();
                resolve([]);
            }
        }else{
            resolve([])
        }
    });
};



var finalizeMapcompsPromise = function(mapcomps, onlyCurrentFrame, optFinalizationQuality){
    //set mapcomp stati to -1
    gl.isFinalizingStatus = 0;
    mapcomps = setMapcompsStatiNotFinalised(mapcomps);
    dlg_finalization("Finalization in Progress", mapcomps);//show
    gl.isFinalizingStatus = 1;
    if(onlyCurrentFrame){
        dlg_finalization("Finalization current Frame 1/" + String(mapcomps.length), mapcomps);
    }else{
        dlg_finalization("Finalization in Progress 1/" + String(mapcomps.length), mapcomps);
    }

    var promisesAry = [];
    var promiseInputsAry = [];
    mapcomps.forEach(function(mapcomp){
        promisesAry.push(updateActiveMapcompprofileFromMapcompPromise);
        promiseInputsAry.push(mapcomp);
        promisesAry.push(finalizeMapcompPromise);
        promiseInputsAry.push([mapcomp, onlyCurrentFrame, optFinalizationQuality]);
    })
    //promisesAry.push(updateActiveMapcompprofileFromMapcompPromise);
    //promiseInputsAry.push(interfaceData.selectedMapcomp);

    var answerAry = [];
    var filteredAnswerAry = [];
    var allMapcomps = angular.copy(mapcomps);
    var allMapcompsInsertIndex = 0;
    var chainedPromise = promisesAry.reduce(function(previous, item, index){
        return previous.then(function(previousValue){
            answerAry.push(previousValue);
            if(isMapcomp(previousValue)){
                allMapcomps[allMapcompsInsertIndex]=previousValue;
                allMapcompsInsertIndex++
                filteredAnswerAry.push(previousValue);

                if(onlyCurrentFrame){
                    dlg_finalization("Finalization current Frame " + String(allMapcompsInsertIndex+1) + "/" + String(allMapcomps.length), allMapcomps, allMapcompsInsertIndex)
                }else{
                    dlg_finalization("Finalization in Progress " + String(allMapcompsInsertIndex+1) + "/" + String(allMapcomps.length), allMapcomps, allMapcompsInsertIndex)
                }

            }
            if(promiseInputsAry[index] instanceof Array){
                return item(promiseInputsAry[index][0], promiseInputsAry[index][1], promiseInputsAry[index][2])
            }else{
                return item(promiseInputsAry[index])
            }

        })
    }, Promise.resolve());

    chainedPromise
    .then(function(answer){
        answerAry.push(answer);
        if(interfaceData.selectedMapcomp.id){
            updateActiveMapcompprofileFromMapcompPromise(interfaceData.selectedMapcomp)
            revealMapcompUpdateMapPivotPromise(interfaceData.selectedMapcomp).catch(function(err){console.log(err)})
        }
        //enablePanelButtons()
        filteredAnswerAry = answerAry.filter(isMapcomp)
        mapcomps.forEach(function(mapcomp){
            writeTileCacheFile(mapcomp.mapcompprofile.serverprofile);
        })
        gl.isFinalizingStatus = 2;

        if(onlyCurrentFrame){
             dlg_finalization("Current Frame finalized", filteredAnswerAry)
        }else{
             dlg_finalization("Finalization completed", filteredAnswerAry)
        }
        angular.element($("#baseController")).scope().updateMapcompDropdown()
    })
    .catch(function(answer){
        if(interfaceData.selectedMapcomp.id){
            updateActiveMapcompprofileFromMapcompPromise(interfaceData.selectedMapcomp)
            revealMapcompUpdateMapPivotPromise(interfaceData.selectedMapcomp).catch(function(err){console.log(err)})
        }//enablePanelButtons()
        if(gl.isFinalizingStatus === -1){gl.isFinalizingStatus = 0;}

        dlg_simpleAlert("Error finalizing Mapcomps", answer)//, function(){gl.isFinalizingStatus = 0})
    })


    /*finalizeMapcompPromise(mapcomps[0])
    .then(function(missingTiles){
        enablePanelButtons()
        dlg_simpleAlert("'" + mapcomps[0].name + "' finalized", String(missingTiles.length) + " Tiles missing")
    })
    .catch(function(reason){
        enablePanelButtons()
        dlg_simpleAlert("Error finalizing '" + mapcomps[0].name + "'", String(reason))
    })*/

}

var finalizeMapcompPromise = function(mapcomp, onlyCurrentFrame, optFinalizationQuality){
    return new Promise (function(resolve, reject){
        mapcomp.mapcompprofile.status = {//set default for Display
            id : -1,
            description : "not Finalized",
        }
        var usedFinalizationQuality;
        if(optFinalizationQuality){
            usedFinalizationQuality = optFinalizationQuality
        }else{
            if(preferences.useGlobalFinalizationQuality){//for finalization report
                usedFinalizationQuality = preferences.globalFinalizationQuality;
            }else{
                usedFinalizationQuality = mapcomp.mapcompprofile.layerprofile.finalizationQuality;
            }
        }
        if(!mapcomp.mapcompprofile.serverprofile.enableTileDownload){
            resetProgressbar();

            mapcomp.mapcompprofile.status = {
                                            id : 2,
                                            description : "Finalized",
                                            nrTilesInComp : 0,
                                            nrTilesNeeded : 0,
                                            nrTilesLowerZoomlevel : 0,
                                            nrTilesStillMissing : 0,
                                            zoomleveldepth : 0,
                                            finalizationTime : 0,
                                            finalizationQuality : 0
                                        }
            resolve(mapcomp);
        }else{
            if(gl.isFinalizingStatus < 0){reject("finalization canceled")}
            var startTime = new Date().getTime();
            var missingTiles = [];
            setProgressbarInfiniteJob("Analysing Mapcomp...")
            getFinalizationTileRequirementPromise(mapcomp, onlyCurrentFrame, optFinalizationQuality)
            .then(function(requiredTilesAndUsedZoomLevels){
                var tilesInMapcomp = requiredTilesAndUsedZoomLevels.tilesForMapcomp;
                var tilesToDownload = getTilesWithIndividualQuads(tilesInMapcomp.needed);
                downloadAndAddTilesInPackagesPromise(mapcomp, tilesToDownload)
                .then(function(addedTiles){
                    var tilesToAddWithAdditionalFullRots = getTilesInAThatAreNotInB(tilesInMapcomp.needed, tilesToDownload);
                    setProgressbarInfiniteJob("Placing additional Tiles...")
                    addTilesToMapcompPromise(mapcomp, tilesToAddWithAdditionalFullRots)
                    .then(function(addedTilesWithFullRots){
                        var tilesNotNeededInMapcomp = getTilesInAThatAreNotInB(tilesInMapcomp.inComp, tilesInMapcomp.forView);
                        removeTilesPromise(mapcomp, tilesNotNeededInMapcomp)
                        .then(function(status){
                            missingTiles = getTilesInAThatAreNotInB(tilesInMapcomp.needed, addedTilesWithFullRots.concat(addedTiles));
                            fixMissingTilesByLowerZoomlevels(mapcomp, missingTiles, preferences.lowerZoomlevelDepth, function(tilesStillMissing, addedLowerZoomlevelTiles, zoomleveldepth){
                                setProgressbarInfiniteJob("Blending Zoomlevels...")
                                setupZoomLevelBlendingPromise(mapcomp, requiredTilesAndUsedZoomLevels.usedZoomLevels)
                                .then(function(answer){
                                    setProgressbarInfiniteJob("creating service provider attribution...")
                                    var imageryProviderAttributionsByFrame = getTileserverApi(getActiveServerprofile()).getServiceProvider(requiredTilesAndUsedZoomLevels.usedZoomLevels);
                                    setServiceProviderTextPromise(mapcomp, imageryProviderAttributionsByFrame);
                                    resetProgressbar();
                                    //FINISHED alert("finalized")
                                    var endTime = new Date().getTime();
                                    //var finalizationTime = Math.floor((endTime - startTime)/1000);
                                    var finalizationTime = new Date(endTime - startTime).toISOString().substr(11, 8);
                                    if(tilesStillMissing.length > 0){
                                        mapcomp.mapcompprofile.status = {
                                            id : 0,
                                            description : "Finalization Error",
                                            nrTilesInComp : tilesInMapcomp.forView.length - missingTiles.length,
                                            nrTilesNeeded : tilesInMapcomp.forView.length,
                                            nrTilesLowerZoomlevel : addedLowerZoomlevelTiles.length,
                                            nrTilesStillMissing : tilesStillMissing.length,
                                            zoomleveldepth : zoomleveldepth,
                                            finalizationTime : finalizationTime,
                                            finalizationQuality : usedFinalizationQuality
                                        }
                                    }else{
                                        if(missingTiles.length > 0){
                                            mapcomp.mapcompprofile.status = {
                                                id : 1,
                                                description : "Finalized with Missing Tiles",
                                                nrTilesInComp : tilesInMapcomp.forView.length - missingTiles.length,
                                                nrTilesNeeded : tilesInMapcomp.forView.length,
                                                nrTilesLowerZoomlevel : addedLowerZoomlevelTiles.length,
                                                nrTilesStillMissing : tilesStillMissing.length,
                                                zoomleveldepth : zoomleveldepth,
                                                finalizationTime : finalizationTime,
                                                finalizationQuality : usedFinalizationQuality
                                            }
                                        }else{
                                            mapcomp.mapcompprofile.status = {
                                                id : 2,
                                                description : "Finalized",
                                                nrTilesInComp : tilesInMapcomp.forView.length,
                                                nrTilesNeeded : tilesInMapcomp.forView.length,
                                                finalizationTime : finalizationTime,
                                                finalizationQuality : usedFinalizationQuality
                                            }
                                        }
                                    }
                                    updateMapPivotPromise(mapcomp, mapcomp.mapcompprofile).catch(function(err){console.log(err)})
                                    resolve(mapcomp);
                                })
                                .catch(function(reason){
                                    resetProgressbar();
                                    reject(reason);//"error blending zoomlevels"
                                })
                            });

                        })
                        .catch(function(reason){
                            resetProgressbar();
                            reject(reason);//"error removing tiles"
                        })
                    })
                    .catch(function(missingTilesWithFullRots){
                        missingTiles = missingTiles.concat(missingTilesWithFullRots);
                        resetProgressbar();
                        reject("error downloading tiles");
                    })
                })
                .catch(function(reason){
                    resetProgressbar();
                    reject(reason);
                });
            })
            .catch(function(reason){
                resetProgressbar()
                reject(reason);
            })
        }
    })
}

var fixMissingTilesByLowerZoomlevels = function(mapcomp, missingTiles, levelOfDepth, callback){
    var collectAddedTiles = [];
    var zoomleveldepth = 1;
    var tilesStillMissing = missingTiles;
    var lowerZLTiles = getLowerZoomlevelTiles(missingTiles);
    downloadAndAddTilesInPackagesPromise(mapcomp, lowerZLTiles)
    .then(function(addedLowerZLTiles){
        collectAddedTiles = collectAddedTiles.concat(addedLowerZLTiles);
        var tilesStillMissing = getTilesInAThatAreNotInB(lowerZLTiles, addedLowerZLTiles);
        if(tilesStillMissing.length === 0 || levelOfDepth < 2){
            callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
        }else{
            zoomleveldepth ++
            lowerZLTiles = getLowerZoomlevelTiles(tilesStillMissing);
            downloadAndAddTilesInPackagesPromise(mapcomp, lowerZLTiles)
            .then(function(addedLowerZLTiles){
                collectAddedTiles = collectAddedTiles.concat(addedLowerZLTiles);
                var tilesStillMissing = getTilesInAThatAreNotInB(lowerZLTiles, addedLowerZLTiles);
                if(tilesStillMissing.length === 0  || levelOfDepth < 3){
                    callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
                }else{
                    zoomleveldepth ++
                    lowerZLTiles = getLowerZoomlevelTiles(tilesStillMissing);
                    downloadAndAddTilesInPackagesPromise(mapcomp, lowerZLTiles)
                    .then(function(addedLowerZLTiles){
                        collectAddedTiles = collectAddedTiles.concat(addedLowerZLTiles);
                        var tilesStillMissing = getTilesInAThatAreNotInB(lowerZLTiles, addedLowerZLTiles);
                        callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
                    })
                    .catch(function(reason){
                        callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
                    })
                }
            })
            .catch(function(reason){
                callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
            })
        }

    })
    .catch(function(reason){
        callback(tilesStillMissing, collectAddedTiles, zoomleveldepth);
    })
}


var setupZoomLevelBlendingPromise = function(mapcomp, usedZoomLevels){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setupZoomLevelBlending(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(usedZoomLevels) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}


var updateRecentProjectsIfProjectIsNewPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.updateRecentProjectsIfProjectIsNew();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                preferences.recentProjects = answerObj.value
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var updateRecentProjectsPromise = function(forceSnapshot){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.updateRecentProjects(' + JSON.stringify(forceSnapshot) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                preferences.recentProjects = answerObj.value
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var openProjectPromise = function(fileName, forceOpenTemplate){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.openProject(' + JSON.stringify(fileName) + ',' + JSON.stringify(forceOpenTemplate) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var loadStyleSetFromFile = function(pathToFile){
    var readData
    try{readData = fs.readJsonSync(pathToFile)}catch(e){console.log(e)}
    if(isImportedStyleSetValid(readData)){
        return readData
    }else{
        return null
    }
}

var isImportedStyleSetValid = function(styleSet){
    if(styleSet && styleSet.type){
        return styleSet.type === gl.exportStyleSetTypeName
    }else if(styleSet && styleSet.standartStyleRule && styleSet.standartDrawingOption){
        var legacy_gl_standardDrawingOptions = {
            name: "default Style",
            id: 1,
            breakdownQuality:true,
            individualLayers:false,
            fill:{
                addFill:true,
                fillColor:0,
                randomizeColors:false,
                opacity: 50
            },
            stroke:{
                addStroke:true,
                strokeColor:16777215,
                sameColorAsFill:false,
                strokeWidth:5,
                calcRelativeWidth:true,
                strokeCap:1,
                strokeJoin:3,
                dashVal:0,
                opacity: 100
            }
        }
        var legacy_gl_standardStyleRule = {
            name : "new Rule",
            applyToPoints : true,
            applyToLines : true,
            applyToPolygons : true,
            drawingOptionsId : 0,
            evaluationLines : [],
        }
        return (angular.toJson(styleSet.standartStyleRule) == angular.toJson(legacy_gl_standardStyleRule) && angular.toJson(styleSet.standartDrawingOption) == angular.toJson(legacy_gl_standardDrawingOptions)) || (angular.toJson(styleSet.standartStyleRule) == angular.toJson(gl_standardStyleRule) && angular.toJson(styleSet.standartDrawingOption) == angular.toJson(gl_standardDrawingOptions))
    }else{
        return false
    }
}


var isImportedServerprofilesValid = function(importedServerprofile){
    if(importedServerprofile && importedServerprofile.type){
        return importedServerprofile.type === gl.exportServerprofilesTypeName
    }else if(importedServerprofile && importedServerprofile.standartListItem){
        var legacy_gl_newServerProfile = {
            name : "New Server Profile",
            enableTileDownload : true,
            providerApi : "universalApi",
            apiKey : "",
            tileUriMask : "",
            maxZL : 18,
            tileSize : 256,
            crossfadeZL : false,
            copyrightStr : "",
            filenameAppendix : "c99",
            fileExtension : "png",
            culture : "",
            pathToLogo : undefined
        }
        return (angular.toJson(gl_newServerProfile) == angular.toJson(importedServerprofile.standartListItem)) || (angular.toJson(legacy_gl_newServerProfile) == angular.toJson(importedServerprofile.standartListItem))
    }else{
        return false
    }
}

var loadServerprofilesFromFile = function(pathToFile){
    var readData
    try{readData = fs.readJsonSync(pathToFile)}catch(e){console.log(e)}
    if(isImportedServerprofilesValid(readData)){
        return readData
    }else{
        return null
    }
}


var isServerprofileNew = function(newServerprofile){
    return preferences.serverprofiles.filter(function(serverprofile){
        if(!serverprofile || !newServerprofile){return false}
        var id1 = getTileCacheID(serverprofile);
        var id2 = getTileCacheID(newServerprofile);
        id1 = id1.substring(id1.indexOf("_"), id1.length)
        id2 = id2.substring(id2.indexOf("_"), id2.length)
        return serverprofile && newServerprofile && id1 == id2 && serverprofile.name == newServerprofile.name
    }).length == 0
}

var applyImportedServerprofiles = function(importedServerprofiles){
    importedServerprofiles.listItems.forEach(function(serverprofile){
        if(isServerprofileNew(serverprofile)){
            addServerProfileToPrefs(JSON.parse(angular.toJson(serverprofile)));
        }
    })
    angular.element($("#baseController")).scope().preferencesChangeHandler(true)
}

var saveStylesetToProjectPromise = function(styleset){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.saveStylesetToProject(' + JSON.stringify(styleset) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(styleset)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var loadStylesetFromProjectPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.loadStylesetFromProject();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var applyStylesetFromProject = function(doPrompt){
    loadStylesetFromProjectPromise().then(function(styleset){
        if(doPrompt){
            dlg_confirm("Import Feature Styles", "Do you really want to replace all your Styles and Rules?", function(){
                angular.element($("#baseController")).scope().applyStyleset(styleset)
            }, function(){}, "Yes", "No");
        }else{
            angular.element($("#baseController")).scope().applyStyleset(styleset)
        }

    }).catch(function(err){
        console.log(err)
        if(doPrompt){dlg_simpleAlert("Error loading Style Set from Project", err)}
    })
}
var saveCurrentStylesetToProject = function(doPrompt){
    saveStylesetToProjectPromise({drawingOptions : preferences.drawingOptionsAry, styleRules : preferences.styleRules}).then(function(styleset){
        if(doPrompt){dlg_simpleAlert("Style Set saved", "The Feature Style Set has been saved in your After Effects Project.")}
    }).catch(function(err){
        console.log(err)
        if(doPrompt){dlg_simpleAlert("Error saving Style Set in Project", err)}
    })
}

var duplicateMapcompPromise = function(mapcomp, options){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.duplicateMapcomp(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(options) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                var newMapcomp = answerObj.value;
                addProviderLogoToContainingComp(newMapcomp)
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var getContainingCompPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getContainingComp(' + JSON.stringify(mapcomp.id) +');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}
var getMapcompsOfContainingCompPromise = function(comp){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getMapcompsOfContainingComp(' + JSON.stringify(comp.id) +');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var createLogoLayerPromise = function(mapcomp, path){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.createLogoLayer(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(path) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var getFinalizationTileRequirementPromise = function(mapcomp, onlyCurrentFrame, finalizationQuality){
    return new Promise (function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getFinalizationTileRequirement(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(onlyCurrentFrame) + ',' + JSON.stringify(finalizationQuality) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
};

var deleteTileLayerBlendFKsPromise = function(mapcomp){
    return new Promise (function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.deleteTileLayerBlendFKs(' + JSON.stringify(mapcomp.id) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(true)
            }else{
                reject(answerObj.description)
            }
        });
    });
};

var getTileRequirement = function(mapcomp, optionalTime, isPixelValue, callback){
    csInterface.evalScript(
    'mb_GEOlayers2HostInterface.getTileRequirement(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(optionalTime) + ',' + JSON.stringify(isPixelValue) +');',
    function (answer) {
        try{var answerObj = JSON.parse(answer);}catch(err){}
        if (answerObj && answerObj.status === 0) {
            setMapcompsStatiNotFinalised(getLinkedMapcomps(interfaceData.mapcomps, mapcomp));
            callback(answerObj.value)
        }
    });
};


var addTileToMapcomp = function(mapcomp, tile, path, callback){
    csInterface.evalScript(
    'mb_GEOlayers2HostInterface.addTile(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(path) + ',' + JSON.stringify(tile) +');',
    function (answer) {
        try{var answerObj = JSON.parse(answer);}catch(err){}
        if (answerObj && answerObj.status === 0) {
            callback(answerObj.value)
        }
    });
}

var clipViewPointsDegrees = function(viewpoints){
    return [[viewpoints[0][0], getFullRotationAndClippedLon(viewpoints[0][1]).clippedDegrees], [viewpoints[1][0], getFullRotationAndClippedLon(viewpoints[1][1]).clippedDegrees]]
}

var updateCurrentViewBBoxGlobalPromise = function(mapcomp, tempPointsOLUR){
    return new Promise(function(resolve, reject){
        if(tempPointsOLUR){
            interfaceData.currentViewPoints = interfaceData.selectedMapcomp.currentViewpoints = [[tempPointsOLUR[3], tempPointsOLUR[0]], [tempPointsOLUR[1], tempPointsOLUR[2]]];
            //updateCurrentPreviewQuadcode()
            resolve(tempPointsOLUR)
        }else{
            getScreenViewPointsPromise(mapcomp)
           .then(function(tempPointsOLUR){
                interfaceData.currentViewPoints = interfaceData.selectedMapcomp.currentViewpoints = [[tempPointsOLUR[3], tempPointsOLUR[0]], [tempPointsOLUR[1], tempPointsOLUR[2]]];
                //updateCurrentPreviewQuadcode()
                resolve(tempPointsOLUR);
           })
           .catch(function(reason){
                reject(reason)
           })
        }
    })
}

var updateCurrentPreviewQuadcode = function() {
    var zl = tileFinder.getZoomLevel(256, [interfaceData.currentViewPoints[1][1] - interfaceData.currentViewPoints[0][1]], 1, undefined, 256, 20, 20);
    var tileXY = tileUtils.LongLatToTileXY(interfaceData.currentViewPoints[0][1]+(interfaceData.currentViewPoints[1][1]-interfaceData.currentViewPoints[0][1])/2, interfaceData.currentViewPoints[0][0]+(interfaceData.currentViewPoints[1][0]-interfaceData.currentViewPoints[0][0])/2, zl, 256);
    var newQuad = tileUtils.tileXYZToQuadKey(tileXY[0], tileXY[1], zl);
    if(newQuad && newQuad.length > 0){
        interfaceData.currentPreviewQuadcode = newQuad;
    }
}
var getPreviewQuadFromMapcomp = function(mapcomp){
    if(mapcomp && mapcomp.mapcompprofile){
        return getPreviewQuadFromServerprofileAndViewpoints(mapcomp.mapcompprofile.serverprofile, mapcomp.currentViewpoints)
    }else{
        return "0"
    };
}
var getPreviewQuadFromServerprofileAndViewpoints = function(serverprofile, iviewpoints){
    var viewpoints = iviewpoints || interfaceData.currentViewPoints;
    if(!viewpoints || !serverprofile){return "0"};

    var maxZL = serverprofile.maxZL;
    var tileSize = serverprofile.tileSize;
    var zl = tileFinder.getZoomLevel(tileSize, [viewpoints[1][1] - viewpoints[0][1]], 1, undefined, tileSize, maxZL, maxZL);
    var tileXY = tileUtils.LongLatToTileXY(viewpoints[0][1]+(viewpoints[1][1]-viewpoints[0][1])/2, viewpoints[0][0]+(viewpoints[1][0]-viewpoints[0][0])/2, zl, tileSize);
    var newQuad = tileUtils.tileXYZToQuadKey(tileXY[0], tileXY[1], zl);
    if(newQuad && newQuad.length > 0){
        return newQuad;
    }else{
        return "0";
    }
}

var getScreenViewPointsPromise = function(mapcomp, optionalTime, isPixelVal){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getScreenViewPoints(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(optionalTime) + ',' + JSON.stringify(isPixelVal) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}


var addTilesToMapcompPromise = function(mapcomp, tiles){
    return new Promise(function(resolve, reject){
        if(tiles.length < 1){resolve(tiles)}
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.addTiles(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(tiles) +');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(getTilesInAThatAreNotInB(tiles, answerObj.value))
            }
        });
    })
}

var addTileToMapcompPromise = function(mapcomp, tile, path){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.addTile(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(path) + ',' + JSON.stringify(tile) +');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value);
            }else{
                reject(answerObj.value);
            }
        });
    })

}
var getValidLinkSourceMapcomp = function(mapcomp){
    if(mapcomp.mapcompprofile.layerprofile && mapcomp.mapcompprofile.layerprofile.linkMapcomp && mapcomp.mapcompprofile.layerprofile.linkMapcomp.id){
        return getItemById(interfaceData.mapcomps, mapcomp.mapcompprofile.layerprofile.linkMapcomp.id)
    }else{
        return null
    }
}

var scrollMapPromise = function(mapcomp, x, y, geoPoint){
    return new Promise(function(resolve, reject){
        var drivingMapcomp = getValidLinkSourceMapcomp(mapcomp) || mapcomp;
        if(preferences.updateLinkedMapcompViews){mapcomp = drivingMapcomp}
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.scrollMap(' + JSON.stringify(drivingMapcomp.id) + ',' + JSON.stringify(x) + ',' + JSON.stringify(y) + ',' + JSON.stringify(geoPoint) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                updateCurrentViewBBoxGlobalPromise(mapcomp, answerObj.value);
                timeoutAndUpdateCurrentViewPromise(mapcomp)
                .then(function(missingTiles){
                    resolve(answerObj.value)
                })
                .catch(function(missingTiles){
                    reject(missingTiles)
                })
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var setZoomPromise = function(mapcomp, zoomValue, isStepVal){
    return new Promise(function(resolve, reject){
        var drivingMapcomp = getValidLinkSourceMapcomp(mapcomp) || mapcomp;
        if(preferences.updateLinkedMapcompViews){mapcomp = drivingMapcomp}
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setZoom(' + JSON.stringify(drivingMapcomp.id) + ',' + JSON.stringify(zoomValue) + ',' + JSON.stringify(isStepVal) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                updateCurrentViewBBoxGlobalPromise(mapcomp, answerObj.value);
                timeoutAndUpdateCurrentViewPromise(mapcomp)
                .then(function(missingTiles){
                    resolve(answerObj.value)
                })
                .catch(function(missingTiles){
                    reject(missingTiles)
                })
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var jumpToLatLongInputPromise = function(mapcomp, OLUR, bufferOLUR){
    var hostScriptPromise = function(resolve, reject){
        var drivingMapcomp = getValidLinkSourceMapcomp(mapcomp) || mapcomp;
        if(preferences.updateLinkedMapcompViews){mapcomp = drivingMapcomp}
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.jumpToLatLongInput(' + JSON.stringify(drivingMapcomp.id) + ',' + JSON.stringify(OLUR) + ',' + JSON.stringify(bufferOLUR) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                updateCurrentViewBBoxGlobalPromise(mapcomp, answerObj.value);
                updateCurrentViewPromise(mapcomp)
                .then(function(missingTiles){
                    resolve(answerObj.value)
                })
                .catch(function(missingTiles){
                    reject(missingTiles)
                })
            }else{
                reject(answerObj.description)
            }
        });
    }

    if(mapcomp.id){
        return new Promise(hostScriptPromise)
    }else{
        return createDefaultServerprofileMapcompPromise().then(function(newMapcomp){
            mapcomp = newMapcomp
            angular.element($("#baseController")).scope().applyMapcompToSelected(mapcomp)
            return new Promise(hostScriptPromise)
        }).catch(function(reason){
            reject("reason")
        })
    }

}

var timeoutAndUpdateCurrentViewPromise = function(mapcomp){
    return new Promise (function(resolve, reject){
        if(gl.timeoutID1){clearTimeout(gl.timeoutID1);}
        gl.timeoutID1 = setTimeout(
            function(){updateCurrentViewPromise(mapcomp)
            .then(function(missingTiles){
                resolve()//answer.value aus uebergeordneter
            })
            .catch(function(missingTiles){
                reject(missingTiles)
            })},
            600
        )
    })
}

var setServiceProviderTextPromise = function(mapcomp, imageryProviderAttributionsByFrame){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setServiceProviderText(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(imageryProviderAttributionsByFrame) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var setMapKFPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        var drivingMapcomp = getValidLinkSourceMapcomp(mapcomp) || mapcomp;
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setMapKF(' + JSON.stringify(drivingMapcomp.id) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var getAllMapcompsPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getAllMapcomps();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var getMapcompprofileFromLayerPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getMapcompprofile(' + JSON.stringify(mapcomp.id) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var updateActiveServerProfilePromise = function(serverprofile){//in case mapcomp is not created
    return new Promise(function(resolve, reject){
        interfaceData.selectedMapcomp.mapcompprofile.serverprofile = serverprofile;
        getTileserverApi(serverprofile).initialize(function(){
            resolve(serverprofile);
        });
    })

}

var updateActiveMapcompprofilePromise = function(mapcompprofile){
    return new Promise(function(resolve, reject){
        interfaceData.selectedMapcomp.mapcompprofile.serverprofile = mapcompprofile.serverprofile;//needs to be tested without error on finalize all
        resolve(mapcompprofile);
    })
}

var updateActiveMapcompprofileFromMapcompPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        getMapcompprofileFromLayerPromise(mapcomp)
        .then(function(mapcompprofile){
            //gl.activeLayerprofile = mapcompprofile.layerprofile;
            updateActiveMapcompprofilePromise(mapcompprofile)
            .then(function(serverprofile){
                resolve(mapcompprofile);
            })
            .catch(function(reason){
                reject(reason);
            });
        })
        .catch(function(reason){
            reject(reason)
        })
    })

}

var setPrefsInHostEnginePromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.setPreferences(' + angular.toJson(preferences) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var removeTilesPromise = function(mapcomp, tiles){
    setProgressbarInfiniteJob("Removing Tiles...")
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.removeTiles(' + JSON.stringify(mapcomp.id) + "," + JSON.stringify(tiles) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resetProgressbar();
                resolve(answerObj.value)
            }else{
                resetProgressbar();
                reject(answerObj.value)
            }
        });
    });
}

var removeTilesButPromise = function(mapcomp, tiles){
    //setProgressbarInfiniteJob("Removing Tiles...")
    return new Promise(function(resolve, reject){

        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.removeTilesBut(' + JSON.stringify(mapcomp.id) + "," + JSON.stringify(tiles) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                //resetProgressbar();
                resolve(answerObj.value)
            }else{

                //resetProgressbar();
                reject(answerObj.value)
            }
        });
    });
}

var revealMapcompUpdateMapPivotPromise = function(mapcomp, optMapcompprofile){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.revealMapcompUpdateMapPivot(' + angular.toJson(mapcomp) + "," + angular.toJson(optMapcompprofile) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.value)
            }
        });
    });
}

var revealItemPromise = function(item, revealInProject){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.revealItem(' + JSON.stringify(item.id) + "," + JSON.stringify(revealInProject) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.value)
            }
        });
    });
}

var updateMapPivotPromise = function(mapcomp, optMapcompprofile){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.updateMapPivot(' + JSON.stringify(mapcomp.id) + "," + JSON.stringify(optMapcompprofile) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.value)
            }
        });
    });
}

var getLabelCompsPromise = function(){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getLabelComps();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}
var addLabelTemplatePromise = function(){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.addLabelTemplate();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}
var importLabelTemplateProjectPromise = function(pathToProject){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.importLabelTemplateProject(' + JSON.stringify(pathToProject) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                angular.element($("#baseController")).scope().updateLabelcomps()
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var getLabelTemplatesDataFieldsPromise = function(labelComp){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getLabelTemplatesDataFields(' + JSON.stringify(labelComp) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var updateMapcompDropdownPromise = function () {
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
            'mb_GEOlayers2HostInterface.getAllMapcomps(' + JSON.stringify(interfaceData) + ');',
            function (answer) {
                try{var answerObj = JSON.parse(answer);}catch(err){}
                if (answerObj && answerObj.status === 0) {
                    var mapcomps = answerObj.value;

                    mapcomps = removeLinkMapcompsIfNotExisting(mapcomps);
                    interfaceData.mapcomps = mapcomps;
                    var selectedMapcompInMapcomps = mapcomps.filter(function (item) {
                        return item.id === interfaceData.selectedMapcomp.id;
                    });
                    if (selectedMapcompInMapcomps.length > 0) {
                        interfaceData.selectedMapcomp = selectedMapcompInMapcomps[0];
                    } else {
                        if(interfaceData.mapcomps.length > 0){
                            interfaceData.selectedMapcomp = interfaceData.mapcomps[0];
                        }else{
                            interfaceData.selectedMapcomp = gl.emptySelectedMapcompObj;
                        }
                    }


                    resolve(mapcomps);
                }
            }
        );
    })
};

var getActiveCompSpecs = function (noMapcomps) {
    return new Promise(function(resolve, reject){
    csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getActiveCompSpecs(' + JSON.stringify(noMapcomps) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                var compSpecsObj = answerObj.value;
                resolve(compSpecsObj)
            } else {
                reject("no active comp")
            }
        }
    );
    });
};

var getActiveCompSpecsOrDefaultPromise = function(noMapcomps){
    return new Promise(function(resolve, reject){
        getActiveCompSpecs(noMapcomps).then(function(compSpecsObj){
            resolve(compSpecsObj)
        }).catch(function(reason){
            var compSpecsObj = {
                            width: 1920,
                            height: 1080,
                            frameRate: 25,
                            duration: 30,
                            pixelAspect: 1
                        }
            resolve(compSpecsObj)
        })
    });
}

var createProjectItemDirPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.createProjectItemDir();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(true)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var getAvailable3DSetupEffectsPromise = function(){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getAvailable3DSetupEffects();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var create3DSetupPromise = function(type){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.create3DSetup(' + JSON.stringify(type) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                var updateMapcomps = function(mapcomps){
                    var createdMapcompIDs = mapcomps.map(function(mapcomp){return mapcomp.id});
                    updateMapcompDropdownPromise().then(function(mapcomps){
                        var controlComp = getItemById(mapcomps, createdMapcompIDs[0])
                        var textureComp = getItemById(mapcomps, createdMapcompIDs[1])
                        var heightComp = getItemById(mapcomps, createdMapcompIDs[2])
                        textureComp.mapcompprofile.layerprofile.linkMapcomp = controlComp;
                        heightComp.mapcompprofile.layerprofile.linkMapcomp = controlComp;
                        return Promise.all([
                            applyMapcompprofilePromise(getItemById(mapcomps, createdMapcompIDs[1]), getItemById(mapcomps, createdMapcompIDs[1]).mapcompprofile).catch(function(err){console.log(err);}),
                            applyMapcompprofilePromise(getItemById(mapcomps, createdMapcompIDs[2]), getItemById(mapcomps, createdMapcompIDs[2]).mapcompprofile).catch(function(err){console.log(err);}),
                            applyMapcompprofilePromise(getItemById(mapcomps, createdMapcompIDs[0]), getItemById(mapcomps, createdMapcompIDs[0]).mapcompprofile).catch(function(err){console.log(err);})
                        ])
                    }).then(function(mapcomps){
                        controlComp = getItemById(mapcomps, createdMapcompIDs[0])
                        if(controlComp){
                            angular.element($("#baseController")).scope().safeApply(function(){
                                interfaceData.selectedMapcomp = controlComp;
                                interfaceData.selectedFinalizationMode = getItemById(interfaceData.finalizationModes, 1);
                            })
                        }
                        resolve(controlComp)
                    }).catch(function(err){console.log(err);})
                }
                switch(type){
                    case "mir":
                        updateMapcomps(answerObj.value)
                        break;
                    case "freeformpro":
                        updateMapcomps(answerObj.value)
                        break;
                    case "plexus":
                        updateMapcomps(answerObj.value)
                        break;
                    default:
                        reject("3D Landscape type not supported");
                }

            }else{
                reject(answerObj.description)
            }
        });
    });
}

var importMapcompTemplateProjPromise = function(pathToFile, name, uniqueCompnames){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.importMapcompTemplateProj(' + JSON.stringify(pathToFile) + "," + JSON.stringify(name) + "," + JSON.stringify(uniqueCompnames) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var create3DSetup = function(){

}


var getCompsPromise = function(){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getComps();',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var addLabelPromise = function(mapcomp, labelComp, data, optLineCoordinates, rotateAlongPath){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.addLabel(' + JSON.stringify(mapcomp.id) + "," + JSON.stringify(labelComp) + "," + JSON.stringify(data) + "," + JSON.stringify(optLineCoordinates) + "," + JSON.stringify(rotateAlongPath) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var swapSelectedLabelsPromise = function(mapcomp, labelComp){
     return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.swapSelectedLabels(' + JSON.stringify(labelComp) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}

var snapLayersToMapPromise = function(mapcomp, pointerType, optLatLng, optDur, smoothenPath, rotateAlongPath){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.snapLayersToMap(' + JSON.stringify(mapcomp.id) + ',' + JSON.stringify(pointerType) + ',' + JSON.stringify(optLatLng) + ',' + JSON.stringify(optDur) + ',' + JSON.stringify(smoothenPath) + ',' + JSON.stringify(rotateAlongPath) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj && answerObj.value || "error snapping layer to Mapcomp")
            }
        });
    });
}

var getDegreePerPixelPromise = function (mapcomp){
    return new Promise(function(resolve, reject){
        getScreenViewPointsPromise(mapcomp)
        .then(function(OLUR){
            var deltaLng = OLUR[2]-OLUR[0];
            resolve(deltaLng/mapcomp.width);
        })
        .catch(function(reason){
            reject(reason);
        })
    })
}

var getGeoJSONFeatureIcon = function(feature){
    if(!(feature && feature.geometry)){return ""}
    if(feature.GEOlayersBrowserIcon){return interfaceData.iconClasses[feature.GEOlayersBrowserIcon]}
    if(feature.geometry.type.toUpperCase() === "POINT" || feature.geometry.type.toUpperCase() === "MULTIPOINT"){return interfaceData.iconClasses.point}
    if(feature.geometry.type.toUpperCase() === "LINESTRING" || feature.geometry.type.toUpperCase() === "MULTILINESTRING"){return interfaceData.iconClasses.line}
    if(feature.geometry.type.toUpperCase() === "POLYGON" || feature.geometry.type.toUpperCase() === "MULTIPOLYGON"){return interfaceData.iconClasses.polygon}
    if(feature.geometry.type.toUpperCase() === "GEOMETRYCOLLECTION"){return interfaceData.iconClasses.geometryCollection}
    return ""
}

var evaluateStyleRule = function(feature, styleRule){
    var isApplyedByGeometryType = false;
    if(styleRule.applyToPoints && isFeaturePoint(feature)){isApplyedByGeometryType = true}
    if(styleRule.applyToLines && isFeatureLinestring(feature)){isApplyedByGeometryType = true}
    if(styleRule.applyToPolygons && isFeaturePolygon(feature)){isApplyedByGeometryType = true}
    //if(!styleRule.applyToPoints && !styleRule.applyToLines && !styleRule.applyToPolygons){isApplyedByGeometryType = true}
    if(!isApplyedByGeometryType){
        return false
    }
    connectedEvalStr = "";
    styleRule.evaluationLines.forEach(function(evaluationLine, index){
        var evalStr = "(false)"
        var realPropValue = null
        if(evaluationLine.propertyName && (evaluationLine.value || evaluationLine.value === 0)){
            for(var propName in feature.properties){
                if(feature.properties.hasOwnProperty(propName)){
                    if(propName.toUpperCase() === evaluationLine.propertyName.toUpperCase()){
                        realPropValue = feature.properties[propName];
                        break;
                    }
                }
            }


                switch(evaluationLine.operator){
                    case "contains":
                        evalStr = '(String(' + JSON.stringify(realPropValue) + ').toUpperCase().indexOf(String(' + JSON.stringify(evaluationLine.value) + ').toUpperCase()) > -1)'
                        break;
                    case "contains not":
                        evalStr = '(String(' + JSON.stringify(realPropValue) + ').toUpperCase().indexOf(String(' + JSON.stringify(evaluationLine.value) + ').toUpperCase()) == -1)'
                        break;
                    case "is":
                        evalStr = '(String(' + JSON.stringify(realPropValue) + ').toUpperCase() == String(' + JSON.stringify(evaluationLine.value) + ').toUpperCase())'
                        break;
                    case "is not":
                        evalStr = '(String(' + JSON.stringify(realPropValue) + ').toUpperCase() != String(' + JSON.stringify(evaluationLine.value) + ').toUpperCase())'
                        break;
                    case "=":
                        evalStr = '(parseFloat(' + JSON.stringify(realPropValue).replace(",", ".") + ') == parseFloat(' + JSON.stringify(evaluationLine.value).replace(",", ".") + '))'
                        break;
                    case "<":
                        evalStr = '(parseFloat(' + JSON.stringify(realPropValue).replace(",", ".") + ') < parseFloat(' + JSON.stringify(evaluationLine.value).replace(",", ".") + '))'
                        break;
                    case ">":
                        evalStr = '(parseFloat(' + JSON.stringify(realPropValue).replace(",", ".") + ') > parseFloat(' + JSON.stringify(evaluationLine.value).replace(",", ".") + '))'
                        break;
                    case "<=":
                        evalStr = '(parseFloat(' + JSON.stringify(realPropValue).replace(",", ".") + ') <= parseFloat(' + JSON.stringify(evaluationLine.value).replace(",", ".") + '))'
                        break;
                    case ">=":
                        evalStr = '(parseFloat(' + JSON.stringify(realPropValue).replace(",", ".") + ') >= parseFloat(' + JSON.stringify(evaluationLine.value).replace(",", ".") + '))'
                        break;
                }


        }
        if(index > 0){
            switch(evaluationLine.connectionOperator){
                case "and":
                    connectedEvalStr += "&&"
                    break;
                case "or":
                    connectedEvalStr += "||"
                    break;
            }

        }
        connectedEvalStr += evalStr;
    })
    if(connectedEvalStr.length === 0){connectedEvalStr = "true"}
    try{var isAppliedByEvaluationLines = eval(connectedEvalStr)}catch(err){console.log(connectedEvalStr);console.log(err)}
    return isAppliedByEvaluationLines;
}

var editDrawingOptions = function(drawingOption){
    preferences.drawingOptions = getItemById(preferences.drawingOptionsAry, drawingOption.id) || preferences.drawingOptions;
    angular.element($("#drawingOptionsEditorController")).scope().show();
}

var injectDrawingOtionsIdStyleRules = function(feature, dontReEvaluate){
    var drawingOtionsIdAndStyleRuleName =  getDrawingOtionsIdAndStyleRuleName(feature, dontReEvaluate);
    if(drawingOtionsIdAndStyleRuleName){
        feature.GEOlayersDrawingOtionsId = drawingOtionsIdAndStyleRuleName.drawingOptionsId;
        return feature.GEOlayersDrawingOtionsId
    }
    return null;
}

var getMatchingStyleRuleName = function(feature, dontReEvaluate){
    var drawingOtionsIdAndStyleRuleName =  getDrawingOtionsIdAndStyleRuleName(feature, dontReEvaluate);
    if(drawingOtionsIdAndStyleRuleName){

        return drawingOtionsIdAndStyleRuleName.styleRuleName
    }
    return null;
}

var getMatchingDrawingOptions = function(feature, dontReEvaluate){
    var drawingOtionsIdAndStyleRuleName = getDrawingOtionsIdAndStyleRuleName(feature, dontReEvaluate);
    if(drawingOtionsIdAndStyleRuleName){
        return transformDataDrivenDrawingOptionsIfTransformersActive(feature, getItemById(preferences.drawingOptionsAry, drawingOtionsIdAndStyleRuleName.drawingOptionsId), dontReEvaluate);
    }
    return null;
}


var getDrawingOtionsIdAndStyleRuleName = function(feature, dontReEvaluate){
    var drawingOptionsId = 0;
    var styleRuleName = "";

    if(feature.properties && feature.properties.GEOlayersCustomDrawingOtionsId && feature.properties.GEOlayersCustomDrawingOtionsId !== 0){
        drawingOptionsId = feature.properties.GEOlayersCustomDrawingOtionsId;
        styleRuleName = "custom Feature Style"
    }else{
        if(!dontReEvaluate || !drawingOptionsId){
            for(var r = preferences.styleRules.length -1; r >= 0; r--){
                var curStyleRule = preferences.styleRules[r];
                if(curStyleRule.drawingOptionsId && evaluateStyleRule(feature, curStyleRule)){
                    drawingOptionsId = curStyleRule.drawingOptionsId;
                    styleRuleName = curStyleRule.name;
                    break;
                }
            }
        }else{
            drawingOptionsId = feature.GEOlayersDrawingOtionsId;
            styleRuleName = ""
        }
    }
    if(drawingOptionsId){
        return {
            drawingOptionsId: drawingOptionsId,
            styleRuleName: styleRuleName
        }
    }else{
        return null
    }

}

var setFeatureGEOlayersCustomDrawingOtionsId = function(feature, id){
    if(id instanceof Object){
        id = id.id
    }
    feature.properties.GEOlayersCustomDrawingOtionsId = id;
    return id;
}


var simlifyGeoJSONbyViewPromise = function(mapcomp, feature, optQuality) {
    if(!optQuality){optQuality = 1}
    return new Promise(function(resolve, reject){
        getDegreePerPixelPromise(mapcomp)
        .then(function(degreePerPixel){
            var tolecance = degreePerPixel * optQuality;
            resolve(simplifyGeojson(feature,tolecance));
        })
        .catch(function(reason){
            resolve(simplifyGeojson(feature,0.001));
        })
    })
}

var clearGEOlayersPropsInFeature = function(feature){
    if(feature.isSelected){delete feature.isSelected}
    if(feature.isExpanded){delete feature.isExpanded}
    if(feature.GEOlayersDepthIndex){delete feature.GEOlayersDepthIndex}
    if(feature.GEOlayersDrawingOtionsId){delete feature.GEOlayersDrawingOtionsId}
    if(feature.properties && feature.properties.GEOlayersFavorit){delete feature.properties.GEOlayersFavorit}
    if(feature.GEOlayersBrowserIcon){delete feature.GEOlayersBrowserIcon}
}

var drawGeoJSONPromise = function(mapcomp, geoJSON, drawingOptions){
    var loadedFeatures
    if(mapcomp.id){
        return drawGeoJSONPromiseToMapcomp(mapcomp, geoJSON, drawingOptions)
    }else{
        return new Promise(function(resolve, reject){
            if(geoJSON instanceof Array){
                loadedFeatures = geoJSON
            }else{
                loadedFeatures = getFeaturesAryFromGeoJsonObj(geoJSON);
            }
            if(loadedFeatures.length > 0){
                createDefaultServerprofileMapcompPromise(getNameOfGeoJSONNode(loadedFeatures[0]) || "Mapcomp").then(function(mapcompCreated){
                    mapcomp = mapcompCreated;
                    return jumpToGeoJSONFeaturesPromise(mapcomp, loadedFeatures, false, preferences.jumpToOLURbuffer);
                }).then(function(){
                    angular.element($("#baseController")).scope().applyMapcompToSelected(mapcomp)
                    return drawGeoJSONPromiseToMapcomp(mapcomp, loadedFeatures, drawingOptions);
                })/*.then(function(){
                    resolve()
                })*/.catch(function(reason){
                    reject(reason)
                });
            }else{
                reject("0 features to draw")
            }
        })


    }
}




var createMapcompPromise = function (createMapcompSpecs, containingCompId) {
    return new Promise(function(resolve, reject){
        getCompsPromise().then(function(comps){
            var compNameUnique = true;
            comps.forEach(function(comp){
                if(comp.name == createMapcompSpecs.name){
                    compNameUnique = false;
                }
            })
            if(isMapcompNameUnique(interfaceData.mapcomps, createMapcompSpecs.name) && compNameUnique){
            createMapcompSpecs.name = replaceAll(createMapcompSpecs.name, ";", "_");
            var selectedProfile = createMapcompSpecs.mapcompprofile.serverprofile;
                //console.log(selectedProfile)
            updateActiveServerProfilePromise(selectedProfile)
            .then(function(s){
                csInterface.evalScript(
                'mb_GEOlayers2HostInterface.createMapcomp(' + angular.toJson(createMapcompSpecs) + ',' + JSON.stringify(containingCompId) + ');',
                function (answer) {
                    try{var answerObj = JSON.parse(answer);}catch(err){}
                    if (answerObj && answerObj.status === 0) {
                        var mapcomp = null;
                        mapcomp = answerObj.value;
                        if(mapcomp){
                            addProviderLogoToContainingComp(mapcomp);
                            addTilesForCurrentViewPromise(mapcomp).catch(function(reason){console.log(reason)});
                            resolve(mapcomp)
                        }
                    }else{
                        reject("no mapcomp created")
                    }
                });
            })
            }else{
                reject("Names of Mapcomps as well as their containing compositions need to be unique in your whole After Effects project. Please choose a different name for your Mapcomp.");
            }
        }).catch(function(reason){reject(reason)})
    });
}
var createControllerMapcompPromise = function(name){
    return createMapcompFromServerprofilePromise(gl.controllerMapcompServerprofile, name || "Controller Mapcomp")
}

var createDefaultServerprofileMapcompPromise = function(name){
    return createMapcompFromServerprofilePromise(preferences.serverprofiles[0], name || "Mapcomp 1")
}

var createMapcompFromServerprofilePromise = function(serverprofile, name){
    return new Promise(function(resolve, reject){
        var controllerName = name || "Mapcomp"
        getCompsPromise().then(function(comps){
            controllerName = getUniqueName(comps.map(function(comp){return comp.name}), controllerName, true);
            return getActiveCompSpecsOrDefaultPromise(true);
        }).then(function(compSpecsObj){
            var containingCompId = compSpecsObj.id;
            var newMapcomp = {}
            newMapcomp.name = controllerName;
            newMapcomp.width = compSpecsObj.width;
            newMapcomp.height = compSpecsObj.height;
            newMapcomp.frameRate = compSpecsObj.frameRate;
            newMapcomp.duration = compSpecsObj.duration;
            newMapcomp.pixelAspect = compSpecsObj.pixelAspect;
            newMapcomp.mapcompprofile = {
                layerprofile: {
                    linkMapcomp: gl.noLinkMapcompObj,
                    useMotionTile: false,
                    blendZoomlevels: true,
                    blendZoomlevelsDuration: 6,
                    finalizationQuality: 70
                },
                serverprofile: serverprofile,
                status: gl.emptySelectedMapcompObj.mapcompprofile.status
            }
            createMapcompPromise(newMapcomp, containingCompId).then(function(mapcomp){
                resolve(mapcomp)
            }).catch(function(reason){
                reject(reason)
            })
        }).catch(function(reason){
            reject(reason)
        })
    });
}

var drawGeoJSONPromiseToMapcomp = function(mapcomp, geoJSON, drawingOptions){
    if(!drawingOptions){
        if(!preferences.useStyleRules){drawingOptions = preferences.drawingOptions}//give drawing options in if explicitly defined
    }
    return new Promise(function(resolve, reject){
        setProgressbarInfiniteJob("simplifying Feature...")
        var curDrawingOptions
        if(drawingOptions){curDrawingOptions = drawingOptions}
        getDegreePerPixelPromise(mapcomp)
        .then(function(degreePerPixel){
            var tolecance = degreePerPixel;
            if(!(geoJSON instanceof Array)){geoJSON = [geoJSON]}
            geoJSON.forEach(function(feature){
                injectLatLonToFeature(feature)
            })
            geoJSON = geoJSON.map(function(feature,index){
                if(!drawingOptions){
                    injectDrawingOtionsIdStyleRules(feature);
                    curDrawingOptions = getItemById(preferences.drawingOptionsAry, feature.GEOlayersDrawingOtionsId) || preferences.drawingOptions
                }
                if((!preferences.useGlobalBreakDownQualityAndIndividualLayers && curDrawingOptions && curDrawingOptions.breakdownQuality) || (preferences.useGlobalBreakDownQualityAndIndividualLayers && preferences.drawingOptionBreakdownQuality)){

                    feature = simplifyGeojson(feature,tolecance);

                    //getLineFeatureGeoJSONGeometryOrFeatureObj(feature)
                }
                return feature;
            })
            var drawActionToHostInterface = function(mapcomp, stringifiedGeoJSON, drawingOptions, OLUR){
            setProgressbarInfiniteJob("drawing Feature...")
                csInterface.evalScript(
                'mb_GEOlayers2HostInterface.drawGeoJSON(' + JSON.stringify(mapcomp.id) + ',' + stringifiedGeoJSON + ',' + JSON.stringify(drawingOptions) + ',' + JSON.stringify(OLUR) + ');',
                function (answer) {
                    try{var answerObj = JSON.parse(answer);}catch(err){}
                    resetProgressbar();
                    if (answerObj && answerObj.status === 0) {
                        resolve(answerObj.value)
                    }else{
                        reject(answerObj && answerObj.description || "error drawing Feature")
                    }
                });
            }
            if(preferences.drawingOptionIndividualLayers){
                var OLUR = geoJSON.map(function(feature){
                    return convertBBoxGeoJSONtoGEOLAYERS(getBBoxFromGeoJSONFeatureAry([feature]))
                })
            }else{
                var OLUR = convertBBoxGeoJSONtoGEOLAYERS(getBBoxFromGeoJSONFeatureAry(geoJSON));
            }
            var stringifiedGeoJSON = JSON.stringify(geoJSON);
            if(stringifiedGeoJSON.length > preferences.geoJSONLengthForDrawWarning || geoJSON.length > preferences.geoJSONFeatureNrForDrawWarning){
                resetProgressbar();
                dlg_confirm("Very large spatial data!", "You're about to draw a huge amount of data. This might take ages! Do you want to continue?", function(){
                    drawActionToHostInterface(mapcomp, stringifiedGeoJSON, drawingOptions, OLUR);
                },function(){}, "Yes, I've got time!", "No")
            }else{
                drawActionToHostInterface(mapcomp, stringifiedGeoJSON, drawingOptions, OLUR);
            }
        })
        .catch(function(reason){
            resetProgressbar();
        })
    });
}

var AEfilePickerPromise = function(prompt, filterAry, startPath, multiselect){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.AEfilePicker(' + JSON.stringify(prompt) + ',' + JSON.stringify(filterAry) + ',' + JSON.stringify(startPath) + ',' + JSON.stringify(multiselect) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.value)
            }
        });
    });
}

var AEfolderPickerPromise = function(prompt, startPath){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.AEfolderPicker(' + JSON.stringify(prompt) + ',' + JSON.stringify(startPath) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value + path.sep)
            }else{
                reject(answerObj.value)
            }
        });
    });
}

var AEsaveFilePickerPromise = function(prompt, startPath, fileName, fileFormat){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.AEsaveFilePicker(' + JSON.stringify(prompt) + ',' + JSON.stringify(startPath) + ',' + JSON.stringify(fileName) + ',' + JSON.stringify(fileFormat) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                var pathToFile = answerObj.value
                if(fileFormat && fileFormat.indexOf(".")>-1){
                    pathToFile = setFileExtensionOnPathStr(pathToFile, fileFormat.substring(fileFormat.lastIndexOf(".")+1, fileFormat.length));
                }
                resolve(pathToFile)
            }else{
                reject(answerObj.value)
            }
        });
    });
}


var AEcolorPickerPromise = function(color){
    return new Promise(function(resolve, reject){
        if(gl.isColorPickerOpen){
            reject("colorpicker allready open")
        }else{
            gl.isColorPickerOpen = true
            csInterface.evalScript(
            'mb_GEOlayers2HostInterface.AEcolorPicker(' + JSON.stringify(color) + ');',
            function (answer) {
                gl.isColorPickerOpen = false;
                try{var answerObj = JSON.parse(answer);}catch(err){}
                if (answerObj && answerObj.status === 0) {
                    resolve(answerObj.value)
                }else{
                    reject(answerObj.description)
                }
            });
        }

    });
}

var applyFeatureStyleToSelectedShapelayersPromise = function(drawingOptions){
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.applyFeatureStyleToSelectedShapelayers(' + JSON.stringify(drawingOptions) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    });
}



var applyMapcompprofile = function(mapcomp, mapcompprofile){
    mapcomp.mapcompprofile = mapcompprofile;//overwrite
    getTileserverApi(mapcompprofile.serverprofile).initialize(function(){
        removeTilesButPromise(mapcomp, [])
        .then(function(removedTiles){
            return revealMapcompUpdateMapPivotPromise(mapcomp, mapcompprofile)
        })
        .then(function(){
            return updateActiveMapcompprofilePromise(mapcompprofile)
        })
        .then(function(){
            addProviderLogoToContainingComp(mapcomp);
            return addTilesForCurrentViewPromise(mapcomp)
        })
        .catch(function(reason){
            dlg_simpleAlert("Error applying Mapcomp Profile", String(reason));
        })
    })
}

var applyMapcompprofilePromise = function(mapcomp, mapcompprofile){
    mapcomp.mapcompprofile = mapcompprofile;//overwrite
    return new Promise(function(resolve, reject){
        getTileserverApi(mapcompprofile.serverprofile).initialize(function(){
            removeTilesButPromise(mapcomp, [])
            .then(function(removedTiles){
                return revealMapcompUpdateMapPivotPromise(mapcomp, mapcompprofile)
            })
            /*.then(function(){
                return updateActiveMapcompprofilePromise(mapcompprofile)
            })*/
            .then(function(){
                addProviderLogoToContainingComp(mapcomp);
                console.log(mapcomp)
                return addTilesForCurrentViewPromise(mapcomp);
            }).then(function(){
                resolve(mapcomp);
            })
            .catch(function(reason){
                reject("couldn't apply mapcompprofile")
            })
        })
    });
}

var removeLinkMapcompsIfNotExisting = function(mapcomps){
    var mapCompNames = mapcomps.map(function(mapcomp){
        return mapcomp.name;
    })
    mapcomps.forEach(function(mapcomp){
        if(!mapCompNames.containsClone(mapcomp.mapcompprofile.layerprofile.linkMapcomp.name)){
            mapcomp.mapcompprofile.layerprofile.linkMapcomp = gl.noLinkMapcompObj;//unlink in hostapp!!
        }
    })
    return mapcomps
}

var getLinkableMapcomps = function(mapcomp){
    var stringifiedMapcomp = JSON.stringify(mapcomp);
    var isSameMapcomp, isLinkLoop
    return interfaceData.mapcomps.filter(function(curMapcomp){
        isSameMapcomp = stringifiedMapcomp === JSON.stringify(curMapcomp)
        isLinkLoop = curMapcomp.mapcompprofile.layerprofile.linkMapcomp.id === mapcomp.id;
        return (!isSameMapcomp && !isLinkLoop)
    })
}


var getBasePath = function(){
    var basePath = path.normalize(window.location.href.substring(location.href.indexOf("/"), location.href.lastIndexOf("/") + 1));
    if(basePath.substr(0,1) == "\\"){basePath = basePath.substring(1,basePath.length)}
    return decodeURI(basePath)

}

var getUserPath = function(){
    var userPath = path.normalize(process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : '/var/local'));
    return userPath + path.sep + "aescripts" + path.sep + "GEOlayers 2" + path.sep;
}

var initProgressbarJob = function(jobID, progressCount, description){
    gl.progessbarJobs[jobID] = {
        countAll: progressCount,
        countUp: 0
    }
    if(description){
        setProgressbarInfiniteJob(description);
    }else{
        resetProgressbar();
    }

}

var countUpProgressbarJob = function(jobID, style, description){
    var curProgressbarJobObj = gl.progessbarJobs[jobID];
    if(curProgressbarJobObj){
        curProgressbarJobObj.countUp ++;
        updateProgressbar(curProgressbarJobObj.countUp/curProgressbarJobObj.countAll*100, style, description + String(curProgressbarJobObj.countUp) + "/" + String(curProgressbarJobObj.countAll));
    }
}

var finishProgressbarJob = function(jobID){
    delete gl.progessbarJobs[jobID]
    resetProgressbar();
}

var resetProgressbar = function(){
    updateProgressbar(0, "progress")
}

var setProgressbarInfiniteJob = function(description){
    updateProgressbar(100, "progress progress-striped active", description)
}

var updateProgressbar = function(percentage, style, description){
    angular.element($("#finalizationProgressbarController")).scope().updateProgressbar(percentage, style, description);
    angular.element($("#progressbarController")).scope().updateProgressbar(percentage, style, description);
}

var dlg_simpleAlert = function(title, body, closingCallback){
    angular.element($("#simpleAlertController")).scope().simpleAlert(title, body, closingCallback);
}

var dlg_featureInfo = function(features){
    angular.element($("#featureInfoController")).scope().featureInfo(features);
}


var dlg_confirm = function(title, body, confirmCallback, cancelCallback, confirmBtnText, cancelBtnText, optXCallback){
    angular.element($("#confirmController")).scope().confirm(title, body, confirmCallback, cancelCallback, confirmBtnText, cancelBtnText, optXCallback);
}

var dlg_finalization = function(title, mapcomps, indexMapcompInProgress){
    if(gl.isFinalizingStatus > 0){
        angular.element($("#finalizationController")).scope().updateFinalizationWindow(title, mapcomps, indexMapcompInProgress);
    }else{
        if(gl.isFinalizingStatus != -1){
            angular.element($("#finalizationController")).scope().updateFinalizationWindow(title, mapcomps, indexMapcompInProgress);
            angular.element($("#finalizationController")).scope().showFinalizationWindow();
        }
    }
}

var dlg_projects = function(){
    $('#modal_projects').modal();
}
var dlg_projects_hide = function(){
    $('#modal_projects').modal('hide');
}



var enablePanelButtons = function(){
    angular.element($("#baseController")).scope().enablePanelButtons();
}
var disablePanelButtons = function(){
    angular.element($("#baseController")).scope().disablePanelButtons();
}

var dlg_editMapcompprofile = function(mapcomp){
    /*getMapcompprofileFromLayerPromise(mapcomp)
    .then(function(mapcompprofile){
        angular.element($("#mapcompprofileController")).scope().editMapcompprofilesDlg(mapcompprofile);
    })*/
    angular.element($("#mapcompprofileController")).scope().editMapcompprofilesDlg(mapcomp);
}

var isMapcompStatusFinalized = function(mapcomp){
    var statusId = -99
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.serverprofile && !mapcomp.mapcompprofile.serverprofile.enableTileDownload){return true}
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.status){statusId = mapcomp.mapcompprofile.status.id;}
    switch (statusId) {
        case -1:
            return false
            break;
        case 0:
            return false
            break;
        case 1:
            return true
            break;
        case 2:
            return true
            break;
        default:
            return false
    }
}

var getMapcompStatusIconClass = function(mapcomp){
    var panelClass = "";
    var statusId
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.serverprofile && !mapcomp.mapcompprofile.serverprofile.enableTileDownload){
        return "glyphicon glyphicon-ban-circle"
    }
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.status){statusId = mapcomp.mapcompprofile.status.id;}
    switch (statusId) {
        case 0:
            panelClass = "glyphicon glyphicon-remove-sign";
            break;
        case 1:
            panelClass = "glyphicon glyphicon-exclamation-sign";
            break;
        case 2:
            panelClass = "glyphicon glyphicon-ok-sign";
            break;
        //case -1:
            //panelClass = "glyphicon glyphicon-move";
            //break;
    }
    return panelClass
}

var getMapcompStatusDescription = function(mapcomp){
    var description = "";
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.serverprofile && !mapcomp.mapcompprofile.serverprofile.enableTileDownload){
        return "Controller Mapcomp"
    }
    if(mapcomp && mapcomp.mapcompprofile && mapcomp.mapcompprofile.status){statusId = mapcomp.mapcompprofile.status.id;}

    switch (statusId) {
        case 0:
            description = String(mapcomp.mapcompprofile.status.finalizationQuality) + "% " + String(mapcomp.mapcompprofile.status.nrTilesStillMissing) + " not fixed";
            break;
        case 1:
            var ZlStr = "Zoomlevel";
            if(mapcomp.mapcompprofile.status.zoomleveldepth > 1){ZlStr += "s"};
            description = String(mapcomp.mapcompprofile.status.finalizationQuality) + "% " + String(mapcomp.mapcompprofile.status.nrTilesLowerZoomlevel) + " Tiles fixed " + String(mapcomp.mapcompprofile.status.zoomleveldepth) + " " + ZlStr + " below";
            break;
        case 2:
            description = String(mapcomp.mapcompprofile.status.finalizationQuality) + "% " + mapcomp.mapcompprofile.status.description;
            break;
        //case -1:
            //panelClass = "glyphicon glyphicon-move";
            //break;

             /*mapcomp.mapcompprofile.status = {
                id : 1,
                description : "Finalized with Missing Tiles",
                nrTilesInComp : tilesInMapcomp.forView.length - missingTiles.length,
                nrTilesNeeded : tilesInMapcomp.forView.length,
                nrTilesLowerZoomlevel : addedLowerZoomlevelTiles.length,
                nrTilesStillMissing : tilesStillMissing.length,
                zoomleveldepth : zoomleveldepth,
                finalizationTime : finalizationTime,
                finalizationQuality : usedFinalizationQuality
            } */
    }
    return description;
}

var getMapcompStatusPanelClass = function(mapcomp){
    var panelClass = "panel-default";
    var statusId = mapcomp.mapcompprofile.status.id;
    switch (statusId) {
        case 0:
            panelClass = "panel-danger";
            break;
        case 1:
            panelClass = "panel-warning";
            break;
        case 2:
            panelClass = "panel-success";
            break;
    }
    return panelClass
}

var drawShapeFileToMapcomp = function(mapcomp){
    var loadedFeatures = [];
    AEfilePickerPromise("please select a geospatial File to draw", gl.supportedShapeFileFormats, preferences.lastPathToPickShapefile, false)
    .then(function(pathToShp){
        preferences.lastPathToPickShapefile = pathToShp;
        return importShapeFileToGeoJSONPromise(pathToShp);
    }).then(function(geoJSON){
        loadedFeatures = getFeaturesAryFromGeoJsonObj(geoJSON);
        return new Promise(function(resolve, reject){
            if(mapcomp.id){
                resolve(geoJSON)
            }else{
                createDefaultServerprofileMapcompPromise(getNameOfGeoJSONNode(loadedFeatures[0]) || "Mapcomp").then(function(createdMapcomp){
                    mapcomp = createdMapcomp
                    angular.element($("#baseController")).scope().applyMapcompToSelected(mapcomp)
                    resolve(geoJSON)
                }).catch(function(reason){
                    reject(reason)
                })
            }
        }       )

    }).then(function(geoJSON){
        return jumpToGeoJSONFeaturesPromise(mapcomp, loadedFeatures, false, preferences.jumpToOLURbuffer);
    }).then(function(){
        return drawGeoJSONPromise(mapcomp, loadedFeatures);
    }).catch(function(reason){console.log(reason)});
}

var getCurrentGEOlayersBBoxPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        getScreenViewPointsPromise(mapcomp)
        .then(function(tempPointsOLUR){
            resolve([[tempPointsOLUR[3],tempPointsOLUR[0]],[tempPointsOLUR[1], tempPointsOLUR[2]]]);
        })
        .catch(function(reason){
           reject(reason)
        })
    })
}

var getLatLongZoomPromise = function(mapcomp, optDetail){
    if(!optDetail){optDetail = 0.3}
    return new Promise(function(resolve, reject){
        getScreenViewPointsPromise(mapcomp)
        .then(function(tempPointsOLUR){
            var LatLong = getLatLongFromGEOlayersBBox(clipViewPointsDegrees([[tempPointsOLUR[3],tempPointsOLUR[0]],[tempPointsOLUR[1], tempPointsOLUR[2]]]));
            resolve({
                lat: LatLong[0],
                long: LatLong[1],
                zoom: tileFinder.getZoomLevel(mapcomp.width, tempPointsOLUR[2]-tempPointsOLUR[0], optDetail, undefined, 256, 23, 23)
            })
        })
        .catch(function(reason){
            reject(reason)
        })
    })
}

var getMapcompCenterPromise = function(mapcomp) {
    return new Promise(function(resolve, reject){
        csInterface.evalScript(
        'mb_GEOlayers2HostInterface.getMapcompCenter(' + JSON.stringify(mapcomp.id) + ');',
        function (answer) {
            try{var answerObj = JSON.parse(answer);}catch(err){}
            if (answerObj && answerObj.status === 0) {
                resolve(answerObj.value)
            }else{
                reject(answerObj.description)
            }
        });
    })
}

var getLocationByMapcompPromise = function(mapcomp, optDetail){
    return new Promise(function(resolve, reject){
        getLatLongZoomPromise(mapcomp, optDetail)
        .then(function(latLongZoom){
            return getSearchApi(preferences.activeSearchprofile).getLocationByLatLongZoomPromise(latLongZoom.lat, getFullRotationAndClippedLon(latLongZoom.long).clippedDegrees, latLongZoom.zoom);
        })
        .then(function(geoJSON){
            resolve(geoJSON);
        })
        .catch(function(reason){
            reject(reason);
        })

    });
}

var getCurrentViewDescriptionPromise = function(mapcomp){
    return new Promise(function(resolve, reject){
        getLocationByMapcompPromise(interfaceData.selectedMapcomp, 0.05)
        .then(function(locations){
            resolve(locations[0].properties.GEOlayersDisplayName);
        }).catch(function(err){
            resolve("Current View")
            console.log(err)
        });
    })
}

var getLatLongZoomDescriptionPromise = function(lat, lon, zoom, isComplete){
    if(!zoom){zoom = 18}
    return new Promise(function(resolve, reject){
        getSearchApi(preferences.activeSearchprofile).getLocationByLatLongZoomPromise(lat, lon, zoom)
        .then(function(locations){
            var desc
            if(isComplete){
                desc = locations[0].properties.display_name;
            }else{
                desc = locations[0].properties.GEOlayersDisplayName;
            }
            resolve(desc);
        }).catch(function(err){
            resolve("")
            console.log(err)
        });
    })
}

var getLatLongFromGEOlayersBBox = function(bbox){
    var p1Px = tileUtils.LatLongToPixelXY(bbox[0][0],bbox[0][1], 23, 256);
    var p2Px = tileUtils.LatLongToPixelXY(bbox[1][0],bbox[1][1], 23, 256);
    var pPx = [p1Px[0] + (p2Px[0]-p1Px[0])/2, p1Px[1] + (p2Px[1]-p1Px[1])/2];

    return tileUtils.PixelXYToLatLong(pPx[0], pPx[1], 23, true, 256);
}

var addGeoJSONsToFavorites = function(geoJSONs){
    geoJSONs.forEach(function(geoJSON){
        angular.element($("#geojsonBrowserController")).scope().addGeoJSONtoFavorites(geoJSON);
        saveFavoriteFeatureCollection(angular.element($("#geojsonBrowserController")).scope().geojsons[0]);
    })
}


var addFeatureLabelPromise = function(mapcomp, feature){
    return addLabelPromise(mapcomp, interfaceData.selectedLabelcomp, injectLatLonToFeature(feature).properties)
}
var addMovingFeatureLabelPromise = function(mapcomp, feature, lineCoordinates, rotateAlongPath){
    return addLabelPromise(mapcomp, interfaceData.selectedLabelcomp, injectLatLonToFeature(feature).properties, lineCoordinates, rotateAlongPath)
}

var isFeatureInCurrentView = function(feature){
    if(interfaceData.currentViewPoints[1][1] - interfaceData.currentViewPoints[0][1] > 360){
        var bbox1 = [[interfaceData.currentViewPoints[0][0], -180],[interfaceData.currentViewPoints[1][0], 180]];
        return IntersectRect(convertBBoxGeoJSONtoTLBRObj(getBBoxFromGeoJSONFeature(feature)), convertBBoxGEOLAYERStoTLBRObj(bbox1));
    }else{
        var clipViewPoints = clipViewPointsDegrees(interfaceData.currentViewPoints);
        if(clipViewPoints[0][1]>clipViewPoints[1][1]){
            var bbox1 = [clipViewPoints[0], [clipViewPoints[1][0], 180]]
            var bbox2 = [[clipViewPoints[0][0], -180], clipViewPoints[1]]
            return IntersectRect(convertBBoxGeoJSONtoTLBRObj(getBBoxFromGeoJSONFeature(feature)), convertBBoxGEOLAYERStoTLBRObj(bbox1)) || IntersectRect(convertBBoxGeoJSONtoTLBRObj(getBBoxFromGeoJSONFeature(feature)), convertBBoxGEOLAYERStoTLBRObj(bbox2));
        }else{
            return IntersectRect(convertBBoxGeoJSONtoTLBRObj(getBBoxFromGeoJSONFeature(feature)), convertBBoxGEOLAYERStoTLBRObj(clipViewPoints));
        }
    }
}

var isFeatureVerticyInCurrentView = function(feature){
    if(interfaceData.currentViewPoints[1][1] - interfaceData.currentViewPoints[0][1] > 360){
        var bbox1 = [[interfaceData.currentViewPoints[0][0], -180],[interfaceData.currentViewPoints[1][0], 180]];
        return isVerticyOfGeoJSONGeometryOrFeatureObjInBB(feature, convertBBoxGEOLAYERStoObj(bbox1));
    }else{
        var clipViewPoints = clipViewPointsDegrees(interfaceData.currentViewPoints);
        if(clipViewPoints[0][1]>clipViewPoints[1][1]){
            var bbox1 = [clipViewPoints[0], [clipViewPoints[1][0], 180]]
            var bbox2 = [[clipViewPoints[0][0], -180], clipViewPoints[1]]
            return isVerticyOfGeoJSONGeometryOrFeatureObjInBB(feature, convertBBoxGEOLAYERStoObj(bbox1)) || isVerticyOfGeoJSONGeometryOrFeatureObjInBB(feature, convertBBoxGEOLAYERStoObj(bbox2));
        }else{
            return isVerticyOfGeoJSONGeometryOrFeatureObjInBB(feature, convertBBoxGEOLAYERStoObj(clipViewPoints));
        }
    }
}

var injectLatLonToFeature = function(feature){
    if(!feature.properties){feature.properties = {};}
    if(!feature.properties.lat || !feature.properties.lon){
        if(isFeatureLinestring(feature)){
            try{
                var pointOnLine = turf.nearest(turf.center(turf.featureCollection([feature])), turf.explode(feature))
                feature.properties.lat = pointOnLine.geometry.coordinates[1];
                feature.properties.lon = pointOnLine.geometry.coordinates[0];
            }catch(e){
                console.log(e)
            }
        }
        if(!feature.properties.lat || !feature.properties.lon){
            try{
                var centroidPt = turf.centroid(feature);
                feature.properties.lat = centroidPt.geometry.coordinates[1];
                feature.properties.lon = centroidPt.geometry.coordinates[0];
            }catch(e){
                console.log("error calculating centroid");
                var bbox = getBBoxFromGeoJSONFeature(feature);
                bbox = normalizeGeoJSONBBox(bbox);
                feature.properties.lat = bbox[0] + (bbox[2]-bbox[0])/2;
                feature.properties.lon = bbox[1] + (bbox[3]-bbox[1])/2;
            }
        }
    }
    return feature
}


var getMeshFeatureCollectionFromFeaturesLatLonPromise = function(features, featureMeshLinesAlongGreatCircle, mapcomp) {
    return new Promise(function(resolve, reject){
        if(features.length < 2){
            reject("please select at least 2 Features")
        }
        if(featureMeshLinesAlongGreatCircle){
            getDegreePerPixelPromise(mapcomp).then(function(degreePerPixel){
                resolve(createMeshFeatureCollectionFromFeaturesLatLon(features, true, Math.max(degreePerPixel, gl.greatCircleMinDegreePerPixel)))
            }).catch(function(err){
                console.log(err)
                resolve(createMeshFeatureCollectionFromFeaturesLatLon(features, true, gl.greatCircleMinDegreePerPixel))
            })
        }else{
            resolve(createMeshFeatureCollectionFromFeaturesLatLon(features, false))
        }
    })
}

var createConnectionLineStringsPromise = function(features, featureMeshLinesAlongGreatCircle, mapcomp) {
    return new Promise(function(resolve, reject){
        if(features.length < 2){
            reject("please select at least 2 Features")
        }
        if(featureMeshLinesAlongGreatCircle){
            getDegreePerPixelPromise(mapcomp).then(function(degreePerPixel){
                resolve(createConnectionLineStrings(features, true, Math.max(degreePerPixel, gl.greatCircleMinDegreePerPixel)))
            }).catch(function(err){
                console.log(err)
                resolve(createConnectionLineStrings(features, true, gl.greatCircleMinDegreePerPixel))
            })
        }else{
            resolve(createConnectionLineStrings(features, false))
        }
    })
}

var createConnectionLineStrings = function(features, greatCircle, degreePerPixel){
    var p1Feature, p1, p2Feature, p2;
    var linePts = [];
    var lineFeatures = [];
    var legDistance, units, fc;
    var overallDistance = 0;
    var overallDistanceStr = "";
    features.forEach(function(feature, index){
        if(index > 0){
            p1Feature = injectLatLonToFeature(features[index-1]);
            p1 = [parseFloat(p1Feature.properties.lon), parseFloat(p1Feature.properties.lat)];
            p2Feature = injectLatLonToFeature(feature);
            p2 = [parseFloat(p2Feature.properties.lon), parseFloat(p2Feature.properties.lat)];
            properties = {GEOlayersDisplayName : String(p1Feature.properties.GEOlayersDisplayName) + " - " + String(p2Feature.properties.GEOlayersDisplayName)}
            console.log([p1Feature, p2Feature])
            if(greatCircle){
                properties.type = "great circle route"
                linePts = getGreatCircleRouteLineGeometry(p1, p2, degreePerPixel);
            }else{
                linePts = getGreatCircleRouteLineGeometry(p1, p2, 360);
            }
            if(greatCircle){
                units = "kilometers";
                if(preferences.languageSettings.units === "mi"){units = "miles"}
                legDistance = turf.distance(turf.point(p1), turf.point(p2), units);
                properties.distance = legDistance
                properties.GEOlayersDisplayName += " (" + String(legDistance.toFixed(3)) + " " + units + ")"
                overallDistance = overallDistance + legDistance;
            }
            lineFeatures.push(turf.lineString(linePts, properties))
        }
    })
    if(greatCircle){
        overallDistanceStr = " (" + String(overallDistance.toFixed(3)) + " " + units + ")"
    }
    fc = turf.featureCollection(lineFeatures);
    fc.properties = {
        GEOlayersDisplayName: features.map(function(feature){return feature.properties.GEOlayersDisplayName}).join(" - ") + overallDistanceStr,
        distance: overallDistance
    }
    return fc
}

var createMeshFeatureCollectionFromFeaturesLatLon = function(features, greatCircle, degreePerPixel){
    var p1, p2, p1Feature, p2Feature, linePts, properties, linesFeatureCollection, pointsFeatureCollection;
    var lineFeatures = [];
    var pointFeatures = [];
    while(features.length > 0){
        p1Feature = injectLatLonToFeature(features[0]);
        p1 = [parseFloat(p1Feature.properties.lon), parseFloat(p1Feature.properties.lat)];
        features.forEach(function(feature){
            p2Feature = injectLatLonToFeature(feature);
            if(p1Feature != p2Feature){
                p2 = [parseFloat(p2Feature.properties.lon), parseFloat(p2Feature.properties.lat)];
                properties = {GEOlayersDisplayName : String(p1Feature.properties.GEOlayersDisplayName) + " - " + String(p2Feature.properties.GEOlayersDisplayName)}
                if(greatCircle){
                    properties.type = "great circle route"
                    linePts = getGreatCircleRouteLineGeometry(p1, p2, degreePerPixel);
                }else{
                    linePts = [p1, p2];
                }
                lineFeatures.push(turf.lineString(linePts, properties))
            }
        })
        pointFeatures.push(turf.point(p1, features[0].properties));
        features.splice(0,1);
    }
    pointsFeatureCollection = turf.featureCollection(pointFeatures);
    pointsFeatureCollection.properties = {
        GEOlayersDisplayName : "Points"
    }
    if(lineFeatures.length > 1){
        linesFeatureCollection = turf.featureCollection(lineFeatures);
        linesFeatureCollection.properties = {
            GEOlayersDisplayName : "Lines"
        }
    }else{
        linesFeatureCollection = lineFeatures[0];
    }
    var meshFeatureCollection = turf.featureCollection([pointsFeatureCollection, linesFeatureCollection]);
    meshFeatureCollection.properties = {
        GEOlayersDisplayName : "Feature Mesh"
    }
    meshFeatureCollection.GEOlayersBrowserIcon = "featureMesh";
    return meshFeatureCollection
}

var getGreatCircleRouteLineGeometry = function(p1, p2, degreePerPixel){
    var haversDist = turf.distance(turf.point(p1), turf.point(p2), "degrees");
    var stepsPerCurve = haversDist/(degreePerPixel*3);
    var midPointFeature = turf.point(p1);
    var linePts = [p1];
    var currBearing
    for(var h=1; h<=stepsPerCurve; h++){
        currBearing = turf.bearing(midPointFeature, turf.point(p2));
        console.log(currBearing)
        midPointFeature = turf.destination(midPointFeature, haversDist/stepsPerCurve, currBearing, "degrees");
        linePts.push(midPointFeature.geometry.coordinates);
    }
    haversDist = turf.distance(midPointFeature, turf.point(p2), "degrees");
    currBearing = turf.bearing(midPointFeature, turf.point(p2));
    midPointFeature = turf.destination(midPointFeature, haversDist, currBearing, "degrees");
    linePts.push(midPointFeature.geometry.coordinates);
    return linePts
}

var parseCoordinateStr = function(pointStr){
    pointStr = pointStr.replace(/°/g, "");
   if(pointStr.match(/^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/)){
        geoPoint = pointStr.split(",").map(function(valStr){
            return parseFloat(valStr.trim());
        })
        return geoPoint
    }else{
        return null
    }
}

var getFeatureFromLatLon = function(point, name){
    if(!name){name = point.toString()}
    return turf.point([point[1], point[0]], {lat:point[0], lon:point[1], osm_type: "node", GEOlayersDisplayName: name})
}


var Base64 = {

// private property
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

// public method for encoding
encode : function (input) {
    var output = "";
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    input = Base64._utf8_encode(input);

    while (i < input.length) {

        chr1 = input.charCodeAt(i++);
        chr2 = input.charCodeAt(i++);
        chr3 = input.charCodeAt(i++);

        enc1 = chr1 >> 2;
        enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
        enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
        enc4 = chr3 & 63;

        if (isNaN(chr2)) {
            enc3 = enc4 = 64;
        } else if (isNaN(chr3)) {
            enc4 = 64;
        }

        output = output +
        this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

    }

    return output;
},

// public method for decoding
decode : function (input) {
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

    while (i < input.length) {

        enc1 = this._keyStr.indexOf(input.charAt(i++));
        enc2 = this._keyStr.indexOf(input.charAt(i++));
        enc3 = this._keyStr.indexOf(input.charAt(i++));
        enc4 = this._keyStr.indexOf(input.charAt(i++));

        chr1 = (enc1 << 2) | (enc2 >> 4);
        chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
        chr3 = ((enc3 & 3) << 6) | enc4;

        output = output + String.fromCharCode(chr1);

        if (enc3 != 64) {
            output = output + String.fromCharCode(chr2);
        }
        if (enc4 != 64) {
            output = output + String.fromCharCode(chr3);
        }

    }

    output = Base64._utf8_decode(output);

    return output;

},

// private method for UTF-8 encoding
_utf8_encode : function (string) {
    string = string.replace(/\r\n/g,"\n");
    var utftext = "";

    for (var n = 0; n < string.length; n++) {

        var c = string.charCodeAt(n);

        if (c < 128) {
            utftext += String.fromCharCode(c);
        }
        else if((c > 127) && (c < 2048)) {
            utftext += String.fromCharCode((c >> 6) | 192);
            utftext += String.fromCharCode((c & 63) | 128);
        }
        else {
            utftext += String.fromCharCode((c >> 12) | 224);
            utftext += String.fromCharCode(((c >> 6) & 63) | 128);
            utftext += String.fromCharCode((c & 63) | 128);
        }

    }

    return utftext;
},

// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
    var string = "";
    var i = 0;
    var c = c1 = c2 = 0;

    while ( i < utftext.length ) {

        c = utftext.charCodeAt(i);

        if (c < 128) {
            string += String.fromCharCode(c);
            i++;
        }
        else if((c > 191) && (c < 224)) {
            c2 = utftext.charCodeAt(i+1);
            string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
            i += 2;
        }
        else {
            c2 = utftext.charCodeAt(i+1);
            c3 = utftext.charCodeAt(i+2);
            string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
            i += 3;
        }

    }

    return string;
}

}

var getStrBetween = function(str, begin, end){
        var indexBegin = str.indexOf(begin);
        var indexEnd = str.indexOf(end);
        var between = "";
        if(indexBegin > -1 && indexEnd > -1){
            between = str.substring(indexBegin + begin.length, indexEnd);
        }
        return between
}

var getInnerFromATags = function(str){
    var strBetween = ">";
    while(strBetween && strBetween.length > 0){
        strBetween = getStrBetween (str, "<a", "</a>");
        if(strBetween && strBetween.indexOf(">")>-1){
            str = str.replace("<a" + strBetween + "</a>", strBetween.split(">")[1]);
        }
    }
    return str
}

var getBase64Credentials = function(username, password){
    if(typeof username == "string" && typeof password == "string" && username.length > 0 && password.length > 0 ){
        var encodeInput = username + ":" + password;
        return Base64.encode(encodeInput);
    }else{
        return null
    }
}

var getPropCaseinsenitive = function(obj, propname){
    for(var p in obj){
        if(obj.hasOwnProperty(p) && String(p).toUpperCase() === String(propname).toUpperCase()){
            return obj[p];
        }
    }
}

var setFileExtensionOnPathStr = function(pathStr, fileFormatStr){
    var lastDot = pathStr.lastIndexOf(".");
    var lastSep = pathStr.lastIndexOf(path.sep);
    if(lastDot > lastSep){
        pathStr = pathStr.substring(0, lastDot) + "." + fileFormatStr;
    }else{
        pathStr = pathStr + "." + fileFormatStr;
    }
    return pathStr
}

var getDirectoryOfFileStr = function(pathStr){
    if(!pathStr){return pathStr}
    var lastSep = pathStr.lastIndexOf(path.sep);
    if(lastSep > -1){
        return pathStr.substring(0, lastSep) + path.sep
    }else{
        return pathStr
    }
}

var preferencesChangeHandler = function(avoidTimeout){
    angular.element($("#baseController")).scope().preferencesChangeHandler(avoidTimeout);
}

var openPrefsExtension = function(){
    csInterface.requestOpenExtension("com.markusbergelt.geolayers2.preferences", "");
    var newEvent = new CSEvent("com.markusbergelt.geolayers2.updatePrefs", "APPLICATION");
    newEvent.data = {preferences : preferences}
    csInterface.dispatchEvent(newEvent);
}

var openHelpExtension = function(){
    csInterface.requestOpenExtension("com.markusbergelt.geolayers2.help", "");
}

var openOnlineProjectPromise = function(urlToProject, fileName, projectType, progressCallback, forceDownload){
    if(aesp){aesp[ /* @mangle */'analytics' /* @/mangle */].event("onlineProjectClicked", {"PROJECTNAME": fileName})}
    if(!progressCallback){progressCallback = function(err, progress){}}
    fs.mkdirsSync(preferences.sampleProjectsPath);
    return new Promise(function(resolve, reject){
        var loadGeoJsons = function(fileName){
            var geoJsons = getFilesInDirectory(preferences.sampleProjectsPath + fileName + path.sep,/\.geojson$/);
            geoJsons.forEach(function(geoJson){
                angular.element($("#geojsonBrowserController")).scope().importShapefile(geoJson, true, {isSelected:true});
            })
        }
        var handleProject = function(aepFile){
            switch(projectType){
                case "labelTemplates":
                    dlg_confirm("Label Template Project", "Do you want to open the Project or import the Label Templates to your GEOlayers 2 Project?", function(){
                        importLabelTemplateProjectPromise(aepFile).then(function(fileNam){
                            dlg_projects_hide()
                            resolve(fileNam)
                        }).catch(function(err){
                            reject(err)
                        })
                    }, function(){
                        openProjectPromise(aepFile, true).then(function(fileNam){
                            dlg_projects_hide()
                            resolve(fileNam)
                        }).catch(function(err){
                            reject(err)
                        })
                    }, "Add Label Templates", "Open", function(){
                        resolve()
                    })
                    break;
                default:
                    openProjectPromise(aepFile, true).then(function(fileNam){
                        loadGeoJsons(fileName)
                        dispatchInitializeTemplateProjectEvent()
                        dlg_projects_hide()
                        resolve(fileNam)
                    }).catch(function(err){
                        reject(err)
                    })
                    break;
            }

        }
        var aepFile = getFileInDirectory(preferences.sampleProjectsPath + fileName + path.sep,/\.aep|aet$/);
        if(aepFile && !forceDownload){
            handleProject(aepFile)
        }else{
            httpreq.download(urlToProject, preferences.sampleProjectsPath + fileName + ".zip", JSON.parse(JSON.stringify(preferences.conOptions)),
                progressCallback,
                function(err, res){
                    if (err) {
                        console.log(err)
                    }
                    if(res && res.statusCode == 200){
                        var pathToZip = res.downloadlocation;
                        fs.createReadStream(pathToZip)
                          .pipe(unzipper.Extract({ path: preferences.sampleProjectsPath + fileName + path.sep }))
                          .on('finish', function(){
                            var aepFile = getFileInDirectory(preferences.sampleProjectsPath + fileName + path.sep,/\.aep|aet$/);
                            if(aepFile){
                                handleProject(aepFile)
                            }else{
                                reject("no aep found")
                            }
                          }).on('error', reject);
                    }else{
                        reject("error downloading project")
                    }
                }

            )
        }
    })

}

var shortenPath = function(lPath, maxChars){
    if(!lPath || lPath.length < maxChars){
        return lPath
    }
    var sPath = lPath;
    var parsed = path.parse(sPath)
    if(parsed){
        sPath = parsed.dir.substr(0, maxChars - 3 - parsed.base.length) + "..." + path.sep + parsed.base
    }
    return sPath
}

var dispatchInitializeTemplateProjectEvent = function(data){
    if(!data){data={}}
    var event = new CSEvent("com.markusbergelt.geolayers2.initializeTemplateProject", "APPLICATION");
    event.data = data
    csInterface.dispatchEvent(event);
}
