package SBC;

import com.sun.jna.Platform;
import org.jetbrains.annotations.NotNull;
import org.tinylog.Logger;

import java.nio.file.Files;
import java.nio.file.Path;

public class GPIO {

    public static final Path gpioPath = Path.of("/sys/class/gpio");
    private static final Path gpioExportPath = Path.of("/sys/class/gpio/export");
    private static final Path gpioUnexportPath = Path.of("/sys/class/gpio/unexport");


    public static boolean HaveGPIO(){
        if (Platform.isLinux()){
            if (gpioPath.toFile().isDirectory()){
                if (gpioExportPath.toFile().isFile()){
                    if (gpioUnexportPath.toFile().isFile()){
                        return true;
                    } else Logger.error("GPIO unexport path is not found");
                } else Logger.error("GPIO export path is not found");
            } else Logger.error("GPIO path is not found");
        } else Logger.info("OS is not Linux");
        return false;
    }

    /**
     * Check if the pin is already exported
     * @param pin GPIO pin number
     * @return true if the pin is already exported
     */
    public static boolean GpioPinExists(@NotNull RaspberryPi5BPins pin){
        Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber);
        return pinPath.toFile().isDirectory();
    }

    /**
     * Export the pin
     * @param pin GPIO pin number
     * @return true if the pin is successfully exported
     */
    public static boolean ExportPin(@NotNull RaspberryPi5BPins pin){
        try{
            if (Files.isWritable(gpioExportPath)){
                Files.write(gpioExportPath, String.valueOf(pin.gpionumber).getBytes());
                Logger.info("Pin "+pin.pin+" exported");
                return GpioPinExists(pin);
            } else Logger.error("GPIO export path is not writable");
        } catch (Exception e){
            Logger.error("Failed to export pin: "+pin.pin+", Message: "+e.getMessage());
        }
        return false;
    }

    /**
     * Unexport the pin
     * @param pin GPIO pin number
     * @return true if the pin is successfully unexported
     */
    public static boolean UnexportPin(@NotNull RaspberryPi5BPins pin){
        if (Files.isWritable(gpioUnexportPath)){
            try{
                Files.write(gpioUnexportPath, String.valueOf(pin.gpionumber).getBytes());
                Logger.info("Pin "+pin.pin+" unexported");
                return true;
            } catch (Exception e){
                Logger.error("Failed to unexport pin: "+pin.pin+", Message: "+e.getMessage());
            }
        } else Logger.error("GPIO unexport path is not writable");

        return false;
    }

    /**
     * Get Direction of the pin
     * @param pin GPIO pin number
     * @return "in" if the pin is input, "out" if the pin is output, "unknown" if the direction is unknown
     */
    @SuppressWarnings("unused")
    public static String GetPinDirection(@NotNull RaspberryPi5BPins pin){
        Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("direction");
        if (pinPath.toFile().isFile()){
            if (Files.isReadable(pinPath)){
                try{
                    return Files.readString(pinPath).trim();
                } catch (Exception e){
                    Logger.error("Failed to read pin direction: "+pin.pin+", Message: "+e.getMessage());
                }
            } else Logger.error("Pin direction file is not readable: "+pin.pin);
        } else Logger.error("Pin direction file not found: "+pin.pin);
        return "unknown";
    }

    /**
     * Set the direction of the pin
     * @param pin GPIO pin number
     * @param direction "in" for input, "out" for output
     * @return true if the direction is successfully set
     */
    public static boolean SetPinDirection(@NotNull RaspberryPi5BPins pin, String direction){
        Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("direction");
        if (pinPath.toFile().isFile()){
            if (Files.isWritable(pinPath)){
                direction = direction.trim().toLowerCase();
                if ("in".equals(direction) || "out".equals(direction)){
                    try{
                        Files.write(pinPath, direction.getBytes());
                        Logger.info("Pin "+pin.pin+" direction set to "+direction);
                        return true;
                    } catch (Exception e){
                        Logger.error("Failed to set pin direction: "+pin.pin+", Message: "+e.getMessage());
                    }
                } else Logger.error("Invalid direction: "+direction);
            } else Logger.error("Pin direction file is not writable: "+pin.pin);
        } else Logger.error("Pin direction file not found: "+pin.pin);
        return false;
    }

    /**
     * Set the value of the output pin
     * @param pin GPIO pin number
     * @param isON true to set the pin value to 1, false to set the pin value to 0
     * @return true if the value is successfully set
     */
    @SuppressWarnings("UnusedReturnValue")
    public static boolean SetValue(@NotNull RaspberryPi5BPins pin, boolean isON){
        Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("value");
        if (pinPath.toFile().isFile()){
            if (Files.isWritable(pinPath)){
                try{
                    Files.write(pinPath, isON?"1".getBytes():"0".getBytes());
                    //Logger.info("Pin "+pin.pin+" value set to "+(isON?"1":"0"));
                    return true;
                } catch (Exception e){
                    Logger.error("Failed to set pin value: "+pin.pin+", Message: "+e.getMessage());
                }
            } else Logger.error("Pin value file is not writable: "+pin.pin);
        } else Logger.error("Pin value file not found: "+pin.pin);
        return false;
    }

    /**
     * Get the value of the pin
     * @param pin GPIO pin number
     * @return "1" if the pin value is 1, "0" if the pin value is 0, "unknown" if the value is unknown
     */
    @SuppressWarnings("unused")
    public static String GetValue(@NotNull RaspberryPi5BPins pin){
        Path pinPath = gpioPath.resolve("gpio"+pin.gpionumber).resolve("value");
        if (pinPath.toFile().isFile()){
            if (Files.isReadable(pinPath)){
                try{
                    return Files.readString(pinPath).trim();
                } catch (Exception e){
                    Logger.error("Failed to read pin value: "+pin.pin+", Message: "+e.getMessage());
                }
            } else Logger.error("Pin value file is not readable: "+pin.pin);
        } else Logger.error("Pin value file not found: "+pin.pin);
        return "unknown";
    }
}
