import Base: peek

abstract type AbstractParser end
# .buf, .pos, .len must exist.

# Minimal parser for parsing strings with AbstractParser interface
mutable struct StringParser <: AbstractParser
    buf::String
    pos::Int
    len::Int
end
StringParser(s::AbstractString) = StringParser(String(s), 1, ncodeunits(s))

Base.String(p::AbstractParser) = p.buf
Base.position(p::AbstractParser) = p.pos
Base.length(p::AbstractParser) = p.len
Base.eof(p::AbstractParser) = position(p) > length(p)
Base.seek(p::AbstractParser, pos) = p.pos = pos
Base.seekstart(p::AbstractParser) = p.pos = 1

Base.peek(p::AbstractParser, ::Type{Char}) = p.buf[position(p)]

trypeek(p::AbstractParser, ::Type{Char}, default = nothing) =
    get(p.buf, thisind(p), default)

function Base.read(p::AbstractParser, ::Type{Char})
    obj = peek(p, Char)
    seek(p, nextind(p))
    return obj
end

rest(p::AbstractParser) = SubString(String(p), thisind(p))

buffer(p::AbstractParser) = _buffer(p.buf)
_buffer(s::AbstractString) = codeunits(s)
_buffer(other) = other

bytes(p::AbstractParser, from, to) = view(buffer(p), from:to)

or(value, default) = value
or(::Nothing, default) = default

prev(p::AbstractParser, ::Type{Char}) = p.buf[prevind(p)]
next(p::AbstractParser, ::Type{Char}) = p.buf[nextind(p)]

tryprev(p::AbstractParser, ::Type{Char}, default = nothing) =
    get(p.buf, prevind(p), default)
trynext(p::AbstractParser, ::Type{Char}, default = nothing) =
    get(p.buf, nextind(p), default)

Base.prevind(p::AbstractParser) = prevind(String(p), position(p))
Base.thisind(p::AbstractParser) = thisind(String(p), position(p))
Base.nextind(p::AbstractParser) = nextind(String(p), position(p))

Base.findnext(f::Function, p::AbstractParser) = findnext(f, String(p), position(p))
Base.findprev(f::Function, p::AbstractParser) = findprev(f, String(p), position(p))

Base.startswith(p::AbstractParser, prefix) = startswith(rest(p), prefix)
Base.occursin(pat::Regex, p::AbstractParser) = occursin(pat, rest(p))

Base.match(re::Regex, p::AbstractParser) = match(re, rest(p))

function consume(parser::AbstractParser, m::RegexMatch)
    parser.pos += (m.offset - 1) + m.match.ncodeunits
    return m
end
consume(p::AbstractParser, n::Nothing) = n

function consume(p::AbstractParser, index::Integer)
    seek(p, index)
    seek(p, nextind(p))
    return index
end

dispatch(p::AbstractParser, args...) = dispatch(p.table, args...)

include("parsers/rules.jl")
include("parsers/inlines.jl")
include("parsers/blocks.jl")
