/*
* Licensed Materials - Property of IBM Corp.
* IBM UrbanCode Build
* IBM UrbanCode Deploy
* IBM UrbanCode Release
* IBM AnthillPro
* (c) Copyright IBM Corporation 2002, 2013 - 2016. All Rights Reserved.
*
* U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
* GSA ADP Schedule Contract with IBM Corp.
*/
import java.util.HashSet;
import java.net.URISyntaxException;
import java.net.URLEncoder;

import org.codehaus.jettison.json.JSONObject;
import org.codehaus.jettison.json.JSONArray;
import org.apache.commons.lang.StringUtils;
import com.urbancode.air.AirPluginTool;
import com.urbancode.air.plugin.websphere.WebSphereCmdLineHelper
import com.urbancode.ud.client.UDRestClient;
import com.urbancode.ud.client.ResourceClient;

def apTool = new AirPluginTool(this.args[0], this.args[1]);
def props = apTool.getStepProperties();

def weburl = System.getenv("AH_WEB_URL");
def user = apTool.getAuthTokenUsername();
def password = apTool.getAuthToken();
def rootResourcePath = props['resourcePath'];
def webSphereUser = props['wasuser'];
def webSpherePassword = props['waspassword'];
def profilePath = props['profilePath'];
def wsadminPath = props['commandPath'];
def wasPort = props['port'];
def wasHost = props['host'];
def wasConnType = props['connType'];
def resObjectString = props['fullResourceConfiguration'];

def WSDISC_HOME = System.getenv("PLUGIN_HOME");

def udResourceClient = null;

String [] requiredRolePropNames = [
    "websphere.cell.configurationtypes",
    "websphere.cell.enablebidi",
    "websphere.cell.cellregistered",
    "websphere.cell.shortname",
    "websphere.cell.biditextdirection",
    "websphere.cell.celldiscoveryprotocol",
    "websphere.cell.celltype",
    "websphere.cell.discoveryaddressendpointname",
    "websphere.cell.multicastdiscoveryaddressendpointname"];

// Include impersonation settings as part of Escalation 72732
String [] impersonationSettingsPropNames = [
    "impersonationUser",
    "impersonationPassword",
    "impersonationGroup",
    "impersonationUseSudo",
    "impersonationForce",
    "description",
    "inheritTeam"];

// Add the impersonation settings to 'props'
def addImpersonationSettingstoProps = { myResource ->
    for (String impersonationPropName: impersonationSettingsPropNames) {
        //Check whether the setting is present
        if (!myResource.isNull(impersonationPropName)) {
            props[impersonationPropName] = myResource.getString(impersonationPropName);
        }
    }
}

// -----------------------------------------------------------------------------------------------------------
// If we have an input property "fullResourceConfiguration", then we are invoked by a wrapper step,
// retrieve the role properties from this input property and DO NOT create resources (let wrapper do that).
// Otherwise, use REST API calls to get the role properties from the resource and set the
// flag to create resources in this step (with REST API call).
// -----------------------------------------------------------------------------------------------------------
def createResourcesFlag = false;
if (resObjectString != null) {
    // Invoked by wrapper step...
    def resObjectInput = new JSONObject(resObjectString);
    JSONObject roleProperties = resObjectInput.getJSONObject("roleProperties");

    //Add impersonation settings to the global variable 'props'
    addImpersonationSettingstoProps(resObjectInput);

    // For each required role property name, get from input property, fullResourceConfiguration.roleProperties,
    // and add to "props" array.
    for (String rolePropName: requiredRolePropNames) {
        props[rolePropName] = roleProperties.optString(rolePropName);
    }
} else {
    // Not invoked by wrapper step, use REST APIs to get required role properties for resource...
    def finalURI = new URI(weburl);
    udResourceClient = new ResourceClient(finalURI, user, password);
    JSONObject myResource = udResourceClient.getResourceByPath(rootResourcePath);
    addImpersonationSettingstoProps(myResource);
    getRequiredRolePropertiesForResource(rootResourcePath, requiredRolePropNames, props, udResourceClient);
    createResourcesFlag = true;
}


//this code parses the json that represents a resource and creates the resources
def createOrUpdateResources = { data ->
    JSONObject cellObject = new JSONObject(data);
    String cellName = cellObject.getString("cell");
    String port = cellObject.getString("port");
    String connType = cellObject.getString("connType");
    String host = cellObject.getString("host");
    JSONArray nodeJSONs = cellObject.getJSONArray("nodes");
    JSONArray clusterJSONs = cellObject.getJSONArray("clusters");

    JSONArray portalJSONs = cellObject.getJSONArray("portalservers");

    nodes = [:];
    nodeHost = [:];
    nodeProfileName = [:];
    clusters = [:];

    portalservers = [:];

    for (int i = 0 ; i < nodeJSONs.length(); i++) {
        JSONObject node = nodeJSONs.getJSONObject(i);
        String nodeName = node.getString("node");
        nodes[nodeName] = [];
        nodeHost[nodeName] = node.getString("nodeHost");
        nodeProfileName[nodeName] = node.getString("nodeProfileName");
        JSONArray serverJSONs = node.getJSONArray("servers");

        for (int j = 0; j < serverJSONs.length(); j++) {
            JSONObject server = serverJSONs.get(j);
            if (server.has("was")) {
                nodes[nodeName] << ["type":"was", "name":server.getString("was")];
            }
            else if (server.has("portal")) {
                nodes[nodeName] << ["type":"portal", "name":server.getString("portal")];
            }
        }
    }

    for (int i =0 ; i < clusterJSONs.length(); i ++) {
        JSONObject cluster = clusterJSONs.getJSONObject(i);
        String clusterName = cluster.getString("cluster");
        clusters[clusterName] = [];

        JSONArray memberJSONs = cluster.getJSONArray("members");

        for (int j = 0 ; j < memberJSONs.length(); j++) {
            JSONObject member = memberJSONs.get(j);
            String nodeName =  member.getString("node");
            String memberName = member.getString("member");

            //nodes[nodeName].remove(memberName);

            clusters[clusterName] << ["node":nodeName, "member":memberName];
        }
    }

    for (int i =0 ; i < portalJSONs.length(); i ++) {
        JSONObject portal = portalJSONs.getJSONObject(i);
        String portalId = portal.getString("portalserver");
        portalservers[portalId] = [];

        JSONArray attrJSONs = portal.getJSONArray("attributes");

        for (int j = 0 ; j < attrJSONs.length(); j++) {
            JSONObject attr = attrJSONs.get(j);
            String serverName =  attr.getString("server_name");
            String portalHome = attr.getString("portal_home");
            String profileHome =  attr.getString("profile_home");
            String configPort = attr.getString("config_port");
            String serverHost = attr.getString("server_host");

            portalservers[portalId] << ["server_name":serverName, "portal_home":portalHome,
                                        "profile_home":profileHome, "config_port":configPort,
                                        "server_host":serverHost];
        }
    }


    def resArray = new JSONArray();
    def cellResource = new JSONObject();
    resArray.put(cellResource);

    def cellRoleProperties = new JSONObject();
    cellRoleProperties.put("websphere.cell", cellName);
    cellRoleProperties.put("websphere.port", port);
    cellRoleProperties.put("websphere.host", host);
    cellRoleProperties.put("websphere.profilePath", profilePath);
    cellRoleProperties.put("websphere.commandPath", wsadminPath);
    cellRoleProperties.put("websphere.user", webSphereUser);
    cellRoleProperties.put("websphere.password", webSpherePassword);
    cellRoleProperties.put("websphere.connType", connType);
    cellRoleProperties.put("websphere.cell.configurationtypes", props['websphere.cell.configurationtypes']);
    cellRoleProperties.put("websphere.cell.enablebidi", props['websphere.cell.enablebidi']);
    cellRoleProperties.put("websphere.cell.cellregistered", props['websphere.cell.cellregistered']);
    cellRoleProperties.put("websphere.cell.shortname", props['websphere.cell.shortname']);
    cellRoleProperties.put("websphere.cell.biditextdirection", props['websphere.cell.biditextdirection']);
    cellRoleProperties.put("websphere.cell.celldiscoveryprotocol", props['websphere.cell.celldiscoveryprotocol']);
    cellRoleProperties.put("websphere.cell.celltype", props['websphere.cell.celltype']);
    cellRoleProperties.put("websphere.cell.discoveryaddressendpointname", props['websphere.cell.discoveryaddressendpointname']);
    cellRoleProperties.put("websphere.cell.multicastdiscoveryaddressendpointname", props['websphere.cell.multicastdiscoveryaddressendpointname']);

    cellResource.put("path", "/${cellName}");
    cellResource.put("name", cellName);
    cellResource.put("roleName", "WebSphereCell");
    cellResource.put("roleProperties", cellRoleProperties);

    //Adding impersonation settings as part of cell resource
    if (props.containsKey("impersonationUser")) {
        cellResource.put("impersonationUser", props["impersonationUser"]);
    }

    if (props.containsKey("impersonationPassword")) {
        cellResource.put("impersonationPassword", props["impersonationPassword"]);
    }

    if (props.containsKey("impersonationGroup")) {
        cellResource.put("impersonationGroup", props["impersonationGroup"]);
    }

    if (props.containsKey("impersonationUseSudo")) {
        cellResource.put("impersonationUseSudo", props["impersonationUseSudo"]);
    }

    if (props.containsKey("impersonationForce")) {
        cellResource.put("impersonationForce", props["impersonationForce"]);
    }

    if (props.containsKey("description")) {
        cellResource.put("description", props["description"]);
    }

    if(props.containsKey("inheritTeam")) {
        cellResource.put("inheritTeam", props["inheritTeam"]);
    }

    def nodesFolder = new JSONObject();
    nodesFolder.put("name", "Nodes");
    nodesFolder.put("path", "/${cellName}/Nodes");
    resArray.put(nodesFolder);

    def clustersFolder = new JSONObject();
    clustersFolder.put("name", "ServerClusters");
    clustersFolder.put("path", "/${cellName}/ServerClusters");
    resArray.put(clustersFolder);

    JSONArray createdResourcesJsonArr = new JSONArray();                // output property "createdResources", intended for external use

    JSONArray nodeResourcesJsonArr = new JSONArray();
    JSONArray serverResourcesJsonArr = new JSONArray();
    JSONArray portalServerResourcesJsonArr = new JSONArray();
    JSONArray clusterResourcesJsonArr = new JSONArray();
    String rootResourceParentPath = "";

    if (createResourcesFlag) {
        JSONObject createdResourcesObj = new JSONObject();
        createdResourcesJsonArr.put(createdResourcesObj);

        // Determine the new path of the root resource, the root resource's path may change
        // if it gets renamed (by the resource-batch-create REST API command).
        // The new path name will be root resource's parent path + "/${cellName}".
        String tmpResourcePath = rootResourcePath;
        rootResourceParentPath = tmpResourcePath.substring(0, tmpResourcePath.lastIndexOf("/"));
        String rootResourceNewPath = rootResourceParentPath + "/${cellName}";

        createdResourcesObj.put("profilePathResource", rootResourceNewPath);   // path to parent resource (WebSphereCell)
        createdResourcesObj.put("nodes", nodeResourcesJsonArr);
        createdResourcesObj.put("servers", serverResourcesJsonArr);
        createdResourcesObj.put("portalServers", portalServerResourcesJsonArr);
        createdResourcesObj.put("clusters", clusterResourcesJsonArr);
    }

    for (node in nodes) {
        println "Adding node: " + node.key.toString() + " with hostname: " + nodeHost[node.key] + " and profilename: " + nodeProfileName[node.key];

        def nodeResource =new JSONObject();
        nodeResource.put("name", node.key);
        def nodeResourcePath = "/${cellName}/Nodes/${node.key}";
        nodeResource.put("path", nodeResourcePath);
        nodeResource.put("roleName", "WebSphereNode");

        if (createResourcesFlag) {
            addResourceToJsonArray(nodeResourcesJsonArr, node.key, rootResourceParentPath + nodeResourcePath);
        }

        def nodeRoleProperties = new JSONObject();
        nodeRoleProperties.put("websphere.node", node.key);
        nodeRoleProperties.put("websphere.node.hostname", nodeHost[node.key]);
        nodeRoleProperties.put("websphere.node.profilename", nodeProfileName[node.key]);
        nodeResource.put("roleProperties", nodeRoleProperties);

        resArray.put(nodeResource);

        def serversFolder = new JSONObject();
        serversFolder.put("name", "Servers");
        serversFolder.put("path", "/${cellName}/Nodes/${node.key}/Servers");

        def portalServersFolder = new JSONObject();
        portalServersFolder.put("name", "Portal Servers");
        portalServersFolder.put("path", "/${cellName}/Nodes/${node.key}/Portal Servers");

        resArray.put(serversFolder);
        resArray.put(portalServersFolder);

        for (server in node.value) {
            println "Adding server: " + server.get("name");
            def type = server.get("type");
            switch (type) {
                case "was":
                    def serverResource = new JSONObject();

                    def servername = server.get("name");
                    def serverResourcePath = "/${cellName}/Nodes/${node.key}/Servers/${servername}";
                    serverResource.put("name", servername);
                    serverResource.put("roleName", "WebSphereServer");
                    serverResource.put("path", serverResourcePath);

                    if (createResourcesFlag) {
                        addResourceToJsonArray(serverResourcesJsonArr, servername, rootResourceParentPath + serverResourcePath);
                    }

                    def serverRoleProperties = new JSONObject();
                    serverResource.put("roleProperties", serverRoleProperties);
                    serverRoleProperties.put("websphere.server", servername);

                    resArray.put(serverResource);
                    break;
                case "portal":
                    def portalIndex = server.get("name");
                    JSONObject portalAttrs = portalservers.get(portalIndex);
                    def servername = portalAttrs.get("server_name");
                    def portalHome = portalAttrs.get("portal_home");
                    def profileHome = portalAttrs.get("profile_home");
                    def configPort = portalAttrs.get("config_port");
                    def serverHost = portalAttrs.get("server_host");
                    def serverResourcePath = "/${cellName}/Nodes/${node.key}/Servers/${servername}";

                    def serverResource = new JSONObject();
                    def serverRoleProperties = new JSONObject();
                    serverResource.put("roleProperties", serverRoleProperties);

                    serverResource.put("name", servername);
                    serverResource.put("path", serverResourcePath);
                    serverResource.put("roleName", "WebSphereServer");

                    if (createResourcesFlag) {
                        addResourceToJsonArray(serverResourcesJsonArr, servername, rootResourceParentPath + serverResourcePath);
                    }

                    serverRoleProperties.put("websphere.server", servername);
                    resArray.put(serverResource);

                    def portalResource = new JSONObject();
                    def portalRoleProperties = new JSONObject();
                    portalResource.put("roleProperties", portalRoleProperties);

                    def portalServerName = "${servername}(Portal)";
                    def portalServerResourcePath = "/${cellName}/Nodes/${node.key}/Portal Servers/${servername}(Portal)";
                    portalResource.put("name", portalServerName);
                    portalResource.put("path", portalServerResourcePath);
                    portalResource.put("roleName", "PortalServer");

                    if (createResourcesFlag) {
                        addResourceToJsonArray(portalServerResourcesJsonArr, portalServerName, rootResourceParentPath + portalServerResourcePath);
                    }

                    portalRoleProperties.put("websphere.server", servername);
                    portalRoleProperties.put("portal.host",serverHost);
                    portalRoleProperties.put("portal.home", portalHome);
                    portalRoleProperties.put("portal.profile.home", profileHome);
                    portalRoleProperties.put("portal.admin.user", "");
                    portalRoleProperties.put("portal.admin.password", "");
                    portalRoleProperties.put("portal.config.port", configPort);
                    portalRoleProperties.put("websphere.password", webSpherePassword);

                    resArray.put(portalResource);
                    break;
            }
        }
    }

    for (cluster in clusters) {
        def clusterResourceName = cluster.key.toString();
        println "Adding cluster: " + clusterResourceName;

        def clusterResource = new JSONObject();
        def clusterRoleProperties = new JSONObject();
        clusterResource.put("roleProperties", clusterRoleProperties);

        clusterResource.put("name", clusterResourceName);
        def clusterResourcePath = "/${cellName}/ServerClusters/${clusterResourceName}";
        clusterResource.put("path", clusterResourcePath);
        clusterResource.put("roleName", "WebSphereCluster");

        if (createResourcesFlag) {
            addResourceToJsonArray(clusterResourcesJsonArr, clusterResourceName, rootResourceParentPath + clusterResourcePath);
        }

        clusterRoleProperties.put("websphere.cluster", clusterResourceName);

        resArray.put(clusterResource);
    }

    String reses = resArray.toString();
    println reses

    // Send the list of resources to create to the resource restapi for batch create
    if (createResourcesFlag) {
        if (reses != null && !reses.trim().isEmpty()) {
            udResourceClient.batchSaveResource(rootResourcePath, reses);
        }
        // Set output property "createdResources", intended for external use
        String createdResources = createdResourcesJsonArr.toString();
        apTool.setOutputProperty("createdResources", createdResources);
    } else {
        // Else, invoked by wrapper step, we must return the output property "resources".
        apTool.setOutputProperty("resources", reses);
    }

    apTool.setOutputProperty("websphere.cell", cellName);
    apTool.setOutputProperties();
}


def getDataFromWsadmin = {
    def wsadminExe = wsadminPath;

    while (wsadminExe.endsWith(File.separator)) {
        wsadminExe = wsadminExe.substring(0, wsadminExe.length()-1);
    }

    wsadminExe = wsadminExe + File.separator + WebSphereCmdLineHelper.getWSADMINValue();

    def cmdArgs = [wsadminExe];

    if (wasHost?.trim() && !wasHost.startsWith("p:")) {
        cmdArgs << "-host";
        cmdArgs << wasHost.trim();
    }

    if (wasPort?.trim() && !wasPort.startsWith("p:")) {
        cmdArgs << "-port";
        cmdArgs << wasPort.trim();
    }

    if (wasConnType?.trim() && !wasConnType.startsWith("p:")) {
        cmdArgs << "-conntype";
        cmdArgs << wasConnType.trim();
    }

    if ( webSphereUser?.trim() && !webSphereUser.trim().startsWith("p:")) {
        println "Using credentials";
        cmdArgs << "-user" << webSphereUser << "-password" << webSpherePassword;
    }

    cmdArgs << "-f" << "${WSDISC_HOME}/jythonScripts/discover.py";
    println "Command: " + cmdArgs.join(' ');
    Process proc = cmdArgs.execute();
    proc.out.close();
    OutputStream oStream = new ByteArrayOutputStream();
    OutputStream eStream = new ByteArrayOutputStream();
    proc.waitForProcessOutput(oStream, eStream);
    String output = oStream.toString();
    String errorOutput = eStream.toString();

    println output;
    println errorOutput;

    //if output does not contain "{" then Topology JSON was not returned
    //and instead an error was returned
    if (output.contains("{")) {
        int start = output.indexOf("{");
        output = output.substring(start-1, output.length());
    } else {
        throw new RuntimeException("Did not receive WAS Topology JSON from server. Discovery stopped.");
    }

    return output;
}

/*
 * This adds a resource-name and resource-path as a JSON Object to the specified JSON Array
 */
private addResourceToJsonArray(JSONArray jsonArr, String resourceName, String resourcePath) {
    // If a resource was created, add it to the array of resource paths
    JSONObject resourcePair = new JSONObject();
    resourcePair.put("resourceName", resourceName);
    resourcePair.put("resourcePath", resourcePath);
    jsonArr.put(resourcePair);
}

/*
 * When not invoked by wrapper step, use this method to use REST API calls to
 * get the role properties from the resource and put in global array, props.
 */
private getRequiredRolePropertiesForResource(
    String myResourcePath, String[] myRolePropNames, Properties myProps,
    ResourceClient myUdResourceClient) {

    // Add required role properties to lookup set.
    HashSet<String> requiredRolePropNamesSet = new HashSet(Arrays.asList(myRolePropNames));

    // Get the resource roles for the root resource
    //   - returns JSON array of JSONObject [ {id: "xxx", name: "xxx", ... } ] (either 0 or 1 entry)
    //
    JSONArray resourceRolesArr = myUdResourceClient.getResourceRoles(myResourcePath);
    
    if (resourceRolesArr == null || resourceRolesArr.length() == 0) {
        throw new Exception("Resource does not have required 'Resource Role', resource: $myResourcePath");
    }

    String resourceRoleId = resourceRolesArr.getJSONObject(0).getString("id");

    // Get all of the properties for this given resource role.
    //   - returns JSON Array of JSON Object: [ { propDef: { name: "xxx", value: "xxx" } }, ... ]
    //
    JSONArray rolePropertiesArr = myUdResourceClient.getResourceRoleProperties(resourceRoleId, myResourcePath);

    // Loop thru the role properties looking for values to our required role properties, stop when we find all of them.
    int requiredPropCt = requiredRolePropNamesSet.size();
    int jsonArrLen = rolePropertiesArr.length();

    for (int jsonIndex = 0; jsonIndex < jsonArrLen; jsonIndex++) {
        JSONObject propValueJson = rolePropertiesArr.getJSONObject(jsonIndex).getJSONObject("propDef");
        String propName = propValueJson.getString("name");
        // If this is one of the properties we seek, put the value in myProps.
        if (requiredRolePropNamesSet.contains(propName)) {
            myProps[propName] = propValueJson.getString("value");
            --requiredPropCt;
            // Check if we have found all we seek, if so, stop.
            if (requiredPropCt == 0) {
                break;
            }
        }
    }
}

    
def data = getDataFromWsadmin();

createOrUpdateResources(data);

