// Generic Interrupt Pins.

package generic

import (
	"errors"
	"fmt"
	"netease_control_center/interfaces/log"
	embd "netease_control_center/nembd"
	"sync"
	"syscall"
	"time"
)

const (
	MaxGPIOInterrupt = 64
)

var ErrorPinAlreadyRegistered = errors.New("pin interrupt already registered")

type interrupt struct {
	pin            embd.DigitalPin
	initialTrigger bool
	handler        func(embd.DigitalPin)
}

func (i *interrupt) Signal() {
	if !i.initialTrigger {
		i.initialTrigger = true
		return
	}
	i.handler(i.pin)
}

type epollListener struct {
	mu                sync.Mutex // Guards the following.
	fd                int
	interruptablePins map[int]*interrupt
}

var epollListenerInstance *epollListener

func getEpollListenerInstance() *epollListener {
	if epollListenerInstance == nil {
		epollListenerInstance = initEpollListener()
	}
	return epollListenerInstance
}

func initEpollListener() *epollListener {
	fd, err := syscall.EpollCreate1(0)
	if err != nil {
		panic(fmt.Sprintf("Unable to create epoll: %v", err))
	}
	listener := &epollListener{fd: fd, interruptablePins: make(map[int]*interrupt)}

	go func() {
		var epollEvents [MaxGPIOInterrupt]syscall.EpollEvent

		for {
			n, err := syscall.EpollWait(listener.fd, epollEvents[:], -1)
			if err != nil {
				log.Println(log.ErrorLog, "EpollWait error:", err)
				time.Sleep(time.Millisecond * 10)
				continue
			}
			listener.mu.Lock()
			for i := 0; i < n; i++ {
				if irq, ok := listener.interruptablePins[int(epollEvents[i].Fd)]; ok {
					irq.Signal()
				}
			}
			listener.mu.Unlock()
		}
	}()
	return listener
}

func registerInterrupt(pin *digitalPin, handler func(embd.DigitalPin)) (reterr error) {
	defer func() {
		if err := recover(); err != nil {
			reterr = errors.New(fmt.Sprintln(err))
		}
	}()
	l := getEpollListenerInstance()

	pinFd := int(pin.val.Fd())

	l.mu.Lock()
	defer l.mu.Unlock()

	if _, ok := l.interruptablePins[pinFd]; ok {
		return ErrorPinAlreadyRegistered
	}

	var event syscall.EpollEvent
	event.Events = syscall.EPOLLIN | (syscall.EPOLLET & 0xffffffff) | syscall.EPOLLPRI

	if err := syscall.SetNonblock(pinFd, true); err != nil {
		return err
	}

	event.Fd = int32(pinFd)

	if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_ADD, pinFd, &event); err != nil {
		return err
	}

	l.interruptablePins[pinFd] = &interrupt{pin: pin, handler: handler}

	return nil
}

func unregisterInterrupt(pin *digitalPin) (reterr error) {
	defer func() {
		if err := recover(); err != nil {
			reterr = errors.New(fmt.Sprintln(err))
		}
	}()

	l := getEpollListenerInstance()

	pinFd := int(pin.val.Fd())

	l.mu.Lock()
	defer l.mu.Unlock()

	if _, ok := l.interruptablePins[pinFd]; !ok {
		return nil
	}

	if err := syscall.EpollCtl(l.fd, syscall.EPOLL_CTL_DEL, pinFd, nil); err != nil {
		return err
	}

	if err := syscall.SetNonblock(pinFd, false); err != nil {
		return err
	}

	delete(l.interruptablePins, pinFd)
	return nil
}
