package org.kson

import java.io.File
import java.io.StringWriter
import java.util.Stack

/**
 * This class implements a C preprocessor, providing the absolute minimum of features necessary to
 * handle the C header files generated by Kotlin Native (i.e., `kson.h`).
 *
 * Our Kson bindings are calling Kotlin code through a native library that exposes a C ABI. The
 * accompanying `kson.h` header file describes the API and gets loaded by programming languages to know which
 * functions, structs, etc. are available in the native library. Unfortunately, not all languages support the basic C
 * features present in the generated `kson.h` file. Hence, the need for our own tiny C preprocessor
 * that simplifies the file before passing it on to programming languages (for context, the immediate cause
 * for creating [TinyCPreprocessor] was the inability of Python's CFFI library to load the original `kson.h`).
 *
 * For more details about the role of the C Preprocessor in C programs, see the (very) concise
 * overview at https://www.w3schools.com/c/c_macros.php
 */
class TinyCPreprocessor {
    fun preprocess(sourcePath: String) : String {
        val targetFile = StringWriter()
        val scopes = Stack<Boolean>()
        File(sourcePath).forEachLine {
            // Handle directives
            if (it.startsWith("#define")) {
                // We ignore defines
                return@forEachLine
            } else if (it.startsWith("#ifndef")) {
                // Nothing is ever defined, so this will always be true
                scopes.push(true)
                return@forEachLine
            } else if (it.startsWith("#ifdef")) {
                // Nothing is ever defined, so this will always be false
                scopes.push(false)
                return@forEachLine
            } else if (it.startsWith("#else")) {
                // Flip the scope's include/exclude flag
                if (scopes.isNotEmpty()) {
                    scopes.push(!scopes.pop())
                }
                return@forEachLine
            } else if (it.startsWith("#endif")) {
                // Exit scope
                if (scopes.isNotEmpty()) {
                    scopes.pop()
                }
                return@forEachLine
            }

            // Remove unsupported `__attribute__` usage
            if (it.contains("typedef float __attribute__")) {
                return@forEachLine
            }

            if (scopes.isEmpty() || scopes.peek()) {
                targetFile.append(it)
                targetFile.append("\n")
            }
        }

        return targetFile.toString()
    }
}