package main

import (
	"bytes"
	"fmt"
	"os"

	"github.com/lestrrat-go/codegen"
)

type definition struct {
	Filename     string
	Package      string
	ReturnType   string
	ParseOptions bool
}

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

func _main() error {
	definitions := []definition{
		{
			Package:      "jwk",
			ReturnType:   "Set",
			Filename:     "jwk/io.go",
			ParseOptions: true,
		},
		{
			Package:    "jws",
			ReturnType: "*Message",
			Filename:   "jws/io.go",
		},
		{
			Package:    "jwe",
			ReturnType: "*Message",
			Filename:   "jwe/io.go",
		},
		{
			Package:      "jwt",
			ReturnType:   "Token",
			Filename:     "jwt/io.go",
			ParseOptions: true,
		},
	}

	for _, def := range definitions {
		if err := generateFile(def); err != nil {
			return err
		}
	}
	return nil
}

func generateFile(def definition) error {
	var buf bytes.Buffer
	o := codegen.NewOutput(&buf)

	o.LL("// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT.")
	o.LL("package %s", def.Package)

	o.WriteImports("io/fs", "os", "github.com/lestrrat-go/option/v2")

	o.LL(`type sysFS struct {}`)
	o.LL(`func (sysFS) Open(path string) (fs.File, error) {`)
	o.L(`return os.Open(path)`)
	o.L(`}`)

	o.LL("func ReadFile(path string, options ...ReadFileOption) (%s, error) {", def.ReturnType)
	if def.ParseOptions {
		o.L("var parseOptions []ParseOption")
		o.L(`for _, option := range options {`)
		o.L(`if po, ok := option.(ParseOption); ok {`)
		o.L(`parseOptions = append(parseOptions, po)`)
		o.L(`}`)
		o.L(`}`)
	}
	o.LL(`var srcFS fs.FS = sysFS{}`)
	o.L("for _, option := range options {")
	o.L(`switch option.Ident() {`)
	o.L(`case identFS{}:`)
	o.L(`if err := option.Value(&srcFS); err != nil {`)
	o.L(`return nil, fmt.Errorf("failed to set fs.FS: %%w", err)`)
	o.L(`}`)
	o.L("}")
	o.L("}")
	o.LL("f, err := srcFS.Open(path)")
	o.L("if err != nil {")
	o.L("return nil, err")
	o.L("}")
	o.LL("defer f.Close()")
	if def.ParseOptions {
		o.L("return ParseReader(f, parseOptions...)")
	} else {
		o.L("return ParseReader(f)")
	}
	o.L("}")
	if err := o.WriteFile(def.Filename, codegen.WithFormatCode(true)); err != nil {
		if cfe, ok := err.(codegen.CodeFormatError); ok {
			fmt.Fprint(os.Stderr, cfe.Source())
		}
		return fmt.Errorf(`failed to write to %s: %w`, def.Filename, err)
	}
	return nil
}
