package net.jselby.kotgb.desktop

import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
import com.badlogic.gdx.files.FileHandle
import javafx.application.Application
import mu.KotlinLogging
import net.jselby.kotgb.EmulationEnvironment
import net.jselby.kotgb.KotGB
import net.jselby.kotgb.cpu.CPU
import net.jselby.kotgb.desktop.debugger.DebuggingApplication
import net.jselby.kotgb.desktop.jit.JITExecutor
import net.jselby.kotgb.desktop.jit.JITTest
import net.jselby.kotgb.lwjgl.io.FileDialog
import net.sourceforge.argparse4j.ArgumentParsers
import net.sourceforge.argparse4j.impl.Arguments
import net.sourceforge.argparse4j.inf.ArgumentParserException
import net.sourceforge.argparse4j.inf.Namespace
import org.apache.logging.log4j.Level
import org.apache.logging.log4j.core.Filter
import org.apache.logging.log4j.core.appender.ConsoleAppender
import org.apache.logging.log4j.core.config.Configurator
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory
import java.io.File
import java.util.*
import kotlin.concurrent.thread

object DesktopLauncher {
    @JvmStatic fun main(args: Array<String>) {
        // Set up logging via Log4j
        val builder = ConfigurationBuilderFactory.newConfigurationBuilder()
        builder.setStatusLevel(Level.ERROR)
        builder.setConfigurationName("KotGB on Desktop")
        builder.add(builder.newFilter("ThresholdFilter", Filter.Result.ACCEPT, Filter.Result.NEUTRAL)
                .addAttribute("level", Level.DEBUG))
        val appenderBuilder = builder.newAppender("Stdout", "CONSOLE").addAttribute("target",
                ConsoleAppender.Target.SYSTEM_OUT)
        appenderBuilder.add(builder.newLayout("PatternLayout")
                .addAttribute("pattern", "[%d{HH:mm:ss.SSS}] [%t] [%level] %C{1}: %msg%n"))
        appenderBuilder.add(builder.newFilter("MarkerFilter", Filter.Result.DENY, Filter.Result.NEUTRAL)
                .addAttribute("marker", "FLOW"))
        builder.add(appenderBuilder)
        builder.add(builder.newLogger("org.apache.logging.log4j", Level.DEBUG)
                .add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false))
        builder.add(builder.newRootLogger(Level.DEBUG).add(builder.newAppenderRef("Stdout")).addAttribute("additivity", false))
        Configurator.initialize(builder.build())

        val logger = KotlinLogging.logger {}

        val parser = ArgumentParsers.newArgumentParser("KotGB", true)
                .description("A Gameboy Emulator, written in Kotlin")
        parser.addArgument("--file")
                .dest("file")
                .help("The path of a file to load immediately")
                .required(false)
                .nargs(1)
                .type(String::class.java)
        parser.addArgument("--breakAt")
                .dest("breakpoints")
                .help("Adds a breakpoint")
                .required(false)
                .nargs("+")
                .setDefault(ArrayList<String>())
                .type(String::class.java)
        parser.addArgument("--debug")
                .dest("debug")
                .action(Arguments.storeTrue())
                .help("Enables debugging by default")
                .setDefault(false)
                .required(false)
        parser.addArgument("--jit")
                .dest("jit")
                .action(Arguments.storeTrue())
                .help("Uses the experimental JIT recompiler")
                .setDefault(false)
                .required(false)
        var namespace: Namespace? = null
        try {
            namespace = parser.parseArgs(args)
        } catch (e: ArgumentParserException) {
            parser.handleError(e)
            System.exit(1)
        }

        val parsedArgs = namespace!!.attrs

        val useJit = parsedArgs.containsKey("jit")
                && parsedArgs["jit"] != null && parsedArgs["jit"] as Boolean
        var jitWorks = false

        if (useJit) {
            try {
                JITTest().test()

                jitWorks = true
            } catch (e: Exception) {
                logger.error(e, { "JIT is unavailable" })
            } catch (e: Error) {
                logger.error(e, { "JIT is unavailable" })
            }
        }

        val env = EmulationEnvironment(
                cpuFactory = {
                    if (useJit && jitWorks)
                        CPU(it, JITExecutor())
                    else
                        CPU(it)
                },
                cpuState = {
                    if (useJit && jitWorks) {
                        "Cached blocks: ${(it.executor as JITExecutor).manager.getCount()}," +
                                " errors: ${(it.executor as JITExecutor).manager.errorCount}"
                    } else {
                        null
                    }
                },
                openFile = {
                    callback ->
                    val dialog = FileDialog()
                    dialog.addFilter(".gb", ".gbc")
                    val path = dialog.openSingle()
                    if (path != null) {
                        callback(FileHandle(path))
                    }
                }
        )

        thread {
            Thread.sleep(10000)
            Application.launch(DebuggingApplication::class.java)
        }

        val config = Lwjgl3ApplicationConfiguration()
        config.setWindowedMode(800, 600)
        config.useVsync(true)
        Lwjgl3Application(KotGB(parsedArgs, env), config)
    }
}
