// 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 (
	"periph.io/x/conn/v3/gpio"
	"periph.io/x/conn/v3/physic"
)

// ServoGroup a group of servos connected to a pca9685 module
type ServoGroup struct {
	*Dev
	minPwm   gpio.Duty
	maxPwm   gpio.Duty
	minAngle physic.Angle
	maxAngle physic.Angle
}

// Servo individual servo from a group of servos connected to a pca9685 module
type Servo struct {
	group    *ServoGroup
	channel  int
	minAngle physic.Angle
	maxAngle physic.Angle
}

// NewServoGroup returns a servo group connected through the pca9685 module
// some pwm and angle limits can be set
func NewServoGroup(dev *Dev, minPwm, maxPwm gpio.Duty, minAngle, maxAngle physic.Angle) *ServoGroup {
	return &ServoGroup{
		Dev:      dev,
		minPwm:   minPwm,
		maxPwm:   maxPwm,
		minAngle: minAngle,
		maxAngle: maxAngle,
	}
}

// SetMinMaxPwm change pwm and angle limits
func (s *ServoGroup) SetMinMaxPwm(minAngle, maxAngle physic.Angle, minPwm, maxPwm gpio.Duty) {
	s.maxPwm = maxPwm
	s.minPwm = minPwm
	s.minAngle = minAngle
	s.maxAngle = maxAngle
}

// SetAngle set an angle in a given channel of the servo group
func (s *ServoGroup) SetAngle(channel int, angle physic.Angle) error {
	value := mapValue(int(angle), int(s.minAngle), int(s.maxAngle), int(s.minPwm), int(s.maxPwm))
	return s.Dev.SetPwm(channel, 0, gpio.Duty(value))
}

// GetServo returns a individual Servo to be controlled
func (s *ServoGroup) GetServo(channel int) *Servo {
	return &Servo{
		group:    s,
		channel:  channel,
		minAngle: s.minAngle,
		maxAngle: s.maxAngle,
	}
}

// SetMinMaxAngle change angle limits for the servo
func (s *Servo) SetMinMaxAngle(min, max physic.Angle) {
	s.minAngle = min
	s.maxAngle = max
}

// SetAngle set an angle on the servo
// will consider the angle limits set
func (s *Servo) SetAngle(angle physic.Angle) error {
	if angle < s.minAngle {
		angle = s.minAngle
	}
	if angle > s.maxAngle {
		angle = s.maxAngle
	}
	return s.group.SetAngle(s.channel, angle)
}

// SetPwm set an pmw value to the servo
func (s *Servo) SetPwm(pwm gpio.Duty) error {
	return s.group.SetPwm(s.channel, 0, pwm)
}

func mapValue(x, inMin, inMax, outMin, outMax int) int {
	return (x-inMin)*(outMax-outMin)/(inMax-inMin) + outMin
}
