package can

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strconv"
)

type jsonFrame struct {
	ID       uint32  `json:"id"`
	Data     *string `json:"data"`
	Length   *uint16 `json:"length"`
	Extended *bool   `json:"extended"`
	Remote   *bool   `json:"remote"`
}

// JSON returns the JSON-encoding of f, using hex-encoding for the data.
//
// Examples:
//
//  {"id":32,"data":"0102030405060708"}
//  {"id":32,"extended":true,"remote":true,"length":4}
func (f Frame) JSON() string {
	switch {
	case f.IsRemote && f.IsExtended:
		return `{"id":` + strconv.Itoa(int(f.ID)) +
			`,"extended":true,"remote":true,"length":` +
			strconv.Itoa(int(f.Length)) + `}`
	case f.IsRemote:
		return `{"id":` + strconv.Itoa(int(f.ID)) +
			`,"remote":true,"length":` +
			strconv.Itoa(int(f.Length)) + `}`
	case f.IsExtended && f.Length == 0:
		return `{"id":` + strconv.Itoa(int(f.ID)) + `,"extended":true}`
	case f.IsExtended:
		return `{"id":` + strconv.Itoa(int(f.ID)) +
			`,"data":"` + hex.EncodeToString(f.Data[:f.Length]) + `"` +
			`,"extended":true}`
	case f.Length == 0:
		return `{"id":` + strconv.Itoa(int(f.ID)) + `}`
	default:
		return `{"id":` + strconv.Itoa(int(f.ID)) +
			`,"data":"` + hex.EncodeToString(f.Data[:f.Length]) + `"}`
	}
}

// MarshalJSON returns the JSON-encoding of f, using hex-encoding for the data.
//
// See JSON for an example of the JSON schema.
func (f Frame) MarshalJSON() ([]byte, error) {
	return []byte(f.JSON()), nil
}

// UnmarshalJSON sets *f using the provided JSON-encoded values.
//
// See MarshalJSON for an example of the expected JSON schema.
//
// The result should be checked with Validate to guard against invalid JSON data.
func (f *Frame) UnmarshalJSON(jsonData []byte) error {
	jf := jsonFrame{}
	if err := json.Unmarshal(jsonData, &jf); err != nil {
		return err
	}
	if jf.Data != nil {
		data, err := hex.DecodeString(*jf.Data)
		if err != nil {
			return fmt.Errorf("failed to hex-decode CAN data: %v: %w", string(jsonData), err)
		}
		f.Data = Data{}
		copy(f.Data[:], data)
		f.Length = uint16(len(data))
	} else {
		f.Data = Data{}
		f.Length = 0
	}
	f.ID = jf.ID
	if jf.Remote != nil {
		f.IsRemote = *jf.Remote
	} else {
		f.IsRemote = false
	}
	if f.IsRemote {
		if jf.Length == nil {
			return fmt.Errorf("missing length field for remote JSON frame: %v", string(jsonData))
		}
		f.Length = *jf.Length
	}
	if jf.Extended != nil {
		f.IsExtended = *jf.Extended
	} else {
		f.IsExtended = false
	}
	return nil
}
