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

import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
import com.urbancode.commons.util.IO;

import org.codehaus.jettison.json.JSONObject;
import org.codehaus.jettison.json.JSONArray;
import com.urbancode.air.AirPluginTool
import com.urbancode.ud.client.PropertyClient
import com.urbancode.ud.client.ComponentClient
import com.urbancode.ud.client.ResourceClient

class UrbanCodeDeployHelper {
    private AirPluginTool apTool;
    private Properties props;
    private PropertyClient propClient;
    private ComponentClient compClient;
    private ResourceClient resClient;
    private Map<String, JSONObject> compResPropDefs;

    public UrbanCodeDeployHelper(AirPluginTool apTool) {
        this.apTool = apTool;
        this.props = apTool.getStepProperties();
        def udUser = apTool.getAuthTokenUsername()
        def udPass = apTool.getAuthToken()
        def weburl = System.getenv("AH_WEB_URL")
        propClient = new PropertyClient(new URI(weburl), udUser, udPass)
        compClient = new ComponentClient(new URI(weburl), udUser, udPass)
        resClient = new ResourceClient(new URI(weburl), udUser, udPass)
    }

    private String getResourceRoleId(def resourceID) {
        def resourceObject = resClient.getResourceById(resourceID);
        def resourceRole = resourceObject.role;
        def resourceRoleId = resourceRole.id;
        return resourceRoleId;
    }

    private String getResourcePropSheetDefId(def componentID) {
        def componentObject = compClient.getComponent(componentID);
        def resourceRole = componentObject.resourceRole;
        def resourcePropSheetDef = resourceRole.propSheetDef;
        def resourcePropSheetDefId = resourcePropSheetDef.id;
        return resourcePropSheetDefId;
    }

    /*
     * Get current value of component resource properties.  For efficiency
     * on access, we load the properties into a map using the property names
     * as keys.
     */
    private void loadComponentResourceProperties() {
        compResPropDefs = new HashMap<String, JSONObject>();
        def resourceRoleId = getResourceRoleId(props["resourceID"]);
        JSONArray propDefs = resClient.getResourceRoleProperties(resourceRoleId, props["resourceID"]);
        int cnt = 0;
        while (cnt < propDefs.length()) {
            JSONObject prop = propDefs.getJSONObject(cnt++);
            if (prop.has("propValue")) {
                JSONObject propValue = prop.getJSONObject("propValue");
                compResPropDefs.put(propValue.getString("name"), propValue);
            }
        }
    }

    private void createComponentResourceProperties(def propertyDefs) {
        def deleteExtraProps = false;
        def resourceRoleId = getResourceRoleId(props["resourceID"]);
        def resourcePropSheetDefId = getResourcePropSheetDefId(props["componentID"]);
        // We need to check if any of the specified properties don't yet exist,
        // and create them if not. Then set the new values of the component
        // resource properties for the rest.
        JSONArray propsToCreate = new JSONArray();
        JSONObject propsToSet = new JSONObject();
        loadComponentResourceProperties();
        int cnt = 0;
        while (cnt < propertyDefs.length()) {
            JSONObject prop = propertyDefs.getJSONObject(cnt++);
            if (compResPropDefs.containsKey(prop.getString("name"))) {
                propsToSet.put(prop.getString("name"), prop.getString("value"));
            }
            else { // Need to create new property
                propsToCreate.put(prop);
            }
        }

        // Update existing properties first, then create new properties
        if (propsToSet.length() > 0) {
            resClient.setResourceRoleProperties(resourceRoleId, props["resourceID"], propsToSet);
        }
        if (propsToCreate.length() > 0) {
            propClient.updateUnversionedPropDefs(resourcePropSheetDefId, propsToCreate, deleteExtraProps);
        }
    }

    private String findComponentResourceProperty(String name) {
        def value = null;
        if (compResPropDefs.containsKey(name)) {
            JSONObject prop = compResPropDefs.get(name);
            value = prop.getString("value");
            println "Found resource property: ${name}=${value}";
        }
        return value;
    }

    public void generateComponentResourceProperties(String fileLocation) {
        def file = new File(fileLocation);
        if (!file.exists() || file.isDirectory()) {
            throw new IllegalArgumentException("Server configuration file not located at: ${fileLocation}");
        }
        println "Processing server configuration file located at: ${fileLocation}";
        def server = new XmlSlurper().parse(file);
        // Find any included files in config file, use recursion to process them
        for (def include in server.include) {
            def includedFileLocation = include.@location.toString();
            println "Found included server configuration file located at: ${includedFileLocation}";
            /*
             *  Try to handle include file paths
             *  that contain Liberty vars like ${server.config.dir}
             */
            if (includedFileLocation.contains("\${")) {
                includedFileLocation = expandLibertyVars(includedFileLocation);
            }
            if (!new File(includedFileLocation).isAbsolute()) {
                includedFileLocation = new File(file.getParentFile(), includedFileLocation).getAbsolutePath();
            }
            generateComponentResourceProperties(includedFileLocation);
        }
        def propDefs = new JSONArray();
        for (def variable in server.variable) {
            def propDef = new JSONObject();
            propDef.put("name", variable.@name.toString());
            propDef.put("value", variable.@value.toString());
            propDef.put("type", "TEXT");
            propDef.put("label", variable.@name.toString());
            // Should we set values for 'required' or 'description' fields/keys?
            propDefs.put(propDef);
        }
        if (propDefs.length() > 0) {
            createComponentResourceProperties(propDefs);
        }
        println "Done processing server configuration file located at: ${fileLocation}";
    }

    public void replaceVariableValuesWithComponentResourceProperties(String fileLocation) {
        def file = new File(fileLocation);
        if (!file.exists() || file.isDirectory()) {
            throw new IllegalArgumentException("Server configuration file not located at: ${fileLocation}");
        }
        println "Processing server configuration file located at: ${fileLocation}";
        loadComponentResourceProperties();
        def server = new XmlSlurper().parse(file);
        // Find any included files in config file, use recursion to process them
        for (def include in server.include) {
            def includedFileLocation = include.@location.toString();
            println "Found included server configuration file located at: ${includedFileLocation}";
            /*
             *  Try to handle include file paths
             *  that contain Liberty vars like ${server.config.dir}
             */
            if (includedFileLocation.contains("\${")) {
                includedFileLocation = expandLibertyVars(includedFileLocation);
            }
            if (!new File(includedFileLocation).isAbsolute()) {
                includedFileLocation = new File(file.getParentFile(), includedFileLocation).getAbsolutePath();
            }
            replaceVariableValuesWithComponentResourceProperties(includedFileLocation);
        }
        for (def variable in server.variable) {
            String newValue = findComponentResourceProperty(variable.@name.toString());
            if (newValue != null) {
                println "Replacing variable value with resource property: name=${variable.@name} old value=${variable.@value} new value=${newValue}";
                variable.@value = newValue;
            }
        }
        File newServerXmlFile = File.createTempFile("server", ".xml");
        newServerXmlFile.deleteOnExit();
        String result = XmlUtil.serialize(new StreamingMarkupBuilder().bind { mkp.yield server });
        newServerXmlFile.append(result, "utf-8");
        IO.move(newServerXmlFile, file);
        println "Done processing server configuration file located at: ${fileLocation}"
    }

    /*
     * Attempt to evaluate Liberty variables like ${server.config.dir}.
     * The input value is likely a path with a variable reference contained
     * in it.
     */
    private String expandLibertyVars(String val) {
        int start, end;
        StringBuffer sbuf = new StringBuffer();
        String curr;
        for (curr = val; (start = curr.indexOf("\${")) >= 0;
             curr = curr.substring(end + 1)) {
            end = curr.indexOf('}');
            if (end < 0) {
                /* No ending brace, just return value passed in */
                return val;
            }
            /* Save contents up to beginning of variable */
            if (start > 0) {
                sbuf.append(curr, 0, start);
            }
            /*
             * Get variable value and append to what we have so far.  If
             * we can't get a variable value we'll return the entire
             * original value passed in.
             */
            def varValue = getLibertyVariableValue(curr.substring(start+2, end));
            if (!varValue) {
                return val;
            }
            sbuf.append(varValue);
        }
        sbuf.append(curr); // Append any remaining value after var substitions
        return sbuf.toString();
    }

    /*
     * Attempt to substitute a value for the specified Liberty variable.  If
     * we don't know how to handle it, just return the passed-in string.
     */
    private String getLibertyVariableValue(String name) {
        String result = null;
        switch (name) {
            /*
             * We make the assumption that server.config.dir is the directory
             * containing the server.xml we are processing, so references
             * will be relative to that directory.
             */
            case "server.config.dir":
                result = ".";
                break;
            case "shared.config.dir":
                result = "../../shared/config";
                break;
            default:
                println "getLibertyVariableValue(): Not sure how to get value of ${name}";
        }
        return result;
    }
}
