# Copyright (C) 2020 Zeropoint Dynamics

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
# ======================================================================


class RegsApi:
    """
    Allows accessing registers directly by their name.

    Methods also exist for accessing registers that hold the
    instruction, stack, and frame pointers in a platform agnostic way,
    as well as functions for manipulating the stack.

    .. code-block:: python

        from zelos import Zelos, HookType

        # 32 bit x86 binary
        z = Zelos("binary_to_emulate")

        # Increment the starting address by 2
        z.regs.eip = z.regs.eip + 2

        # A platform agnostic way of adjusting the Instruction Pointer
        z.regs.setIP(z.regs.getIP() + 2)

    """

    def __init__(self, zelos):
        # Called on super() object to avoid triggering the __setattr__
        # on the RegsApi class.
        super().__setattr__("_zelos", zelos)

    @property
    def _current_thread(self):
        return self._zelos.internal_engine.current_thread

    def __getattr__(self, attr):
        return self._current_thread.get_reg(attr)

    def __setattr__(self, attr, value):
        self._current_thread.set_reg(attr, value)

    def getIP(self) -> int:
        return self._current_thread.getIP()

    def setIP(self, new_ip: int) -> None:
        self._current_thread.setIP(new_ip)

    def getSP(self) -> int:
        return self._current_thread.getSP()

    def setSP(self, new_sp: int) -> None:
        self._current_thread.setSP(new_sp)

    def getFP(self) -> int:
        return self._current_thread.getFP()

    def setFP(self, new_fp: int) -> None:
        return self._current_thread.setFP(new_fp)

    def getstack(self, offset: int) -> int:
        """
        Returns data that is `offset * word_size` bytes from the top of
        the stack.
        """
        return self._current_thread.getstack(offset)

    def setstack(self, offset: int, val: int) -> None:
        """
        Sets data that is `offset * word_size` bytes from the top of
        the stack.
        """
        self._current_thread.setstack(offset, val)

    def popstack(self) -> int:
        return self._current_thread.popstack()

    def pushstack(self, data: int) -> None:
        return self._current_thread.pushstack(data)
