'''
Copyright 2018-2020 VMware, Inc.
All rights reserved. -- VMware Confidential
'''

"""
   Group of functions which touch the staging area.

   These need to be platform independent. Specs might be loaded from the DKVS
   or etc/vmware or some other place depending on where the image manager is
   running.
"""
import os
import platform
import json
import logging

if platform.system() == 'VMkernel':
   from ..HostImage import HostImage
   runningOnEsx = True
   STAGING_ROOT = '/var/vmware/lifecycle/'
   SOFTWARE_SPEC_FILE = os.path.join(STAGING_ROOT, 'softwareSpec.json')
else:
   runningOnEsx = False
   STAGING_ROOT = None
   SOFTWARE_SPEC_FILE = None

log = logging.getLogger(__name__)

class SoftwareSpecParsingError(Exception):
   pass

class SoftwareSpecSavingError(Exception):
   pass

class SoftwareSpecExtactError(Exception):
   pass

class SoftwareSpecNotFoundError(Exception):
   pass


def extractAndInitSoftwareSpec():
   """ Extract the Image profile from the Host and initialize a Software Spec
       using it.

       Raises: SoftwareSpecExtactError if the 'esx' version is not set in the
               return spec.
   """

   if runningOnEsx:
      currentProfile = HostImage().GetProfile()
   else:
      currentProfile = None

   if currentProfile is None or len(currentProfile.vibIDs) == 0:
      log.error("Couldn't extract profile from host. Check if the host is ESX.")
      raise SoftwareSpecExtactError("No Profile found on the host")

   softwareSpec = currentProfile.ToSoftwareSpec()

   if softwareSpec.get('esx'):
      return softwareSpec

   log.error("Couldn't initialize the SoftwareSpec.")
   raise SoftwareSpecExtactError

def getStagedSoftwareSpec(extract=False):
   """ Load up the software spec from the staging area.

       Note: Caller of this function takes care of locking the file if needed.

       Parameter:
          extract: True if the spec needs to be extracted from the host and
                   intialized.
       Raises: SoftwareSpecParsingError if we are unable to parse the spec.
   """
   try:
      if not runningOnEsx:
         raise SoftwareSpecNotFoundError('Cannot get spec on non-ESX')

      if os.path.isfile(SOFTWARE_SPEC_FILE):
         with open(SOFTWARE_SPEC_FILE) as f:
            log.info("Retrieved the SoftwareSpec from the staging area.")
            return json.load(f)
      elif extract:
         # Hack to support stage_1: Extract the spec from the current host.
         log.info("Extracting and initializing the spec from the current host.")
         return extractAndInitSoftwareSpec()

      # SOFTWARE_SPEC_FILE is not found and extract is False.
      raise SoftwareSpecNotFoundError
   except (ValueError, IOError):
      raise SoftwareSpecParsingError("Unable to parse the software spec.")

def setStagedSoftwareSpec(spec):
   """ Overwrite the software spec in the staging area with a new and complete
       SoftwareSpec. This functions assumes that caller has validated the
       document and is trying to persist a complete document.

       Note: Caller of this function takes care of locking the file. Because
       all the scenarios that we have have analyzed requires locking at a higer
       level.

       Raises: SoftwareSpecSavingError if we are unable to parse the spec.
   """
   try:
      if not runningOnEsx:
         raise SoftwareSpecSavingError('Cannot save spec on non-ESX')

      with open(SOFTWARE_SPEC_FILE, 'w') as f:
         log.debug("New spec = %s " % spec)
         json.dump(spec, f)
         log.info("Replaced the SoftwareSpec in staging area with new content.")
   except (TypeError, IOError):
      raise SoftwareSpecSavingError("Unable to save the software spec")
