/* 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.
 */


public class UnbufferedTokenStream: TokenStream {
    internal var tokenSource: TokenSource

    /// 
    /// A moving window buffer of the data being scanned. While there's a marker,
    /// we keep adding to buffer. Otherwise, _#consume consume()_ resets so
    /// we start filling at index 0 again.
    /// 
    internal var tokens = [Token]()

    /// 
    /// The number of tokens currently in `self.tokens`.
    /// 
    /// This is not the buffer capacity, that's `self.tokens.count`.
    /// 
    internal var n = 0

    /// 
    /// `0...n-1` index into `self.tokens` of next token.
    /// 
    /// The `LT(1)` token is `tokens[p]`. If `p == n`, we are
    /// out of buffered tokens.
    /// 
    internal var p = 0

    /// 
    /// Count up with _#mark mark()_ and down with
    /// _#release release()_. When we `release()` the last mark,
    /// `numMarkers` reaches 0 and we reset the buffer. Copy
    /// `tokens[p]..tokens[n-1]` to `tokens[0]..tokens[(n-1)-p]`.
    /// 
    internal var numMarkers = 0

    /// 
    /// This is the `LT(-1)` token for the current position.
    /// 
    internal var lastToken: Token!

    /// 
    /// When `numMarkers > 0`, this is the `LT(-1)` token for the
    /// first token in _#tokens_. Otherwise, this is `null`.
    /// 
    internal var lastTokenBufferStart: Token!

    /// 
    /// Absolute token index. It's the index of the token about to be read via
    /// `LT(1)`. Goes from 0 to the number of tokens in the entire stream,
    /// although the stream size is unknown before the end is reached.
    /// 
    /// This value is used to set the token indexes if the stream provides tokens
    /// that implement _org.antlr.v4.runtime.WritableToken_.
    /// 
    internal var currentTokenIndex = 0


    public init(_ tokenSource: TokenSource) throws {
        self.tokenSource = tokenSource
        try fill(1) // prime the pump
    }


    public func get(_ i: Int) throws -> Token {
        // get absolute index
        let bufferStartIndex = getBufferStartIndex()
        if i < bufferStartIndex || i >= bufferStartIndex + n {
            throw ANTLRError.indexOutOfBounds(msg: "get(\(i)) outside buffer: \(bufferStartIndex)..\(bufferStartIndex + n)")
        }
        return tokens[i - bufferStartIndex]
    }


    public func LT(_ i: Int) throws -> Token? {
        if i == -1 {
            return lastToken
        }

        try sync(i)
        let index: Int = p + i - 1
        if index < 0 {
            throw ANTLRError.indexOutOfBounds(msg: "LT(\(i) gives negative index")
        }

        if index >= n {
            //Token.EOF
            assert(n > 0 && tokens[n - 1].getType() == CommonToken.EOF, "Expected: n>0&&tokens[n-1].getType()==Token.EOF")
            return tokens[n - 1]
        }

        return tokens[index]
    }


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


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


    public func getText() -> String {
        return ""
    }


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


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


    public func consume() throws {
        //Token.EOF
        if try  LA(1) == CommonToken.EOF {
            throw ANTLRError.illegalState(msg: "cannot consume EOF")
        }

        // buf always has at least tokens[p==0] in this method due to ctor
        lastToken = tokens[p]   // track last token for LT(-1)

        // if we're at last token and no markers, opportunity to flush buffer
        if p == n - 1 && numMarkers == 0 {
            n = 0
            p = -1 // p++ will leave this at 0
            lastTokenBufferStart = lastToken
        }

        p += 1
        currentTokenIndex += 1
        try sync(1)
    }

    /// Make sure we have 'need' elements from current position _#p p_. Last valid
    /// `p` index is `tokens.length-1`.  `p+need-1` is the tokens index 'need' elements
    /// ahead.  If we need 1 element, `(p+1-1)==p` must be less than `tokens.length`.
    /// 
    internal func sync(_ want: Int) throws {
        let need: Int = (p + want - 1) - n + 1 // how many more elements we need?
        if need > 0 {
            try fill(need)
        }
    }

    /// 
    /// Add `n` elements to the buffer. Returns the number of tokens
    /// actually added to the buffer. If the return value is less than `n`,
    /// then EOF was reached before `n` tokens could be added.
    /// 
    @discardableResult
    internal func fill(_ n: Int) throws -> Int {
        for i in 0..<n {
            if self.n > 0 && tokens[self.n - 1].getType() == CommonToken.EOF {
                return i
            }

            let t: Token = try tokenSource.nextToken()
            add(t)
        }

        return n
    }

    internal func add(_ t: Token) {
        if n >= tokens.count {
            //TODO: array count buffer size
            //tokens = Arrays.copyOf(tokens, tokens.length * 2);
        }

        if let wt = t as? WritableToken {
            wt.setTokenIndex(getBufferStartIndex() + n)
        }

        tokens[n] = t
        n += 1
    }

    /// 
    /// Return a marker that we can release later.
    /// 
    /// The specific marker value used for this class allows for some level of
    /// protection against misuse where `seek()` is called on a mark or
    /// `release()` is called in the wrong order.
    /// 

    public func mark() -> Int {
        if numMarkers == 0 {
            lastTokenBufferStart = lastToken
        }

        let mark = -numMarkers - 1
        numMarkers += 1
        return mark
    }


    public func release(_ marker: Int) throws {
        let expectedMark = -numMarkers
        if marker != expectedMark {
            throw ANTLRError.illegalState(msg: "release() called with an invalid marker.")
        }

        numMarkers -= 1
        if numMarkers == 0 {
            // can we release buffer?
            if p > 0 {
                // Copy tokens[p]..tokens[n-1] to tokens[0]..tokens[(n-1)-p], reset ptrs
                // p is last valid token; move nothing if p==n as we have no valid char
                tokens = Array(tokens[p ... n - 1])
                n = n - p
                p = 0
            }

            lastTokenBufferStart = lastToken
        }
    }


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


    public func seek(_ index: Int) throws {
        var index = index
        // seek to absolute index
        if index == currentTokenIndex {
            return
        }

        if index > currentTokenIndex {
            try sync(index - currentTokenIndex)
            index = min(index, getBufferStartIndex() + n - 1)
        }

        let bufferStartIndex = getBufferStartIndex()
        let i = index - bufferStartIndex
        if i < 0 {
            throw ANTLRError.illegalState(msg: "cannot seek to negative index \(index)")

        }
        else if i >= n {
            throw ANTLRError.unsupportedOperation(msg: "seek to index outside buffer: \(index) not in \(bufferStartIndex)..<\(bufferStartIndex + n)")
        }

        p = i
        currentTokenIndex = index
        if p == 0 {
            lastToken = lastTokenBufferStart
        } else {
            lastToken = tokens[p - 1]
        }
    }


    public func size() -> Int {
        fatalError("Unbuffered stream cannot know its size")
    }


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


    public func getText(_ interval: Interval) throws -> String {
        let bufferStartIndex = getBufferStartIndex()
        let bufferStopIndex = bufferStartIndex + tokens.count - 1

        let start = interval.a
        let stop = interval.b
        if start < bufferStartIndex || stop > bufferStopIndex {
            throw ANTLRError.unsupportedOperation(msg: "interval \(interval) not in token buffer window: \(bufferStartIndex)...\(bufferStopIndex)")
        }

        let a = start - bufferStartIndex
        let b = stop - bufferStartIndex

        var buf = ""
        for t in tokens[a...b] {
            buf += t.getText()!
        }
        return buf
    }

    internal final func getBufferStartIndex() -> Int {
        return currentTokenIndex - p
    }
}
