// Copyright 2016 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package allwinner

import (
	"errors"
	"fmt"
	"os"
	"path"
	"strconv"
	"strings"
	"time"

	"periph.io/x/conn/v3/driver/driverreg"
	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/gpio/gpioreg"
	"periph.io/x/conn/v3/physic"
	"periph.io/x/conn/v3/pin"
	"periph.io/x/host/v3/pmem"
	"periph.io/x/host/v3/sysfs"
)

// All the pins in the PL group.
var PL0, PL1, PL2, PL3, PL4, PL5, PL6, PL7, PL8, PL9, PL10, PL11, PL12 *PinPL

// PinPL defines one CPU supported pin in the PL group.
//
// PinPL implements gpio.PinIO.
type PinPL struct {
	// Immutable.
	offset      uint8     // as per register offset calculation
	name        string    // name as per datasheet
	defaultPull gpio.Pull // default pull at startup

	// Immutable after driver initialization.
	sysfsPin  *sysfs.Pin // Set to the corresponding sysfs.Pin, if any.
	available bool       // Set when the pin is available on this CPU architecture.

	// Mutable.
	usingEdge bool // Set when edge detection is enabled.
}

// String implements conn.Resource.
//
// It returns the pin name and number, ex: "PL5(352)".
func (p *PinPL) String() string {
	return fmt.Sprintf("%s(%d)", p.name, p.Number())
}

// Halt implements conn.Resource.
//
// It stops edge detection if enabled.
func (p *PinPL) Halt() error {
	if p.usingEdge {
		if err := p.sysfsPin.Halt(); err != nil {
			return p.wrap(err)
		}
		p.usingEdge = false
	}
	return nil
}

// Name implements pin.Pin.
//
// It returns the pin name, ex: "PL5".
func (p *PinPL) Name() string {
	return p.name
}

// Number implements pin.Pin.
//
// It returns the GPIO pin number as represented by gpio sysfs.
func (p *PinPL) Number() int {
	return 11*32 + int(p.offset)
}

// Function implements pin.Pin.
func (p *PinPL) Function() string {
	return string(p.Func())
}

// Func implements pin.PinFunc.
func (p *PinPL) Func() pin.Func {
	if !p.available {
		// We do not want the error message about uninitialized system.
		return pin.FuncNone
	}
	if drvGPIOPL.gpioMemoryPL == nil {
		if p.sysfsPin == nil {
			return pin.FuncNone
		}
		return p.sysfsPin.Func()
	}
	switch f := p.function(); f {
	case in:
		if p.FastRead() {
			return gpio.IN_HIGH
		}
		return gpio.IN_LOW
	case out:
		if p.FastRead() {
			return gpio.OUT_HIGH
		}
		return gpio.OUT_LOW
	case alt1:
		if s := mappingPL[p.offset][0]; len(s) != 0 {
			return pin.Func(s)
		}
		return pin.Func("ALT1")
	case alt2:
		if s := mappingPL[p.offset][1]; len(s) != 0 {
			return pin.Func(s)
		}
		return pin.Func("ALT2")
	case alt3:
		if s := mappingPL[p.offset][2]; len(s) != 0 {
			return pin.Func(s)
		}
		return pin.Func("ALT3")
	case alt4:
		if s := mappingPL[p.offset][3]; len(s) != 0 {
			return pin.Func(s)
		}
		return pin.Func("ALT4")
	case alt5:
		if s := mappingPL[p.offset][4]; len(s) != 0 {
			if strings.Contains(string(s), "_EINT") {
				// It's an input supporting interrupts.
				if p.FastRead() {
					return gpio.IN_HIGH
				}
				return gpio.IN_LOW
			}
			return pin.Func(s)
		}
		return pin.Func("ALT5")
	case disabled:
		return pin.FuncNone
	default:
		return pin.FuncNone
	}
}

// SupportedFuncs implements pin.PinFunc.
func (p *PinPL) SupportedFuncs() []pin.Func {
	f := make([]pin.Func, 0, 2+2)
	f = append(f, gpio.IN, gpio.OUT)
	for _, m := range mappingPL[p.offset] {
		if m != pin.FuncNone && !strings.Contains(string(m), "_EINT") {
			f = append(f, m)
		}
	}
	return f
}

// SetFunc implements pin.PinFunc.
func (p *PinPL) SetFunc(f pin.Func) error {
	switch f {
	case gpio.FLOAT:
		return p.In(gpio.Float, gpio.NoEdge)
	case gpio.IN:
		return p.In(gpio.PullNoChange, gpio.NoEdge)
	case gpio.IN_LOW:
		return p.In(gpio.PullDown, gpio.NoEdge)
	case gpio.IN_HIGH:
		return p.In(gpio.PullUp, gpio.NoEdge)
	case gpio.OUT_HIGH:
		return p.Out(gpio.High)
	case gpio.OUT_LOW:
		return p.Out(gpio.Low)
	default:
		isGeneral := f == f.Generalize()
		for i, m := range mappingPL[p.offset] {
			if m == f || (isGeneral && m.Generalize() == f) {
				if err := p.Halt(); err != nil {
					return err
				}
				switch i {
				case 0:
					p.setFunction(alt1)
				case 1:
					p.setFunction(alt2)
				case 2:
					p.setFunction(alt3)
				case 3:
					p.setFunction(alt4)
				case 4:
					p.setFunction(alt5)
				}
				return nil
			}
		}
		return p.wrap(errors.New("unsupported function"))
	}
}

// In implements gpio.PinIn.
func (p *PinPL) In(pull gpio.Pull, edge gpio.Edge) error {
	if !p.available {
		// We do not want the error message about uninitialized system.
		return p.wrap(errors.New("not available on this CPU architecture"))
	}
	if p.usingEdge && edge == gpio.NoEdge {
		if err := p.sysfsPin.Halt(); err != nil {
			return p.wrap(err)
		}
		p.usingEdge = false
	}
	if drvGPIOPL.gpioMemoryPL == nil {
		if p.sysfsPin == nil {
			return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?"))
		}
		if pull != gpio.PullNoChange {
			return p.wrap(errors.New("pull cannot be used when subsystem gpiomem not initialized; try running as root?"))
		}
		if err := p.sysfsPin.In(pull, edge); err != nil {
			return p.wrap(err)
		}
		p.usingEdge = edge != gpio.NoEdge
		return nil
	}
	if !p.setFunction(in) {
		return p.wrap(errors.New("failed to set pin as input"))
	}
	if pull != gpio.PullNoChange {
		off := p.offset / 16
		shift := 2 * (p.offset % 16)
		// Do it in a way that is concurrent safe.
		drvGPIOPL.gpioMemoryPL.pull[off] &^= 3 << shift
		switch pull {
		case gpio.PullDown:
			drvGPIOPL.gpioMemoryPL.pull[off] = 2 << shift
		case gpio.PullUp:
			drvGPIOPL.gpioMemoryPL.pull[off] = 1 << shift
		default:
		}
	}
	if edge != gpio.NoEdge {
		if p.sysfsPin == nil {
			return p.wrap(fmt.Errorf("pin %d is not exported by sysfs", p.Number()))
		}
		// This resets pending edges.
		if err := p.sysfsPin.In(gpio.PullNoChange, edge); err != nil {
			return p.wrap(err)
		}
		p.usingEdge = true
	}
	return nil
}

// Read implements gpio.PinIn.
func (p *PinPL) Read() gpio.Level {
	if drvGPIOPL.gpioMemoryPL == nil {
		if p.sysfsPin == nil {
			return gpio.Low
		}
		return p.sysfsPin.Read()
	}
	return gpio.Level(drvGPIOPL.gpioMemoryPL.data&(1<<p.offset) != 0)
}

// FastRead reads without verification.
func (p *PinPL) FastRead() gpio.Level {
	return gpio.Level(drvGPIOPL.gpioMemoryPL.data&(1<<p.offset) != 0)
}

// WaitForEdge implements gpio.PinIn.
func (p *PinPL) WaitForEdge(timeout time.Duration) bool {
	if p.sysfsPin != nil {
		return p.sysfsPin.WaitForEdge(timeout)
	}
	return false
}

// Pull implements gpio.PinIn.
func (p *PinPL) Pull() gpio.Pull {
	if drvGPIOPL.gpioMemoryPL == nil {
		// If gpioMemoryPL is set, p.available is true.
		return gpio.PullNoChange
	}
	switch (drvGPIOPL.gpioMemoryPL.pull[p.offset/16] >> (2 * (p.offset % 16))) & 3 {
	case 0:
		return gpio.Float
	case 1:
		return gpio.PullUp
	case 2:
		return gpio.PullDown
	default:
		// Confused.
		return gpio.PullNoChange
	}
}

// DefaultPull implements gpio.PinIn.
func (p *PinPL) DefaultPull() gpio.Pull {
	return p.defaultPull
}

// Out implements gpio.PinOut.
func (p *PinPL) Out(l gpio.Level) error {
	if !p.available {
		// We do not want the error message about uninitialized system.
		return p.wrap(errors.New("not available on this CPU architecture"))
	}
	if drvGPIOPL.gpioMemoryPL == nil {
		if p.sysfsPin != nil {
			return p.wrap(errors.New("subsystem gpiomem not initialized and sysfs not accessible; try running as root?"))
		}
		return p.sysfsPin.Out(l)
	}
	// First disable edges.
	if err := p.Halt(); err != nil {
		return err
	}
	p.FastOut(l)
	if !p.setFunction(out) {
		return p.wrap(errors.New("failed to set pin as output"))
	}
	return nil
}

// FastOut sets a pin output level with Absolutely No error checking.
//
// See Pin.FastOut for more information.
func (p *PinPL) FastOut(l gpio.Level) {
	bit := uint32(1 << p.offset)
	if l {
		drvGPIOPL.gpioMemoryPL.data |= bit
	} else {
		drvGPIOPL.gpioMemoryPL.data &^= bit
	}
}

// PWM implements gpio.PinOut.
func (p *PinPL) PWM(gpio.Duty, physic.Frequency) error {
	// TODO(maruel): PWM support for PL10.
	return p.wrap(errors.New("not available on this CPU architecture"))
}

//

// function returns the current GPIO pin function.
//
// It must not be called if drvGPIOPL.gpioMemoryPL is nil.
func (p *PinPL) function() function {
	shift := 4 * (p.offset % 8)
	return function((drvGPIOPL.gpioMemoryPL.cfg[p.offset/8] >> shift) & 7)
}

// setFunction changes the GPIO pin function.
//
// Returns false if the pin was in AltN. Only accepts in and out
//
// It must not be called if drvGPIOPL.gpioMemoryPL is nil.
func (p *PinPL) setFunction(f function) bool {
	if f != in && f != out {
		return false
	}
	// Interrupt based edge triggering is Alt5 but this is only supported on some
	// pins.
	// TODO(maruel): This check should use a whitelist of pins.
	if actual := p.function(); actual != in && actual != out && actual != disabled && actual != alt5 {
		// Pin is in special mode.
		return false
	}
	off := p.offset / 8
	shift := 4 * (p.offset % 8)
	mask := uint32(disabled) << shift
	v := (uint32(f) << shift) ^ mask
	// First disable, then setup. This is concurrent safe.
	drvGPIOPL.gpioMemoryPL.cfg[off] |= mask
	drvGPIOPL.gpioMemoryPL.cfg[off] &^= v
	if p.function() != f {
		panic(f)
	}
	return true
}

func (p *PinPL) wrap(err error) error {
	return fmt.Errorf("allwinner-gpio-pl (%s): %v", p, err)
}

//

// cpuPinsPL is all the pins as supported by the CPU. There is no guarantee that
// they are actually connected to anything on the board.
var cpuPinsPL = []PinPL{
	{offset: 0, name: "PL0", defaultPull: gpio.PullUp},
	{offset: 1, name: "PL1", defaultPull: gpio.PullUp},
	{offset: 2, name: "PL2", defaultPull: gpio.Float},
	{offset: 3, name: "PL3", defaultPull: gpio.Float},
	{offset: 4, name: "PL4", defaultPull: gpio.Float},
	{offset: 5, name: "PL5", defaultPull: gpio.Float},
	{offset: 6, name: "PL6", defaultPull: gpio.Float},
	{offset: 7, name: "PL7", defaultPull: gpio.Float},
	{offset: 8, name: "PL8", defaultPull: gpio.Float},
	{offset: 9, name: "PL9", defaultPull: gpio.Float},
	{offset: 10, name: "PL10", defaultPull: gpio.Float},
	{offset: 11, name: "PL11", defaultPull: gpio.Float},
	{offset: 12, name: "PL12", defaultPull: gpio.Float},
}

// See gpio.go for details.
var mappingPL = [13][5]pin.Func{
	{"RSB_SCK", "I2C_SCL", "", "", "PL_EINT0"}, // PL0
	{"RSB_SDA", "I2C_SDA", "", "", "PL_EINT1"}, // PL1
	{"UART_TX", "", "", "", "PL_EINT2"},        // PL2
	{"UART_RX", "", "", "", "PL_EINT3"},        // PL3
	{"JTAG_TMS", "", "", "", "PL_EINT4"},       // PL4
	{"JTAG_TCK", "", "", "", "PL_EINT5"},       // PL5
	{"JTAG_TDO", "", "", "", "PL_EINT6"},       // PL6
	{"JTAG_TDI", "", "", "", "PL_EINT7"},       // PL7
	{"I2C_SCL", "", "", "", "PL_EINT8"},        // PL8
	{"I2C_SDA", "", "", "", "PL_EINT9"},        // PL9
	{"PWM0", "", "", "", "PL_EINT10"},          // PL10
	{"CIR_RX", "", "", "", "PL_EINT11"},        // PL11
	{"", "", "", "", "PL_EINT12"},              // PL12
}

func init() {
	PL0 = &cpuPinsPL[0]
	PL1 = &cpuPinsPL[1]
	PL2 = &cpuPinsPL[2]
	PL3 = &cpuPinsPL[3]
	PL4 = &cpuPinsPL[4]
	PL5 = &cpuPinsPL[5]
	PL6 = &cpuPinsPL[6]
	PL7 = &cpuPinsPL[7]
	PL8 = &cpuPinsPL[8]
	PL9 = &cpuPinsPL[9]
	PL10 = &cpuPinsPL[10]
	PL11 = &cpuPinsPL[11]
	PL12 = &cpuPinsPL[12]
}

// getBaseAddressPL queries the virtual file system to retrieve the base address
// of the GPIO registers for GPIO pins in group PL.
//
// Defaults to 0x01F02C00 as per datasheet if could query the file system.
func getBaseAddressPL() uint64 {
	base := uint64(0x01F02C00)
	link, err := os.Readlink("/sys/bus/platform/drivers/sun50i-r-pinctrl/driver")
	if err != nil {
		return base
	}
	parts := strings.SplitN(path.Base(link), ".", 2)
	if len(parts) != 2 {
		return base
	}
	base2, err := strconv.ParseUint(parts[0], 16, 64)
	if err != nil {
		return base
	}
	return base2
}

// driverGPIOPL implements periph.Driver.
type driverGPIOPL struct {
	// gpioMemoryPL is only the PL group in that case. Note that groups PI, PJ, PK
	// do not exist.
	gpioMemoryPL *gpioGroup
}

func (d *driverGPIOPL) String() string {
	return "allwinner-gpio-pl"
}

func (d *driverGPIOPL) Prerequisites() []string {
	return nil
}

func (d *driverGPIOPL) After() []string {
	return []string{"sysfs-gpio"}
}

func (d *driverGPIOPL) Init() (bool, error) {
	// BUG(maruel): H3 supports group PL too.
	if !IsA64() {
		return false, errors.New("no A64 CPU detected")
	}

	// Mark the right pins as available even if the memory map fails so they can
	// callback to sysfs.Pins.
	functions := map[pin.Func]struct{}{}
	for i := range cpuPinsPL {
		name := cpuPinsPL[i].Name()
		num := strconv.Itoa(cpuPinsPL[i].Number())
		cpuPinsPL[i].available = true
		gpion := "GPIO" + num

		// Unregister the pin if already registered. This happens with sysfs-gpio.
		// Do not error on it, since sysfs-gpio may have failed to load.
		_ = gpioreg.Unregister(gpion)
		_ = gpioreg.Unregister(num)

		// Register the pin with gpio.
		if err := gpioreg.Register(&cpuPinsPL[i]); err != nil {
			return true, err
		}
		if err := gpioreg.RegisterAlias(gpion, name); err != nil {
			return true, err
		}
		if err := gpioreg.RegisterAlias(num, name); err != nil {
			return true, err
		}
		switch f := cpuPinsPL[i].Func(); f {
		case gpio.IN, gpio.OUT, pin.FuncNone:
		default:
			// Registering the same alias twice fails. This can happen if two pins
			// are configured with the same function.
			if _, ok := functions[f]; !ok {
				// TODO(maruel): We'd have to clear out the ones from allwinner-gpio
				// too.
				functions[f] = struct{}{}
				_ = gpioreg.RegisterAlias(string(f), name)
			}
		}
	}

	// Now do a second loop but do the alternate functions.
	for i := range cpuPinsPL {
		for _, f := range cpuPinsPL[i].SupportedFuncs() {
			switch f {
			case gpio.IN, gpio.OUT:
			default:
				if _, ok := functions[f]; !ok {
					// TODO(maruel): We'd have to clear out the ones from allwinner-gpio
					// too.
					functions[f] = struct{}{}
					_ = gpioreg.RegisterAlias(string(f), cpuPinsPL[i].name)
				}
			}
		}
	}

	m, err := pmem.Map(getBaseAddressPL(), 4096)
	if err != nil {
		if os.IsPermission(err) {
			return true, fmt.Errorf("need more access, try as root: %v", err)
		}
		return true, err
	}
	if err := m.AsPOD(&d.gpioMemoryPL); err != nil {
		return true, err
	}

	return true, nil
}

func init() {
	if isArm {
		driverreg.MustRegister(&drvGPIOPL)
	}
}

var drvGPIOPL driverGPIOPL

var _ gpio.PinIO = &PinPL{}
var _ gpio.PinIn = &PinPL{}
var _ gpio.PinOut = &PinPL{}
var _ pin.PinFunc = &PinPL{}
