import operator
import logging
import itertools
import contextlib

import claripy

from .plugin import SimStatePlugin
from .. import sim_options
from ..state_plugins.sim_action import SimActionObject

l = logging.getLogger(name=__name__)


class SimStateHistory(SimStatePlugin):
    """
    This class keeps track of historically-relevant information for paths.
    """

    STRONGREF_STATE = True

    def __init__(self, parent=None, clone=None):
        SimStatePlugin.__init__(self)

        # attributes handling the progeny of this history object
        self.parent = parent if clone is None else clone.parent
        self.merged_from = [ ] if clone is None else list(clone.merged_from)
        self.merge_conditions = [ ] if clone is None else list(clone.merge_conditions)
        self.depth = (0 if parent is None else parent.depth + 1) if clone is None else clone.depth
        self.previous_block_count = (0 if parent is None else parent.block_count) if clone is None else \
            clone.previous_block_count

        # a string description of this history
        self.recent_description = None if clone is None else clone.recent_description

        # the control flow transfer information from this history onwards (to the current state)
        self.jump_target = None if clone is None else clone.jump_target
        self.jump_source = None if clone is None else clone.jump_source
        self.jump_avoidable = None if clone is None else clone.jump_avoidable
        self.jump_guard = None if clone is None else clone.jump_guard
        self.jumpkind = None if clone is None else clone.jumpkind

        # the execution log for this history
        self.recent_events = [ ] if clone is None else list(clone.recent_events)
        self.recent_bbl_addrs = [ ] if clone is None else list(clone.recent_bbl_addrs)
        self.recent_ins_addrs = [ ] if clone is None else list(clone.recent_ins_addrs)
        self.recent_stack_actions = [ ] if clone is None else list(clone.recent_stack_actions)
        self.last_stmt_idx = None if clone is None else clone.last_stmt_idx

        # numbers of blocks, syscalls, and instructions that were executed in this step
        self.recent_block_count = 0 if clone is None else clone.recent_block_count
        self.recent_syscall_count = 0 if clone is None else clone.recent_syscall_count
        self.recent_instruction_count = -1 if clone is None else clone.recent_instruction_count

        # satness stuff
        self._all_constraints = ()
        self._satisfiable = None

        self.successor_ip = None if clone is None else clone.successor_ip

        self.strongref_state = None if clone is None else clone.strongref_state

    def init_state(self):
        self.successor_ip = self.state._ip

    def __getstate__(self):
        # flatten ancestry, otherwise we hit recursion errors trying to get the entire history...
        # the important intuition here is that if we provide the entire linked list to pickle, pickle
        # will traverse it recursively. If we provide it as a real list, it will not do any recursion.
        # the nuance is in whether the list we provide has live parent links, in which case it matters
        # what order pickle iterates the list, as it will suddenly be able to perform memoization.
        ancestry = []
        parent = self.parent
        self.parent = None
        while parent is not None:
            ancestry.append(parent)
            parent = parent.parent
            ancestry[-1].parent = None

        rev_ancestry = list(reversed(ancestry))
        d = super(SimStateHistory, self).__getstate__()
        d['strongref_state'] = None
        d['rev_ancestry'] = rev_ancestry
        d['successor_ip'] = self.successor_ip

        # reconstruct chain
        child = self
        for parent in ancestry:
            child.parent = parent
            child = parent

        d.pop('parent')
        return d

    def __setstate__(self, d):
        child = self
        ancestry = list(reversed(d.pop('rev_ancestry')))
        for parent in ancestry:
            if hasattr(child, 'parent'):
                break
            child.parent = parent
            child = parent
        else:
            child.parent = None
        self.__dict__.update(d)

    def __repr__(self):
        addr = self.addr
        if addr is None:
            addr_str = "Unknown"
        else:
            addr_str = "%#x" % addr

        return "<StateHistory @ %s>" % addr_str

    def set_strongref_state(self, state):
        if sim_options.EFFICIENT_STATE_MERGING in state.options:
            self.strongref_state = state

    @property
    def addr(self):
        if not self.recent_bbl_addrs:
            return None
        return self.recent_bbl_addrs[-1]

    def merge(self, others, merge_conditions, common_ancestor=None):

        if not others:
            return False

        self.merged_from.extend(h for h in others)
        self.merge_conditions = merge_conditions

        # we must fix this in order to get
        # correct results when using constraints_since()
        self.parent = common_ancestor if common_ancestor is not None else self.parent