// Copyright 2018 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 pca9685

import (
	"fmt"
	"time"

	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/i2c"
	"periph.io/x/conn/v3/physic"
)

// I2CAddr i2c default address.
const I2CAddr uint16 = 0x40

// PCA9685 registers.
const (
	mode1    byte = 0x00
	mode2    byte = 0x01
	prescale byte = 0xFE
	// Each channel has two 12-bit registers (on & off time).
	led0OnL   byte = 0x06 // Start address for setting channel 0.
	allLedOnL byte = 0xFA // Start address for setting all channels.
)

// Mode register 1, mode1.
const (
	restart byte = 0x80
	ai      byte = 0x20 // Auto-increment register after each read and write.
	sleep   byte = 0x10
	allCall byte = 0x01
)

// Mode register 2, mode2.
const (
	invrt  byte = 0x10
	outDrv byte = 0x04
)

// Dev is a handler to pca9685 controller.
type Dev struct {
	dev  *i2c.Dev
	freq physic.Frequency
}

// NewI2C returns a Dev object that communicates over I2C.
//
// To use on the default address, pca9685.I2CAddr must be passed as argument.
func NewI2C(bus i2c.Bus, address uint16) (*Dev, error) {
	dev := &Dev{
		dev: &i2c.Dev{Bus: bus, Addr: address},
	}
	err := dev.init()
	if err != nil {
		return nil, err
	}

	return dev, nil
}

func (d *Dev) init() error {
	if err := d.SetAllPwm(0, 0); err != nil {
		return err
	}

	if _, err := d.dev.Write([]byte{mode2, outDrv}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, allCall}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	mode := (modeRead[0] & ^sleep) | ai
	if _, err := d.dev.Write([]byte{mode1, mode}); err != nil {
		return err
	}

	time.Sleep(5 * time.Millisecond)

	return d.SetPwmFreq(50 * physic.Hertz)
}

// SetPwmFreq set the PWM frequency.
func (d *Dev) SetPwmFreq(freqHz physic.Frequency) error {
	if d.freq == freqHz {
		// Don't need to write frequency if it's not changed.
		// Note: this is required to avoid setting it each time
		// when PWM value is changed via gpio.PinOut.PWM() API
		return nil
	}

	p := (25*physic.MegaHertz/4096 + freqHz/2) / freqHz

	modeRead := [1]byte{}
	if err := d.dev.Tx([]byte{mode1}, modeRead[:]); err != nil {
		return err
	}

	oldmode := modeRead[0]
	if _, err := d.dev.Write([]byte{mode1, (oldmode & ^restart) | sleep}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{prescale, byte(p)}); err != nil {
		return err
	}
	if _, err := d.dev.Write([]byte{mode1, oldmode}); err != nil {
		return err
	}

	time.Sleep(100 * time.Millisecond)

	_, err := d.dev.Write([]byte{mode1, oldmode | restart})
	d.freq = freqHz
	return err
}

// setPWM writes a PWM value in a specific register.
func (d *Dev) setPWM(register uint8, on, off gpio.Duty) error {
	// Chained writes are possible due to auto-increment.
	_, err := d.dev.Write([]byte{
		register,
		byte(on),
		byte(on >> 8),
		byte(off),
		byte(off >> 8),
	})
	return err
}

// SetAllPwm set a PWM value for all outputs.
func (d *Dev) SetAllPwm(on, off gpio.Duty) error {
	return d.setPWM(allLedOnL, on, off)
}

// SetPwm set a PWM value for a given PCA9685 channel.
func (d *Dev) SetPwm(channel int, on, off gpio.Duty) error {
	err := verifyChannel(channel)
	if err != nil {
		return err
	}
	return d.setPWM(led0OnL+byte(4*channel), on, off)
}

// SetFullOff sets PWM duty to 0%.
//
// This function uses the dedicated bit to reduce bus traffic.
func (d *Dev) SetFullOff(channel int) error {
	err := verifyChannel(channel)
	if err != nil {
		return err
	}
	_, err = d.dev.Write([]byte{
		led0OnL + byte(4*channel) + 3, // LEDX_OFF_H
		0x10,                          // bit 4 is full-off
	})
	return err
}

// SetFullOn sets PWM duty to 100%.
//
// This function uses the dedicated FULL_ON bit.
func (d *Dev) SetFullOn(channel int) error {
	err := verifyChannel(channel)
	if err != nil {
		return err
	}
	_, err = d.dev.Write([]byte{
		led0OnL + byte(4*channel) + 1, // LEDX_ON_H
		0x10,                          // bit 4 is full-on
		0,
		0, // LEDX_OFF_H is cleared because full-off has a priority over full-on
	})
	return err
}

func verifyChannel(channel int) error {
	if channel < 0 || channel > 15 {
		return fmt.Errorf("PCA9685: invalid channel: %d", channel)
	}
	return nil
}
