import sys


class gds_static_item(object):
    """ gds_static_item: base class for objects that don't do internal calculations """
    def is_call_internal(self, level= 1):
        """ checks is a call to a function is done from within the object or from outside """
        f = sys._getframe(1 + level).f_locals
        if not "self" in f: return False
        return (f["self"] is self)

    def is_calculated(self):
        """ returns True if the object is calculated """
        return True

    def calc(self, Force = False):
        """ calculates the object """
        pass        
        #self.__calc__() #should be overwritten
    
    def clear(self):
        """ clears content from the object """
        pass

# this is an item which can be calculated
# when a parameter is changed from the outside, the object needs to be recalculated
class gds_dynamic_item(gds_static_item):
    """ gds_dynamic_item: base class for objects that do internal calculations 
        this class takes care of checking whether calculations need to be executed
        based on changes in object properties """
    __static_item_class__ = gds_static_item
    def __init__(self, calculated = False):
        self.__reset_calc__(False)
        self.__internal_calc__ = False

        
    def __make_static__(self, static_class = None):
        """ makes the dynamic object static """
        if static_class == None: static_class = self.__static_item_class__
        self.calc() # calculate coordinates
        # upcast: static_class must be parent class of dynamic class
        object.__setattr__(self, "__class__", static_class)

    def __reset_calc__ (self, value = False):
        """ resets the calculation flag """
        object.__setattr__(self, "__calculated__",  value)

    def __set_internal_calc__ (self, value = False):
        """ resets the calculation flag """
        object.__setattr__(self, "__internal_calc__",  value)

    def is_calc_internal(self, level= 1):
        """ checks is a call to a function is done from within the object or from outside """
        if not hasattr(self, "__internal_calc__"): self.__set_internal_calc__(False)
        return self.__internal_calc__

    def is_calculated(self):
        """ returns True is the object is already calculated """
        if not hasattr(self, "__calculated__"): self.__reset_calc__(False)
        return self.__calculated__
    
    # override set_attribute
    def __setattr__(self, name, value):
        if not self.is_call_internal():
            if hasattr(self, name):
                if value == object.__getattribute__(self,name): return
            self.__reset_calc__()
        object.__setattr__(self, name, value)

    def calc(self, Force = False):
        """ calculates the object, when necessary (or when Force is True) """
        if Force or not self.is_calculated(): 
            self.__set_internal_calc__(True)
            self.clear()
            self.__calc__() 
        self.__reset_calc__(True)
        self.__set_internal_calc__(False)

        