## @file ShellEnvironment.py
# This module contains code that helps to manage the build environment
# including PATH, PYTHONPATH, and ENV variables.
#
##
# Copyright (c) 2017, Microsoft Corporation
#
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##
import os
import sys
import logging
from MuEnvironment import VarDict


# NOTE: It's entirely possible that this should be a singleton.
#       See here for more ideas...
#       https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python
class ShellEnvironment(object):
  def __init__(self):
    super(ShellEnvironment, self).__init__()

    self.global_path = None
    self.global_pypath = None

    self.import_environment()

    self.original_path = self.global_path
    self.original_pypath = self.global_pypath

  def set_path(self, path_elements):
    logging.debug("Overriding PATH with new value.")
    self.global_path = list(path_elements)
    os.environ["PATH"] = os.pathsep.join(path_elements)
    self.export_environment()

  def set_pypath(self, path_elements):
    logging.debug("Overriding PYTHONPATH with new value.")
    self.global_pypath = list(path_elements)
    self.export_environment()

  def append_path(self, path_element):
    logging.debug("Appending PATH element '%s'." % path_element)
    if path_element not in self.global_path:
      self.global_path.append(path_element)
      self.export_environment()

  def append_pypath(self, path_element):
    logging.debug("Appending PYTHONPATH element '%s'." % path_element)
    if path_element not in self.global_pypath:
      self.global_pypath.append(path_element)
      self.export_environment()

  def insert_path(self, path_element):
    logging.debug("Inserting PATH element '%s'." % path_element)
    if path_element not in self.global_path:
      self.global_path.insert(0, path_element)
      self.export_environment()

  def insert_pypath(self, path_element):
    logging.debug("Inserting PYTHONPATH element '%s'." % path_element)
    if path_element not in self.global_path:
      self.global_pypath.insert(0, path_element)
      self.export_environment()

  def get_build_var(self, var_name):
    return GetBuildVars().GetValue(var_name)

  def set_build_var(self, var_name, var_data):
    logging.debug("Updating BUILD VAR element '%s': '%s'." % (var_name, var_data))
    GetBuildVars().SetValue(var_name, var_data, '', overridable=True)

  def get_shell_var(self, var_name):
    return os.environ.get(var_name, None)

  def set_shell_var(self, var_name, var_data):
    logging.debug("Updating SHELL VAR element '%s': '%s'." % (var_name, var_data))
    os.environ[var_name] = var_data

  def import_environment(self):
    # Record the PATH elements of the current environment.
    path = os.environ.get('PATH', "")
    # Filter removes empty elements.
    # List creates an actual list rather than a generator.
    self.global_path = list(filter(None, path.split(os.pathsep)))  # Filter removes empty strings.

    # Record the PYTHONPATH elements of the current environment.
    # When reading PYTHONPATH, try reading the live path from sys.
    self.global_pypath = sys.path

  def export_environment(self):
    os.environ["PATH"] = os.pathsep.join(self.global_path)
    os.environ["PYTHONPATH"] = os.pathsep.join(self.global_pypath)
    sys.path = self.global_pypath

  def log_environment(self):
    logging.debug("FINAL PATH:")
    logging.debug(", ".join(self.global_path))

    logging.debug("FINAL PYTHONPATH:")
    logging.debug(", ".join(self.global_pypath))


rootEnvironment = None
def GetEnvironment():
  global rootEnvironment

  if not rootEnvironment:
    rootEnvironment = ShellEnvironment()
  return rootEnvironment

  if len(rootVarDict) == 0:
    rootVarDict.append(VarDict.VarDict())
  return rootVarDict[0]


rootVarDict = list()
def GetBuildVars():
  global rootVarDict

  if len(rootVarDict) == 0:
    rootVarDict.append(VarDict.VarDict())
  return rootVarDict[-1]

def CheckpointBuildVars():
  global rootVarDict
  import copy
  newBuildVars = copy.copy(GetBuildVars())
  rootVarDict.append(newBuildVars)
  logging.debug("Created checkpoint {0} for build vars".format(len(rootVarDict)))

def RevertBuildVars():
  global rootVarDict
  rootVarDict.pop()
  logging.debug("Reverting to checkpoint {0} for build vars".format(len(rootVarDict)))

def ClearEnvironment():
  global rootEnvironment
  rootEnvironment = None
