from __future__ import annotations

from mf_core import SENTINEL, CHUNK_SIZE, CoreError, BASE_SCORE

CodecError = CoreError

class Frame:
    data: list = []
    offset: int = 0
    length: int = 0
    chunk_size: int = CHUNK_SIZE

    def is_complete(self: "Frame") -> bool:
        return self.offset >= self.length

    def remaining(self: "Frame") -> int:
        diff: int = self.length - self.offset
        if diff < 0:
            return 0
        return diff

    def advance(self: "Frame", n: int) -> None:
        self.offset = self.offset + n

    def reset(self: "Frame") -> None:
        self.offset = 0


class Encoder:
    chunk_size: int = CHUNK_SIZE
    total_written: int = 0
    frames_produced: int = 0

    def encode(self: "Encoder", payload_size: int) -> int:
        if payload_size <= 0:
            return SENTINEL
        frames: int = payload_size // self.chunk_size + 1
        self.total_written = self.total_written + payload_size
        self.frames_produced = self.frames_produced + frames
        return frames

    def efficiency_score(self: "Encoder") -> int:
        if self.total_written == 0:
            return BASE_SCORE
        overhead: int = self.frames_produced * self.chunk_size - self.total_written
        if self.total_written == 0:
            return BASE_SCORE
        penalty: int = overhead * 100 // self.total_written
        if penalty > BASE_SCORE:
            return 0
        return BASE_SCORE - penalty

    def reset(self: "Encoder") -> None:
        self.total_written = 0
        self.frames_produced = 0


class Decoder:
    chunk_size: int = CHUNK_SIZE
    total_read: int = 0
    frames_consumed: int = 0
    error: CodecError = CodecError()

    def decode(self: "Decoder", frames: int) -> int:
        if frames <= 0:
            return SENTINEL
        payload: int = frames * self.chunk_size
        self.total_read = self.total_read + payload
        self.frames_consumed = self.frames_consumed + frames
        return payload

    def efficiency_score(self: "Decoder") -> int:
        if self.frames_consumed == 0:
            return BASE_SCORE
        return self.total_read * 100 // (self.frames_consumed * self.chunk_size)

    def reset(self: "Decoder") -> None:
        self.total_read = 0
        self.frames_consumed = 0


def encode_batch(encoder: Encoder, sizes: list) -> int:
    total_frames: int = 0
    idx: int = 0
    while idx < len(sizes):
        frames: int = encoder.encode(sizes[idx])
        if frames != SENTINEL:
            total_frames = total_frames + frames
        idx = idx + 1
    return total_frames


def decode_frames(decoder: Decoder, frame_counts: list) -> int:
    total_payload: int = 0
    idx: int = 0
    while idx < len(frame_counts):
        payload: int = decoder.decode(frame_counts[idx])
        if payload != SENTINEL:
            total_payload = total_payload + payload
        idx = idx + 1
    return total_payload


GLOBAL_ENCODER: Encoder = Encoder()
GLOBAL_DECODER: Decoder = Decoder()
CODEC_SENTINEL: int = SENTINEL
DEFAULT_CHUNK: int = CHUNK_SIZE
