# MIT License
#
# Copyright (c) 2023 Neil Webber
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

#
# TOP LEVEL OP CODE DISPATCH AND INSTRUCTION IMPLEMENTATION
#
# NOTES:
#
#  DISPATCH
#     2-operand instructions are implemented here and are dispatched
#     off the top-4 bits of the instruction (hence "op4" name).
#
#     The other instructions are encoded into the 0o00, 0o07, and 0o10
#     portions of this top-4 bit address space. They are dispatched
#     via d3dispatch and respective tables from other modules.
#
#  BYTE vs WORD operations:
#     Most of them come in two flavors - word and byte, with the
#     top-bit distinguishing. This is communicated to the implementation
#     functions via "opsize=1" or "opsize=2" when a single function can
#     implement both variations. Note that MOV/MOVB are specifically
#     optimized, separately, for performance.
#
#  PSW updates:
#     All instructions must be careful to do their final result writes
#     AFTER setting the PSW, because the PSW is addressible via memory
#     (a write to unibus 777776) and such a write is supposed to override
#     the otherwise-native instruction CC results.
#

# dispatchers to next level for 00, 07, and 10 instructions:
from op00 import op00_dispatch_table
from op07 import op07_dispatch_table
from op10 import op10_dispatch_table
from pdptraps import PDPTraps


def d3dispatcher(d3table, cpu, inst):
    try:
        d3table[(inst & 0o7000) >> 9](cpu, inst)
    except TypeError:              # means a None was in d3table
        raise PDPTraps.ReservedInstruction from None


# This is ALWAYS a 16-bit MOV
def op01_mov(cpu, inst):
    """MOV src,dst -- always 16 bits"""

    # avoid call to the more-general operandx for mode 0, direct register.
    # This optimization is a substantial speed up for register MOVs.
    srcb6 = (inst & 0o7700) >> 6
    try:
        val = cpu.r[srcb6]     # only works if srcb6 < 8; register direct
    except IndexError:
        val = cpu.operandx(srcb6)

    cpu.psw_v = 0              # per manual; V is cleared
    cpu.psw_z = (val == 0)
    cpu.psw_n = (val > 32767)

    # same optimization on the write side.
    dstb6 = (inst & 0o77)
    if dstb6 < 8:
        cpu.r[dstb6] = val
    else:
        cpu.operandx(dstb6, val)


# This is ALWAYS an 8-bit MOVB
def op11_movb(cpu, inst):
    """MOVB src,dst -- always 8 bits"""

    # avoid call to the more-general operandx for mode 0, direct register.
    # This optimization is a substantial speed up for register MOVs.
    srcb6 = (inst & 0o7700) >> 6
    if srcb6 < 8:
        val = cpu.r[srcb6] & 0o377
    else:
        val = cpu.operandx(srcb6, opsize=1)

    cpu.psw_v = 0
    cpu.psw_z = (val == 0)
    cpu.psw_n = (val & 0o200)

    # avoid call to the more-general operandx for mode 0, direct register
    # not only as an optimization, but because unlike other byte operations
    # in register-direct mode, MOVB does a sign-extend.
    dstb6 = inst & 0o0077
    if (dstb6 < 8):             # i.e., mode 0
        if val > 127:
            val |= 0o177400
        cpu.r[dstb6] = val
    else:
        cpu.operandx(dstb6, val, opsize=1)


def op02_cmp(cpu, inst, opsize=2):
    """CMP(B) src,dst"""
    srcb6 = (inst & 0o7700) >> 6
    dstb6 = (inst & 0o0077)

    # this is a bold "just go for it" strategy to optimize reg-reg compares
    try:
        src = cpu.r[srcb6]
        dst = cpu.r[dstb6]
    except IndexError:
        if srcb6 > 7:
            src = cpu.operandx(srcb6, opsize=opsize)
        dst = cpu.operandx(dstb6, opsize=opsize)
    else:
        if opsize == 1:
            src &= 0o377
            dst &= 0o377

    # note: this is other order than SUB
    t = (src - dst) & cpu.MASK816[opsize]
    cpu.psw_c = (src < dst)
    signbit = cpu.SIGN816[opsize]
    cpu.psw_n = (t & signbit)
    cpu.psw_z = (t == 0)

    # definition of V is: operands were of opposite signs and the sign
    # of the destination was the same as the sign of the result
    src_sign = src & signbit
    dst_sign = dst & signbit
    t_sign = t & signbit
    cpu.psw_v = (dst_sign == t_sign) and (src_sign != dst_sign)


def op03_bit(cpu, inst, opsize=2):
    """BIT(B) src,dst"""
    src = cpu.operandx((inst & 0o7700) >> 6, opsize=opsize)
    dst = cpu.operandx(inst & 0o0077, opsize=opsize)
    t = dst & src

    cpu.psw_n = t & cpu.SIGN816[opsize]
    cpu.psw_z = (t == 0)
    cpu.psw_v = 0
    # cpu.logger.debug(f"BIT: {src=}, {dst=}, PSW={oct(cpu.psw)}")


def op04_bic(cpu, inst, opsize=2):
    """BIC(B) src,dst"""
    src = cpu.operandx((inst & 0o7700) >> 6, opsize=opsize)
    dst, xb6 = cpu.operandx(inst & 0o0077, opsize=opsize, rmw=True)
    dst &= ~src

    cpu.psw_n = dst & cpu.SIGN816[opsize]
    cpu.psw_z = (dst == 0)
    cpu.psw_v = 0

    cpu.operandx(xb6, dst, opsize=opsize)


def op05_bis(cpu, inst, opsize=2):
    """BIS(B) src,dst"""
    src = cpu.operandx((inst & 0o7700) >> 6, opsize=opsize)
    dst, xb6 = cpu.operandx(inst & 0o0077, opsize=opsize, rmw=True)
    dst |= src

    cpu.psw_n = dst & cpu.SIGN816[opsize]
    cpu.psw_z = (dst == 0)
    cpu.psw_v = 0

    cpu.operandx(xb6, dst, opsize=opsize)


def op06_add(cpu, inst):
    """ADD src,dst"""
    # avoid call to the more-general operandx for mode 0, direct register.
    srcb6 = (inst & 0o7700) >> 6
    if srcb6 < 8:
        src = cpu.r[srcb6]
    else:
        src = cpu.operandx(srcb6)

    dstb6 = inst & 0o0077
    if dstb6 < 8:
        dst = cpu.r[dstb6]
        xb6 = dstb6
    else:
        dst, xb6 = cpu.operandx(dstb6, rmw=True)
    t = src + dst

    cpu.psw_c = (t > cpu.MASK16)
    if cpu.psw_c:
        t &= cpu.MASK16

    cpu.psw_n = (t & cpu.SIGN16)
    cpu.psw_z = (t == 0)

    # definition of V is: operands were of the same signs and the
    # sign of the result is different.
    src_sign = src & cpu.SIGN16
    dst_sign = dst & cpu.SIGN16
    t_sign = t & cpu.SIGN16
    cpu.psw_v = (dst_sign != t_sign) and (src_sign == dst_sign)

    if dstb6 < 8:
        cpu.r[dstb6] = t
    else:
        cpu.operandx(xb6, t)


def op16_sub(cpu, inst):
    """SUB src,dst"""
    src = cpu.operandx((inst & 0o7700) >> 6)
    dst, xb6 = cpu.operandx(inst & 0o0077, rmw=True)

    t = (dst - src) & cpu.MASK16     # note: this is opposite of CMP
    cpu.psw_n = (t & cpu.SIGN16)
    cpu.psw_z = (t == 0)

    # definition of V is: operands were of opposite signs and the sign
    # of the source was the same as the sign of the result
    src_sign = src & cpu.SIGN16
    dst_sign = dst & cpu.SIGN16
    t_sign = t & cpu.SIGN16
    cpu.psw_v = (src_sign == t_sign) and (src_sign != dst_sign)
    cpu.psw_c = (src > dst)

    cpu.operandx(xb6, t)


def op17_reserved(cpu, inst):
    raise PDPTraps.ReservedInstruction


op4_dispatch_table = (
    lambda c, i: d3dispatcher(op00_dispatch_table, c, i),
    op01_mov,
    op02_cmp,
    op03_bit,
    op04_bic,
    op05_bis,
    op06_add,
    lambda c, i: d3dispatcher(op07_dispatch_table, c, i),
    lambda c, i: d3dispatcher(op10_dispatch_table, c, i),
    op11_movb,                               # NOTE: optimized; not mov+lambda
    lambda c, i: op02_cmp(c, i, opsize=1),   # 12
    lambda c, i: op03_bit(c, i, opsize=1),   # 13
    lambda c, i: op04_bic(c, i, opsize=1),   # 14
    lambda c, i: op05_bis(c, i, opsize=1),   # 15
    op16_sub,
    op17_reserved)                           # 17 reserved