package api

import (
	"bufio"
	"encoding/json"
	"errors"
	"net"

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

// Default socket address
const DefaultAddr = "/tmp/itd/socket"

// Client is the socket API client
type Client struct {
	conn            net.Conn
	respCh          chan types.Response
	heartRateCh     chan types.Response
	battLevelCh     chan types.Response
	stepCountCh     chan types.Response
	motionCh        chan types.Response
	dfuProgressCh   chan types.Response
	readProgressCh  chan types.FSTransferProgress
	writeProgressCh chan types.FSTransferProgress
}

// New creates a new client and sets it up
func New(addr string) (*Client, error) {
	conn, err := net.Dial("unix", addr)
	if err != nil {
		return nil, err
	}

	out := &Client{
		conn:   conn,
		respCh: make(chan types.Response, 5),
	}

	go func() {
		scanner := bufio.NewScanner(conn)
		for scanner.Scan() {
			var res types.Response
			err = json.Unmarshal(scanner.Bytes(), &res)
			if err != nil {
				continue
			}
			out.handleResp(res)
		}
	}()
	return out, err
}

func (c *Client) Close() error {
	err := c.conn.Close()
	if err != nil {
		return err
	}
	close(c.respCh)
	return nil
}

// request sends a request to itd and waits for and returns the response
func (c *Client) request(req types.Request) (types.Response, error) {
	// Encode request into connection
	err := json.NewEncoder(c.conn).Encode(req)
	if err != nil {
		return types.Response{}, err
	}

	res := <-c.respCh

	if res.Error {
		return res, errors.New(res.Message)
	}

	return res, nil
}

// requestNoRes sends a request to itd and does not wait for the response
func (c *Client) requestNoRes(req types.Request) error {
	// Encode request into connection
	err := json.NewEncoder(c.conn).Encode(req)
	if err != nil {
		return err
	}
	return nil
}

// handleResp handles the received response as needed
func (c *Client) handleResp(res types.Response) error {
	switch res.Type {
	case types.ReqTypeWatchHeartRate:
		c.heartRateCh <- res
	case types.ReqTypeWatchBattLevel:
		c.battLevelCh <- res
	case types.ReqTypeWatchStepCount:
		c.stepCountCh <- res
	case types.ReqTypeWatchMotion:
		c.motionCh <- res
	case types.ReqTypeFwUpgrade:
		c.dfuProgressCh <- res
	case types.ReqTypeFS:
		if res.Value == nil {
			c.respCh <- res
			break
		}
		var progress types.FSTransferProgress
		if err := mapstructure.Decode(res.Value, &progress); err != nil {
			c.respCh <- res
			break
		}
		switch progress.Type {
		case types.FSTypeRead:
			c.readProgressCh <- progress
		case types.FSTypeWrite:
			c.writeProgressCh <- progress
		default:
			c.respCh <- res
		}
	default:
		c.respCh <- res
	}
	return nil
}

func decodeUint8(val interface{}) uint8 {
	return uint8(val.(float64))
}

func decodeUint32(val interface{}) uint32 {
	return uint32(val.(float64))
}

func decodeMotion(val interface{}) (infinitime.MotionValues, error) {
	out := infinitime.MotionValues{}
	err := mapstructure.Decode(val, &out)
	if err != nil {
		return out, err
	}
	return out, nil
}

func decodeDFUProgress(val interface{}) (DFUProgress, error) {
	out := DFUProgress{}
	err := mapstructure.Decode(val, &out)
	if err != nil {
		return out, err
	}
	return out, nil
}