//! Laufzeitfehler: Codes und Meldungstexte kompatibel zum Vorbild.
//!
//! Die Codes und (englischen) Meldungstexte entsprechen dem Katalog des
//! Vorbilds (Original-Hilfe, Topic „Run-Time Error Codes"): klassische
//! Codes 1–76 (QB-Erbe), 80–89 (ISAM), 260–480 (Forms). Programme prüfen
//! `ERR` gegen diese Nummern, daher sind sie Teil des Kompatibilitäts-
//! vertrags. Nicht belegte Nummern liefern "Unprintable error" (wie im
//! Vorbild bei `ERROR n`); Nummer 105 ist im Vorbild intern reserviert.

/// Ein Laufzeitfehler des Dialekts, identifiziert durch seinen klassischen Code.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RuntimeError(pub u16);

impl RuntimeError {
    pub const NEXT_WITHOUT_FOR: Self = Self(1);
    pub const SYNTAX_ERROR: Self = Self(2);
    pub const RETURN_WITHOUT_GOSUB: Self = Self(3);
    pub const OUT_OF_DATA: Self = Self(4);
    pub const ILLEGAL_FUNCTION_CALL: Self = Self(5);
    pub const OVERFLOW: Self = Self(6);
    pub const OUT_OF_MEMORY: Self = Self(7);
    pub const LABEL_NOT_DEFINED: Self = Self(8);
    pub const SUBSCRIPT_OUT_OF_RANGE: Self = Self(9);
    pub const DUPLICATE_DEFINITION: Self = Self(10);
    pub const DIVISION_BY_ZERO: Self = Self(11);
    pub const ILLEGAL_IN_DIRECT_MODE: Self = Self(12);
    pub const TYPE_MISMATCH: Self = Self(13);
    pub const OUT_OF_STRING_SPACE: Self = Self(14);
    pub const STRING_TOO_COMPLEX: Self = Self(16);
    pub const CANNOT_CONTINUE: Self = Self(17);
    pub const FUNCTION_NOT_DEFINED: Self = Self(18);
    pub const NO_RESUME: Self = Self(19);
    pub const RESUME_WITHOUT_ERROR: Self = Self(20);
    pub const DEVICE_TIMEOUT: Self = Self(24);
    pub const DEVICE_FAULT: Self = Self(25);
    pub const FOR_WITHOUT_NEXT: Self = Self(26);
    pub const OUT_OF_PAPER: Self = Self(27);
    pub const WHILE_WITHOUT_WEND: Self = Self(29);
    pub const WEND_WITHOUT_WHILE: Self = Self(30);
    pub const DUPLICATE_LABEL: Self = Self(33);
    pub const SUBPROGRAM_NOT_DEFINED: Self = Self(35);
    pub const ARGUMENT_COUNT_MISMATCH: Self = Self(37);
    pub const ARRAY_NOT_DEFINED: Self = Self(38);
    pub const VARIABLE_REQUIRED: Self = Self(40);
    pub const FIELD_OVERFLOW: Self = Self(50);
    pub const INTERNAL_ERROR: Self = Self(51);
    pub const BAD_FILE_NAME_OR_NUMBER: Self = Self(52);
    pub const FILE_NOT_FOUND: Self = Self(53);
    pub const BAD_FILE_MODE: Self = Self(54);
    pub const FILE_ALREADY_OPEN: Self = Self(55);
    pub const FIELD_STATEMENT_ACTIVE: Self = Self(56);
    pub const DEVICE_IO_ERROR: Self = Self(57);
    pub const FILE_ALREADY_EXISTS: Self = Self(58);
    pub const BAD_RECORD_LENGTH: Self = Self(59);
    pub const DISK_FULL: Self = Self(61);
    pub const INPUT_PAST_END_OF_FILE: Self = Self(62);
    pub const BAD_RECORD_NUMBER: Self = Self(63);
    pub const BAD_FILE_NAME: Self = Self(64);
    pub const TOO_MANY_FILES: Self = Self(67);
    pub const DEVICE_UNAVAILABLE: Self = Self(68);
    pub const COMM_BUFFER_OVERFLOW: Self = Self(69);
    pub const PERMISSION_DENIED: Self = Self(70);
    pub const DISK_NOT_READY: Self = Self(71);
    pub const DISK_MEDIA_ERROR: Self = Self(72);
    pub const FEATURE_UNAVAILABLE: Self = Self(73);
    pub const RENAME_ACROSS_DISKS: Self = Self(74);
    pub const PATH_FILE_ACCESS_ERROR: Self = Self(75);
    pub const PATH_NOT_FOUND: Self = Self(76);
    // Forms-Fehler (Auswahl mit benannten Konstanten; vollständige Texte in message()):
    pub const INVALID_SCREEN_MODE: Self = Self(271);
    pub const INVALID_WHEN_FORMS_SHOWING: Self = Self(272);
    pub const INVALID_PROPERTY_VALUE: Self = Self(380);
    pub const DESIGN_TIME_CONTROL: Self = Self(362);
    pub const FORM_ALREADY_DISPLAYED: Self = Self(400);
    pub const NON_MODAL_WHILE_MODAL: Self = Self(401);
    pub const MODAL_NOT_TOPMOST: Self = Self(402);
    pub const MDI_FORM_MODAL: Self = Self(403);
    pub const NO_ACTIVE_CONTROL: Self = Self(430);
    pub const NO_ACTIVE_FORM: Self = Self(431);

    /// Klassischer Fehlercode für `ERR`.
    pub fn code(self) -> u16 {
        self.0
    }

    /// Meldungstext des Vorbilds; unbelegte Codes: "Unprintable error".
    pub fn message(self) -> &'static str {
        match self.0 {
            1 => "NEXT without FOR",
            2 => "Syntax error",
            3 => "RETURN without GOSUB",
            4 => "Out of DATA",
            5 => "Illegal function call",
            6 => "Overflow",
            7 => "Out of memory",
            8 => "Label not defined",
            9 => "Subscript out of range",
            10 => "Duplicate definition",
            11 => "Division by zero",
            12 => "Illegal in direct mode",
            13 => "Type mismatch",
            14 => "Out of string space",
            16 => "String formula too complex",
            17 => "Cannot continue",
            18 => "Function not defined",
            19 => "No RESUME",
            20 => "RESUME without error",
            24 => "Device timeout",
            25 => "Device fault",
            26 => "FOR without NEXT",
            27 => "Out of paper",
            29 => "WHILE without WEND",
            30 => "WEND without WHILE",
            33 => "Duplicate label",
            35 => "Subprogram not defined",
            37 => "Argument-count mismatch",
            38 => "Array not defined",
            40 => "Variable required",
            50 => "FIELD overflow",
            51 => "Internal error",
            52 => "Bad file name or number",
            53 => "File not found",
            54 => "Bad file mode",
            55 => "File already open",
            56 => "FIELD statement active",
            57 => "Device I/O error",
            58 => "File already exists",
            59 => "Bad record length",
            61 => "Disk full",
            62 => "Input past end of file",
            63 => "Bad record number",
            64 => "Bad file name",
            67 => "Too many files",
            68 => "Device unavailable",
            69 => "Communication-buffer overflow",
            70 => "Permission denied",
            71 => "Disk not ready",
            72 => "Disk-media error",
            73 => "Feature unavailable",
            74 => "Can't rename with different drive",
            75 => "Path/File access error",
            76 => "Path not found",
            // ISAM (PDS-Erbe) — Terminal Basic implementiert ISAM nicht,
            // die Codes/Texte gehören aber zum Kompatibilitätsvertrag.
            80 => "Feature removed",
            81 => "ISAM - Invalid name",
            82 => "ISAM - Table not found",
            83 => "ISAM - Index not found",
            84 => "ISAM - Invalid column",
            85 => "ISAM - No current record",
            86 => "ISAM - Duplicate value for unique index",
            87 => "ISAM - Invalid operation on NULL index",
            88 => "ISAM - Database inconsistent",
            89 => "ISAM - Insufficient ISAM buffers",
            // Forms-Fehler des Vorbilds
            260 => "No timer available",
            271 => "Invalid screen mode",
            272 => "Invalid when forms are showing",
            340 => "Control array element does not exist",
            341 => "Invalid object array index",
            342 => "Not enough room to allocate control array",
            343 => "Object not an array",
            344 => "Must specify index for object array",
            345 => "Reached limit: cannot create any more controls for this form",
            360 => "Object already loaded",
            361 => "Can't load or unload this object",
            362 => "Can't unload controls created at design time",
            363 => "Custom control not found",
            364 => "Object was unloaded",
            365 => "Unable to unload within this context",
            380 => "Invalid property value",
            381 => "Invalid property array index",
            382 => "Property can't be set at run time",
            383 => "Property is read-only",
            384 => "Property can't be modified when form is minimized or maximized",
            385 => "Must specify index when using property array",
            386 => "Property not available at run time",
            387 => "Property can't be set on this control",
            400 => "Form already displayed; can't show form modally",
            401 => "Can't show non-modal form when a modal form is being displayed",
            402 => "Must close or hide topmost modal form first",
            403 => "MDI form cannot be shown modally",
            410 => "Property can't be modified on MDI form",
            420 => "Invalid object reference",
            421 => "Method not applicable for this object",
            422 => "Property not found",
            423 => "Property or control not found",
            424 => "Object required",
            425 => "Invalid object use",
            430 => "No currently active control",
            431 => "No currently active form",
            480 => "Can't create AutoRedraw image",
            _ => "Unprintable error",
        }
    }
}

impl std::fmt::Display for RuntimeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message())
    }
}

impl std::error::Error for RuntimeError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn bekannte_codes() {
        assert_eq!(RuntimeError::OVERFLOW.code(), 6);
        assert_eq!(RuntimeError::OVERFLOW.message(), "Overflow");
        assert_eq!(RuntimeError::SUBSCRIPT_OUT_OF_RANGE.code(), 9);
        assert_eq!(RuntimeError::FILE_NOT_FOUND.message(), "File not found");
        assert_eq!(RuntimeError::FEATURE_UNAVAILABLE.code(), 73);
    }

    #[test]
    fn forms_und_isam_codes() {
        assert_eq!(RuntimeError(80).message(), "Feature removed");
        assert_eq!(
            RuntimeError::INVALID_WHEN_FORMS_SHOWING.message(),
            "Invalid when forms are showing"
        );
        assert_eq!(RuntimeError::NO_ACTIVE_FORM.code(), 431);
        assert_eq!(
            RuntimeError(403).message(),
            "MDI form cannot be shown modally"
        );
    }

    #[test]
    fn unbelegte_codes_sind_unprintable() {
        assert_eq!(RuntimeError(15).message(), "Unprintable error");
        assert_eq!(RuntimeError(105).message(), "Unprintable error"); // intern reserviert
        assert_eq!(RuntimeError(200).message(), "Unprintable error");
        assert_eq!(RuntimeError(500).message(), "Unprintable error");
    }
}
