/*
* Licensed Materials - Property of IBM* and/or HCL**
* UrbanCode Deploy
* UrbanCode Build
* UrbanCode Release
* AnthillPro
* (c) Copyright IBM Corporation 2011, 2017. All Rights Reserved.
* (c) Copyright HCL Technologies Ltd. 2018. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*
* * Trademark of International Business Machines
* ** Trademark of HCL Technologies Limited
*/
import com.ibm.urbancode.zos.common.DeploymentConstants;
import com.ibm.urbancode.zos.common.DeploymentHelper;
import com.ibm.urbancode.zos.common.PackageManifestXMLHelper;
import com.ibm.urbancode.zos.deploy.common.DeploymentResultHelper;
import com.urbancode.air.AirPluginTool
import com.urbancode.ud.client.ResourceClient
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files


import org.codehaus.jettison.json.JSONObject
import org.codehaus.jettison.json.JSONArray

try {
    final def workDir = new File('.').canonicalFile
	def apTool = new AirPluginTool(this.args[0], this.args[1])
	def props = apTool.getStepProperties();
    	
	//Plugin input parameters
    final def resourceId     = DeploymentHelper.getStringInput(props['resourceId']);
    final def componentId    = DeploymentHelper.getStringInput(props['componentId']);
    final def componentName  = DeploymentHelper.getStringInput(props['componentName']);
    final def versionName    = DeploymentHelper.getStringInput(props['versionName']);
    final def versionId    = DeploymentHelper.getStringInput(props['versionId']);
    final def versionType    = DeploymentHelper.getStringInput(props['versionType']);
    final def deployBasePath = DeploymentHelper.getStringInput(props['deployBasePath']);
    final def tempDsnPrefix  = DeploymentHelper.getStringInput(props['tempDsnPrefix']);
    final def pdsMapping     = DeploymentHelper.getStringInput(props['pdsMapping']);
    final def checkAccess    = DeploymentHelper.getBooleanInput(props['checkAccess']);
    final def doBackup       = DeploymentHelper.getBooleanInput(props['doBackup']);
    final def allowCreateDataset  = DeploymentHelper.getBooleanInput(props['allowCreateDataset']);
	final def hfsTargetDir     = DeploymentHelper.getStringInput(props['hfsTargetDir']);
	final def allowCreateDirectory  = DeploymentHelper.getBooleanInput(props['allowCreateDirectory']);
    final def incrementalMode  = DeploymentHelper.getStringInput(props['incrementalMode']);
    final def versionMerge  = DeploymentHelper.getBooleanInput(props['versionMerge']);
	
	def oldVersionIdentifier=false;
	def deleteCurrentContents=false;
    
	//global variables
    //check if hfsTargetDir don't contain ',' means it is old version so append *, before hfsTargetDir
	if(!hfsTargetDir.contains(DeploymentConstants.MAPPING_RULER_DELIMETER))
	{
	hfsTargetDir='*'+DeploymentConstants.MAPPING_RULER_DELIMETER+hfsTargetDir;
	oldVersionIdentifier=true;
	}
	
	
	if(DeploymentHelper.getBooleanInput(props['deleteCurrentContents']))
	{
	deleteCurrentContents=true;
	}
	
	if(doBackup && deleteCurrentContents)
	{
	 DeploymentHelper.printProcessOutput("Backup of the target directory can not be taken since we are deleting the target directory contents. Either the checkbox 'Delete the Target Directory contents' should be selected or the checkbox 'Backup for Rollback' checkbox should be selected. Please make the necessary changes and redeploy");
	 System.exit(1);
	}
	
	def pkgManifestHelper = new PackageManifestXMLHelper();
    final def workDirPath = workDir.canonicalPath;
    final def componentVersionWorkingDir  = DeploymentHelper.getVersionDirPathInWorkingDir(workDirPath,versionName,resourceId);
    final def packageManifestFileNamePath = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.PACKAGE_MANIFEST_FILE_NAME);
    final def packageManifestFileDeployNamePath = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.PACKAGE_MANIFEST_FILE_DEPLOY_NAME);
    final def packageZipFilePath          = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.PACKAGE_FILE_NAME);
    
	Path path = Paths.get(packageManifestFileNamePath);
	String content = new String(Files.readAllBytes(path));
	content = content.replaceAll("xml version=\"1.0\" encoding=\"UTF-8\"", "xml version=\"1.0\" encoding=\"IBM-1047\"");
	Files.write(path, content.getBytes());
	
	//Verify packageManifest.xml exists, otherwise we can not deploy
    DeploymentHelper.validateFileExist(packageManifestFileNamePath, "${packageManifestFileNamePath} not found. Please make sure Copy Artifacts, FTP Artifacts or Download Artifacts for zOS plug-in step is successfully executed before the Deploy Data Set plug-in step.");
	//Verify deployBasePath exists, otherwise we can not deploy
	DeploymentHelper.validateDirectoryExist(deployBasePath, "Deploy Base Path ${deployBasePath} does not exist.");
	
	//we need the deployment tools to deploy data sets
	def toolKitHome = DeploymentHelper.getStringInput(System.getenv().get("BUZ_TOOLKIT_HOME"));
	if (!toolKitHome) {//try to compatible with application build on old plug-in(before v5)
		toolKitHome = DeploymentHelper.getStringInput(props['ucdToolKitHome']);
	}

	//Define path for all the files
	final def conainerMapperXmlFilePath = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.CONTAINER_MAPPER_FILE_NAME);
	
	final def conainerMapperHFSXmlFilePath = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.CONTAINER_MAPPER_HFS_FILE_NAME);
	final def dsListFile4BackUpPath     = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.CHECK_ACCESS_DS_FILE_NAME_BACKUP);
	final def dsListFilePath            = DeploymentHelper.getFilePathInWorkingDir(workDirPath, versionName, resourceId, DeploymentConstants.CHECK_ACCESS_DS_FILE_NAME);
	final def zosScriptPath             = DeploymentHelper.getPath4ExecScriptInToolkit(toolKitHome , DeploymentConstants.ZOS_TOOLKIT_ISPF_SCRIPT_NAME);
	final def checkAccessExePath        = DeploymentHelper.getPath4Checkaccess(toolKitHome , DeploymentConstants.CHECK_ACCESS_EXE);
	
	//Verify ispf gateway script exists
	DeploymentHelper.validateFileExist(zosScriptPath, "Can not find the ispf gateway script:${zosScriptPath}. Please make sure z/OS Deploy Toolkit is installed and z/OS Toolkit Home is set to the correct location.");
	
	
	//A version which only has generic resource has nothing to deploy. We will just copy the pacakgeManifest.xml 
	//file to the deploy directory to indicate this version has been deployed.  
	if (pkgManifestHelper.isGenericOnlyVersion(packageManifestFileNamePath)) {
        DeploymentHelper.printActionTitle("No file artifacts to deploy. Version contains only generic artifacts.");
        DeploymentHelper.postDeployForGenericOnly(deployBasePath, resourceId, componentName, versionName, componentVersionWorkingDir);
        DeploymentHelper.cleanWorkDir(componentVersionWorkingDir);
        System.exit(0);
    } else {
        //calculate incremental and update package manifest file
        if(incrementalMode == "INVENTORY" && pkgManifestHelper.hasDataSets(packageManifestFileNamePath)) {
            DeploymentHelper.printActionTitle("Calculate delta deployment using inventory status:");
     
            def udUser = apTool.getAuthTokenUsername();
            def udPass = apTool.getAuthToken();
            def weburl = System.getenv("AH_WEB_URL");
            ResourceClient resClient = new ResourceClient(new URI(weburl), udUser, udPass);
            com.urbancode.air.XTrustProvider.install();
    
            JSONArray inventoryArtifacts = resClient.intersectArtifactsInTargetResource(resourceId, componentId,versionId);
            pkgManifestHelper.calculateIncrementalManifest(inventoryArtifacts, packageManifestFileNamePath);
        }
		//Remove generic resource and proceed to deploy the data set and HFS files. 
		pkgManifestHelper.reFormatXML(packageManifestFileNamePath,packageManifestFileDeployNamePath);
		pkgManifestHelper.removeNodes4Deployment(packageManifestFileNamePath, packageManifestFileDeployNamePath);
    }	
	
	//verify input parameters for HFS file deployment
	final def hasDataSets = pkgManifestHelper.hasDataSets(packageManifestFileNamePath);
	final def hasHFSFiles = pkgManifestHelper.hasHFSFiles(packageManifestFileNamePath);

	if(hasHFSFiles){
		if (null == hfsTargetDir || hfsTargetDir.length()<=0) {
			throw new IllegalArgumentException("Version contains HFS files. HFS target directory is required. ");
		}
		if(!(new File(hfsTargetDir).exists())){
			if(allowCreateDirectory){
				new File(hfsTargetDir).mkdir()
			}else{
				throw new IllegalArgumentException("Deploy failed because HFS target directory(${hfsTargetDir}) does not exist and Allow Creating Directory is set to false.");
			}
			 
		}else if (!(new File(hfsTargetDir).isDirectory())){
			throw new IllegalArgumentException("Deploy failed because HFS target directory(${hfsTargetDir}) is not a directory.");
		}
	}
	
	//TODO Since a version which do <deleted> only may not have package.zip, this should be reconsidered.
	//Verify package.zip exists.   
	//DeploymentHelper.validateFileExist(packageZipFilePath, "${packageZipFilePath} not found. Please make sure a Copy or FTP plug-in step is successfully executed before the Deploy plug-in step.");
    
	//Check is backup is needed based on input parameter and the inventory status. 
	//When it is a re-deploy of the last deployed version, we are not going to 
	//take another backup. Rollback will use the existing backup. 
    if (DeploymentResultHelper.checkVersionDeployed(deployBasePath, resourceId, componentName, versionName)) {
		DeploymentHelper.printActionTitle("Check last deployed version:");
		try{
	        def udUser = apTool.getAuthTokenUsername();
	        def udPass = apTool.getAuthToken();
	        def weburl = System.getenv("AH_WEB_URL");
	        ResourceClient resClient = new ResourceClient(new URI(weburl), udUser, udPass);
	        com.urbancode.air.XTrustProvider.install();
	
	        JSONObject latestVesionJson = resClient.getLatestVersionByResourceAndComponent(resourceId, componentId);
	        if (null != latestVesionJson && latestVesionJson.has("version")) {
	            latestVesionJson = latestVesionJson.getJSONObject("version");
	            if (null != latestVesionJson && latestVesionJson.has("name")) {
	                def latestVersionName = latestVesionJson.getString("name")?.trim();
	                if (latestVersionName == versionName) {
	                    doBackup = false; 
	                    DeploymentHelper.printProcessOutput("A backup is already taken by the previous deploy.");
	                
					}
	        }
		}
		}catch(Exception e){
			DeploymentHelper.printProcessOutput("Warning: check last deployed version failed.");
		}
		DeploymentHelper.printProcessOutput("")
    }

	
	//Deploy data sets
	if(hasDataSets){
		//Calculate data set mapping
	    //Combine the user input and the environment pds mapping
	    def src2TargetMap = [:];
	    //collect those rules with wildcard "*" character, the key will be an regular express pattern
	    def containsWildcardMap = [:];
	
	    //Parse input mapping rules from text into map
	    DeploymentHelper.handleMappingRule(pdsMapping, src2TargetMap, containsWildcardMap);	
	    //Check the mapping rule to see if some datasets were missed
	    def dsNeedToDeploy   = pkgManifestHelper.getAllDeployDatasets(packageManifestFileDeployNamePath);
	    def dsIncludedByUser = src2TargetMap.keySet();
	    def missedDsByUser   = dsNeedToDeploy - dsIncludedByUser;
	
	    def notResolvedByWildcard = dsNeedToDeploy - dsIncludedByUser;
	    if(missedDsByUser && missedDsByUser.size() > 0){
	        missedDsByUser.each { missedDsItem ->
	            containsWildcardMap.keySet().each {keywithWildcardPattern->
	                if(missedDsItem ==~ keywithWildcardPattern) {
	                    if(!src2TargetMap.get(missedDsItem)) {
	                        src2TargetMap.put(missedDsItem, containsWildcardMap.get(keywithWildcardPattern));
	                        notResolvedByWildcard.remove(missedDsItem);
	                    }
	                    return(true);//If find the rule, escape the loop
	                }
	            }
	        }
	    }
	
	/*	println "src2TargetMap : "
		println src2TargetMap*/
	    //#comment it first, will add back later#If the dataset was not set with a.b.c,a1.b1.c1 and not set by *.c,a2.b2.c2. We will use a.b.c,a.b.c rule as the default 
	    if(notResolvedByWildcard && notResolvedByWildcard.size() >0) {
	//        notResolvedByWildcard.each { noRulerItem ->
	//            if(!src2TargetMap.get(noRulerItem)) {
	//                src2TargetMap.put(noRulerItem, noRulerItem);
	//            }
	//        }
	        def dsStr = "";
	        for (ds in notResolvedByWildcard) {
	            dsStr += " " + ds;
	        }
	        DeploymentHelper.printProcessOutput("Missing PDS mapping rules for: ${dsStr}");
	        System.exit(1);
	    }
	
	    def dsToDeploySrc2TargetMap = [:];
	    dsNeedToDeploy.each {ds2Deploy ->
	        dsToDeploySrc2TargetMap.put(ds2Deploy, src2TargetMap.get(ds2Deploy));
	    }
	
/*		println "dsToDeploySrc2TargetMap : "
		println dsToDeploySrc2TargetMap*/
		
	    def targetDsSet =  dsToDeploySrc2TargetMap.values().toSet();
		
/*		println "targetDsSet : "
		println targetDsSet*/
	
	    //Target data sets must already exist if allowCreateDataset=false
	    if(!allowCreateDataset){
	        DeploymentHelper.printActionTitle("Check data sets exist:");
	        def datasetsExist = DeploymentHelper.checkDatasetsExist(targetDsSet)
	        if(!datasetsExist){
	            DeploymentHelper.printProcessOutput("Deploy failed because target data set could not be opened and Allow Creating Data Set is set to false.");
	            System.exit(1);
	        }
	        println "";
	    }
	
	    //If backup is required, check the target datasets can be read
	    if(doBackup && checkAccess){
	        DeploymentHelper.printActionTitle("Check access for backup action:")
	        DeploymentHelper.checkAccess4Input(targetDsSet, dsListFile4BackUpPath, checkAccessExePath)
		
	        println "";
	    }
		
	    //Check target datasets can be written
	    if(checkAccess){
	        DeploymentHelper.printActionTitle("Check access for deployment action:")
	        DeploymentHelper.checkAccess4Output(targetDsSet, dsListFilePath, checkAccessExePath)
	        println "";
	    }
	    
		if(incrementalMode == "RUNTIME" && pkgManifestHelper.hasDataSets(packageManifestFileDeployNamePath)) {
			DeploymentHelper.printActionTitle("Calculate delta deployment using Target Environment status:");
			pkgManifestHelper.calculateTargetIncrementalManifest(packageManifestFileNamePath,dsToDeploySrc2TargetMap,zosScriptPath);
			pkgManifestHelper.reFormatXML(packageManifestFileNamePath,packageManifestFileDeployNamePath);
		}
	    //Generate the containerMapping file logic
	    def pdsXmlStrWriter = new StringWriter();
	    pdsXmlStrWriter.append('<?xml version="1.0" encoding="IBM-1047"?>');
	    def containerMapperXml = new groovy.xml.MarkupBuilder(pdsXmlStrWriter);
	    containerMapperXml.setDoubleQuotes(true);
	    if(dsToDeploySrc2TargetMap.size() > 0){
	        containerMapperXml.maps(){
	            dsToDeploySrc2TargetMap.each { pdsSrc, pdsTarget->
	                map(type:"PDS"){
	                    sourceContainer(name:pdsSrc);
	                    targetContainer(name:pdsTarget);
	                }
	            }
	        }
	    }else{
	        //Handle the issue of no mapping
	        throw new IllegalArgumentException("PDS mapping not found. Make sure you have specified the mapping in the plugin step or in environment.");
	    }
	
	    //Put the content into log
	    DeploymentHelper.printActionTitle("The containerMapper.xml contents:")
	    def pdsXmlStr = pdsXmlStrWriter.toString();
	    DeploymentHelper.printProcessOutput(pdsXmlStr);
	    println "";
	
	    //Generate the containerMapper.xml file
	    new File(conainerMapperXmlFilePath).withWriter('IBM-1047'){ out ->
	        out.print(pdsXmlStrWriter);
	    }
	}	
	if(hasHFSFiles){
	    //Deploy HFS files
        //Calculate HFS mapping
	    //Combine the user input and the environment hfs mapping
	    def src2TargetMapHfsTagrgetDir = [:];
	    //collect those rules with wildcard "*" character, the key will be an regular express pattern
	    def containsWildcardMapHfsTagrgetDir = [:];

	    //Parse input mapping rules from text into map
	    DeploymentHelper.handleMappingRuleHfsTagrgetDir(hfsTargetDir, src2TargetMapHfsTagrgetDir, containsWildcardMapHfsTagrgetDir);
        //Check the mapping rule to see if some directories were missed
	    def hfsNeedToDeploy   = pkgManifestHelper.getAllDeployHFSFiles(packageManifestFileDeployNamePath);
		def hfsIncludedByUserHfs = src2TargetMapHfsTagrgetDir.keySet(); 
		def missedDsByUserHfs   = hfsNeedToDeploy - hfsIncludedByUserHfs;

		def notResolvedByWildcardHfs = hfsNeedToDeploy - hfsIncludedByUserHfs;
		
		//added for HFS files
		if(missedDsByUserHfs && missedDsByUserHfs.size() > 0){
	        missedDsByUserHfs.each { missedDsItem ->
	            containsWildcardMapHfsTagrgetDir.keySet().each {keywithWildcardPattern->
	                if(missedDsItem ==~ keywithWildcardPattern) {
	                    if(!src2TargetMapHfsTagrgetDir.get(missedDsItem)) {
	                        src2TargetMapHfsTagrgetDir.put(missedDsItem, containsWildcardMapHfsTagrgetDir.get(keywithWildcardPattern));
	                        notResolvedByWildcardHfs.remove(missedDsItem);
	                    }
	                    return(true);//If find the rule, escape the loop
	                }
	            }
	        }
	    }
        
		//added for HFS files
		 if(notResolvedByWildcardHfs && notResolvedByWildcardHfs.size() >0) {
	
	        def hfsString = "";
	        for (hfs in notResolvedByWildcardHfs) {
	            hfsString += " " + hfs;
	        }
	        DeploymentHelper.printProcessOutput("Missing HFS mapping rules for: ${hfsString}");
	        System.exit(1);
	    }
	
	  //  def dsToDeploySrc2TargetMap = [:];
		def hfsToDeploySrc2TargetMap = [:];

		hfsNeedToDeploy.each {hfs2Deploy ->
	        hfsToDeploySrc2TargetMap.put(hfs2Deploy, src2TargetMapHfsTagrgetDir.get(hfs2Deploy));
	    }
        def targetHfsSet =  hfsToDeploySrc2TargetMap.values().toSet();
		
      
      	  //Generate the containerMapping file logic
	    def pdsXmlStrWriter = new StringWriter();
	    pdsXmlStrWriter.append('<?xml version="1.0" encoding="IBM-1047"?>');
	    def containerMapperXml = new groovy.xml.MarkupBuilder(pdsXmlStrWriter);
	    containerMapperXml.setDoubleQuotes(true);
	 
	
	//added for HFS
	if(hfsToDeploySrc2TargetMap.size() > 0){
	        containerMapperXml.maps(){
	            hfsToDeploySrc2TargetMap.each { pdsSrc, pdsTarget->
	                map(type:"Directory"){
	                    sourceContainer(name:pdsSrc);
	                    targetContainer(name:pdsTarget);
	                }
	            }
	        }
	    }else{
	        //Handle the issue of no mapping
	        throw new IllegalArgumentException("HFS mapping not found. Make sure you have specified the mapping in the plugin step or in environment.");
	    }
		 //Put the content into log
	    DeploymentHelper.printActionTitle("The containerMapperHFS.xml contents:")
	    def pdsXmlStr = pdsXmlStrWriter.toString();
	    DeploymentHelper.printProcessOutput(pdsXmlStr);
	    println "";
		
		new File(conainerMapperHFSXmlFilePath).withWriter('IBM-1047'){ out ->
	        out.print(pdsXmlStrWriter);
	    }
	
	}

	//deploy
	if(hasHFSFiles || hasDataSets){
		DeploymentHelper.doDeploymentAction(deployBasePath, tempDsnPrefix, resourceId, componentName, versionName, versionType, zosScriptPath, componentVersionWorkingDir, doBackup, hfsTargetDir, versionMerge,oldVersionIdentifier,deleteCurrentContents);
		//Clear the working directory to avoid dirty data
		//Just delete those files we created
		DeploymentHelper.cleanWorkDir(componentVersionWorkingDir);
	}else{
		DeploymentHelper.printActionTitle("There is nothing to deploy.");
        if(incrementalMode == "INVENTORY") { 
            //nothing to deploy because nothing is new. We still copy the results
            //to the deploy result directory so generate artifact knows what happens
            def versionPathInDeployBase = DeploymentHelper.getVersionPathInDeployBasePath(deployBasePath, resourceId, componentName, versionName, false);
            DeploymentHelper.postDeploy(componentVersionWorkingDir,versionPathInDeployBase,false);
         }
	}

} catch (Exception e) {
    DeploymentHelper.printActionTitle("Error deploying version. ${e.message}");
//    e.printStackTrace();
    System.exit(1);
}

System.exit(0);