namespace Core {

    enum _LIB_CORE_ExType {
        // These must be kept in sync with CoreExceptionTypes in Constants.cry in the interpreter.
        FATAL = 0,
        GENERIC_EXCEPTION = 1,
        ASSERTION_FAILED = 2,
        DIVISION_BY_ZERO = 3,
        INDEX_OUT_OF_RANGE = 4,
        INVALID_ARGUMENT = 5,
        INVALID_ASSIGNMENT = 6,
        INVALID_INVOCATION = 7,
        INVALID_KEY = 8,
        KEY_NOT_FOUND = 9,
        NULL_REFERENCE = 10,
        UNASSIGNED_VARIABLE = 11,
        UNKNOWN_FIELD = 12,
        UNSUPPORTED_OPERATION = 13,
    }

    /*
        When there are VM-generated exceptions (as opposed to exceptions thrown directly from code with the
        throw keyword) the easiest way to go about this is to simply treat it as a function invocation to this
        particular function which itself just uses the throw keyword.
    */
    function _LIB_CORE_generateException(type, message) {
        switch (type) {
            case _LIB_CORE_ExType.FATAL: throw new FatalException(message);
            case _LIB_CORE_ExType.ASSERTION_FAILED: throw new AssertionFailedException(message);
            case _LIB_CORE_ExType.DIVISION_BY_ZERO: throw new DivisionByZeroException(message);
            case _LIB_CORE_ExType.INDEX_OUT_OF_RANGE: throw new IndexOutOfRangeException(message);
            case _LIB_CORE_ExType.INVALID_ARGUMENT: throw new InvalidArgumentException(message);
            case _LIB_CORE_ExType.INVALID_ASSIGNMENT: throw new InvalidAssignmentException(message);
            case _LIB_CORE_ExType.INVALID_INVOCATION: throw new InvalidInvocationException(message);
            case _LIB_CORE_ExType.INVALID_KEY: throw new InvalidKeyException(message);
            case _LIB_CORE_ExType.KEY_NOT_FOUND: throw new KeyNotFoundException(message);
            case _LIB_CORE_ExType.NULL_REFERENCE: throw new NullReferenceException(message);
            case _LIB_CORE_ExType.UNASSIGNED_VARIABLE: throw new UnassignedVariableException(message);
            case _LIB_CORE_ExType.UNKNOWN_FIELD: throw new UnknownFieldException(message);
            case _LIB_CORE_ExType.UNSUPPORTED_OPERATION: throw new UnsupportedOperationException(message);
            default: throw new Exception(message);
        }
    }

    @localized('en', 'Exception')
    @localized('es', 'ESException')
    @localized('jp', 'JPException')
    class Exception {
        /*
            nativeData[0] -> (boolean) is fatal exception?
            nativeData[1] -> (List<int>) list of PC's this exception bubbles through
                PC trace is only set when throw is invoked.
        */

        // Do not re-order these.
        // .message field position within the class is assumed at compile time.
        @localized('en', 'cause')
        @localized('es', 'EScause')
        @localized('jp', 'JPcause')
        field cause;

        @localized('en', 'message')
        @localized('es', 'ESmessage')
        @localized('jp', 'JPmessage')
        field message;

        constructor(message = null, cause = null) {
            $$$('allocNativeData', this, 2);
            $$$('setNativeData', this, 0, false); // is fatal?
            this.message = message;
            this.cause = cause;
        }

        @localized('en', 'getTrace')
        @localized('es', 'ESgetTrace')
        @localized('jp', 'JPgetTrace')
        function getTrace() {
            return $$$('getExceptionTrace', this);
        }
    }

    @localized('en', 'FatalException')
    @localized('es', 'ESFatalException')
    @localized('jp', 'JPFatalException')
    class FatalException : Exception {
        constructor(m = null, c = null) : base(m, c) {
            $$$('setNativeData', this, 0, true); // is fatal?
        }
    }

    @localized('en', 'AssertionFailedException')
    @localized('es', 'ESAssertionFailedException')
    @localized('jp', 'JPAssertionFailedException')
    class AssertionFailedException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'DivisionByZeroException')
    @localized('es', 'ESDivisionByZeroException')
    @localized('jp', 'JPDivisionByZeroException')
    class DivisionByZeroException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'IndexOutOfRangeException')
    @localized('es', 'ESIndexOutOfRangeException')
    @localized('jp', 'JPIndexOutOfRangeException')
    class IndexOutOfRangeException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'InvalidArgumentException')
    @localized('es', 'ESInvalidArgumentException')
    @localized('jp', 'JPInvalidArgumentException')
    class InvalidArgumentException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'InvalidAssignmentException')
    @localized('es', 'ESInvalidAssignmentException')
    @localized('jp', 'JPInvalidAssignmentException')
    class InvalidAssignmentException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'InvalidInvocationException')
    @localized('es', 'ESInvalidInvocationException')
    @localized('jp', 'JPInvalidInvocationException')
    class InvalidInvocationException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'InvalidKeyException')
    @localized('es', 'ESInvalidKeyException')
    @localized('jp', 'JPInvalidKeyException')
    class InvalidKeyException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'InvalidOperationException')
    @localized('es', 'ESInvalidOperationException')
    @localized('jp', 'JPInvalidOperationException')
    class InvalidOperationException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'KeyNotFoundException')
    @localized('es', 'ESKeyNotFoundException')
    @localized('jp', 'JPKeyNotFoundException')
    class KeyNotFoundException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'NotImplementedException')
    @localized('es', 'ESNotImplementedException')
    @localized('jp', 'JPNotImplementedException')
    class NotImplementedException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'NullReferenceException')
    @localized('es', 'ESNullReferenceException')
    @localized('jp', 'JPNullReferenceException')
    class NullReferenceException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'UnassignedVariableException')
    @localized('es', 'ESUnassignedVariableException')
    @localized('jp', 'JPUnassignedVariableException')
    class UnassignedVariableException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'UnknownFieldException')
    @localized('es', 'ESUnknownFieldException')
    @localized('jp', 'JPUnknownFieldException')
    class UnknownFieldException : Exception { constructor(m = null, c = null) : base(m, c) { } }

    @localized('en', 'UnsupportedOperationException')
    @localized('es', 'ESUnsupportedOperationException')
    @localized('jp', 'JPUnsupportedOperationException')
    class UnsupportedOperationException : Exception { constructor(m = null, c = null) : base(m, c) { } }
}
