{{ define "packet" }}
{{- $structName := camelCase .Name true}}

{{- /* generate any bitfield structs */ -}}
{{range .Data -}}
{{ if .Bits -}}
{{- $bfname := (printf "%s%s" $structName (camelCase .Name true)) }}
type {{$bfname}} struct {
	{{- range $el := .Bits}}
	{{camelCase $el.Name true}} bool `json:"{{$el.Name}}"`
	{{- end}}
}

func (p *{{$bfname}}) MarshalByte() byte {
	var b byte
	{{- range $idx, $el := .Bits}}
	{{- $bitName := camelCase $el.Name true}}
	if p.{{$bitName}} {
		b |= 1 << {{$idx}}
	}
	{{- end}}
	return b
}

func (p *{{$bfname}}) UnmarshalByte(b byte) {
	{{- range $idx, $el := .Bits}}	
	{{- $bitName := camelCase $el.Name true }}
	p.{{$bitName}} = (b & (1 << {{ $idx }})) != 0
	{{- end}}
}
{{end}}
{{- end}}

// {{$structName}} is {{.Description}}
type {{$structName}} struct {
{{- range .Data}}
	{{- if .Units }}
	// {{.Conversion}} {{.Units}}
	{{- end }}
	{{ .ToStructMember $structName }} `json:"{{.Name}}"`
{{- end}}
{{- if .Repeat }}
	// Idx is the packet index. The accepted range is 0-{{.Repeat}}
	Idx uint32 `json:"idx"`
{{- end }}
}

func (p *{{$structName}}) CanId() (can.CanID, error) {
	c := can.CanID{Extended: {{.IsExtended}}}
{{- if .Repeat }}
	if p.Idx >= {{.Repeat}} {
		return c, &UnknownIdError{ {{ printf "0x%X" .Id }} }
	}
	c.Id = {{ printf "0x%X" .Id }} + p.Idx
{{- else }}
	c.Id = {{ printf "0x%X" .Id }}
{{- end }}
	return c, nil
}

func (p *{{$structName}}) Size() uint {
	return {{.CalcSize}}
}

func (p *{{$structName}}) MarshalPacket() ([]byte, error) {
	b := make([]byte, {{ .CalcSize }})
{{.MakeMarshal}}
	return b, nil
}

func (p *{{$structName}}) UnmarshalPacket(b []byte) error {
	if len(b) != {{.CalcSize}} {
		return &BadLengthError{expected: {{.CalcSize}}, actual: uint32(len(b))}
	}
{{.MakeUnmarshal}}
	return nil
}

func (p *{{$structName}}) String() string {
	return "{{ .Name }}"
}

{{ end }}

{{- /* begin actual file template */ -}}

// generated by gen_skylab.go at {{ Time }} DO NOT EDIT!

package skylab

import (
	"errors"
	"encoding/binary"
	"github.com/kschamplin/gotelem/internal/can"
	"encoding/json"
)

type SkylabId uint32

const (
{{- range .Packets }}
	{{camelCase .Name true}}Id SkylabId = {{.Id | printf "0x%X"}}
{{- end}}
)

// list of every packet ID. Can be used for O(1) checks.
var idMap = map[can.CanID]bool{
	{{ range $p := .Packets -}}
	{{ if $p.Repeat }} 
	{{ range $idx := Nx (int $p.Id) $p.Repeat $p.Offset -}}
	{ Id: {{ $idx | printf "0x%X"}}, Extended: {{$p.IsExtended}} }: true,
	{{ end }}
	{{- else }}
	{ Id: {{ $p.Id | printf "0x%X" }}, Extended: {{$p.IsExtended}} }: true,
	{{- end}}
	{{- end}}
}

// FromCanFrame creates a Packet from a given CAN ID and data payload.
// If the CAN ID is unknown, it will return an error.
func FromCanFrame(f can.Frame) (Packet, error) {
	id := f.Id
	if !idMap[id] {
		return nil, &UnknownIdError{ id.Id }
	}
	switch id {
{{- range $p := .Packets }}
	{{- if $p.Repeat }}
	case {{ $p | idToString -}}:
		var res = &{{camelCase $p.Name true}}{}
		res.UnmarshalPacket(f.Data)
		res.Idx = id.Id - {{$p.Id | printf "0x%X" }}
		return res, nil
	{{- else }}
	case {{ $p | idToString }}:
		var res = &{{camelCase $p.Name true}}{}
		res.UnmarshalPacket(f.Data)
		return res, nil
	{{- end}}
{{- end}}
	}

	panic("This should never happen. CAN ID didn't match but was in ID map")
}


func FromJson (name string, raw []byte) (Packet, error) {
	switch name {
{{- range $p := .Packets }}
	case "{{ $p.Name }}":
		var res = &{{camelCase $p.Name true}}{}
		err := json.Unmarshal(raw, res)
		return res, err
{{- end }}
	}

	return nil, errors.New("unknown packet name")

}

{{range .Packets -}}
{{template "packet" .}}
{{- end}}

// The json representation that was used to generate this data.
// can be used to share the parsing data for i.e dynamic python gui.
const SkylabDefinitions = `{{json . | printf "%s" }}`