#!/usr/bin/env python
#SETMODE 777

#----------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------ HEADER --#

"""
:author:
    <NAME>
    jmw150530

:synopsis:
    Creates the Pipeline Context.

:description:
    This module creates the pipeline context using the formula manager to import
    formulas from a formulas.xml file. The asset_manager context is set by passing these
    formulas to the PathContext class which validates the path on disk and returns
    a completed path. The PipeContext class then has it's variables created with kwargs
    based upon the project formulas and the disk path.

:applications:
    Any applications that are required to run this runner, i.e. Maya.

:see_also:
    Any other code that you have written that this module is similar to.

"""

#----------------------------------------------------------------------------------------#
#----------------------------------------------------------------------------- IMPORTS --#

# Built-in
import os
import re as re
import path_lib as fm
from pipe_utils import IO
preferences = None
bpy = None
sep = os.path.sep
#----------------------------------------------------------------------------------------#
#--------------------------------------------------------------------------- FUNCTIONS --#


#----------------------------------------------------------------------------------------#
#----------------------------------------------------------------------------- CLASSES --#

from contextlib import contextmanager, AbstractContextManager
from pipe_enums import PIPELINE


class PipeContext(AbstractContextManager):
    """
    this stores pipeline values
    """

    def __init__(self, **kwargs):

        self._context:str = None
        self._old_context:list = []
        global preferences
        self.asset = kwargs.setdefault('asset', None)
        self.asset_type = kwargs.setdefault('asset_type', None)
        self.context_area = kwargs.setdefault('context_area', "pipeline")
        self.disk_type = kwargs.setdefault('disk_type', None)
        drive = PIPELINE.OS
        self.drive = kwargs.setdefault('drive', drive)
        self.project = kwargs.setdefault('project', None)
        self.preferences = None        
        self.user = False
        

    def __enter__(self):
        """
        Upon entering a fresh PipeContext Manager assign the current context to the platform specific base drive
        :return:
        """
        try:
            if not self.context and self.drive is not None:
                self.context = self.drive
        except AttributeError:
            IO.error("'drive' keyword not set")
        finally:
            pass


    def __exit__(self, exc_type, exc_value, traceback):
        """
        Set current context to the cached context
        :param exc_type:
        :param exc_value:
        :param traceback:
        :return:
        """
        
        if self.context and self.context == self.old_context[-1:]:
            self.old_context.pop()
        return self.context

    def context_init(self):
        if not self.context and self.drive is not None:
                self.context = self.drive

    @classmethod
    def get_path(cls, formula="pipe_base_dir", *args, **kwargs) -> str or list:
        """
        Evaluate a formula and return a path
        :param formula:
        :param args: positional arguments, e.g. parent_formula
        :param kwargs: keyword_arguments to pass to PipeContext
        :return: path
        """
        
        argv = None
        if args:
            argv = args

        IO.debug(f"Formula               : {formula}")
        IO.debug(f"Additional Forumlas   : {argv}")
        IO.debug(f"Keyword Arguments     : {kwargs}")

        pc = cls(**kwargs)
        with pc:
            if isinstance(formula, list):
                # Evaluate Multiple Paths
                for f in formula:
                    _eval = pc.eval_path
                    path = _eval(f, *args, **kwargs)
            else:
                # Evaluate Single Path
                _eval = pc.eval_path
                path = _eval(formula, *args, **kwargs)
            return pc.context


    def eval_path(self, formula, *args, **kwargs):
        """
        Evaluate a formula and return a path object
        :param formula: A string representing the formula to evaluate
        :param kwargs: Variables to pass to PipeContext
        :return: path
        """
        # Initialize PipeContext Instance in PathContext
        pcontext = PathContext(self)
        path = pcontext.get_path(formula, *args, **kwargs)
        self.context = path
        return path


    @classmethod
    def relpath(cls, new_path, start=None, path_type="bpy"):
        """Generate a relative path"""
        ctx = cls.context
        new_ctx = os.path.normpath(new_path)
        if start == ctx:
            start = ctx
        if path_type == 'bpy':
            return cls._bpy_relpath(new_path, start)
        else:
            return os.path.relpath(new_ctx, start=start)


    @staticmethod
    def _bpy_relpath(path, start=None):
        import bpy.path.relpath as relpath
        return relpath(path, start)


    @property
    def context(self) -> str:
        return self._context

    @context.setter
    def context(self, context_path:str):
        """
        Sets the current context
        Evaluates current context agains
        """
        # Get the current set context
        current_context = self._context
        if current_context and current_context != self._old_context[-1:]:
            # Check against the new context before appending the current one.
            if context_path != current_context:
                # Append the current context to the old context before switching
                self._old_context.append(current_context)
        self._context = context_path

    @context.deleter
    def context(self):
        base = preferences.pipe_base_dir
        if base:
            self._context = base
        else:
            self._context = None


    @property
    def old_context(self) -> list:
        return self._old_context

    @old_context.setter
    def old_context(self, context_path:str or list):
        if isinstance(context_path, str):
            self._old_context.append(context_path)
        elif isinstance(context_path, list):
            self._old_context = context_path


    def examine_path(self, path, pipe_base_dir=None, var=None) -> str or dict:
        """
        NOTE: NOT YET IMPLEMENTED
        Examine a path on disk and derive the current context
        :param path: Current Path to evaluate
        :param pipe_base_dir: Pipeline Base Directory
        :param var: Specific formula/path
        :return: path_item or path_dict
        """
        pcontext = PathContext(self)
        # Return a Single Path
        if var is None:
            path_item = pcontext.examine_path(path, pipe_base_dir=pipe_base_dir, var=var)
            return path_item
        # Return the entire Path Context
        else:
            path_dict = pcontext.examine_path(path, pipe_base_dir=pipe_base_dir, var=var)
            return path_dict
            

class PathContext(object):
    """
    this class resolves a real path on disk
    """

    def __init__(self, pipe_context):
        self.pipe_context = pipe_context
        self.path = None
        self.path_dict = None


    @staticmethod
    def create_path(path):
        os.makedirs(path, exist_ok=True)


    def get_path(self, formula, *args, **kwargs):
        """
        Return or Yield a formula path
        :param formula: formula to evaluate
        :param args[0]: parent_formula
        :param kwargs: keyword_arguments
        :return:
        """

        # Load Formula manager
        formula_manager = fm.FormulaManager(self.pipe_context.user)
        if args:
            for arg in args:
                parent_formula = arg
                formula_manager.get_formula(parent_formula)
        formula_pieces = formula_manager.get_formula(formula)
        return self._get_form_path(formula_pieces, **kwargs)


    def _get_form_path(self, formula_pieces, **kwargs):
        """Return a single formula using the passed in formula pieces"""
        return self._return_path(formula_pieces, **kwargs)


    def _return_path(self, pieces, **kwargs) -> str :
        """
        Return a normalized path by pulling values of formula pieces out of a dict
        :param path: list or set
        :param pieces: formula pieces
        :param y: yield
        :param kwargs: keyword arguments
        :return:
        """
        # Iterate over each piece
        path = []
        for piece in pieces:
            # No Bracketed Values to clean, put in path
            if not '{' in piece:
                path.append(piece)
                continue
            # Regex to find all bracketed values and clean them
            cleaned = re.findall(r'{(.*?)\}', piece, 0)
            # Check if the cleaned value is in kwargs
            if cleaned[0] in kwargs:
                value = kwargs[cleaned[0]]
                path.append(value)
            # Check if the cleaned value is in PipeContext
            elif cleaned[0] in self.pipe_context.__dict__.keys():
                value = self.pipe_context.__dict__[cleaned[0]]
                path.append(value)
        # IO.debug("Returning Path")
        return os.path.normpath((os.path.sep).join(path))


    def examine_path(self, path, pipe_base_dir=None, var=None):
        """

        :param path: Current context path we want to examine
        :param pipe_base_dir: Pipeline Base Directory Path
        :param var: Variable we want to examine in this path
        :return:
        """

        return ""
#!/usr/bin/env python
#SETMODE 777

#----------------------------------------------------------------------------------------#
#------------------------------------------------------------------------------ HEADER --#

"""
:author:
    <NAME>
    jmw150530

:synopsis:
    Creates the Pipeline Context.

:description:
    This module creates the pipeline context using the formula manager to import
    formulas from a formulas.xml file. The asset_manager context is set by passing these
    formulas to the PathContext class which validates the path on disk and returns
    a completed path. The PipeContext class then has it's variables created with kwargs
    based upon the project formulas and the disk path.

:applications:
    Any applications that are required to run this runner, i.e. Maya.

:see_also:
    Any other code that you have written that this module is similar to.

"""

#----------------------------------------------------------------------------------------#
#----------------------------------------------------------------------------- IMPORTS --#

# Built-in
import os
import re as re
import path_lib as fm
from pipe_utils import IO
preferences = None
bpy = None
sep = os.path.sep
#----------------------------------------------------------------------------------------#
#--------------------------------------------------------------------------- FUNCTIONS --#


#----------------------------------------------------------------------------------------#
#----------------------------------------------------------------------------- CLASSES --#

from contextlib import contextmanager, AbstractContextManager
from pipe_enums import PIPELINE


class PipeContext(AbstractContextManager):
    """
    this stores pipeline values
    """

    def __init__(self, **kwargs):

        self._context:str = None
        self._old_context:list = []
        global preferences
        self.asset = kwargs.setdefault('asset', None)
        self.asset_type = kwargs.setdefault('asset_type', None)
        self.context_area = kwargs.setdefault('context_area', "pipeline")
        self.disk_type = kwargs.setdefault('disk_type', None)
        drive = PIPELINE.OS
        self.drive = kwargs.setdefault('drive', drive)
        self.project = kwargs.setdefault('project', None)
        self.preferences = None        
        self.user = False
        

    def __enter__(self):
        """
        Upon entering a fresh PipeContext Manager assign the current context to the platform specific base drive
        :return:
        """
        try:
            if not self.context and self.drive is not None:
                self.context = self.drive
        except AttributeError:
            IO.error("'drive' keyword not set")
        finally:
            pass


    def __exit__(self, exc_type, exc_value, traceback):
        """
        Set current context to the cached context
        :param exc_type:
        :param exc_value:
        :param traceback:
        :return:
        """
        
        if self.context and self.context == self.old_context[-1:]:
            self.old_context.pop()
        return self.context

    def context_init(self):
        if not self.context and self.drive is not None:
                self.context = self.drive

    @classmethod
    def get_path(cls, formula="pipe_base_dir", *args, **kwargs) -> str or list:
        """
        Evaluate a formula and return a path
        :param formula:
        :param args: positional arguments, e.g. parent_formula
        :param kwargs: keyword_arguments to pass to PipeContext
        :return: path
        """
        
        argv = None
        if args:
            argv = args

        IO.debug(f"Formula               : {formula}")
        IO.debug(f"Additional Forumlas   : {argv}")
        IO.debug(f"Keyword Arguments     : {kwargs}")

        pc = cls(**kwargs)
        with pc:
            if isinstance(formula, list):
                # Evaluate Multiple Paths
                for f in formula:
                    _eval = pc.eval_path
                    path = _eval(f, *args, **kwargs)
            else:
                # Evaluate Single Path
                _eval = pc.eval_path
                path = _eval(formula, *args, **kwargs)
            return pc.context


    def eval_path(self, formula, *args, **kwargs):
        """
        Evaluate a formula and return a path object
        :param formula: A string representing the formula to evaluate
        :param kwargs: Variables to pass to PipeContext
        :return: path
        """
        # Initialize PipeContext Instance in PathContext
        pcontext = PathContext(self)
        path = pcontext.get_path(formula, *args, **kwargs)
        self.context = path
        return path


    @classmethod
    def relpath(cls, new_path, start=None, path_type="bpy"):
        """Generate a relative path"""
        ctx = cls.context
        new_ctx = os.path.normpath(new_path)
        if start == ctx:
            start = ctx
        if path_type == 'bpy':
            return cls._bpy_relpath(new_path, start)
        else:
            return os.path.relpath(new_ctx, start=start)


    @staticmethod
    def _bpy_relpath(path, start=None):
        import bpy.path.relpath as relpath
        return relpath(path, start)


    @property
    def context(self) -> str:
        return self._context

    @context.setter
    def context(self, context_path:str):
        """
        Sets the current context
        Evaluates current context agains
        """
        # Get the current set context
        current_context = self._context
        if current_context and current_context != self._old_context[-1:]:
            # Check against the new context before appending the current one.
            if context_path != current_context:
                # Append the current context to the old context before switching
                self._old_context.append(current_context)
        self._context = context_path

    @context.deleter
    def context(self):
        base = preferences.pipe_base_dir
        if base:
            self._context = base
        else:
            self._context = None


    @property
    def old_context(self) -> list:
        return self._old_context

    @old_context.setter
    def old_context(self, context_path:str or list):
        if isinstance(context_path, str):
            self._old_context.append(context_path)
        elif isinstance(context_path, list):
            self._old_context = context_path


    def examine_path(self, path, pipe_base_dir=None, var=None) -> str or dict:
        """
        NOTE: NOT YET IMPLEMENTED
        Examine a path on disk and derive the current context
        :param path: Current Path to evaluate
        :param pipe_base_dir: Pipeline Base Directory
        :param var: Specific formula/path
        :return: path_item or path_dict
        """
        pcontext = PathContext(self)
        # Return a Single Path
        if var is None:
            path_item = pcontext.examine_path(path, pipe_base_dir=pipe_base_dir, var=var)
            return path_item
        # Return the entire Path Context
        else:
            path_dict = pcontext.examine_path(path, pipe_base_dir=pipe_base_dir, var=var)
            return path_dict
            

class PathContext(object):
    """
    this class resolves a real path on disk
    """

    def __init__(self, pipe_context):
        self.pipe_context = pipe_context
        self.path = None
        self.path_dict = None


    @staticmethod
    def create_path(path):
        os.makedirs(path, exist_ok=True)


    def get_path(self, formula, *args, **kwargs):
        """
        Return or Yield a formula path
        :param formula: formula to evaluate
        :param args[0]: parent_formula
        :param kwargs: keyword_arguments
        :return:
        """

        # Load Formula manager
        formula_manager = fm.FormulaManager(self.pipe_context.user)
        if args:
            for arg in args:
                parent_formula = arg
                formula_manager.get_formula(parent_formula)
        formula_pieces = formula_manager.get_formula(formula)
        return self._get_form_path(formula_pieces, **kwargs)


    def _get_form_path(self, formula_pieces, **kwargs):
        """Return a single formula using the passed in formula pieces"""
        return self._return_path(formula_pieces, **kwargs)


    def _return_path(self, pieces, **kwargs) -> str :
        """
        Return a normalized path by pulling values of formula pieces out of a dict
        :param path: list or set
        :param pieces: formula pieces
        :param y: yield
        :param kwargs: keyword arguments
        :return:
        """
        # Iterate over each piece
        path = []
        for piece in pieces:
            # No Bracketed Values to clean, put in path
            if not '{' in piece:
                path.append(piece)
                continue
            # Regex to find all bracketed values and clean them
            cleaned = re.findall(r'{(.*?)\}', piece, 0)
            # Check if the cleaned value is in kwargs
            if cleaned[0] in kwargs:
                value = kwargs[cleaned[0]]
                path.append(value)
            # Check if the cleaned value is in PipeContext
            elif cleaned[0] in self.pipe_context.__dict__.keys():
                value = self.pipe_context.__dict__[cleaned[0]]
                path.append(value)
        # IO.debug("Returning Path")
        return os.path.normpath((os.path.sep).join(path))


    def examine_path(self, path, pipe_base_dir=None, var=None):
        """

        :param path: Current context path we want to examine
        :param pipe_base_dir: Pipeline Base Directory Path
        :param var: Variable we want to examine in this path
        :return:
        """

        return ""
#!/usr/bin/env python #SETMODE 777 #----------------------------------------------------------------------------------------# #------------------------------------------------------------------------------ HEADER --# :author:     <NAME>     jmw150530  :synopsis:     Creates the Pipeline Context.  :description:     This module creates the pipeline context using the formula manager to import     formulas from a formulas.xml file. The asset_manager context is set by passing these     formulas to the PathContext class which validates the path on disk and returns     a completed path. The PipeContext class then has it's variables created with kwargs     based upon the project formulas and the disk path.  :applications:     Any applications that are required to run this runner, i.e. Maya.  :see_also:     Any other code that you have written that this module is similar to. #----------------------------------------------------------------------------------------# #----------------------------------------------------------------------------- IMPORTS --# # Built-in #----------------------------------------------------------------------------------------# #--------------------------------------------------------------------------- FUNCTIONS --# #----------------------------------------------------------------------------------------# #----------------------------------------------------------------------------- CLASSES --# this stores pipeline values Upon entering a fresh PipeContext Manager assign the current context to the platform specific base drive         :return: Set current context to the cached context         :param exc_type:         :param exc_value:         :param traceback:         :return: Evaluate a formula and return a path         :param formula:         :param args: positional arguments, e.g. parent_formula         :param kwargs: keyword_arguments to pass to PipeContext         :return: path # Evaluate Multiple Paths # Evaluate Single Path Evaluate a formula and return a path object         :param formula: A string representing the formula to evaluate         :param kwargs: Variables to pass to PipeContext         :return: path # Initialize PipeContext Instance in PathContext Generate a relative path Sets the current context         Evaluates current context agains # Get the current set context # Check against the new context before appending the current one. # Append the current context to the old context before switching NOTE: NOT YET IMPLEMENTED         Examine a path on disk and derive the current context         :param path: Current Path to evaluate         :param pipe_base_dir: Pipeline Base Directory         :param var: Specific formula/path         :return: path_item or path_dict # Return a Single Path # Return the entire Path Context this class resolves a real path on disk Return or Yield a formula path         :param formula: formula to evaluate         :param args[0]: parent_formula         :param kwargs: keyword_arguments         :return: # Load Formula manager Return a single formula using the passed in formula pieces Return a normalized path by pulling values of formula pieces out of a dict         :param path: list or set         :param pieces: formula pieces         :param y: yield         :param kwargs: keyword arguments         :return: # Iterate over each piece # No Bracketed Values to clean, put in path # Regex to find all bracketed values and clean them # Check if the cleaned value is in kwargs # Check if the cleaned value is in PipeContext # IO.debug("Returning Path") :param path: Current context path we want to examine         :param pipe_base_dir: Pipeline Base Directory Path         :param var: Variable we want to examine in this path         :return: