// dialects-gen command.
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"
)

var tplTest = template.Must(template.New("").Parse(
	`//autogenerated:yes
//nolint:revive
package dialects

import (
	"testing"
	"encoding"
	"reflect"

	"github.com/stretchr/testify/require"

	"github.com/bluenviron/gomavlib/v3/pkg/dialects/common"
)

var casesEnum = []struct {
	name string
	dec  encoding.TextMarshaler
	enc  string
}{
	{
		"bitmask",
		common.POSITION_TARGET_TYPEMASK_VX_IGNORE | common.POSITION_TARGET_TYPEMASK_VY_IGNORE,
		"POSITION_TARGET_TYPEMASK_VX_IGNORE | POSITION_TARGET_TYPEMASK_VY_IGNORE",
	},
	{
		"value",
		common.GPS_FIX_TYPE_NO_FIX,
		"GPS_FIX_TYPE_NO_FIX",
	},
}

func TestEnumUnmarshalText(t *testing.T) {
	for _, ca := range casesEnum {
		t.Run(ca.name, func(t *testing.T) {
			dec := reflect.New(reflect.TypeOf(ca.dec)).Interface().(encoding.TextUnmarshaler)
			err := dec.UnmarshalText([]byte(ca.enc))
			require.NoError(t, err)
			require.Equal(t, ca.dec, reflect.ValueOf(dec).Elem().Interface())
		})
	}
}

func TestEnumMarshalText(t *testing.T) {
	for _, ca := range casesEnum {
		t.Run(ca.name, func(t *testing.T) {
			byts, err := ca.dec.MarshalText()
			require.NoError(t, err)
			require.Equal(t, ca.enc, string(byts))
		})
	}
}
`))

var tplDialectTest = template.Must(template.New("").Parse(
	`//autogenerated:yes
//nolint:revive
package {{ .PkgName }}

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/bluenviron/gomavlib/v3/pkg/dialect"
)

func TestDialect(t *testing.T) {
	d := &dialect.ReadWriter{Dialect: Dialect}
	err := d.Initialize()
	require.NoError(t, err)
}
`))

var tplEnumTest = template.Must(template.New("").Parse(
	`//autogenerated:yes
//nolint:revive,govet,errcheck
package {{ .PkgName }}

import (
	"testing"
	"github.com/stretchr/testify/require"
)

func TestEnum_{{ .Name }}(t *testing.T) {
	t.Run("zero", func(t *testing.T) {
		var e {{ .Name }}
		e.UnmarshalText([]byte{})
		e.MarshalText()
		e.String()
	})

	t.Run("first entry", func(t *testing.T) {
		enc, err := {{ .FirstEntry }}.MarshalText()
		require.NoError(t, err)

		var dec {{ .Name }}
		err = dec.UnmarshalText(enc)
		require.NoError(t, err)

		require.Equal(t, {{ .FirstEntry }}, dec)
	})
}
`))

func writeTemplate(fpath string, tpl *template.Template, args map[string]interface{}) error {
	f, err := os.Create(fpath)
	if err != nil {
		return err
	}
	defer f.Close()

	return tpl.Execute(f, args)
}

func shellCommand(cmdstr string) error {
	fmt.Fprintf(os.Stderr, "%s\n", cmdstr)
	cmd := exec.Command("sh", "-c", cmdstr)
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	return cmd.Run()
}

func downloadJSON(addr string, data interface{}) error {
	req, err := http.NewRequest(http.MethodGet, addr, nil)
	if err != nil {
		return err
	}

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	return json.NewDecoder(res.Body).Decode(data)
}

func processDialect(commit string, name string) error {
	fmt.Fprintf(os.Stderr, "[%s]\n", name)

	err := shellCommand(fmt.Sprintf("go run ../../cmd/dialect-import --link %s",
		"https://raw.githubusercontent.com/mavlink/mavlink/"+commit+"/message_definitions/v1.0/"+name+".xml",
	))
	if err != nil {
		return err
	}

	pkgName := strings.ToLower(strings.ReplaceAll(name, "_", ""))

	err = writeTemplate(
		"./"+pkgName+"/dialect_test.go",
		tplDialectTest,
		map[string]interface{}{
			"PkgName": pkgName,
		})
	if err != nil {
		return err
	}

	entries, err := os.ReadDir(pkgName)
	if err != nil {
		return err
	}

	for _, f := range entries {
		if !strings.HasPrefix(f.Name(), "enum_") {
			continue
		}

		var buf []byte
		buf, err = os.ReadFile(filepath.Join(pkgName, f.Name()))
		if err != nil {
			return err
		}
		str := string(buf)

		if !strings.Contains(str, "MarshalText(") {
			continue
		}

		rx := regexp.MustCompile("const \\(\\n" +
			"(\\t// .+?\\n)*" +
			"\\t(.*?) (.*?) = (.*?)\\n")
		matches := rx.FindStringSubmatch(str)
		if matches == nil {
			return fmt.Errorf("first entry of %s not found", f.Name())
		}

		enumName := f.Name()
		enumName = enumName[len("enum_"):]
		enumName = enumName[:len(enumName)-len(".go")]
		enumName = strings.ToUpper(enumName)

		err = writeTemplate(
			"./"+pkgName+"/"+strings.ReplaceAll(f.Name(), ".go", "_test.go"),
			tplEnumTest,
			map[string]interface{}{
				"PkgName":    pkgName,
				"Name":       enumName,
				"FirstEntry": matches[2],
			})
		if err != nil {
			return err
		}
	}

	fmt.Fprintf(os.Stderr, "\n")
	return nil
}

func run() error {
	err := shellCommand("rm -rf pkg/dialects/*/")
	if err != nil {
		return err
	}

	os.Mkdir(filepath.Join("pkg", "dialects"), 0o755)
	os.Chdir(filepath.Join("pkg", "dialects"))

	var res struct {
		Sha string `json:"sha"`
	}
	err = downloadJSON("https://api.github.com/repos/mavlink/mavlink/commits/master", &res)
	if err != nil {
		return err
	}

	var files []struct {
		Name string `json:"name"`
	}
	err = downloadJSON("https://api.github.com/repos/mavlink/mavlink/contents/message_definitions/v1.0?ref="+res.Sha,
		&files)
	if err != nil {
		return err
	}

	for _, f := range files {
		if !strings.HasSuffix(f.Name, ".xml") {
			continue
		}
		name := f.Name[:len(f.Name)-len(".xml")]

		err = processDialect(res.Sha, name)
		if err != nil {
			return err
		}
	}

	err = writeTemplate(
		"package_test.go",
		tplTest,
		map[string]interface{}{})
	if err != nil {
		return err
	}

	return nil
}

func main() {
	err := run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERR: %s\n", err)
		os.Exit(1)
	}
}
