"""
    Module detecting state uninitialized variables
    Recursively check the called functions

    The heuristic checks:
    - state variables including mappings/refs
    - LibraryCalls, InternalCalls, InternalDynamicCalls with storage variables

    Only analyze "leaf" contracts (contracts that are not inherited by another contract)
"""

from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import InternalCall, LibraryCall


class UninitializedStateVarsDetection(AbstractDetector):
    """
    Constant function detector
    """

    ARGUMENT = 'uninitialized-state'
    HELP = 'Uninitialized state variables'
    IMPACT = DetectorClassification.HIGH
    CONFIDENCE = DetectorClassification.HIGH

    WIKI = 'https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables'

    WIKI_TITLE = 'Uninitialized state variables'
    WIKI_DESCRIPTION = 'Uninitialized state variables.'
    WIKI_EXPLOIT_SCENARIO = '''
```solidity
contract Uninitialized{
    address destination;

    function transfer() payable public{
        destination.transfer(msg.value);
    }
}
```
Bob calls `transfer`. As a result, the ethers are sent to the address 0x0 and are lost.
'''
    WIKI_RECOMMENDATION = '''
Initialize all the variables. If a variable is meant to be initialized to zero, explicitly set it to zero.
'''

    @staticmethod
    def _written_variables(contract):
        ret = []
        for f in contract.all_functions_called + contract.modifiers:
            for n in f.nodes:
                ret += n.state_variables_written
                for ir in n.irs:
                    if isinstance(ir, LibraryCall) \
                            or isinstance(ir, InternalCall):
                        idx = 0
                        if ir.function:
                            for param in ir.function.parameters:
                                if param.location == 'storage':
                                    ret.append(ir.arguments[idx])
                                idx = idx + 1

        return ret

    def _variable_written_in_proxy(self):
        # Hack to memoize without having it define in the init
        if hasattr(self, '__variables_written_in_proxy'):
            return self.__variables_written_in_proxy

        variables_written_in_proxy = []
        for c in self.slither.contracts:
            if c.is_upgradeable_proxy:
                variables_written_in_proxy += self._written_variables(c)

        self.__variables_written_in_proxy = list(set([v.name for v in variables_written_in_proxy]))
        return self.__variables_written_in_proxy

    def _written_variables_in_proxy(self, contract):
        variables = []
        if contract.is_upgradeable:
            variables_name_written_in_proxy = self._variable_written_in_proxy()
            if variables_name_written_in_proxy:
                variables_in_contract = [contract.get_state_variable_from_name(v) for v in variables_name_written_in_proxy]
                variables_in_contract = [v for v in variables_in_contract if v]
                variables += variables_in_contract
        return list(set(variables))

    @staticmethod
    def _read_variables(contract):
        ret = []
        for f in contract.all_functions_called + contract.modifiers:
            ret += f.state_variables_read
        return ret

    def _detect_uninitialized(self, contract):
        written_variables = self._written_variables(contract)
        written_variables += self._written_variables_in_proxy(contract)
        read_variables = self._read_variables(contract)
        return [(variable, contract.get_functions_reading_from_variable(variable))
                for variable in contract.state_variables if variable not in written_variables and \
                not variable.expression and \
                variable in read_variables]

    def _detect(self):
        """ Detect uninitialized state variables

        Recursively visit the calls
        Returns:
            dict: [contract name] = set(state variable uninitialized)
        """
        results = []
        for c in self.slither.contracts_derived:
            ret = self._detect_uninitialized(c)
            for variable, functions in ret:

                info = [variable, " is never initialized. It is used in:\n"]

                for f in functions:
                    info += ["\t- ", f, "\n"]

                json = self.generate_result(info)
                results.append(json)

        return results
