// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer;

import java.io.IOException;
import net.avadeaux.klipspringer.codec.Library;

/** Unix terminal stuff. */
public class Terminal {
    static {
        Library.init();

        Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    try { cancelNonCanonical(); } catch (IOException ex) { }
                }
            });
    }

    private static boolean nonCanonical = false, interruptEnabled = false;

    /** Checks if input is a terminal that can be used for interaction. */
    public static native boolean inTty();

    /** Checks if output is a terminal that can be used for interaction. */
    public static native boolean outTty();

    /** Checks if error output is a terminal that can be used for interaction. */
    public static native boolean errTty();

    /** Prepares for using {@link #getChar()} for getting keypresses interactively, by disabling
      * canonical mode and echoing. Terminal input will not function normally again until {@link
      * #stopKeyListen()} called. Does nothing if already in key listening mode.
      */
    public static synchronized void startKeyListen() throws IOException {
        if (nonCanonical) { return; }
        setupNonCanonical();
        nonCanonical = true;
    }

    /** Reset terminal input to normal operation after invoking {@link #startKeyListen()}. Does
      * nothing if terminal is not in key listening mode.
      */
    public static synchronized void stopKeyListen() throws IOException {
        if (!nonCanonical) { return; }
        cancelNonCanonical();
        nonCanonical = false;
    }

    /** Prepares for prematurely interrupting {@link #getChar()} by setting up a signal
      * handler. Does nothing if already in interruptable mode.
      */
    public static synchronized void enableInterruptGetChar() throws IOException {
        if (interruptEnabled) { return; }
        setupSigaction();
        interruptEnabled = true;
    }

    /** Restores signal handler to normal, disabling the possibility to interrupt {@link
      * #getChar()}. Does nothing if not in interruptable mode.
      */
    public static synchronized void disableInterruptGetChar() throws IOException {
        if (!interruptEnabled) { return; }
        cancelSigaction();
        interruptEnabled = false;
    }

    /** Gets a single character from terminal input. If canonical mode has been disabled by calling
      * {@link #startKeyListen()}, this can be used for interactively getting a keypress, without
      * waiting for newline. Returns {@code -1} if interrupted.
      */
    public static native int getChar() throws IOException;

    /** If there is a {@link #getChar()} in progress, and {@link #enableInterruptGetChar} has been
      * enable, make it terminate early and return {@code -1}, by sending a signal to its
      * thread. Throws {@link IllegalStateExe
      */
    public static synchronized void interruptGetChar() throws IOException {
        if (!interruptEnabled) { throw new IllegalStateException("Not in interruptable state"); }
        sigThread();
    }

    private static native void setupNonCanonical() throws IOException;
    private static native void cancelNonCanonical() throws IOException;
    private static native void setupSigaction() throws IOException;
    private static native void cancelSigaction() throws IOException;
    private static native void sigThread() throws IOException;
}
