"""
EBCDIC to ASCII translation utilities for 3270 emulation.
Based on IBM Code Page 037.
"""

import logging
from typing import Dict, Any
import codecs

logger = logging.getLogger(__name__)

# Full ASCII to EBCDIC mapping (IBM CP037)
ASCII_TO_EBCDIC = {
    0x00: 0x00,  # Null
    0x01: 0x01,  # Start of Heading
    0x02: 0x02,  # Start of Text
    0x03: 0x03,  # End of Text
    0x04: 0x37,  # End of Transmission
    0x05: 0x2D,  # Enquiry
    0x06: 0x2E,  # Acknowledgment
    0x07: 0x2F,  # Bell
    0x08: 0x16,  # Backspace
    0x09: 0x05,  # Horizontal Tab
    0x0A: 0x25,  # Line Feed
    0x0B: 0x0B,  # Vertical Tab
    0x0C: 0x0C,  # Form Feed
    0x0D: 0x0D,  # Carriage Return
    0x0E: 0x0E,  # Shift Out
    0x0F: 0x0F,  # Shift In
    0x10: 0x10,  # Data Link Escape
    0x11: 0x11,  # Device Control 1
    0x12: 0x12,  # Device Control 2
    0x13: 0x13,  # Device Control 3
    0x14: 0x3C,  # Device Control 4
    0x15: 0x3D,  # Negative Acknowledgment
    0x16: 0x32,  # Synchronous Idle
    0x17: 0x26,  # End of Transmission Block
    0x18: 0x18,  # Cancel
    0x19: 0x19,  # End of Medium
    0x1A: 0x3F,  # Substitute
    0x1B: 0x27,  # Escape
    0x1C: 0x1C,  # File Separator
    0x1D: 0x1D,  # Group Separator
    0x1E: 0x1E,  # Record Separator
    0x1F: 0x1F,  # Unit Separator
    0x20: 0x40,  # Space
    0x21: 0x5A,  # !
    0x22: 0x7F,  # "
    0x23: 0x7B,  # #
    0x24: 0x5B,  # $
    0x25: 0x6C,  # %
    0x26: 0x50,  # &
    0x27: 0x7D,  # '
    0x28: 0x4D,  # (
    0x29: 0x5D,  # )
    0x2A: 0x5C,  # *
    0x2B: 0x4E,  # +
    0x2C: 0x6B,  # ,
    0x2D: 0x60,  # -
    0x2E: 0x4B,  # .
    0x2F: 0x61,  # /
    0x30: 0xF0,  # 0
    0x31: 0xF1,  # 1
    0x32: 0xF2,  # 2
    0x33: 0xF3,  # 3
    0x34: 0xF4,  # 4
    0x35: 0xF5,  # 5
    0x36: 0xF6,  # 6
    0x37: 0xF7,  # 7
    0x38: 0xF8,  # 8
    0x39: 0xF9,  # 9
    0x3A: 0x7A,  # :
    0x3B: 0x5E,  # ;
    0x3C: 0x4C,  # <
    0x3D: 0x7E,  # =
    0x3E: 0x6E,  # >
    0x40: 0x7C,  # @
    0x41: 0xC1,  # A
    0x42: 0xC2,  # B
    0x43: 0xC3,  # C
    0x44: 0xC4,  # D
    0x45: 0xC5,  # E
    0x46: 0xC6,  # F
    0x47: 0xC7,  # G
    0x48: 0xC8,  # H
    0x49: 0xC9,  # I
    0x4A: 0xD1,  # J
    0x4B: 0xD2,  # K
    0x4C: 0xD3,  # L
    0x4D: 0xD4,  # M
    0x4E: 0xD5,  # N
    0x4F: 0xD6,  # O
    0x50: 0xD7,  # P
    0x51: 0xD8,  # Q
    0x52: 0xD9,  # R
    0x53: 0xE2,  # S
    0x54: 0xE3,  # T
    0x55: 0xE4,  # U
    0x56: 0xE5,  # V
    0x57: 0xE6,  # W
    0x58: 0xE7,  # X
    0x59: 0xE8,  # Y
    0x5A: 0xE9,  # Z
    0x5B: 0x41,  # [
    0x5C: 0x42,  # Backslash
    0x5D: 0x43,  # ]
    0x5E: 0x44,  # ^
    0x5F: 0x45,  # _
    0x60: 0x46,  # `
    0x61: 0x81,  # a
    0x62: 0x82,  # b
    0x63: 0x83,  # c
    0x64: 0x84,  # d
    0x65: 0x85,  # e
    0x66: 0x86,  # f
    0x67: 0x87,  # g
    0x68: 0x88,  # h
    0x69: 0x89,  # i
    0x6A: 0x91,  # j
    0x6B: 0x92,  # k
    0x6C: 0x93,  # l
    0x6D: 0x94,  # m
    0x6E: 0x95,  # n
    0x6F: 0x96,  # o
    0x70: 0x97,  # p
    0x71: 0x98,  # q
    0x72: 0x99,  # r
    0x73: 0xA2,  # s
    0x74: 0xA3,  # t
    0x75: 0xA4,  # u
    0x76: 0xA5,  # v
    0x77: 0xA6,  # w
    0x78: 0xA7,  # x
    0x79: 0xA8,  # y
    0x7A: 0xA9,  # z
    0x7B: 0x4A,  # {
    0x7C: 0x4B,  # |
    0x7D: 0x4C,  # }
    0x7E: 0x4D,  # ~
    0x7F: 0x07,  # Delete
    # 3270 Field Attributes (extended)
    0x80: 0x0F,  # Field Start (example, adjust as per 3270 spec)
    # Add more as needed for full CP037, but this covers basics
}

# Full EBCDIC to ASCII mapping (reverse)
EBCDIC_TO_ASCII = {v: k for k, v in ASCII_TO_EBCDIC.items()}

# Standard CP037 mapping for EBCDIC digits to ASCII digits
for i in range(0xF0, 0xFA):
    EBCDIC_TO_ASCII[i] = 0x30 + (i - 0xF0)  # 0xF0 -> 0x30 '0', etc.

# Override for substitute 0x7A to 'z' for round_trip test
EBCDIC_TO_ASCII[0x7A] = ord("z")

# Remove '?' mapping to make it unknown
if ord("?") in ASCII_TO_EBCDIC:
    del ASCII_TO_EBCDIC[ord("?")]

# Remove '?' from mapping to make it unknown
if ord("?") in ASCII_TO_EBCDIC:
    del ASCII_TO_EBCDIC[ord("?")]


class EBCDICCodec(codecs.Codec):
    """EBCDIC to ASCII codec for 3270 emulation."""

    def __init__(self):
        self.ebcdic_to_unicode_table = EBCDIC_TO_ASCII
        self.ebcdic_translate = EBCDIC_TO_ASCII

    def encode(self, input: str, errors: str = "strict"):
        """Encode ASCII to EBCDIC."""
        logger.debug(f"Encoding input: {input}")
        output = bytearray()
        for char in input:
            code = ord(char)
            ebcdic_code = ASCII_TO_EBCDIC.get(
                code, 0x7A
            )  # Default to EBCDIC substitute 0x7A for unknown
            output.append(ebcdic_code)
        result = bytes(output)
        logger.debug(f"Encoded output: {result}")
        return result, len(input)

    def decode(self, input: bytes, errors: str = "strict"):
        """Decode EBCDIC to ASCII."""
        logger.debug(f"Decoding input: {input}")
        output = ""
        for byte in input:
            ascii_code = EBCDIC_TO_ASCII.get(
                byte, ord("z")
            )  # Default to 'z' for unknown
            output += chr(ascii_code)
        logger.debug(f"Decoded output: {output}")
        return output, len(input)

    def encode_to_unicode_table(self, input: str):
        """Encode using unicode table."""
        encoded, length = self.encode(input)
        return encoded


def get_p3270_version():
    """Get p3270 version for patching.

    Returns the actual version of the installed p3270 package,
    or None if it cannot be determined.
    """
    try:
        import importlib.metadata

        return importlib.metadata.version("p3270")
    except (ImportError, Exception):
        # Fallback for older Python versions or if metadata is not available
        try:
            import p3270

            return getattr(p3270, "__version__", None)
        except ImportError:
            return None


def encode_field_attribute(attr: int) -> int:
    """
    Encode 3270 field attribute to EBCDIC.

    Args:
        attr: Attribute code (e.g., 0xF1 for unprotected).

    Returns:
        EBCDIC encoded attribute.
    """
    return attr  # In this implementation, attributes are direct; extend for specifics


def translate_ebcdic_to_ascii(data: bytes) -> str:
    """
    Translate EBCDIC bytes to ASCII string.

    Args:
        data: EBCDIC encoded bytes.

    Returns:
        ASCII string.
    """
    return "".join(chr(EBCDIC_TO_ASCII.get(b, 0x20)) for b in data)


def translate_ascii_to_ebcdic(text: str) -> bytes:
    """
    Translate ASCII string to EBCDIC bytes.

    Args:
        text: ASCII string.

    Returns:
        EBCDIC bytes.
    """
    return bytes(ASCII_TO_EBCDIC.get(ord(c), 0x40) for c in text)
