import java.io.File;

import groovy.json.JsonSlurper
import groovy.json.JsonBuilder
import groovy.io.FileType

import com.urbancode.air.AirPluginTool;


/*
  For a snippet to be applied individually (for example, adding a single JDBC Provider to WAS),
  two things are needed from the configuration data:

  1. The resource itself and all of its children.

  2. JSON entries for all items in the path above the resource we wish to extract.
     For example, if the resource we wish to extract has the path:

        /CloudBurstCell_1/Nodes/CloudBurstNode_3_1/Servers/Member2/JDBC Providers/myJDBCProvider

     We need to extract the JSON representing:

        JDBC Providers
        Member2
        Servers
        CloudBurstNode_3_1
        Nodes
        CloudBurstCell_1

     We don't need to extract all of the children for each item on this list.
*/

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

def configFile = props['configFile'].trim();
def outputFile = props['outputFile'].trim();
def newConfigFile = props['newConfigFile'].trim();
def pathsToExtractProp = props['pathToExtract'].trim();
def objectTypesToExtract = props['objectTypesToExtract'].trim();
def multipleOutputFile = props['multipleOutputFile'].trim();

file =  new File(configFile)
String fileContents = file.getText('UTF-8')
// we expect the fileContents to be in brackets
fileContents = fileContents.trim();
if (fileContents.substring(0, 1) != "[") {
  fileContents = "[" + fileContents + "]"
}
def list = new JsonSlurper().parseText(fileContents)

pathsToExtract = [];
pathsToExtractPropValues = pathsToExtractProp.split('\n')
for (path in pathsToExtractPropValues) {
  if (path != "") {
    pathsToExtract.push(path);
  }
}

// if the user specified object types to extract, we will build a list of
// resource paths for those objects
if (objectTypesToExtract != "") {
  // go through json and find paths for the resources of each type
  objectTypesToExtract = objectTypesToExtract.split('\n');
  for (objectTypeToExtract in objectTypesToExtract) {
    // add the WebSphere prefix to objectTypeToExtract
    // for example, if a user specified JDBCProvider, the roleName in the JSON would be WebSphereJDBCProvider
    targetRoleName = "WebSphere" + objectTypeToExtract;
    println "Collecting resource paths for resources with roleName = " + targetRoleName;
    list.each {
      if (it.roleName == targetRoleName) {
        pathsToExtract.push(it.path);
      }
    }
  }
}

println "Paths of resources to extract = " + pathsToExtract

def newArrayObj = []
def targetResource = []
def remainingJson = []
def targetResourceParent = ""
def intermediateObjects = []
def first = true;

resourceCounterMap = [:];

for (pathToExtract in pathsToExtract) {
  // reset newArrayObj if we are creating a file for each object to be extracted
  if (multipleOutputFile == "true") {
    newArrayObj = [];
  }
  // fix the path to extract in case it has \ characters
  pathToExtract = pathToExtract.replace("\\", "");
  // remove trailing / if it exists
  pathSize = pathToExtract.size()
  lastChar = pathToExtract.substring(pathSize - 1, pathSize);
  if (lastChar == "/") {
    pathToExtract = pathToExtract.substring(0, pathSize - 1)
  }
  if (first) {
    first = false;
  }
  else {
    list = remainingJson;
    remainingJson = [];
    intermediateObjects = [];
    targetResourceParent = "";
    targetResource = [];
  }

  println "Extracting resource at path " + pathToExtract;

  // get all resources under and including the path
  extractedRoleName = "";
  list.each {
    if (it.path == pathToExtract || it.path.indexOf(pathToExtract + "/", 0) == 0) {
      targetResource.push(it)
      if (it.path == pathToExtract) {
        // we will build a map to keep track of how many resources of
        // each type we have discovered (to be used if creating mutlipe snippet files)
        if (it.roleName != null) {
          extractedRoleName = it.roleName;
        }
        else {
          extractedRoleName = "WebSphereNoObjectType";
        }
        if (resourceCounterMap.containsKey(extractedRoleName)) {
          // get the current counter value for this resource type
          currentCounterValue = resourceCounterMap.get(extractedRoleName);
          newCounterValue = currentCounterValue + 1;
          resourceCounterMap[extractedRoleName] = newCounterValue;
        }
        else {
          resourceCounterMap.put(extractedRoleName, 1);
        }
      }
    }
    else {
      remainingJson.push(it)
    }
  }

  // determine and get the path of the resource that represents the snippet's resource type
  // For example, if pathToExtract is: /MattExemplarCell01/Nodes/MattExemplarNode1/JDBC Providers/myJDBC,
  // we want to get:                   /MattExemplarCell01/Nodes/MattExemplarNode1/JDBC Providers
  lastSlash = pathToExtract.lastIndexOf("/")
  parentPath = pathToExtract.substring(0, lastSlash)

  list.find {
    if (it.path == parentPath) {
      println "Found the snippet type resource at path " + parentPath
      targetResourceParent = it;
      return true
    }
    else {
      return false
    }
  }

  // determine and get the parent resource (Cell, Node, Server, Cluster)
  // can't rely on a scope being set because a scope is optional
  parentScope = "Cell"
  parentResourcePath = ""
  if (pathToExtract.contains("/Servers/")){
    parentScope = "/Servers/"
  } else if (pathToExtract.contains("/Nodes/")){
    parentScope = "/Nodes/"
  } else if (pathToExtract.contains("/ServerClusters/")){
    parentScope = "/ServerClusters/"
  }

  if (parentScope != "Cell"){
    // if pathToExtract is: /MattExemplarCell01/Nodes/MattExemplarNode1/JDBC Providers/myJDBC,
    // we want to get:      /MattExemplarCell01/Nodes/MattExemplarNode1
    parentScopeIndex = pathToExtract.indexOf(parentScope)
    parentScopeLength = parentScope.length()
    nextSlash = pathToExtract.indexOf("/", parentScopeIndex + parentScopeLength)
    parentResourcePath = pathToExtract.substring(0, nextSlash)
  } else {
    // for Cell, get the first piece of of the path
    nextSlash = pathToExtract.indexOf("/", 1)
    parentResourcePath = pathToExtract.substring(0, nextSlash)
  }

  // get the parent resource
  if (parentResourcePath == ""){
    throw new RuntimeException("Error: Could not find a parent resource of path " + parentResourcePath)
  }
  else {
    list.find {
      if (it.path == parentResourcePath) {
        println "Found the parent resource at path " + parentResourcePath
        newArrayObj.push(it);
        return true
      }
      else {
        return false
      }
    }
  }

  // we've gotten the target resource, it's immediate parent, and its top level parent.
  // next, we need to get everything in between.
  // start by getting the path minus the found resource and the found resource's parent
  // (because we already extracted them)
  // for example, if our target resource's path is:
  //    /@websphere.cell@/Securities/Security/JAAS Auth Data/JAASAuthData0
  // we want to get the path:
  //    /@websphere.cell@/Securities/Security
  lastSlash = parentPath.lastIndexOf("/")
  remainingPath = parentPath.substring(0, lastSlash)

  // let's work up the remaining path until we get to the top level path,
  // getting each resource as we go.
  if (remainingPath) {
    while (remainingPath != parentResourcePath){
      list.find {
        if (it.path == remainingPath) {
          println "Extracting path " + remainingPath
          intermediateObjects.push(it);
          // trim the path before we loop again
          lastSlash = remainingPath.lastIndexOf("/")
          remainingPath = remainingPath.substring(0, lastSlash)
          return true
        }
        else {
          return false
        }
      }
    }
  }

  // merge everything into newArrayObj
  // newArrayObj already has the top-level resource
  // next, merge the intermediate objects in the reverse order that we found them
  for (counter = intermediateObjects.size() - 1; counter >= 0; counter--) {
    newArrayObj.push(intermediateObjects[counter]);
  }
  // now add the target resource's parent
  newArrayObj.push(targetResourceParent);

  // finally, add the target resource and it's children
  for (counter = 0; counter <= targetResource.size() -1; counter++) {
    newArrayObj.push(targetResource[counter]);
  }

  // if the users specified to create a snippet file for each resource,
  // write to the snippet file now.
  if (multipleOutputFile == "true") {
    if (outputFile.toUpperCase().endsWith(".JSON")) {
      jIndex = outputFile.toUpperCase().indexOf(".JSON");
      newOutputFile = outputFile.substring(0, jIndex);
    }
    else {
      newOutputFile = outputFile;
    }
    // get the counter value
    counterValue = resourceCounterMap.get(extractedRoleName);
    // remove the "WebSphere" prefix from the roleName
    shortExtractedRoleName = extractedRoleName.substring(9);
    newOutputFile = newOutputFile + shortExtractedRoleName + "_" + counterValue + ".json";
    writeToOutputFile(newOutputFile, newArrayObj);
  }
}

// remove duplicate entries
if (pathsToExtract.size() > 1) {
  def handledPaths = [];
  def uniqueArray = [];
  newArrayObj.each {
    if (!handledPaths.contains(it.path)) {
      uniqueArray.push(it);
      handledPaths.push(it.path);
    }
  }
  newArrayObj = uniqueArray;
}

if (multipleOutputFile == "false") {
  // write extracted snippets to one file
  writeToOutputFile(outputFile, newArrayObj)
}

// create the new configuration file (source config file minus extracted snippets) if desired
if (newConfigFile) {
  // create newConfigFile dir if needed
  newConfigFile = new File(newConfigFile)
  newConfigFileParent = newConfigFile.getParent()
  if (newConfigFileParent) {
    newConfigFileDir = new File(newConfigFileParent)
    if (!newConfigFileDir.exists()) {
      newConfigFileDir.mkdirs()
    }
  }
  else {
    newConfigFileParent = "."
  }
  // delete the newConfig file if it already exists
  if (newConfigFile.exists()) {
    newConfigFile.delete()
  }

  // write the results to the outputFile
  println "Creating new new configuration file which does not contain the extracted resource: " + newConfigFile
  contents = new JsonBuilder(remainingJson).toPrettyString()
  // fix "" saving as "\"\""
  contents = contents.replace("\"\\\"\\\"\"", "\"\"")
  // remove brackets, if needed
  firstChar = contents.charAt(0)
  if (firstChar == "[") {
    startIndex = 1
    endIndex = contents.length()-1
    contents = contents.substring(startIndex, endIndex)
  }
  new File(newConfigFileParent+File.separator+newConfigFile.getName()).write(contents, 'UTF-8')
}
else {
  println "Not creating a new configuration file because the New Configuration File field is blank."
}


// method to write to the output file
void writeToOutputFile(String outputFileString, List jsonContents) {
  if (outputFileString) {
    // create output file dir if needed
    outputFile = new File(outputFileString)
    outputFileParent = outputFile.getParent()
    if (outputFileParent) {
      outputFileDir = new File(outputFileParent)
      if (!outputFileDir.exists()) {
        outputFileDir.mkdirs()
      }
    }
    else {
      outputFileParent = "."
    }
    // delete the output file if it already exists
    if (outputFile.exists()) {
      outputFile.delete()
    }

    // write the results to the outputFile
    println "Creating new snippet configuration file: " + outputFile
    contents = new JsonBuilder(jsonContents).toPrettyString()
    // fix "" saving as "\"\""
    contents = contents.replace("\"\\\"\\\"\"", "\"\"")
    // remove brackets, if needed
    firstChar = contents.charAt(0)
    if (firstChar == "[") {
      startIndex = 1
      endIndex = contents.length()-1
      contents = contents.substring(startIndex, endIndex)
    }
    new File(outputFileParent+File.separator+outputFile.getName()).write(contents, 'UTF-8')
  }
  else {
    println "Not creating a snippet configuration file because the Extracted Snippet File field is blank."
  }
}
