###
# Licensed Materials - Property of IBM Corp.
# @product.name.full@
# (c) Copyright IBM Corporation 2003, 2016. All Rights Reserved.
#
# U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
# GSA ADP Schedule Contract with IBM Corp.
#
# Filename: PluginInputHelper.py
#
#
###

import codecs;
import os;
import sys;
from java.util import Properties;
from java.io import File, FileInputStream, ByteArrayInputStream, IOException;
from java.security import GeneralSecurityException;
from org.codehaus.jettison.json import JSONObject, JSONArray, JSONException;
from com.urbancode.air.securedata import BadEnvelopeException, Base64Codec, SecretContainer, SecretContainerImpl, SecureBlob;
from com.urbancode.commons.util import IO;

class PluginInputHelper:

  UCD_ENCRYPT_PROPERTIES_ENV_VAR = "UCD_USE_ENCRYPTED_PROPERTIES";
  UCD_SECRET_VAR = "ucd.properties.secret";

  hasReadStdInProps = "false";
  secret = None;

  def __init__(self, inputPropsLocation, secret = None):
    self.secret = secret;
    self.inputPropsFile = File(inputPropsLocation);
    self.parsedResourceConfiguration = 0;
    self.inputProperties = None;
    self.fullResourceConfiguration = None;
    
  def getLogDirectory(self):
    return self.inputPropsFile.getParentFile();

  def getFullResourceConfiguration(self, forceRootPath = ""):
    if self.parsedResourceConfiguration == 0:
      self._parseResourceConfiguration(forceRootPath);
    return self.fullResourceConfiguration;

  def getInputProperties(self):
    if self.inputProperties is None:
      self.inputProperties = self._loadInputProperties(self.inputPropsFile);
    return self.inputProperties;

  def _parseResourceConfiguration(self, forceRootPath = ""):
    self.parsedResourceConfiguration = 1;
    if self.fullResourceConfiguration is None:
      resourceJSONObject = None;
      if self.inputProperties == None:
        self.getInputProperties();
      configFile = self.inputProperties.getProperty("configurationFile");
      if configFile is not None and len(configFile) > 0:
        if os.path.isfile(configFile):
          resourceJSONObject = self._parseConfigurationFile(configFile, forceRootPath);
      if resourceJSONObject is None:
        fullResConf = self.inputProperties.getProperty("fullResourceConfiguration");
        if fullResConf is not None and len(fullResConf) > 0:
          resourceJSONObject = JSONObject(fullResConf);
      self.fullResourceConfiguration = resourceJSONObject;
    else:
      pass;

  def _parseConfigurationFile(self, configFile, forceRootPath = ""):
    print "Processing configuration file %s" % configFile;
    resourceJSONObject = None;
    inputFile = codecs.open(configFile, "r", "utf-8");

    inputData = inputFile.readlines();

    if inputData == []:
      raise ValueError("Could not read the contents of configuration file " + str(configFile) + ". Verify the file is not empty.");
    # Our provided components create a new json file (ibm-ucd-resources.json) as part of the apply process.
    # This file contains the merged contents of a component version's artifacts
    # If there are no version artifacts, the new json file has a value of ['[\n', '\n', ']']
    # Let's throw an exception with a clear message in this case
    if str(inputData) == "['[\\n', '\\n', ']']":
      raise ValueError("No configuration data was found. Verify your component version artifacts are present.")
    inputData = ''.join(inputData);
    # add surrounding brackets if none are present
    inputDataStripped = inputData.strip();
    firstChar = inputDataStripped[0:1];
    if not firstChar == "[":
      inputData = "[" + inputData + "]";
    try:
      resourceJSONObject = JSONObject(inputData);
    except JSONException, je:
      resourceJSONArray = JSONArray(inputData);
      resourceJSONObject = self._convertResourceJSONArrayToJSONObject(resourceJSONArray, forceRootPath);
    return resourceJSONObject;

  def _convertResourceJSONArrayToJSONObject(self, resourceJSONArray, forceRootPath = ""):
    objMap = self._getBaseMapFromJSONArray(resourceJSONArray);
    return self._convertObjectDictToJSONObject(objMap, forceRootPath);

  def _getBaseMapFromJSONArray(self, resourceJSONArray):
    objMap = {};
    for i in range(resourceJSONArray.length()):
      myObj = resourceJSONArray.getJSONObject(i);
      thispath = myObj.getString('path');
      objMap[thispath] = myObj;
      children = myObj.optJSONArray("children");
      if children is None:
          myObj.put("children", JSONArray());
    return objMap;

  def _convertObjectDictToJSONObject(self, objMap, forceRootPath = ""):
    root = None;
    objMapKeys = objMap.keys();

    # this method works by grabbing an object from the JSON, getting it's path,
    # then looping through all objects and comparing paths to find the root parent object.
    # in the case where we have multiple objects that may be a root parent object,
    # we may want to force which root parent we want to find.
    # for example, for dynamic clusters, the config data contains two root objects with these paths:
    #   /cellName/Dynamic Clusters/clusterName
    #   /cellName/ServerClusters/clusterName
    # if we wanted to ensure we find the dynamic clusters in this case,
    # we'd set forceRootPath to "/Dynamic Clusters/"

    # if forceRootPath is set, ensure we start looping with an object of that path
    if forceRootPath != "":
      for path in objMapKeys:
        if path.find(forceRootPath) != -1:
          root = objMap[path]
          rootPath = root.getString("path");
          break;

    for path in objMapKeys:
      curObj = objMap[path];

      if root is None:
        root = curObj;
      else:
        rootPath = root.getString("path");
        if rootPath.startswith(path + "/"):
          root = curObj;

      ind = path.rfind("/");
      if ind != -1:
        parentPath = path[0:ind];
        if parentPath in objMapKeys:
          parent = objMap[parentPath];
          children = parent.optJSONArray("children");
          if children is None:
            children = JSONArray();
            parent.put("children", children);
          children.put(curObj);

    self._normalizePaths(root, objMap);
    return root;

  def _normalizePaths(self, root, objMap):
    rootPath = root.getString("path");
    stripPath = rootPath[0:rootPath.rfind("/")];
    if len(stripPath) > 0:
      self._stripPaths(stripPath, objMap);

  def _stripPaths(self, stripPath, objMap):
    for x in objMap.values():
      curPath = x.getString("path");
      if curPath.startswith(stripPath):
        newPath = curPath[len(stripPath):];
        x.put("path", newPath);

  def getSecret(self):
    if self.hasReadStdInProps == "true" or self.secret is not None:
      return self.secret;
    props = Properties();
    useEnc = os.environ.get(self.UCD_ENCRYPT_PROPERTIES_ENV_VAR, "false");
    if useEnc.lower() == "true" :
      props.load(sys.stdin);
      self.secret = props.getProperty(self.UCD_SECRET_VAR);
      self.hasReadStdInProps = "true";
    return self.secret;

  def _loadInputProperties(self, inputPropsFile):
    encSecret = self.getSecret();
    inputProperties = None;
    try :
      inputPropertiesStream = FileInputStream(inputPropsFile);
      if encSecret == None :
        inputProperties = self._loadProperties(inputPropertiesStream);
      else :
        secretContainer = SecretContainerImpl(Base64Codec().decodeFromString(encSecret));
        inputProperties = self._loadEncryptedProperties(secretContainer, inputPropertiesStream);
    except IOException :
      raise PluginExecutionException("Could not locate input properties file at " + inputPropsFile.getAbsolutePath());
    return inputProperties;

  def _loadEncryptedProperties(self, secret, inStream):
    try :
      blob = SecureBlob.fromEncryptedBytes(secret, IO.read(inStream));
      return self._loadProperties(ByteArrayInputStream(blob.get()));
    except BadEnvelopeException, bee :
      raise IOException("Couldn't read encrypted properties file", bee);
    except GeneralSecurityException, gse :
      raise IOException("Couldn't read encrypted properties file", gse);

  def _loadProperties(self, inStream):
    props = Properties();
    props.load(inStream);
    return props;

  def _writeProperties(self, props, outputFile):
    encSecret = self.getSecret();
    fileOutputStream = FileOutputStream(outputFile);
    if encSecret == None :
      self._writeProperties(props, fileOutputStream);
    else :
      secretContainer = SecretContainerImpl(Base64Codec().decodeFromString(encSecret));
      self._writeEncryptedProperties(props, fileOutputStream, secretContainer);

  def writeEncryptedProperties(self, props, str, secret):
    baos = ByteArrayOutputStream();
    self._writeProperties(props, baos);
    try :
      blob = SecureBlob.fromUnencryptedBytes(secret, baos.toByteArray());
      str.write(blob.getEncryptedBytes());
    except BadEnvelopeException, bee :
      raise IOException("Couldn't write encrypted properties file", bee);
    except GeneralSecurityException, gse :
      raise IOException("Couldn't write encrypted properties file", gse);
    #finally:
    #    IO.close(str);

  def _writeProperties(self, props, str):
    try :
      props.store(str, "");
    except IOException, ioe :
      raise IOException("Couldn't store properties", ioe);
    #finally:
    #    IO.close(str);
