/*
 * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package net.sergeych.lyng

data class Pos(val source: Source, val line: Int, val column: Int) {
    override fun toString(): String {
        val col = if (column >= 0) column + 1 else column
        return "${source.fileName}:${line+1}:$col"
    }

    @Suppress("unused")
    fun back(): Pos =
        if( column > 0) Pos(source, line, column-1)
        else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
        else throw IllegalStateException("can't go back from line 0, column 0")

    val currentLine: String get() =
        if( end ) "EOF"
        else if( line >= 0 ) source.lines[line] else "<no line information>"

    val currentLineTrimmedStart: String get() = currentLine.trimStart()

    val currentLineIndentWidth: Int
        get() {
            val lineText = currentLine
            val firstNonWhitespace = lineText.indexOfFirst { !it.isWhitespace() }
            return if (firstNonWhitespace >= 0) firstNonWhitespace else 0
        }

    val visualColumn: Int get() = (column - currentLineIndentWidth).coerceAtLeast(0)

    val end: Boolean get() = line >= source.lines.size

    companion object {
        val builtIn = Pos(Source.builtIn, 0, 0)
        val UNKNOWN = Pos(Source.UNKNOWN, -1, -1)
    }

}

class MutablePos(private val from: Pos) {

    val lines = from.source.lines
    var line = from.line
        private set
    var column = from.column
        private set

    val end: Boolean get() =
        line == lines.size

    fun toPos(): Pos = Pos(from.source, line, column)

    fun advance(): Char? {
        if (end) return null
        val current = lines[line]
        return if (column < current.length) {
            column++
            currentChar
        } else {
            column = 0
            if(++line >= lines.size) null
            else currentChar
        }
    }

    fun resetTo(pos: Pos) { line = pos.line; column = pos.column }

    fun back() {
        if (column > 0) column--
        else if (line > 0)
            column = lines[--line].length - 1
        else throw IllegalStateException("can't go back from line 0, column 0")
    }

    val currentChar: Char
        get() {
            if (end) return 0.toChar()
            val current = lines[line]
            return if (column >= current.length) '\n'
            else current[column]
        }

    /**
     * If the next characters are equal to the fragment, advances the position and returns true.
     * Otherwise, does nothing and returns false.
     */
    fun readFragment(fragment: String): Boolean {
        val mark = toPos()
        for(ch in fragment)
            if( currentChar != ch ) { resetTo(mark); return false }
            else advance()
        return true
    }

    override fun toString() = "($line:$column)"
    
}
