package main

import (
	"fmt"
	"os"
	"sync"

	"k8s.io/klog"
)

// gpio is an abstract GPIO output.
type gpio interface {
	// set returns the GPIO value. The meaning of the logic level is
	// implementation-dependent.
	set(state bool) error
}

// bbGPIO implements gpio using BeagleBone's built-in GPIO pins.
//
// The value of a GPIO is logically non-inverted: false is 0V, true is 3.3V.
//
// The GPIO can be repeatedly set to the same value without performance penalty
// - only level changes are actually written to hardware registers.
type bbGPIO struct {
	path  string
	mu    sync.Mutex
	state bool
}

// newBBGPIO returns a BeagleBone GPIO for a given GPIO number.
//
// See the following for GPIO pin numbers ('GPIO NO.' column):
//
//	 https://vadl.github.io/images/bbb/P8Header.png
//		https://vadl.github.io/images/bbb/P9Header.png
func newBBGPIO(num int, value bool) (*bbGPIO, error) {
	path := fmt.Sprintf("/sys/class/gpio/gpio%d", num)

	if _, err := os.Stat(path); err != nil {
		return nil, fmt.Errorf("could not access: %w", err)
	}

	pathDir := path + "/direction"
	if err := os.WriteFile(pathDir, []byte("out"), 0); err != nil {
		return nil, fmt.Errorf("could not set direction: %w", err)
	}

	pathValue := path + "/value"
	res := &bbGPIO{
		path: pathValue,
	}
	if err := res.set(value); err != nil {
		return nil, fmt.Errorf("when setting initial value: %w", err)
	}

	return res, nil
}

func (b *bbGPIO) set(state bool) error {
	b.mu.Lock()
	defer b.mu.Unlock()
	if state && !b.state {
		if err := os.WriteFile(b.path, []byte("1"), 0); err != nil {
			return fmt.Errorf("could not turn on: %w", err)
		}
	} else if !state && b.state {
		if err := os.WriteFile(b.path, []byte("0"), 0); err != nil {
			return fmt.Errorf("could not turn off: %w", err)
		}
	}
	b.state = state
	return nil
}

// fakeGPIO implements a GPIO that logs state changes.
type fakeGPIO struct {
	desc  string
	mu    sync.Mutex
	state bool
}

func (b *fakeGPIO) set(state bool) error {
	b.mu.Lock()
	defer b.mu.Unlock()
	if state && !b.state {
		klog.Infof("%s on", b.desc)
	} else if !state && b.state {
		klog.Infof("%s off", b.desc)
	}
	b.state = state
	return nil
}
