/// 
/// Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
/// Use of this file is governed by the BSD 3-clause license that
/// can be found in the LICENSE.txt file in the project root.
/// 



/// 
/// This implementation of _org.antlr.v4.runtime.TokenStream_ loads tokens from a
/// _org.antlr.v4.runtime.TokenSource_ on-demand, and places the tokens in a buffer to provide
/// access to any previous token by index.
/// 
/// 
/// This token stream ignores the value of _org.antlr.v4.runtime.Token#getChannel_. If your
/// parser requires the token stream filter tokens to only those on a particular
/// channel, such as _org.antlr.v4.runtime.Token#DEFAULT_CHANNEL_ or
/// _org.antlr.v4.runtime.Token#HIDDEN_CHANNEL_, use a filtering token stream such a
/// _org.antlr.v4.runtime.CommonTokenStream_.
///

public class BufferedTokenStream: TokenStream {
    /// 
    /// The _org.antlr.v4.runtime.TokenSource_ from which tokens for this stream are fetched.
    /// 
    internal var tokenSource: TokenSource

    /// 
    /// A collection of all tokens fetched from the token source. The list is
    /// considered a complete view of the input once _#fetchedEOF_ is set
    /// to `true`.
    /// 
    internal var tokens = [Token]()

    /// 
    /// The index into _#tokens_ of the current token (next token to
    /// _#consume_). _#tokens_`[`_#p_`]` should be
    /// _#LT LT(1)_.
    /// 
    /// This field is set to -1 when the stream is first constructed or when
    /// _#setTokenSource_ is called, indicating that the first token has
    /// not yet been fetched from the token source. For additional information,
    /// see the documentation of _org.antlr.v4.runtime.IntStream_ for a description of
    /// Initializing Methods.
    /// 
    internal var p = -1

    /// 
    /// Indicates whether the _org.antlr.v4.runtime.Token#EOF_ token has been fetched from
    /// _#tokenSource_ and added to _#tokens_. This field improves
    /// performance for the following cases:
    /// 
    /// * _#consume_: The lookahead check in _#consume_ to prevent
    /// consuming the EOF symbol is optimized by checking the values of
    /// _#fetchedEOF_ and _#p_ instead of calling _#LA_.
    /// 
    /// * _#fetch_: The check to prevent adding multiple EOF symbols into
    /// _#tokens_ is trivial with this field.
    /// 
    internal var fetchedEOF = false


    public init(_ tokenSource: TokenSource) {
        self.tokenSource = tokenSource
    }


    public func getTokenSource() -> TokenSource {
        return tokenSource
    }


    public func index() -> Int {
        return p
    }


    public func mark() -> Int {
        return 0
    }

    public func release(_ marker: Int) {
        // no resources to release
    }

    public func reset() throws {
        try seek(0)
    }


    public func seek(_ index: Int) throws {
        try lazyInit()
        p = try adjustSeekIndex(index)
    }


    public func size() -> Int {
        return tokens.count
    }


    public func consume() throws {
        var skipEofCheck: Bool
        if p >= 0 {
            if fetchedEOF {
                // the last token in tokens is EOF. skip check if p indexes any
                // fetched token except the last.
                skipEofCheck = p < tokens.count - 1
            } else {
                // no EOF token in tokens. skip check if p indexes a fetched token.
                skipEofCheck = p < tokens.count
            }
        } else {
            // not yet initialized
            skipEofCheck = false
        }

        if try !skipEofCheck && LA(1) == BufferedTokenStream.EOF {
            throw ANTLRError.illegalState(msg: "cannot consume EOF")
        }

        if try sync(p + 1) {
            p = try adjustSeekIndex(p + 1)
        }
    }

    /// 
    /// Make sure index `i` in tokens has a token.
    /// 
    /// - returns: `true` if a token is located at index `i`, otherwise
    /// `false`.
    /// - seealso: #get(int i)
    /// 
    @discardableResult
    internal func sync(_ i: Int) throws -> Bool {
        assert(i >= 0, "Expected: i>=0")
        let n = i - tokens.count + 1 // how many more elements we need?
        //print("sync("+i+") needs "+n);
        if n > 0 {
            let fetched = try fetch(n)
            return fetched >= n
        }

        return true
    }

    /// 
    /// Add `n` elements to buffer.
    /// 
    /// - returns: The actual number of elements added to the buffer.
    /// 
    internal func fetch(_ n: Int) throws -> Int {
        if fetchedEOF {
            return 0
        }

        for i in 0..<n {
            let t = try tokenSource.nextToken()
            if let wt = t as? WritableToken {
                wt.setTokenIndex(tokens.count)
            }

            tokens.append(t)
            if t.getType() == BufferedTokenStream.EOF {
                fetchedEOF = true
                return i + 1
            }
        }

        return n
    }

    public func get(_ i: Int) throws -> Token {
        guard tokens.indices.contains(i) else {
            throw ANTLRError.indexOutOfBounds(msg: "token index \(i) out of range 0 ..< \(tokens.count)")
        }
        return tokens[i]
    }

    /// 
    /// Get all tokens from start...stop inclusively
    /// 
    public func get(_ start: Int,_ stop: Int) throws -> Array<Token>? {
        var stop = stop
        if start < 0 || stop < 0 {
            return nil
        }
        try lazyInit()
        var subset = [Token]()
        if stop >= tokens.count {
            stop = tokens.count - 1
        }
        for i in start...stop {
            let t = tokens[i]
            if t.getType() == BufferedTokenStream.EOF {
                break
            }
            subset.append(t)
        }
        return subset
    }

    public func LA(_ i: Int) throws -> Int {
        return try LT(i)!.getType()
    }

    internal func LB(_ k: Int) throws -> Token? {
        if (p - k) < 0 {
            return nil
        }
        return tokens[p - k]
    }


    public func LT(_ k: Int) throws -> Token? {
        try lazyInit()
        if k == 0 {
            return nil
        }
        if k < 0 {
            return try LB(-k)
        }

        let i = p + k - 1
        try sync(i)
        if i >= tokens.count {
            // return EOF token
            // EOF must be last token
            return tokens.last!
        }
        return tokens[i]
    }

    /// 
    /// Allowed derived classes to modify the behavior of operations which change
    /// the current stream position by adjusting the target token index of a seek
    /// operation. The default implementation simply returns `i`. If an
    /// exception is thrown in this method, the current stream index should not be
    /// changed.
    /// 
    /// For example, _org.antlr.v4.runtime.CommonTokenStream_ overrides this method to ensure that
    /// the seek target is always an on-channel token.
    /// 
    /// - parameter i: The target token index.
    /// - returns: The adjusted target token index.
    /// 
    internal func adjustSeekIndex(_ i: Int) throws -> Int {
        return i
    }

    internal final func lazyInit() throws {
        if p == -1 {
            try setup()
        }
    }

    internal func setup() throws {
        try sync(0)
        p = try adjustSeekIndex(0)
    }

    /// 
    /// Reset this token stream by setting its token source.
    /// 
    public func setTokenSource(_ tokenSource: TokenSource) {
        self.tokenSource = tokenSource
        tokens.removeAll()
        p = -1
        fetchedEOF = false
    }

    public func getTokens() -> [Token] {
        return tokens
    }

    public func getTokens(_ start: Int, _ stop: Int) throws -> [Token]? {
        return try getTokens(start, stop, nil)
    }

    /// 
    /// Given a start and stop index, return a List of all tokens in
    /// the token type BitSet.  Return null if no tokens were found.  This
    /// method looks at both on and off channel tokens.
    /// 
    public func getTokens(_ start: Int, _ stop: Int, _ types: Set<Int>?) throws -> [Token]? {
        try lazyInit()
        guard tokens.indices.contains(start),
              tokens.indices.contains(stop) else {
            throw ANTLRError.indexOutOfBounds(msg: "start \(start) or stop \(stop) not in 0 ..< \(tokens.count)")

        }
        if start > stop {
            return nil
        }

        var filteredTokens = [Token]()
        for i in start...stop {
            let t = tokens[i]
            if types?.contains(t.getType()) ?? true {
                filteredTokens.append(t)
            }
        }
        if filteredTokens.isEmpty {
            return nil
        }
        return filteredTokens
    }

    public func getTokens(_ start: Int, _ stop: Int, _ ttype: Int) throws -> [Token]? {
        return try getTokens(start, stop, [ttype])
    }

    /// 
    /// Given a starting index, return the index of the next token on channel.
    /// Return `i` if `tokens[i]` is on channel. Return the index of
    /// the EOF token if there are no tokens on channel between `i` and
    /// EOF.
    /// 
    internal func nextTokenOnChannel(_ i: Int, _ channel: Int) throws -> Int {
        var i = i
        try sync(i)
        if i >= size() {
            return size() - 1
        }

        var token = tokens[i]
        while token.getChannel() != channel {
            if token.getType() == BufferedTokenStream.EOF {
                return i
            }

            i += 1
            try sync(i)
            token = tokens[i]
        }

        return i
    }

    /// 
    /// Given a starting index, return the index of the previous token on
    /// channel. Return `i` if `tokens[i]` is on channel. Return -1
    /// if there are no tokens on channel between `i` and 0.
    /// 
    /// 
    /// If `i` specifies an index at or after the EOF token, the EOF token
    /// index is returned. This is due to the fact that the EOF token is treated
    /// as though it were on every channel.
    /// 
    internal func previousTokenOnChannel(_ i: Int, _ channel: Int) throws -> Int {
        var i = i
        try sync(i)
        if i >= size() {
            // the EOF token is on every channel
            return size() - 1
        }

        while i >= 0 {
            let token = tokens[i]
            if token.getType() == BufferedTokenStream.EOF || token.getChannel() == channel {
                return i
            }

            i -= 1
        }

        return i
    }

    /// 
    /// Collect all tokens on specified channel to the right of
    /// the current token up until we see a token on DEFAULT_TOKEN_CHANNEL or
    /// EOF. If channel is -1, find any non default channel token.
    /// 
    public func getHiddenTokensToRight(_ tokenIndex: Int, _ channel: Int = -1) throws -> [Token]? {
        try lazyInit()
        guard tokens.indices.contains(tokenIndex) else {
            throw ANTLRError.indexOutOfBounds(msg: "\(tokenIndex) not in 0 ..< \(tokens.count)")
        }

        let nextOnChannel = try nextTokenOnChannel(tokenIndex + 1, Lexer.DEFAULT_TOKEN_CHANNEL)
        let from = tokenIndex + 1
        let to: Int
        // if none onchannel to right, nextOnChannel=-1 so set to = last token
        if nextOnChannel == -1 {
            to = size() - 1
        }
        else {
            to = nextOnChannel
        }

        return filterForChannel(from, to, channel)
    }

    ///
    /// Collect all tokens on specified channel to the left of
    /// the current token up until we see a token on DEFAULT_TOKEN_CHANNEL.
    /// If channel is -1, find any non default channel token.
    /// 
    public func getHiddenTokensToLeft(_ tokenIndex: Int, _ channel: Int = -1) throws -> [Token]? {
        try lazyInit()
        guard tokens.indices.contains(tokenIndex) else {
            throw ANTLRError.indexOutOfBounds(msg: "\(tokenIndex) not in 0 ..< \(tokens.count)")
        }

        if tokenIndex == 0 {
            // obviously no tokens can appear before the first token
            return nil
        }

        let prevOnChannel = try previousTokenOnChannel(tokenIndex - 1, Lexer.DEFAULT_TOKEN_CHANNEL)
        if prevOnChannel == tokenIndex - 1 {
            return nil
        }
        // if none onchannel to left, prevOnChannel=-1 then from=0
        let from = prevOnChannel + 1
        let to = tokenIndex - 1
        return filterForChannel(from, to, channel)
    }

    internal func filterForChannel(_ from: Int, _ to: Int, _ channel: Int) -> [Token]? {
        var hidden = [Token]()
        for t in tokens[from...to] {
            if channel == -1 {
                if t.getChannel() != Lexer.DEFAULT_TOKEN_CHANNEL {
                    hidden.append(t)
                }
            } else {
                if t.getChannel() == channel {
                    hidden.append(t)
                }
            }
        }
        if hidden.isEmpty {
            return nil
        }
        return hidden
    }


    public func getSourceName() -> String {
        return tokenSource.getSourceName()
    }

    /// 
    /// Get the text of all tokens in this buffer.
    /// 
    public func getText() throws -> String {
        return try getText(Interval.of(0, size() - 1))
    }

    public func getText(_ interval: Interval) throws -> String {
        let start = interval.a
        if start < 0 {
            return ""
        }
        try fill()
        let stop = min(tokens.count, interval.b + 1)
        var buf = ""
        for t in tokens[start ..< stop] {
            if t.getType() == BufferedTokenStream.EOF {
                break
            }
            buf += t.getText()!
        }
        return buf
    }


    public func getText(_ ctx: RuleContext) throws -> String {
        return try getText(ctx.getSourceInterval())
    }


    public func getText(_ start: Token?, _ stop: Token?) throws -> String {
        if let start = start, let stop = stop {
            return try getText(Interval.of(start.getTokenIndex(), stop.getTokenIndex()))
        }

        return ""
    }

    /// 
    /// Get all tokens from lexer until EOF
    /// 
    public func fill() throws {
        try lazyInit()
        let blockSize = 1000
        while true {
            let fetched = try fetch(blockSize)
            if fetched < blockSize {
                return
            }
        }
    }
}
