/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 * This source file is part of the Cangjie project, licensed under Apache-2.0
 * with Runtime Library Exception.
 *
 * See https://cangjie-lang.cn/pages/LICENSE for license information.
 */

// The Cangjie API is in Beta. For details on its capabilities and limitations, please refer to the README file.

package std.unittest

import std.sync.ReentrantMutex
import std.unittest.common.*
import std.process.Process

foreign func EnableANSITerminal(): Bool

foreign func DisableANSITerminal(): Unit

var isConsoleUtf8: Bool = true

open class TerminalPrettyPrinter <: PrettyPrinter {
    /**
     * Unfortunately, we need a global lock here to make sure EnableANSITerminal() is not called
     * more than once
     */
    private static let globalAnsiLock = ReentrantMutex()
    private static var globalAnsiEnabled: ?Bool = None

    private static var _instance: ?TerminalPrettyPrinter = None
    private static let lock = ReentrantMutex()

    private static func globalInitializeAnsi(): Bool {
        synchronized(globalAnsiLock) {
            match (globalAnsiEnabled) {
                case Some(bound) => return bound
                case _ => ()
            }
            let tryEnable = unsafe { EnableANSITerminal() }
            if (tryEnable) {
                Process.current.atExit {unsafe { DisableANSITerminal() }}
            }
            globalAnsiEnabled = tryEnable
            return tryEnable
        }
    }

    private static let ESC = "\u{1B}"
    // Colors
    private static let ESC_C_DEFAULT_MODE = "${ESC}[0m"
    private static let ESC_C_RED_MODE = "${ESC}[31m"
    private static let ESC_C_GREEN_MODE = "${ESC}[32m"
    private static let ESC_C_YELLOW_MODE = "${ESC}[33m"
    private static let ESC_C_BLUE_MODE = "${ESC}[34m"
    private static let ESC_C_MAGENTA_MODE = "${ESC}[35m"
    private static let ESC_C_CYAN_MODE = "${ESC}[36m"
    private static let ESC_C_GRAY_MODE = "${ESC}[90m"

    // Cursor
    private static func escLinesUp(lines: Int64) { "${ESC}[${lines}F" } // `lines` up and move to the column start
    private static func escLinesDown(lines: Int64) { "${ESC}[${lines}E" } // `lines` down and move to the column start
    private static func escCursorHorizontalPos(column: Int64) { "${ESC}[${column}G" } // Sets cursor column
    private static func escCursorRight(shift: Int64) { "${ESC}[${shift}C" } // Sets cursor column
    private static let ESC_CURSOR_SAVE = "${ESC}7" // Saves cursor position & attributes
    private static let ESC_CURSOR_RESTORE = "${ESC}8" // Restores previously saved cursor position & attributes
    private static let ESC_CURSOR_HIDE = "${ESC}[?25l"
    private static let ESC_CURSOR_SHOW = "${ESC}[?25h"

    // Scrollable margins
    // sets scrollable margins. Start value begins from 1.
    private static func escSetScrollableMargins(start: Int64, endInclusive: Int64) { "${ESC}[${start};${endInclusive}r" }
    // sets scrollable margins from the first line to `endInclusive`
    private static func escSetScrollableMarginsEnd(endInclusive!: Int64) { "${ESC}[1;${endInclusive}r" }
    // sets scrollable margins from the `start` to the last line of the terminal bounds
    private static func escSetScrollableMarginsStart(start!: Int64) { "${ESC}[${start};r" }
    // resets scrollable margins to the whole terminal bounds
    private static let ESC_RESET_SCROLLABLE_MARGINS = "${ESC}[;r"

    // Clear
    private static let ESC_CLEAR_AHEAD = "${ESC}[0J" // Clears the whole screen after the current cursor position
    private static let ESC_CLEAR_LINE = "${ESC}[2K" // Clears the whole line

    private let areColorsEnabled: Bool
    private let areOtherAnsiEnabled: Bool

    /**
     * @p isColored: should colors be displayed (via ANSI)
     * @p isOtherAnsiAble: should other ANSI escape codes be displayed such as "move cursor", "clear screen", etc
     */
    TerminalPrettyPrinter(isColored: Bool, isOtherAnsiAble!: Bool = true) {
        let isAnsiEnabled = globalInitializeAnsi()
        this.areColorsEnabled = isColored && isAnsiEnabled
        this.areOtherAnsiEnabled = isOtherAnsiAble && isAnsiEnabled
        isConsoleUtf8 = setUtf8OutputCP()
    }

    init(configuration: Configuration) {
        this(!(configuration.get(KeyNoColor.noColor) ?? false))
    }

    /**
     * Actual terminal height. Could equal `None` in case of terminal size is unavailable for some reason.
     */
    prop terminalHeight: ?UInt64 {
        get() {
            match (unsafe { GetTerminalHeight() }) {
                case 0u64 => None
                case x => x
            }
        }
    }

    /**
     * Actual terminal width. Could equal `None` in case of terminal size is unavailable for some reason.
     */
    prop terminalWidth: ?UInt64 {
        get() {
            match (unsafe { GetTerminalWidth() }) {
                case 0u64 => None
                case x => x
            }
        }
    }

    func exclusive<T>(action: (TerminalPrettyPrinter) -> T): T {
        synchronized(lock) {
            action(this)
        }
    }

    /**
     * Performs the cursor move for [lines] lines up. Scrolling-down is absent.
     */
    func up(lines: Int64) {
        if (!areOtherAnsiEnabled || lines <= 0) { return }
        put(escLinesUp(lines))
    }

    /**
     * Performs the cursor move for [lines] lines down. Scrolling-down is absent.
     */
    func down(lines: Int64) {
        if (!areOtherAnsiEnabled || lines <= 0) { return }
        put(escLinesDown(lines))
    }

    func toBottom() {
        down(9999) // There is no common way to ask the terminal go to the bottom
    }

    func setColumn(column!: Int64 = 1) {
        if (!areOtherAnsiEnabled) { return }
        put(escCursorHorizontalPos(column))
    }

    func saveCursorPos() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CURSOR_SAVE)
        flush()
    }

    func restoreCursorPos() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CURSOR_RESTORE)
        flush()
    }

    func hideCursor() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CURSOR_HIDE)
        flush()
    }

    func showCursor() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CURSOR_SHOW)
        flush()
    }

    func setScrollableMargins(start!: ?Int64 = None, end!: ?Int64 = None) {
        if (!areOtherAnsiEnabled) { return }

        match ((start, end)) {
            case (None, None) => put(ESC_RESET_SCROLLABLE_MARGINS)
            case (Some(s), None) => put(escSetScrollableMarginsStart(start: s))
            case (None, Some(e)) => put(escSetScrollableMarginsEnd(endInclusive: e))
            case (Some(s), Some(e)) => put(escSetScrollableMargins(s, e))
        }
        flush()
    }

    func clearAhead() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CLEAR_AHEAD)
    }

    func clearLine() {
        if (!areOtherAnsiEnabled) { return }
        put(ESC_CLEAR_LINE)
    }

    protected override open func put(s: String): Unit {
        print(s)
    }

    protected func flush(): Unit {
        print("", flush: true)
    }

    protected override func setColor(color: Color): Unit {
        if (!areColorsEnabled) {
            return
        }

        let command = match (color) {
            case RED => ESC_C_RED_MODE
            case GREEN => ESC_C_GREEN_MODE
            case YELLOW => ESC_C_YELLOW_MODE
            case BLUE => ESC_C_BLUE_MODE
            case CYAN => ESC_C_CYAN_MODE
            case MAGENTA => ESC_C_MAGENTA_MODE
            case GRAY => ESC_C_GRAY_MODE
            case DEFAULT_COLOR => ESC_C_DEFAULT_MODE
        }
        put(command)
    }

    static prop instance: TerminalPrettyPrinter {
        get() {
            synchronized(lock) {
                match (_instance) {
                    case Some(inst) => inst
                    case None =>
                        let defaultConfig = defaultConfiguration()
                        let inst = TerminalPrettyPrinter(defaultConfig)
                        _instance = inst
                        inst
                }
            }
        }
    }

    public func fillLimitedSpace(spaceSize: Int64, body: () -> Unit): PrettyPrinter {
        if (areOtherAnsiEnabled && areColorsEnabled) {
            put(ESC_CURSOR_SAVE)
        }
        body()
        if (areOtherAnsiEnabled && areColorsEnabled) {
            put(ESC_CURSOR_RESTORE)
            put(escCursorRight(spaceSize))
        }
        return this
    }

    static func fromDefaultConfiguration(): TerminalPrettyPrinter {
        instance
    }
}

@When[os != "Windows"]
func setUtf8OutputCP(): Bool { true }

@When[os == "Windows"]
foreign func SetConsoleOutputCP(cp: UInt32): Int32

@When[os == "Windows"]
func setUtf8OutputCP(): Bool {
    const CP_UTF8: UInt32 = 65001
    unsafe { SetConsoleOutputCP(CP_UTF8) != 0 }
}

foreign func GetTerminalWidth(): UInt64

foreign func GetTerminalHeight(): UInt64