package net.torvald.terrarum.modulecomputers.virtualcomputer.luaapi

import org.luaj.vm2.*
import org.luaj.vm2.lib.*
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype
import net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal
import java.nio.charset.Charset

/**
 * Controls terminal as if it was a monitor
 * (not sending control sequences but just drives it directly)
 *
 * Created by minjaesong on 2016-09-12.
 */
internal class Term(globals: Globals, term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) {

    init {
        // load things. WARNING: THIS IS MANUAL!
        globals["term"] = LuaValue.tableOf()
        globals["term"]["write"] = Term.WriteString(term)
        globals["term"]["print"] = Term.PrintString(term)
        globals["term"]["newLine"] = Term.NewLine(term)
        globals["term"]["moveCursor"] = Term.MoveCursor(term) // TTY function
        globals["term"]["width"] = Term.GetWidth(term)
        globals["term"]["scroll"] = Term.Scroll(term)
        globals["term"]["isTeletype"] = Term.IsTeletype(term)
        globals["term"]["bell"] = Term.Bell(term)

        if (term is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) {
            globals["term"]["emitRaw"] = Term.EmitRaw(term)
            globals["term"]["emit"] = Term.Emit(term)
            globals["term"]["emitString"] = Term.EmitString(term)
            globals["term"]["resetColor"] = Term.ResetColour(term)
            globals["term"]["resetColour"] = Term.ResetColour(term)
            globals["term"]["clear"] = Term.Clear(term)
            globals["term"]["clearLine"] = Term.ClearLine(term)
            globals["term"]["setCursor"] = Term.SetCursor(term)
            globals["term"]["getCursor"] = Term.GetCursorPos(term)
            globals["term"]["getX"] = Term.GetCursorX(term)
            globals["term"]["getY"] = Term.GetCursorY(term)
            globals["term"]["setX"] = Term.SetCursorX(term)
            globals["term"]["setY"] = Term.SetCursorY(term)
            globals["term"]["setCursorBlink"] = Term.SetCursorBlink(term)
            globals["term"]["size"] = Term.GetSize(term)
            globals["term"]["height"] = Term.GetHeight(term)
            globals["term"]["isCol"] = Term.IsColour(term)
            globals["term"]["setForeCol"] = Term.SetForeColour(term)
            globals["term"]["setBackCol"] = Term.SetBackColour(term)
            globals["term"]["foreCol"] = Term.GetForeColour(term)
            globals["term"]["backCol"] = Term.GetBackColour(term)
        }
    }

    companion object {
        fun LuaValue.checkIBM437(): String {
            if (this is LuaString)
                return m_bytes.copyOfRange(m_offset, m_offset + m_length).toString(Charset.forName("CP437"))
                // it only works if Charset is ISO-8859, despite of the name "IBM437"
                // --> then would "CP437" work? -- Torvald at 2017-04-05
            else
                throw LuaError("bad argument (string expected, got ${this.typename()})")
        }
    }

    class Bell(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
        override fun call(pattern: LuaValue): LuaValue {
            tty.bell(pattern.checkjstring())
            return LuaValue.NONE
        }
    }

    class WriteString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
        override fun call(p0: LuaValue): LuaValue {
            if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
                tty.writeString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
            else
                tty.writeChars(p0.checkIBM437())
            return LuaValue.NONE
        }

        override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
            if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
                tty.writeString(s.checkIBM437(), x.checkint(), y.checkint())
            else
                throw LuaError("couldn't move cursor; TTY is one-dimensional")
            return LuaValue.NONE
        }
    }

    class PrintString(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : LuaFunction() {
        override fun call(p0: LuaValue): LuaValue {
            if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
                tty.printString(p0.checkIBM437(), tty.cursorX, tty.cursorY)
            else
                tty.printChars(p0.checkIBM437())
            return LuaValue.NONE
        }

        override fun call(s: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
            if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal)
                tty.printString(s.checkIBM437(), x.checkint(), y.checkint())
            else
                throw LuaError("couldn't move cursor; TTY is one-dimensional")
            return LuaValue.NONE
        }
    }

    class NewLine(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
        override fun call(): LuaValue {
            tty.newLine()
            return LuaValue.NONE
        }
    }

    class EmitRaw(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
        override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
            term.emitChar(p0.checkint(), x.checkint() - 1, y.checkint() - 1)
            return LuaValue.NONE
        }
    }

    // emitchar
    class Emit(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
        override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
            term.emitChar(p0.checkint().toChar(), x.checkint() - 1, y.checkint() - 1)
            return LuaValue.NONE
        }
    }

    class EmitString(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ThreeArgFunction() {
        override fun call(p0: LuaValue, x: LuaValue, y: LuaValue): LuaValue {
            term.emitString(p0.checkIBM437(), x.checkint() - 1, y.checkint() - 1)
            return LuaValue.NONE
        }
    }

    class ResetColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            term.resetColour()
            return LuaValue.NONE
        }
    }

    class Clear(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            term.clear()
            return LuaValue.NONE
        }
    }

    class ClearLine(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            term.clearLine()
            return LuaValue.NONE
        }
    }

    /** term.setCursorPos(number x) */
    class MoveCursor(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            for (i in 1..p0.checkint())
                tty.printChar(' ')
            return LuaValue.NONE
        }
    }

    class SetCursor(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : TwoArgFunction() {
        override fun call(x: LuaValue, y: LuaValue): LuaValue {
            term.setCursor(x.checkint() - 1, y.checkint() - 1)
            return LuaValue.NONE
        }
    }

    /** One-based */
    class GetCursorPos(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
        override fun invoke(args: Varargs?): Varargs {
            val ret = arrayOf(LuaValue.valueOf(term.cursorX + 1), LuaValue.valueOf(term.cursorY + 1))
            return LuaValue.varargsOf(ret)
        }
    }

    class GetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(term.cursorX + 1)
        }
    }

    class GetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(term.cursorY + 1)
        }
    }

    class SetCursorX(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            term.setCursor(p0.checkint() - 1, term.cursorY)
            return LuaValue.NONE
        }
    }

    class SetCursorY(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            term.setCursor(term.cursorX - 1, p0.checkint())
            return LuaValue.NONE
        }
    }

    /** term.setCursorBlink(boolean bool) */
    class SetCursorBlink(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            term.cursorBlink = p0.toboolean()
            return LuaValue.NONE
        }
    }

    class GetSize(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : VarArgFunction() {
        override fun invoke(args: Varargs?): Varargs {
            val ret = arrayOf(LuaValue.valueOf(term.width), LuaValue.valueOf(term.height))
            return LuaValue.varargsOf(ret)
        }
    }

    class GetWidth(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(tty.width)
        }
    }

    class GetHeight(val terminal: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(terminal.height)
        }
    }

    class IsColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(term.coloursCount > 4)
        }
    }

    /** term.scroll(number n) */
    class Scroll(val tty: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            if (tty is net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) tty.scroll(p0.checkint())
            else for (i in 1..p0.checkint()) tty.newLine()
            return LuaValue.NONE
        }
    }

    /** term.setTextColor(number color) */
    class SetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            term.foreColour = p0.checkint()
            return LuaValue.NONE
        }
    }

    /** term.setBackgroundColor(number color) */
    class SetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : OneArgFunction() {
        override fun call(p0: LuaValue): LuaValue {
            term.backColour = p0.checkint()
            return LuaValue.NONE
        }
    }

    class GetForeColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(term.foreColour)
        }
    }

    class GetBackColour(val term: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Terminal) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(term.backColour)
        }
    }

    class IsTeletype(val termInQuestion: net.torvald.terrarum.modulecomputers.virtualcomputer.terminal.Teletype) : ZeroArgFunction() {
        override fun call(): LuaValue {
            return LuaValue.valueOf(termInQuestion.coloursCount == 0)
        }
    }

}
