import struct
import binascii
import concurrent.futures
import asyncio
from typing import Union, Optional


__all__ = [
    'encode', 'decode', 'encodebytes', 'decodebytes',
    'b64enc', 'b64dec', 'b32encode', 'b32decode',
    'b32hexencode', 'b32hexdecode', 'b16encode', 'b16decode',
    'b85encode', 'b85decode', 'a85encode', 'a85decode',
    'z85encode', 'z85decode',
    'standard_b64enc', 'standard_b64dec',
    'urlsafe_b64enc', 'urlsafe_b64dec',
    'cython', 'aiomultiprocess', 'uvloop', 'asyncio'
]

bytes_types = (bytes, bytearray)

def _bytes_from_decode_data(s: Union[bytes, bytearray, str, memoryview]) -> bytes:
    """Convert input to bytes for decoding operations."""
    if isinstance(s, str):
        try:
            return s.encode('ascii')
        except UnicodeEncodeError:
            raise ValueError('string argument should contain only ASCII characters')
    if isinstance(s, bytes_types):
        return s
    try:
        return memoryview(s).tobytes()
    except TypeError:
        raise TypeError(f"argument should be a bytes-like object or ASCII "
                       f"string, not {s.__class__.__name__}")

def _bytes_from_encode_data(s: Union[bytes, bytearray, str, memoryview]) -> bytes:
    """Convert input to bytes for encoding operations."""
    if isinstance(s, bytes_types):
        return s
    if isinstance(s, str):
        return s.encode('utf-8')
    try:
        return memoryview(s).tobytes()
    except TypeError:
        raise TypeError(f"argument should be a bytes-like object or UTF-8 "
                       f"string, not {s.__class__.__name__}")

# Base64 functions
def b64enc(s: Union[bytes, str], altchars: Optional[bytes] = None) -> bytes:
    """Base64 encode with optional alternative characters."""
    s = _bytes_from_encode_data(s)
    encoded = binascii.b2a_base64(s, newline=False)
    if altchars is not None:
        if len(altchars) != 2:
            raise ValueError("altchars must be exactly 2 bytes long")
        return encoded.translate(bytes.maketrans(b'+/', altchars))
    return encoded

def b64dec(s: Union[bytes, str], altchars: Optional[bytes] = None, validate: bool = False) -> bytes:
    """Base64 decode with optional alternative characters."""
    s = _bytes_from_decode_data(s)
    if altchars is not None:
        altchars = _bytes_from_decode_data(altchars)
        if len(altchars) != 2:
            raise ValueError("altchars must be exactly 2 bytes long")
        s = s.translate(bytes.maketrans(altchars, b'+/'))
    return binascii.a2b_base64(s)

def standard_b64enc(s: Union[bytes, str]) -> bytes:
    """Standard Base64 encoding."""
    return b64enc(s)

def standard_b64dec(s: Union[bytes, str]) -> bytes:
    """Standard Base64 decoding."""
    return b64dec(s)

_urlsafe_encode_translation = bytes.maketrans(b'+/', b'-_')
_urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/')

def urlsafe_b64enc(s: Union[bytes, str]) -> bytes:
    """URL-safe Base64 encoding."""
    return b64enc(s).translate(_urlsafe_encode_translation)

def urlsafe_b64dec(s: Union[bytes, str]) -> bytes:
    """URL-safe Base64 decoding."""
    s = _bytes_from_decode_data(s)
    s = s.translate(_urlsafe_decode_translation)
    return b64dec(s)

# Base32 implementation
_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
_b32hexalphabet = b'0123456789ABCDEFGHIJKLMNOPQRSTUV'
_b32tab2 = {}
_b32rev = {}

def _b32encode(alphabet: bytes, s: Union[bytes, str]) -> bytes:
    """Internal Base32 encoding with custom alphabet."""
    s = _bytes_from_encode_data(s)
    
    # Build lookup table if not exists
    if alphabet not in _b32tab2:
        b32tab = [bytes((i,)) for i in alphabet]
        _b32tab2[alphabet] = [a + b for a in b32tab for b in b32tab]
    
    # Handle padding
    leftover = len(s) % 5
    if leftover:
        s = s + b'\0' * (5 - leftover)
    
    encoded = bytearray()
    b32tab2 = _b32tab2[alphabet]
    
    for i in range(0, len(s), 5):
        chunk = s[i:i + 5]
        # Ensure we have exactly 5 bytes
        if len(chunk) < 5:
            chunk = chunk + b'\0' * (5 - len(chunk))
        c = int.from_bytes(chunk, 'big')
        encoded += (b32tab2[c >> 30] +
                   b32tab2[(c >> 20) & 0x3ff] +
                   b32tab2[(c >> 10) & 0x3ff] +
                   b32tab2[c & 0x3ff])
    
    # Apply padding
    if leftover == 1:
        encoded[-6:] = b'======'
    elif leftover == 2:
        encoded[-4:] = b'===='
    elif leftover == 3:
        encoded[-3:] = b'==='
    elif leftover == 4:
        encoded[-1:] = b'='
    
    return bytes(encoded)

def _b32decode(alphabet: bytes, s: Union[bytes, str], casefold: bool = False, 
               map01: Optional[bytes] = None) -> bytes:
    """Internal Base32 decoding with custom alphabet."""
    # Build reverse lookup if not exists
    if alphabet not in _b32rev:
        _b32rev[alphabet] = {v: k for k, v in enumerate(alphabet)}
    
    s = _bytes_from_decode_data(s)
    
    # Validate padding
    if len(s) % 8:
        raise binascii.Error('Incorrect padding')
    
    # Handle special mappings
    if map01 is not None:
        map01 = _bytes_from_decode_data(map01)
        if len(map01) != 1:
            raise ValueError("map01 must be exactly 1 byte long")
        s = s.translate(bytes.maketrans(b'01', b'O' + map01))
    
    if casefold:
        s = s.upper()
    
    l = len(s)
    s = s.rstrip(b'=')
    padchars = l - len(s)
    
    decoded = bytearray()
    b32rev = _b32rev[alphabet]
    
    # Process in chunks of 8 characters
    for i in range(0, len(s), 8):
        quanta = s[i:i + 8]
        acc = 0
        try:
            for c in quanta:
                acc = (acc << 5) + b32rev[c]
        except KeyError:
            raise binascii.Error('Non-base32 digit found')
        
        decoded += acc.to_bytes(5, 'big')
    
    # Validate and handle padding
    if l % 8 or padchars not in {0, 1, 3, 4, 6}:
        raise binascii.Error('Incorrect padding')
    
    if padchars and decoded:
        acc <<= 5 * padchars
        last = acc.to_bytes(5, 'big')
        leftover = (43 - 5 * padchars) // 8  # 40 bits = 5 bytes
        decoded[-5:] = last[:leftover]
    
    return bytes(decoded)

def b32encode(s: Union[bytes, str]) -> bytes:
    """Standard Base32 encoding."""
    return _b32encode(_b32alphabet, s)

def b32decode(s: Union[bytes, str], casefold: bool = False, 
              map01: Optional[bytes] = None) -> bytes:
    """Standard Base32 decoding."""
    return _b32decode(_b32alphabet, s, casefold, map01)

def b32hexencode(s: Union[bytes, str]) -> bytes:
    """Base32hex encoding."""
    return _b32encode(_b32hexalphabet, s)

def b32hexdecode(s: Union[bytes, str], casefold: bool = False) -> bytes:
    """Base32hex decoding."""
    return _b32decode(_b32hexalphabet, s, casefold)

# Base16 functions
def b16encode(s: Union[bytes, str]) -> bytes:
    """Base16 (hex) encoding."""
    s = _bytes_from_encode_data(s)
    return binascii.hexlify(s).upper()

def b16decode(s: Union[bytes, str], casefold: bool = False) -> bytes:
    """Base16 (hex) decoding."""
    s = _bytes_from_decode_data(s)
    if casefold:
        s = s.upper()
    if any(c not in b'0123456789ABCDEF' for c in s):
        raise binascii.Error('Non-base16 digit found')
    return binascii.unhexlify(s)

# Ascii85/Base85 implementation
class _Base85Codec:
    """Base class for Ascii85/Base85 codecs."""
    
    def __init__(self):
        self.chars = None
        self.chars2 = None
        self.dec = None
    
    def _ensure_tables(self, alphabet: bytes):
        """Ensure lookup tables are initialized."""
        if self.chars2 is None:
            self.chars = [bytes((i,)) for i in alphabet]
            self.chars2 = [(a + b) for a in self.chars for b in self.chars]
    
    def _85encode(self, b: Union[bytes, str], chars: list, chars2: list, 
                  pad: bool = False, foldnuls: bool = False, 
                  foldspaces: bool = False) -> bytes:
        """Internal Base85 encoding implementation."""
        b = _bytes_from_encode_data(b)
        
        # Handle empty input
        if not b:
            return b''
        
        # Apply padding
        padding = (-len(b)) % 4
        if padding:
            b = b + b'\0' * padding
        
        # Ensure we have enough data for unpack
        if len(b) % 4 != 0:
            raise ValueError("Input length must be multiple of 4 after padding")
        
        try:
            words = struct.unpack('!%dI' % (len(b) // 4), b)
        except struct.error as e:
            raise ValueError(f"Invalid input for Base85 encoding: {e}")
        
        chunks = []
        for word in words:
            if foldnuls and word == 0:
                chunks.append(b'z')
            elif foldspaces and word == 0x20202020:
                chunks.append(b'y')
            else:
                chunks.append(chars2[word // 614125] +        # 85^3
                             chars2[word // 85 % 7225] +     # 85^2  
                             chars[word % 85])
        
        # Remove padding if not requested
        if padding and not pad:
            if chunks and chunks[-1] == b'z':
                chunks[-1] = chars[0] * 5
            if chunks:
                chunks[-1] = chunks[-1][:-padding]
        
        return b''.join(chunks)

# Ascii85 codec
_a85codec = _Base85Codec()
_A85START = b"<~"
_A85END = b"~>"

_a85alphabet = bytes(range(33, 118))

def a85encode(b: Union[bytes, str], *, foldspaces: bool = False, 
              wrapcol: int = 0, pad: bool = False, adobe: bool = False) -> bytes:
    """Ascii85 encoding."""
    _a85codec._ensure_tables(_a85alphabet)
    
    result = _a85codec._85encode(b, _a85codec.chars, _a85codec.chars2, 
                                pad, True, foldspaces)
    
    if adobe:
        result = _A85START + result + _A85END
    
    if wrapcol > 0:
        wrapcol = max(2 if adobe else 1, wrapcol)
        chunks = [result[i:i + wrapcol] 
                  for i in range(0, len(result), wrapcol)]
        if adobe and chunks and len(chunks[-1]) + 2 > wrapcol:
            chunks.append(b'')
        result = b'\n'.join(chunks)
    
    return result

def a85decode(b: Union[bytes, str], *, foldspaces: bool = False, 
              adobe: bool = False, ignorechars: bytes = b' \t\n\r\v') -> bytes:
    """Ascii85 decoding."""
    b = _bytes_from_decode_data(b)
    
    if adobe:
        if not b.endswith(_A85END):
            raise ValueError(f"Ascii85 encoded byte sequences must end with {_A85END!r}")
        if b.startswith(_A85START):
            b = b[2:-2]
        else:
            b = b[:-2]
    
    packI = struct.Struct('!I').pack
    decoded = []
    curr = []
    
    for x in b + b'u' * 4:
        if b'!'[0] <= x <= b'u'[0]:
            curr.append(x)
            if len(curr) == 5:
                acc = 0
                for x in curr:
                    acc = 85 * acc + (x - 33)
                try:
                    decoded.append(packI(acc))
                except struct.error:
                    raise ValueError('Ascii85 overflow')
                curr.clear()
        elif x == b'z'[0]:
            if curr:
                raise ValueError('z inside Ascii85 5-tuple')
            decoded.append(b'\0\0\0\0')
        elif foldspaces and x == b'y'[0]:
            if curr:
                raise ValueError('y inside Ascii85 5-tuple')
            decoded.append(b'\x20\x20\x20\x20')
        elif x in ignorechars:
            continue
        else:
            raise ValueError(f'Non-Ascii85 digit found: {chr(x)!r}')
    
    result = b''.join(decoded)
    if curr:
        padding = 4 - len(curr)
        result = result[:-padding] if padding > 0 else result
    
    return result

# Base85 codec  
_b85codec = _Base85Codec()
_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")

def b85encode(b: Union[bytes, str], pad: bool = False) -> bytes:
    """Base85 encoding."""
    _b85codec._ensure_tables(_b85alphabet)
    return _b85codec._85encode(b, _b85codec.chars, _b85codec.chars2, pad)

def b85decode(b: Union[bytes, str]) -> bytes:
    """Base85 decoding."""
    global _b85dec
    if _b85dec is None:
        _b85dec = [None] * 256
        for i, c in enumerate(_b85alphabet):
            _b85dec[c] = i
    
    b = _bytes_from_decode_data(b)
    padding = (-len(b)) % 5
    b = b + b'~' * padding
    
    out = []
    packI = struct.Struct('!I').pack
    
    for i in range(0, len(b), 5):
        chunk = b[i:i + 5]
        acc = 0
        try:
            for c in chunk:
                acc = acc * 85 + _b85dec[c]
        except TypeError:
            for j, c in enumerate(chunk):
                if _b85dec[c] is None:
                    raise ValueError(f'bad base85 character at position {i + j}')
            raise
        
        try:
            out.append(packI(acc))
        except struct.error:
            raise ValueError(f'base85 overflow in hunk starting at byte {i}')
    
    result = b''.join(out)
    if padding:
        result = result[:-padding]
    
    return result

# Z85 codec
_z85alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz'
                b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#')

_z85_b85_decode_diff = b';`|~'
_z85_decode_translation = bytes.maketrans(
    _z85alphabet + _z85_b85_decode_diff,
    _b85alphabet + b'\x00' * len(_z85_b85_decode_diff)
)
_z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet)

def z85encode(s: Union[bytes, str]) -> bytes:
    """Z85 encoding."""
    return b85encode(s).translate(_z85_encode_translation)

def z85decode(s: Union[bytes, str]) -> bytes:
    """Z85 decoding."""
    s = _bytes_from_decode_data(s)
    s = s.translate(_z85_decode_translation)
    try:
        return b85decode(s)
    except ValueError as e:
        raise ValueError(str(e).replace('base85', 'z85'))

# File I/O functions
MAXLINESIZE = 76
MAXBINSIZE = (MAXLINESIZE // 4) * 3

def encode(input, output):
    """Encode input file to output file."""
    while True:
        s = input.read(MAXBINSIZE)
        if not s:
            break
        while len(s) < MAXBINSIZE:
            ns = input.read(MAXBINSIZE - len(s))
            if not ns:
                break
            s += ns
        line = binascii.b2a_base64(s)
        output.write(line)

def decode(input, output):
    """Decode input file to output file."""
    while True:
        line = input.readline()
        if not line:
            break
        s = binascii.a2b_base64(line)
        output.write(s)

def encodebytes(s: Union[bytes, str]) -> bytes:
    """Encode bytes to base64."""
    s = _bytes_from_encode_data(s)
    pieces = []
    for i in range(0, len(s), MAXBINSIZE):
        chunk = s[i:i + MAXBINSIZE]
        pieces.append(binascii.b2a_base64(chunk))
    return b"".join(pieces)

def decodebytes(s: Union[bytes, str]) -> bytes:
    """Decode base64 bytes."""
    s = _bytes_from_decode_data(s)
    return binascii.a2b_base64(s)

# Initialize global variables
_b85dec = None
