class Text extends Segment {
        protected final @NotNull CharSequence chars;

        public Text(int pos, byte[] bytes, int byteOffset, int indexOffset) {
            super(pos, bytes, byteOffset, indexOffset);
            int type = bytes[byteOffset++] & 0x00ff;
            int segTypeMask = type & TYPE_MASK;

            int length;

            if (hasAll(type, TYPE_NO_SIZE_BYTES)) {
                length = type & 0x000f;
            } else {
                int lengthBytes = (type & TYPE_LENGTH_BYTES) >> 2;
                length = getInt(bytes, byteOffset, lengthBytes + 1);
                byteOffset += lengthBytes + 1;
            }

            switch (segTypeMask) {
                case TYPE_TEXT:
                    chars = new TextCharSequence(bytes, byteOffset, 0, length);
                    break;

                case TYPE_TEXT_ASCII:
                    chars = new TextAsciiCharSequence(bytes, byteOffset, 0, length);
                    break;

                case TYPE_REPEATED_TEXT:
                    chars = new TextRepeatedSequence(getChar(bytes, byteOffset), length);
                    break;

                case TYPE_REPEATED_ASCII:
                    chars = new TextRepeatedSequence((char) (0x00ff & bytes[byteOffset]), length);
                    break;

                case TYPE_REPEATED_SPACE:
                    chars = new TextRepeatedSequence(' ', length);
                    break;

                case TYPE_REPEATED_EOL:
                    chars = new TextRepeatedSequence('\n', length);
                    break;

                default:
                    throw new IllegalStateException("Invalid text type " + segTypeMask);
            }
        }

        @Override
        public int length() {
            return chars.length();
        }

        @Override
        public char charAt(int index) {
            if (index < startIndex || index - startIndex >= chars.length()) {
                throw new IndexOutOfBoundsException("index " + index + " out of bounds [" + startIndex + ", " + startIndex + chars.length() + ")");
            }
            return chars.charAt(index - startIndex);
        }

        @Override
        public boolean isBase() {
            return false;
        }

        @Override
        public boolean isAnchor() {
            return false;
        }

        @Override
        public boolean isText() {
            return true;
        }

        int textType() {
            return bytes[byteOffset] & TYPE_MASK;
        }

        @Override
        public boolean isFirst256Start() {
            int textType = textType();
            return textType == TYPE_TEXT_ASCII || textType == TYPE_REPEATED_ASCII || textType == TYPE_REPEATED_SPACE || textType == TYPE_REPEATED_EOL;
        }

        @Override
        public boolean isRepeatedTextEnd() {
            int textType = textType();
            return textType == TYPE_REPEATED_TEXT || textType == TYPE_REPEATED_ASCII || textType == TYPE_REPEATED_SPACE || textType == TYPE_REPEATED_EOL;
        }

        @Override
        public int getStartOffset() {
            return -1;
        }

        @Override
        public int getEndOffset() {
            return -1;
        }

        @NotNull
        @Override
        public CharSequence getCharSequence() {
            return chars;
        }
    }

    public static Segment getSegment(byte[] bytes, int byteOffset, int pos, int indexOffset, @NotNull BasedSequence basedSequence) {
        int type = bytes[byteOffset] & TYPE_MASK;

        switch (type) {
            case TYPE_ANCHOR:
            case TYPE_BASE:
                return new Base(pos, bytes, byteOffset, indexOffset, basedSequence);

            case TYPE_TEXT:
            case TYPE_REPEATED_TEXT:
            case TYPE_TEXT_ASCII:
            case TYPE_REPEATED_ASCII:
            case TYPE_REPEATED_SPACE:
            case TYPE_REPEATED_EOL:
                return new Text(pos, bytes, byteOffset, indexOffset);

            default:
                throw new IllegalStateException("Invalid text type " + type);
        }
    }

    public static SegType getSegType(@NotNull Seg seg, @NotNull CharSequence textChars) {
        if (seg.isBase()) {
            return seg.isAnchor() ? SegType.ANCHOR : SegType.BASE;
        } else if (seg.isText()) {
            boolean first256Start = seg.isFirst256Start();
            boolean repeatedTextEnd = seg.isRepeatedTextEnd();

            if (first256Start) {
                // ascii text
                if (repeatedTextEnd) {
                    // repeated chars
                    char c = textChars.charAt(seg.getTextStart());
                    if (c == ' ') return SegType.REPEATED_SPACE;
                    else if (c == '\n') return SegType.REPEATED_EOL;
                    else return SegType.REPEATED_ASCII;
                } else {
                    return SegType.TEXT_ASCII;
                }
            } else {
                return repeatedTextEnd ? SegType.REPEATED_TEXT : SegType.TEXT;
            }
        } else {
            throw new IllegalStateException("Unknown seg type " + seg);
        }
    }

    public static int getOffsetBytes(int offset) {
        return offset < 16 ? 0 : offset < 256 ? 1 : offset < 65536 ? 2 : offset < 65536 * 256 ? 3 : 4;
    }

    public static int getLengthBytes(int length) {
        return length < 16 ? 0 : length < 256 ? 1 : length < 65536 ? 2 : length < 65536 * 256 ? 3 : 4;
    }

    public static int getIntBytes(int length) {
        return length < 256 ? 1 : length < 65536 ? 2 : length < 65536 * 256 ? 3 : 4;
    }

    public static int getSegByteLength(@NotNull Segment.SegType segType, int segStart, int segLength) {<FILL_FUNCTION_BODY>

        int length = 1;

        if (segType.hasBoth()) {
            length += getIntBytes(segStart) + getIntBytes(segLength);
        } else if (segType.hasOffset()) {
            length += getOffsetBytes(segStart);
        } else if (segType.hasLength()) {
            length += getLengthBytes(segLength);
        }

        if (segType.hasChar()) length += 2;
        else if (segType.hasChars()) length += 2 * segLength;
        else if (segType.hasByte()) length += 1;
        else if (segType.hasBytes()) length += segLength;

        return length;
    
<no_super_class>