from typing import *
from dataclasses import *
from sccd.util.bitmap import *
from sccd.util.debug import *
from sccd.action_lang.static.scope import *
from sccd.common.exceptions import *
from sccd.action_lang.static.expression import *

@dataclass(frozen=True)
class StackFrame:
  __slots__ = ["storage", "parent", "context", "scope"]

  # Values of variables in the frame.
  storage: List[Any]

  # The previous stack frame, forming a linked list of stack frames representing the "call stack".
  # The parent frame will become the "current frame" when this stack frame is popped.
  parent: Optional['StackFrame']

  # The sequence of 'context' values forms another linked list, often but not always identical to the linked list of 'parent's, for accessing nonlocal variables.
  # The 'context' is the stack frame in which the frame of the currently called function was declared.
  # This could be a stack frame that no longer exists on "the stack", like with a function closure, or some ancestor, for a recursive function.
  # If the current scope is not a function, this value is equal to the 'parent' field.
  context: Optional['StackFrame']

  # We need this to know where the offsets of this scope start relative to the offsets of the parent scope, and to print variable names in error messages.
  scope: Scope

  def __str__(self):
    def short_descr(frame):
      return "StackFrame(%s, len=%d, ...)" % (frame.scope.name, len(frame.storage)) if frame else "None"

    return "StackFrame(%s, len=%d, parent=%s, context=%s)" % (self.scope.name, len(self.storage), short_descr(self.parent), "parent" if self.context is self.parent else short_descr(self.context))

class Memory(MemoryInterface):
  __slots__ = ["_current_frame"]

  def __init__(self):
    self._current_frame = None

  def current_frame(self) -> StackFrame:
    return self._current_frame

  # For function calls: context MAY differ from _current_frame if the function was
  # called from a different context from where it was created.
  # This enables function closures.
  def push_frame_w_context(self, scope: Scope, context: StackFrame):
    self._current_frame = StackFrame(
      storage=[None]*scope.size(),
      parent=self._current_frame,
      context=context,
      scope=scope)

  def push_frame(self, scope: Scope):
    self.push_frame_w_context(scope, self._current_frame)

  def pop_frame(self):
    self._current_frame = self._current_frame.parent

  def _get_frame(self, offset: int) -> Tuple[StackFrame, int]:
    frame = self._current_frame
    while offset < 0:
      offset += frame.scope.parent_offset
      frame = frame.context

    return (frame, offset)

  def load(self, offset: int) -> Any:
    frame, offset = self._get_frame(offset)
    return frame.storage[offset]

  def store(self, offset: int, value: Any):
    frame, offset = self._get_frame(offset)
    frame.storage[offset] = value
