/*
 * 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;

    def int installedVersion=DeploymentHelper.getInstalledVersion();

    if(hfsTargetDir.contains(DeploymentConstants.MAPPING_RULER_DELIMETER)&&installedVersion<703) {
        DeploymentHelper.printProcessOutput("Agent version is less than 703. Give only 1 target directory");
        System.exit(1);
    }

    //global variables
    //check if hfsTargetDir don't contain ',' means it is old version so append *, before hfsTargetDir

    if(!hfsTargetDir.contains(DeploymentConstants.MAPPING_RULER_DELIMETER)&&installedVersion>=703)
    {
        hfsTargetDir='*'+DeploymentConstants.MAPPING_RULER_DELIMETER+hfsTargetDir;
        oldVersionIdentifier=true;
    }


    if(installedVersion<705 && DeploymentHelper.getBooleanInput(props['deleteCurrentContents']))
    {
        DeploymentHelper.printProcessOutput("Installed version is less than 705. Please Use version greater than or equal to 705 to use deleteCurrentContents feauture ");
        System.exit(1);
    }

    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);

    if(!oldVersionIdentifier) {
        File tempFileNewVersion = new File(componentVersionWorkingDir + File.separator + DeploymentConstants.TEMP_FILE_NEW_VERSION);
        BufferedWriter commandWriter = new BufferedWriter(new FileWriter(tempFileNewVersion.getAbsolutePath(),true));
        commandWriter.append("THIS IS DUMMY FILE FOR IDENTIYING NEW VERSION");
        commandWriter.newLine();
        commandWriter.flush();
        commandWriter.close();
    }



    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 && installedVersion>=703){
        //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,oldVersionIdentifier);
        }
    }

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

System.exit(0);