package api

import (
	"github.com/mitchellh/mapstructure"
	"go.arsenm.dev/infinitime"
	"go.arsenm.dev/itd/internal/types"
)

// Address gets the bluetooth address of the connected device
func (c *Client) Address() (string, error) {
	res, err := c.request(types.Request{
		Type: types.ReqTypeBtAddress,
	})
	if err != nil {
		return "", err
	}

	return res.Value.(string), nil
}

// Version gets the firmware version of the connected device
func (c *Client) Version() (string, error) {
	res, err := c.request(types.Request{
		Type: types.ReqTypeFwVersion,
	})
	if err != nil {
		return "", err
	}

	return res.Value.(string), nil
}

// BatteryLevel gets the battery level of the connected device
func (c *Client) BatteryLevel() (uint8, error) {
	res, err := c.request(types.Request{
		Type: types.ReqTypeBattLevel,
	})
	if err != nil {
		return 0, err
	}

	return uint8(res.Value.(float64)), nil
}

// WatchBatteryLevel returns a channel which will contain
// new battery level values as they update. Do not use after
// calling cancellation function
func (c *Client) WatchBatteryLevel() (<-chan uint8, func(), error) {
	c.battLevelCh = make(chan types.Response, 2)
	err := c.requestNoRes(types.Request{
		Type: types.ReqTypeBattLevel,
	})
	if err != nil {
		return nil, nil, err
	}
	res := <-c.battLevelCh
	done, cancel := c.cancelFn(res.ID, c.battLevelCh)
	out := make(chan uint8, 2)
	go func() {
		for res := range c.battLevelCh {
			select {
			case <-done:
				return
			default:
				out <- decodeUint8(res.Value)
			}
		}
	}()
	return out, cancel, nil
}

// HeartRate gets the heart rate from the connected device
func (c *Client) HeartRate() (uint8, error) {
	res, err := c.request(types.Request{
		Type: types.ReqTypeHeartRate,
	})
	if err != nil {
		return 0, err
	}

	return decodeUint8(res.Value), nil
}

// WatchHeartRate returns a channel which will contain
// new heart rate values as they update. Do not use after
// calling cancellation function
func (c *Client) WatchHeartRate() (<-chan uint8, func(), error) {
	c.heartRateCh = make(chan types.Response, 2)
	err := c.requestNoRes(types.Request{
		Type: types.ReqTypeWatchHeartRate,
	})
	if err != nil {
		return nil, nil, err
	}
	res := <-c.heartRateCh
	done, cancel := c.cancelFn(res.ID, c.heartRateCh)
	out := make(chan uint8, 2)
	go func() {
		for res := range c.heartRateCh {
			select {
			case <-done:
				return
			default:
				out <- decodeUint8(res.Value)
			}
		}
	}()
	return out, cancel, nil
}

// cancelFn generates a cancellation function for the given
// request type and channel
func (c *Client) cancelFn(reqID string, ch chan types.Response) (chan struct{}, func()) {
	done := make(chan struct{}, 1)
	return done, func() {
		done <- struct{}{}
		close(ch)
		c.requestNoRes(types.Request{
			Type: types.ReqTypeCancel,
			Data: reqID,
		})
	}
}

// StepCount gets the step count from the connected device
func (c *Client) StepCount() (uint32, error) {
	res, err := c.request(types.Request{
		Type: types.ReqTypeStepCount,
	})
	if err != nil {
		return 0, err
	}

	return uint32(res.Value.(float64)), nil
}

// WatchStepCount returns a channel which will contain
// new step count values as they update. Do not use after
// calling cancellation function
func (c *Client) WatchStepCount() (<-chan uint32, func(), error) {
	c.stepCountCh = make(chan types.Response, 2)
	err := c.requestNoRes(types.Request{
		Type: types.ReqTypeWatchStepCount,
	})
	if err != nil {
		return nil, nil, err
	}
	res := <-c.stepCountCh
	done, cancel := c.cancelFn(res.ID, c.stepCountCh)
	out := make(chan uint32, 2)
	go func() {
		for res := range c.stepCountCh {
			select {
			case <-done:
				return
			default:
				out <- decodeUint32(res.Value)
			}
		}
	}()
	return out, cancel, nil
}

// Motion gets the motion values from the connected device
func (c *Client) Motion() (infinitime.MotionValues, error) {
	out := infinitime.MotionValues{}
	res, err := c.request(types.Request{
		Type: types.ReqTypeMotion,
	})
	if err != nil {
		return out, err
	}
	err = mapstructure.Decode(res.Value, &out)
	if err != nil {
		return out, err
	}
	return out, nil
}

// WatchMotion returns a channel which will contain
// new motion values as they update. Do not use after
// calling cancellation function
func (c *Client) WatchMotion() (<-chan infinitime.MotionValues, func(), error) {
	c.motionCh = make(chan types.Response, 5)
	err := c.requestNoRes(types.Request{
		Type: types.ReqTypeWatchMotion,
	})
	if err != nil {
		return nil, nil, err
	}
	res := <-c.motionCh
	done, cancel := c.cancelFn(res.ID, c.motionCh)
	out := make(chan infinitime.MotionValues, 5)
	go func() {
		for res := range c.motionCh {
			select {
			case <-done:
				return
			default:
				motion, err := decodeMotion(res.Value)
				if err != nil {
					continue
				}
				out <- motion
			}
		}
	}()
	return out, cancel, nil
}
