package com.ibm.urbancode.zos.common
/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Build
 * IBM UrbanCode Deploy
 * IBM UrbanCode Release
 * IBM AnthillPro
 * (c) Copyright IBM Corporation 2002, 2014. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */

import com.ibm.jzos.ZFile
import com.ibm.jzos.ZFileException

class DeploymentHelper {
	def static final filesCanBeDeletedFromWorkDir = new HashSet();
	static{
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.PACKAGE_MANIFEST_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.ROLLBACK_MANIFEST_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.DEPLOY_DELTA_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.DEPLOY_BACKUP_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.CONTAINER_MAPPER_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.CHECK_ACCESS_DS_FILE_NAME_BACKUP);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.CHECK_ACCESS_DS_FILE_NAME);
		filesCanBeDeletedFromWorkDir.add(DeploymentConstants.PACKAGE_FILE_NAME);
	}

	static getStringInput(input) {
		return input?.trim();
	}

	static getBooleanInput(input) {
		return Boolean.valueOf(input?.trim());
	}

	//Repository path not located in the component name folder, we need to add this folder on the path
	static getVersionDirPathInRepository(basePath, componentName, versionName){
		def repobasePathToVersion = basePath + File.separator + componentName + File.separator + versionName;
		return(repobasePathToVersion);
	}

	static getFilePathInRepository(basePath, componentName, versionName, fileName) {
		def patToFileInRepo = getVersionDirPathInRepository(basePath,componentName,versionName) + File.separator + fileName;
		return(patToFileInRepo);
	}
	
	//compatible=true, the return path will first check if <basepath>/deploy/component/version exists. and then the new path since v18
	//compatible=false, the return path will return the new path since v18, which is <basepath>/deploy/resourceId/component/version
	static getVersionPathInDeployBasePath(basePath, resourceId, componentName, versionName, compatible){
		def basePathToVersion = basePath + File.separator +
				DeploymentConstants.ADDITIONAL_FOLDER_FOR_BASE_PATH + File.separator +
				resourceId + File.separator +
				componentName + File.separator +
				versionName;
				
		if (compatible && !directoryExist(basePathToVersion)) {
			basePathToVersion = basePath + File.separator +
				DeploymentConstants.ADDITIONAL_FOLDER_FOR_BASE_PATH + File.separator +
				componentName + File.separator +
				versionName;
		}
		
		return(basePathToVersion);
	}
	
	static getComponentPathInDeployBasePath(basePath, resourceId, componentName, compatible){
		def basePathToComponent = basePath + File.separator +
				DeploymentConstants.ADDITIONAL_FOLDER_FOR_BASE_PATH + File.separator +
				resourceId + File.separator +
				componentName
				
		if (compatible && !directoryExist(basePathToComponent)) {
			basePathToComponent = basePath + File.separator +
				DeploymentConstants.ADDITIONAL_FOLDER_FOR_BASE_PATH + File.separator +
				componentName;
		}
		
		return(basePathToComponent);
	}

	static getFilePathInDeployBasePath(versionPathInBasePah, fileName) {
		def patToFileInBasePath = versionPathInBasePah + File.separator + fileName;
		return(patToFileInBasePath);
	}

	//Working copy default path is located in the component folder
	static getVersionDirPathInWorkingDir(basePath, versionname, resourceId){
		return(basePath + File.separator + resourceId + File.separator + versionname);
	}

	static getFilePathInWorkingDir(basePath, versionname, resourceId, fileName){
		def pathToFileInWorkDir = getVersionDirPathInWorkingDir(basePath, versionname, resourceId) + File.separator + fileName
		return(pathToFileInWorkDir);
	}

	static getPath4ExecScriptInToolkit(toolkitHome, execName){
		return(toolkitHome + File.separator + DeploymentConstants.ZOS_TOOLKIT_EXEC_FOLDER_NAME + File.separator + execName);
	}

	//Get ant script,will depends on our pacakge structure
	static getAntScriptFolderPath(){
		final def pluginHome = System.getenv("PLUGIN_HOME");
		return(pluginHome + File.separator + "scripts" + File.separator + "deployment");
	}

	//Validation methods
	static validateFileExist(filePath, errorInfo){
		def file = new File(filePath);
		if( !(file.exists() && file.isFile()) ){
			throw new IllegalArgumentException(errorInfo);
		}
	}

	static validateDirectoryExist(filePath, errorInfo){
		def file = new File(filePath);
		if( !(file.exists() && file.isDirectory()) ){
			throw new IllegalArgumentException(errorInfo);
		}
	}
	
	static directoryExist(directoryPath){
		def file = new File(directoryPath);
		if(file.exists() && file.isDirectory()){
			return(true);
		}
		
		return(false);
	}

	static fileExists(filePath){
		def fileExist = false;
		def file = new File(filePath);
		if( file.exists() && file.isFile() ){
			fileExist = true;
		}

		return(fileExist);
	}

	//Handle user's input ruler mapping pair
	static handleMappingRule(pdsMapping, Map src2TargetMap, Map wildcardSrc2TargetMap) {
		if(pdsMapping && pdsMapping.size() > 0){
			def pdsMappingStr = "";
			pdsMappingStr = pdsMapping.trim();
			pdsMappingStr.replaceAll(DeploymentConstants.SYSTEM_LINE_SEPARATOR, DeploymentConstants.SRC_TO_TARGET_DELIMETER);
			def pdsMappingPairs = pdsMappingStr.split(DeploymentConstants.SRC_TO_TARGET_DELIMETER);
			pdsMappingPairs.each {pair->
				pair = pair.trim();
				if(pair.length() > 0){//Allow empty line which will be ignore
					def onePair = pair.split(DeploymentConstants.MAPPING_RULER_DELIMETER);
					onePair = onePair*.trim();
					if(onePair.size() == 2){
						if(containsWildcardCharactor(onePair[0])) {
							def wildcardExpressKey = getKeyRegularExpressPattern(onePair[0]);
							if(!wildcardSrc2TargetMap.containsKey(wildcardExpressKey)){
								wildcardSrc2TargetMap.put(wildcardExpressKey, onePair[1]);
							}else{
								println "Warning - The mapping rule ${onePair[0]}, ${onePair[1]} is ignored,which was already set by ${onePair[0]}, ${src2TargetMap[onePair[0]]}";
							}
						} else {
							if(!src2TargetMap.containsKey(onePair[0])){
								src2TargetMap.put(onePair[0], onePair[1]);
							}else{
								println "Warning - The mapping rule ${onePair[0]}, ${onePair[1]} is ignored,which was already set by ${onePair[0]}, ${src2TargetMap[onePair[0]]}";
							}
						}
					}else{//abc,cc,dd or abc, or ,cc will be treat as illegal input
						throw new IllegalArgumentException("The format of ${pair} PDS mapping field is not correctly!");
					}
				}
			}
		}
	}

	//We just support "*" now	
	static containsWildcardCharactor(str) {
		return(str.contains("*"));
	}
	
	//return an pattern which removed "." and change the "*" to ".*"
	static getKeyRegularExpressPattern(str) {
		def pattern = ~str.replaceAll(/\./, "\\.").replaceAll(/\*/, "\\.\\*");
		return(pattern);
	}
	
	//Check access

	static checkAccess4Input(Set targetDsSet, dsListFile4BackUpPath, checkAccessExePath) {
		def InputTargetVolumeMap = [:];
		targetDsSet.each { datasetName->
			def dsn = "'" + datasetName + "'";
			def exists = dsExist(dsn);
			try {
				def vols = ZFile.locateDSN(dsn);
				if(null == vols || vols.size()<1){
					printProcessOutput("Warning - Input dataset ${dsn} is not found!")
				}else if( vols.size() > 1){
				}else{
					InputTargetVolumeMap.put(datasetName, vols[0]);
				}
			}catch(Exception locateException){
				//Can't find the the dsn
			}
		}
		//Generate the datasetlist file to call native checkaccess for deploy
		new File(dsListFile4BackUpPath).withWriter('IBM-1047'){ out ->
			InputTargetVolumeMap.each {datasetName,volume->
				out.println("${DeploymentConstants.CHECK_ACCESS_DS_INPUT_LEADER} ${datasetName} ${volume}");
			}
		}
		//Call native script to get the access code
		callCheckAccess(checkAccessExePath, dsListFile4BackUpPath, "input")
	}

	static checkAccess4Output(Set targetDsSet, dsListFilePath, checkAccessExePath) {
		def OutPutTargetVolumeMap = [:];
		targetDsSet.each { datasetName->
			def dsn = "'" + datasetName + "'";
			def exists = dsExist(dsn);
			if(!exists){
				OutPutTargetVolumeMap.put(datasetName, "******");
			}else{
				def vols = null;
				try{
					vols = ZFile.locateDSN(dsn);
				}catch(Exception locateException){
					println "The volumn of ${dsn} was not found for OUTPUT!";
				}

				if(null == vols || vols.size()<1){
					OutPutTargetVolumeMap.put(datasetName, "******");
				}else if( vols.size() > 1){
				}else{
					OutPutTargetVolumeMap.put(datasetName, vols[0]);
				}
			}
		}
		//Generate the datasetlist file to call native checkaccess for deploy
		new File(dsListFilePath).withWriter('IBM-1047'){ out ->
			OutPutTargetVolumeMap.each {datasetName,volume->
				out.println("${DeploymentConstants.CHECK_ACCESS_DS_OUTPUT_LEADER} ${datasetName} ${volume}");
			}
		}
		callCheckAccess(checkAccessExePath, dsListFilePath, "output")
	}
	
	static checkDatasetsExist(Set targetDsSet) {
		def allExist = true;
		targetDsSet.each { datasetName->
			def dsn = "'" + datasetName + "'";
			def exists = dsExist(dsn);
			if(!exists){
				allExist = false;
			}
		}		
		return (allExist);	
	}
	
	static callCheckAccess(checkAccessExePath, dsListFilePath, ioType) {
		def accessResultProcess = [
			"${checkAccessExePath}",
			"-d",
			"${dsListFilePath}"
		].execute();
		accessResultProcess.waitFor();
		printProcessOutput(accessResultProcess.text);
		def accessResult = accessResultProcess.exitValue();
		if(accessResult != 0){
			printActionTitle("checkaccess:rc=${accessResult}");
			if(137 == accessResult){
				printProcessOutput("checkaccess utility failed. Please verify that checkaccess utility is set as APF authorized by running command \"extattr +a checkaccess\"");
				System.exit(137);
			}
			throw new IOException("These Datasets you listed in PDS mapping can't be used as the ${ioType} of the program!");
		}
	}

	static dsExist(singleQuotedDsName){
		def isExist = false;
		def f = null;
		try {
			f = new ZFile("//" + singleQuotedDsName, "r");
			isExist = true;
		} catch(ZFileException e) {
			if(e.getErrno() == 92) {
				printProcessOutput("Data set $singleQuotedDsName could not be opened for read.")
			}else if (e.getErrno() == 49) {
				printProcessOutput("Data set $singleQuotedDsName could not be located.")
			}else{
				e.printStackTrace();
			}		
			// Assume that the file does not exist in this case.
		} finally {
			if (null != f) {
				f.close();
			}
		}

		return(isExist);
	}

	//Clean up after deploy or rollback
	static cleanWorkDir(workdir){
		def versionWorkingDirectory = new File(workdir);
		if (versionWorkingDirectory.exists() && versionWorkingDirectory.isDirectory()) {
			versionWorkingDirectory.deleteDir();
		}
	}

	//Clean up back up data
	static cleanBackUpData(deployBasePath) {
		cleanWorkDir(deployBasePath);
	}

	//Verify copy/ftp result
	static verifyGetArtifectResult(filePath, errorInfo){
		def file = new File(filePath);
		if(!file.exists() || !file.isFile()){
			throw new IOException(errorInfo);
		}
	}

	//Not null or empty input checking
	static inputNotEmptyCheck(input, errorInformation) {
		if (null == input || input.length()<=0) {
			throw new IllegalArgumentException(errorInformation);
		}
	}
	
	static doDeploymentAction(deployBasePath, resourceId, componentName, versionName, zosScriptPath, componentVersionWorkingDir, backupPds) {
		def versionPathInDeployBase = getVersionPathInDeployBasePath(deployBasePath, resourceId, componentName, versionName, false);
		//Prepare to deploy
		preDeploy(versionPathInDeployBase, versionName);
		//Back Up Pds before deploy current version for rollback
		if(backupPds){
			backUpPdsBeforeDeployment(zosScriptPath, componentVersionWorkingDir, DeploymentConstants.ZOS_TOOLKIT_TRACE_ON);
		}
		//Do the deployment
		deploy(zosScriptPath, componentVersionWorkingDir, DeploymentConstants.ZOS_TOOLKIT_TRACE_ON);
		//Post deployment
		if(backupPds){
			postDeploy(componentVersionWorkingDir, versionPathInDeployBase);
		}
	};

	//Prepare to deploy
	static preDeploy(versionDeploybasePath, versionName) {
		printActionTitle("PreDeploy:");
		if(directoryExist(versionDeploybasePath)) {
			printProcessOutput("${versionName} was deployed before")
		}else{
			printProcessOutput("Creating directory:" + versionDeploybasePath);
			new File(versionDeploybasePath).mkdirs();
		}
		println "";
	}

	//Call this to do pds back up before deployment, and the backup result will be stored in the working directory
	static backUpPdsBeforeDeployment(ispfScriptPath, componentVersionWorkingDir, traceOn) {
		printActionTitle("Backup:");
		def manifestFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.PACKAGE_MANIFEST_FILE_DEPLOY_NAME;
		def containerMapperFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.CONTAINER_MAPPER_FILE_NAME;
		def timestamp = new Date().getTime().toString();
		
		def backUpPdsProcess = [
			"${ispfScriptPath}",
			"${DeploymentConstants.ZOS_TOOLKIT_BACKUP_COMMAND}",
			"${manifestFilePath}",
			"${componentVersionWorkingDir}",
			"${DeploymentConstants.DEPLOY_BACKUP_FILE_NAME}",
			"${containerMapperFilePath}",
			"${DeploymentConstants.ZOS_TOOLKIT_BUILDER_VERSION}",
			"${DeploymentConstants.ZOS_TOOLKIT_RUNTIME_MINIMAL_VERSION}",
			"${timestamp}",
			"${traceOn}"
		].execute();
	
		backUpPdsProcess.waitFor();
		
		def backUpPdsResult = backUpPdsProcess.exitValue();
		if(backUpPdsResult > 0) {
			printProcessOutput(backUpPdsProcess.text);
			printProcessOutput("The backup failed to complete. Look at the CDATA section in previous messages for the failure reason. Check for REASON-CODE entries if they exist.");
			System.exit(1);
		} else {
			printProcessOutput(backUpPdsProcess.text);
		}
		println ""
	}

	//Call this method to do deploy, which will use those artifacts of a version and thos mapping file to move member into target datset
	static deploy(ispfScriptPath, componentVersionWorkingDir, traceOn) {
		printActionTitle("Deploy:");
		
		def manifestFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.PACKAGE_MANIFEST_FILE_DEPLOY_NAME;
		def containerMapperFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.CONTAINER_MAPPER_FILE_NAME;
		def uninstallFile = "";//Just align with the interface used in zosdeploy.xml
		
		def deployPdsProcess = [
			"${ispfScriptPath}",
			"${DeploymentConstants.ZOS_TOOLKIT_DEPLOY_COMMAND}",
			"${manifestFilePath}",
			"${componentVersionWorkingDir}",
			"${DeploymentConstants.PACKAGE_FILE_NAME}",
			"${containerMapperFilePath}",
			"${DeploymentConstants.ZOS_TOOLKIT_BUILDER_VERSION}",
			"${DeploymentConstants.ZOS_TOOLKIT_RUNTIME_MINIMAL_VERSION}",
			"${uninstallFile}",
			"${traceOn}"
		].execute();

		deployPdsProcess.waitFor();
		def deployPdsResult = deployPdsProcess.exitValue();
		if(deployPdsResult>0) {
			printProcessOutput(deployPdsProcess.text);
			printProcessOutput("The deployment failed to complete. Look at the CDATA section in previous messages for the failure reason. Check for REASON-CODE entries if they exist.");
			System.exit(1);
		}else{
			printProcessOutput(deployPdsProcess.text);
		}
		println "";
	}

	//Copy those result file into deploy base path, which will be used to do rollback etc
	static postDeploy(componentVersionWorkingDir, versionDeploybasePath) {
		printActionTitle("PostDeploy:");
		//Print out the deploy result file contents
		printActionTitle("Deploy Report:");
		def deployResult = new File(componentVersionWorkingDir + File.separator + DeploymentConstants.DEPLOY_DELTA_FILE_NAME).getText(DeploymentConstants.ENCODING);
		printProcessOutput(deployResult);
		
		println "";
		printActionTitle("Store backup files:");
		def ant = new AntBuilder();
		
		//Copy backup.zip
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.DEPLOY_BACKUP_FILE_NAME, true);
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.DEPLOY_DELTA_FILE_NAME, DeploymentConstants.ROLLBACK_MANIFEST_FILE_NAME, true);
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.CHECK_ACCESS_DS_FILE_NAME, true);
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.CONTAINER_MAPPER_FILE_NAME, true);
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.PACKAGE_FILE_NAME, true);
		copyFile(ant, componentVersionWorkingDir, versionDeploybasePath, DeploymentConstants.PACKAGE_MANIFEST_FILE_NAME, true);
		
		println "";
	}
	
	static postDeployForGenericOnly(deployBasePath, resourceId, componentName, versionName, componentVersionWorkingDir) {
		printActionTitle("PostDeploy:");
		def versionPathInDeployBase = getVersionPathInDeployBasePath(deployBasePath, resourceId, componentName, versionName, false);
		
		def ant = new AntBuilder();
		copyFile(ant, componentVersionWorkingDir, versionPathInDeployBase, DeploymentConstants.PACKAGE_MANIFEST_FILE_NAME, true);
		
		println "";
	}

	//Call this method to do deploy, which will use those artifacts of a version and thos mapping file to move member into target datset
	static rollback(ispfScriptPath, componentVersionWorkingDir, traceOn) {
		printActionTitle("Rollback:");
		
		def rollbackManifestFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.ROLLBACK_MANIFEST_FILE_NAME;
		def containerMapperFilePath = componentVersionWorkingDir + File.separator + DeploymentConstants.CONTAINER_MAPPER_FILE_NAME;
		def uninstallFile = "";//Just align with the interface used in zosdeploy.xml
		
		def rollbackPdsProcess = [
			"${ispfScriptPath}",
			"${DeploymentConstants.ZOS_TOOLKIT_ROLLBACK_COMMAND}",
			"${rollbackManifestFilePath}",
			"${componentVersionWorkingDir}",
			"${DeploymentConstants.DEPLOY_BACKUP_FILE_NAME}",
			"${containerMapperFilePath}",
			"${DeploymentConstants.ZOS_TOOLKIT_BUILDER_VERSION}",
			"${DeploymentConstants.ZOS_TOOLKIT_RUNTIME_MINIMAL_VERSION}",
			"${uninstallFile}",
			"${traceOn}"
		].execute();

		rollbackPdsProcess.waitFor();
		def deployPdsResult = rollbackPdsProcess.exitValue();
		if(deployPdsResult>0) {
			printProcessOutput(rollbackPdsProcess.text);
			printProcessOutput("The rollback failed to complete. Look at the CDATA section in previous messages for the failure reason. Check for REASON-CODE entries if they exist.");
			System.exit(1);
		} else {
			printProcessOutput(rollbackPdsProcess.text);
		}
		println "";
	}
	
	private static printProcessOutput(text) {
		text.eachLine { it->
			println("\t" + it);
		}
	}
	
	static printActionTitle(text) {
		println "  "+text;
	}

	static copyFile(antbuilder, componentVersionWorkingDir, versionDeploybasePath, fileName,verbose) {
		def src = componentVersionWorkingDir + File.separator + fileName;
		def target = versionDeploybasePath + File.separator + fileName;
		if(fileExists(src)){
			antbuilder.copy(verbose: "true",file:"${src}", tofile:"${target}",overwrite: verbose);
		}
	}

	static copyFile(antbuilder, componentVersionWorkingDir, versionDeploybasePath, fileName, targetFilename, verbose) {
		def src = componentVersionWorkingDir + File.separator + fileName;
		def target = versionDeploybasePath + File.separator + targetFilename;
		if(fileExists(src)){
			antbuilder.copy(verbose: "true",file:"${src}", tofile:"${target}",overwrite: verbose);
		}
	}

}
