#     Copyright 2014, Kay Hayen, mailto:kay.hayen@gmail.com
#
#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
#
""" Variables link the storage and use of a Python variable together.

Different kinds of variables represent different scopes and owners types,
and their links between each other, i.e. references as in closure or
module variable references.

"""

from nuitka import Utils

class Variable:
    def __init__(self, owner, variable_name):
        assert type( variable_name ) is str, variable_name
        assert type( owner ) not in ( tuple, list ), owner

        self.variable_name = variable_name
        self.owner = owner

        self.references = []

        self.read_only_indicator = None
        self.has_del = False

        self.version_number = 0

    def getName(self):
        return self.variable_name

    def getOwner(self):
        return self.owner

    # TODO: Seems obsolete now
    def getRealOwner(self):
        return self.owner

    def addReference(self, reference):
        self.references.append( reference )

    def getReferences(self):
        return self.references

    def getReferenced(self):
        # Abstract method, pylint: disable=R0201,W0613
        return None

    def getReadOnlyIndicator(self):
        return self.read_only_indicator

    def setReadOnlyIndicator(self, value):
        assert value in ( True, False )

        self.read_only_indicator = value

    def getHasDelIndicator(self):
        return self.has_del

    def setHasDelIndicator(self):
        self.has_del = True

    def allocateTargetNumber(self):
        self.version_number += 1

        return self.version_number

    # pylint: disable=R0201
    def isLocalVariable(self):
        return False

    def isClassVariable(self):
        return False

    def isMaybeLocalVariable(self):
        return False

    def isParameterVariable(self):
        return False

    def isNestedParameterVariable(self):
        return False

    def isVariableReference(self):
        return False

    def isClosureReference(self):
        return False

    def isModuleVariableReference(self):
        return False

    def isReference(self):
        return False

    def isModuleVariable(self):
        return False

    def isTempVariableReference(self):
        return False

    def isTempVariable(self):
        return False

    def isTempKeeperVariable(self):
        return False

    # pylint: enable=R0201

    def _checkShared(self, variable, technical):
        for reference in variable.references:
            # print( "Checking", reference, "of", variable )

            if self._checkShared( reference, technical ):
                return True

            top_owner = reference.getReferenced().getOwner()
            owner = reference.getOwner()

            # The generators and functions that are not created, get things
            # passed, and do not need the variable to share.
            while technical and \
                  owner != top_owner and \
                  owner.isExpressionFunctionBody() and \
                  not owner.isGenerator() and not owner.needsCreation():
                owner = owner.getParentVariableProvider()

            # List contractions in Python2 do not really own their variables.
            # TODO: They ought to not be variable providers/takers at all.
            # TODO: This code seems unnecessary now due to "needsCreation" not
            # being true.
            if Utils.python_version < 300:
                while owner != top_owner and owner.code_prefix == "listcontr":
                    owner = owner.getParentVariableProvider()

            # This defines being shared. Owned by one, and references that are
            # owned by another node.
            if owner != top_owner:
                return True
        else:
            return False


    def isShared(self, technical = False):
        variable = self

        while variable.isClosureReference():
            variable = variable.getReferenced()

        return self._checkShared( variable, technical )

    reference_class = None

    def makeReference(self, owner):
        # Need to provider a reference class, or else making references cannot
        # work.
        assert self.reference_class, self

        # Search for existing references to be re-used before making a new one.
        for reference in self.references:
            if reference.getOwner() is owner:
                return reference
        else:
            # The reference_class will be overloaded with something callable,
            # pylint: disable=E1102
            return self.reference_class(
                owner    = owner,
                variable = self
            )

    def getDeclarationCode(self):
        return self.getDeclarationTypeCode( in_context = False ) + \
               " &" + self.getCodeName()

    def getMangledName(self):
        """ Get the mangled name of the variable.

            By default no mangling is applied.
        """

        return self.getName()

    def getDeclarationTypeCode(self, in_context):
        # Abstract method, pylint: disable=R0201,W0613
        assert False

    def getCodeName(self):
        # Abstract method, pylint: disable=R0201
        assert False, self


class VariableReferenceBase(Variable):
    def __init__(self, owner, variable):
        Variable.__init__(
            self,
            owner         = owner,
            variable_name = variable.getName()
        )

        if self.reference_class is None:
            self.reference_class = variable.reference_class

        variable.addReference( self )
        self.variable = variable

        del self.read_only_indicator

    def getReadOnlyIndicator(self):
        return self.getReferenced().read_only_indicator

    def __repr__(self):
        return "<%s to %s>" % (
            self.__class__.__name__,
            str( self.variable )[1:-1]
        )

    def isVariableReference(self):
        return True

    def isReference(self):
        return True

    def getReferenced(self):
        return self.variable

    def __cmp__(self, other):
        # Compare the referenced variable, so de-reference until it's no more
        # possible.

        while other.getReferenced() is not None:
            other = other.getReferenced()

        this = self

        while this.getReferenced() is not None:
            this = this.getReferenced()

        return cmp( this, other )

    def __hash__(self):
        return hash( self.getReferenced() )


class ClosureVariableReference(VariableReferenceBase):
    def __init__(self, owner, variable):
        assert not variable.isModuleVariable()

        VariableReferenceBase.__init__(
            self,
            owner    = owner,
            variable = variable
        )

    def isClosureReference(self):
        return True

    def getProviderVariable(self):
        current = self.getOwner().getParentVariableProvider()

        if current is self.getReferenced().getOwner():
            return self.getReferenced()
        else:
            for variable in current.getClosureVariables():
                if variable.getName() == self.getName():
                    return variable
            else:
                assert False, self

    def getDeclarationTypeCode(self, in_context):
        if self.getReferenced().isShared( True ):
            if in_context:
                return "PyObjectClosureVariable"
            else:
                return "PyObjectSharedLocalVariable"
        else:
            return self.getReferenced().getDeclarationTypeCode(
                in_context = in_context
            )

    def getCodeName(self):
        return "closure_%s" % Utils.encodeNonAscii( self.getName() )


class ModuleVariableReference(VariableReferenceBase):
    def __init__(self, owner, variable):

        # Module variable access are direct pass-through, so de-reference them
        # if possible.
        while variable.isModuleVariableReference():
            variable = variable.getReferenced()

        assert variable.isModuleVariable()

        VariableReferenceBase.__init__(
            self,
            owner    = owner,
            variable = variable
        )

        self.global_statement = False
        self.exec_statement = False

    def __repr__(self):
        return "<ModuleVariableReference '%s' of '%s'%s%s>" % (
            self.variable_name,
            self.getReferenced().getModuleName(),
            " from global statement" if self.global_statement else "",
            " from exec statement" if self.exec_statement else "",
        )

    def markFromGlobalStatement(self):
        self.global_statement = True

    def isFromGlobalStatement(self):
        return self.global_statement

    def markFromExecStatement(self):
        self.exec_statement = True

    def isFromExecStatement(self):
        return self.exec_statement

    def isModuleVariableReference(self):
        return True

    def isModuleVariable(self):
        return True


class LocalVariable(Variable):
    reference_class = ClosureVariableReference

    def __init__(self, owner, variable_name):
        Variable.__init__(
            self,
            owner         = owner,
            variable_name = variable_name
        )

        assert not owner.isExpressionFunctionBody() or \
               owner.local_locals or \
               self.__class__ is not LocalVariable

    def __repr__(self):
        return "<%s '%s' of '%s'>" % (
            self.__class__.__name__,
            self.variable_name,
            self.owner.getName()
        )

    def isLocalVariable(self):
        return True

    def getCodeName(self):
        return "var_" + Utils.encodeNonAscii( self.getName() )

    def getDeclarationTypeCode(self, in_context):
        if self.isShared( True ):
            return "PyObjectSharedLocalVariable"
        else:
            return "PyObjectLocalVariable"

    def getMangledName(self):
        if not self.variable_name.startswith( "__" ) or \
           self.variable_name.endswith( "__" ):
            return self.variable_name
        else:
            # The mangling of function variable names depends on being inside a
            # class. TODO: ClassVariable seems unnecessary now.
            class_container = self.owner.getContainingClassDictCreation()

            if class_container is None:
                return self.variable_name
            else:
                return "_%s%s" % (
                    class_container.getName().lstrip("_"),
                    self.variable_name
                )


class ClassVariable(LocalVariable):

    def getMangledName(self):
        """ Get the mangled name of the variable.

            In classes, names like "__name__" are not mangled, only "__name"
            would be.
        """
        if not self.variable_name.startswith( "__" ) or \
           self.variable_name.endswith( "__" ):
            return self.variable_name
        else:
            return "_%s%s" % (
                self.getOwner().getName().lstrip("_"),
                self.variable_name
            )


class MaybeLocalVariable(Variable):
    reference_class = ClosureVariableReference

    def __init__(self, owner, variable_name):
        Variable.__init__(
            self,
            owner         = owner,
            variable_name = variable_name
        )

    def __repr__(self):
        return "<%s '%s' of '%s' maybe a global reference>" % (
            self.__class__.__name__,
            self.variable_name,
            self.owner.getName()
        )

    def isMaybeLocalVariable(self):
        return True


class ParameterVariable(LocalVariable):
    def __init__(self, owner, parameter_name, kw_only):
        LocalVariable.__init__(
            self,
            owner         = owner,
            variable_name = parameter_name
        )

        self.kw_only = kw_only

    def isParameterVariable(self):
        return True

    def isParameterVariableKwOnly(self):
        return self.kw_only

    def getCodeName(self):
        return "par_" + Utils.encodeNonAscii( self.getName() )

    def getDeclarationTypeCode(self, in_context):
        if self.isShared( True ):
            return "PyObjectSharedLocalVariable"
        elif self.getHasDelIndicator():
            return "PyObjectLocalParameterVariableWithDel"
        else:
            return "PyObjectLocalParameterVariableNoDel"


class NestedParameterVariable(ParameterVariable):
    def __init__(self, owner, parameter_name, parameter_spec):
        ParameterVariable.__init__(
            self,
            owner          = owner,
            parameter_name = parameter_name,
            kw_only        = False
        )

        self.parameter_spec = parameter_spec

    def isNestedParameterVariable(self):
        return True

    def getVariables(self):
        return self.parameter_spec.getVariables()

    def getAllVariables(self):
        return self.parameter_spec.getAllVariables()

    def getTopLevelVariables(self):
        return self.parameter_spec.getTopLevelVariables()

    def getParameterNames(self):
        return self.parameter_spec.getParameterNames()


class ModuleVariable(Variable):
    reference_class = ModuleVariableReference

    def __init__(self, module, variable_name):
        assert type( variable_name ) is str, repr( variable_name )

        Variable.__init__(
            self,
            owner         = module,
            variable_name = variable_name
        )

        self.module = module

    def __repr__(self):
        return "<ModuleVariable '%s' of '%s'>" % (
            self.variable_name,
            self.getModuleName()
        )

    def isModuleVariable(self):
        return True

    def getModule(self):
        return self.module

    def getModuleName(self):
        return self.module.getFullName()

    def _checkShared(self, variable, technical):
        assert False, variable


class TempVariableClosureReference(VariableReferenceBase):
    reference_class = None

    def isClosureReference(self):
        # Virtual method, pylint: disable=R0201
        return True

    def isTempVariableReference(self):
        # Virtual method, pylint: disable=R0201
        return True

    def getDeclarationTypeCode(self, in_context):
        return self.getReferenced().getReferenced().getDeclarationTypeCode(
            in_context = in_context
        )


    def getCodeName(self):
        # Abstract method, pylint: disable=R0201
        return self.getReferenced().getReferenced().getCodeName()

    def getProviderVariable(self):
        return self.getReferenced()


class TempVariableReference(VariableReferenceBase):
    reference_class = TempVariableClosureReference

    def isTempVariableReference(self):
        # Virtual method, pylint: disable=R0201
        return True

    def makeReference(self, owner):
        # Search for existing references to be re-used before making a new one.
        for reference in self.references:
            if reference.getOwner() is owner:
                return reference
        else:
            if owner is self.owner:
                return TempVariableReference(
                    owner    = owner,
                    variable = self
                )
            else:
                return TempVariableClosureReference(
                    owner    = owner,
                    variable = self
                )


class TempVariable(Variable):
    reference_class = TempVariableReference

    def __init__(self, owner, variable_name):
        Variable.__init__(
            self,
            owner         = owner,
            variable_name = variable_name
        )

        # For code generation.
        self.late_declaration = False
        self.late_declared = False

        self.needs_free = None

        self.delete_statement = None

    def __repr__(self):
        return "<TempVariable '%s' of '%s'>" % (
            self.getName(),
            self.getOwner()
        )

    def isTempVariable(self):
        # Virtual method, pylint: disable=R0201
        return True

    def getNeedsFree(self):
        return self.needs_free

    def setNeedsFree(self, needs_free):
        self.needs_free = needs_free

    def getDeclarationTypeCode(self, in_context):
        assert self.needs_free is not None, self

        if self.isShared( True ):
            return "PyObjectSharedTempVariable"
        elif self.needs_free:
            if self.late_declaration:
                if self.getHasDelIndicator():
                    return "PyObjectTemporaryWithDel"
                else:
                    return "PyObjectTemporary"
            else:
                return "PyObjectTempVariable"
        else:
            return "PyObject *"

    def getCodeName(self):
        return "tmp_%s" % self.getName()

    def getDeclarationInitValueCode(self):
        # Virtual method, pylint: disable=R0201
        return "NULL"

    def markAsNeedsLateDeclaration(self):
        self.late_declaration = True

    def needsLateDeclaration(self):
        return self.late_declaration

    def markAsDeclared(self):
        self.late_declared = True

    def markAsDeleteScope(self, delete_statement):
        self.delete_statement = delete_statement

    def getDeleteScope(self):
        return self.delete_statement

    def isDeclared(self):
        return self.late_declared


class TempKeeperVariable(TempVariable):
    def __init__(self, owner, variable_name):
        TempVariable.__init__(
            self,
            owner         = owner,
            variable_name = variable_name
        )

        self.write_only = False

    def __repr__(self):
        return "<TempKeeperVariable '%s' of '%s'>" % (
            self.getName(),
            self.getOwner()
        )

    def isTempKeeperVariable(self):
        return True

    def isWriteOnly(self):
        return self.write_only

    def setWriteOnly(self):
        self.write_only = True


def getNames(variables):
    return [ variable.getName() for variable in variables ]
