package definitions

import (
	"github.com/Xuanwo/gg"
	"github.com/Xuanwo/templateutils"
	log "github.com/sirupsen/logrus"
)

func GenerateObject(path string) {
	g := gg.New()
	f := g.NewGroup()
	f.AddLineComment("Code generated by go generate cmd/definitions; DO NOT EDIT.")
	f.AddPackage("types")
	f.NewImport().
		AddPath("fmt").
		AddPath("time").
		AddPath("sync")

	om := SortInfos(InfosObjectMetaArray)

	f.AddLineComment("Field index in object bit")
	cons := f.NewConst()
	for k, v := range om {
		pname := templateutils.ToPascal(v.Name)
		cons.AddTypedField(
			"objectIndex"+pname, "uint64", gg.S("1<<%d", k))
	}

	f.AddLineComment(`
Object is the smallest unit in go-storage.

NOTES:
  - Object's fields SHOULD not be changed outside services.
  - Object CANNOT be copied
  - Object is concurrent safe.
  - Only ID, Path, Mode are required during list operations, other fields
    could be fetched via lazy stat logic: https://beyondstorage.io/docs/go-storage/internal/object-lazy-stat
`)
	ob := f.NewStruct("Object")
	for _, v := range om {
		if v.Description != "" {
			ob.AddLineComment(v.Description)
		}
		ob.AddField(v.NameForStructField(), v.Type.FullName("types"))
	}

	ob.AddLineComment("client is the client in which Object is alive.")
	ob.AddField("client", "Storager")

	ob.AddLineComment("bit used as a bitmap for object value, 0 means not set, 1 means set")
	ob.AddField("bit", "uint64")
	ob.AddField("done", "uint32")
	ob.AddField("m", "sync.Mutex")

	for _, v := range om {
		pname := templateutils.ToPascal(v.Name)

		if v.export {
			f.NewFunction("Get"+v.NameForFunctionName()).
				WithReceiver("o", "*Object").
				AddResult("", v.Type.FullName("types")).
				AddBody(gg.Return(gg.S("o.%s", v.NameForStructField())))
			f.NewFunction("Set"+v.NameForFunctionName()).
				WithReceiver("o", "*Object").
				AddParameter("v", v.Type.FullName("types")).
				AddResult("", "*Object").
				AddBody(
					gg.S("o.%s = v", v.NameForStructField()),
					gg.Return("o"),
				)
			continue
		}

		f.AddLineComment(`Get%s will get %s from Object.

%s
`, v.NameForFunctionName(), v.NameForFunctionName(), v.Description)
		f.NewFunction("Get"+v.NameForFunctionName()).
			WithReceiver("o", "*Object").
			AddResult("", v.Type.FullName("types")).
			AddResult("", "bool").
			AddBody(
				gg.S("o.stat()"),
				gg.Line(),
				gg.If(gg.S("o.bit & objectIndex%s != 0", pname)).
					AddBody(
						gg.Return("o."+v.NameForStructField(), gg.Lit(true)),
					),
				gg.Return(templateutils.ZeroValue(v.Type.FullName("types")), gg.Lit(false)),
			)

		f.AddLineComment(`MustGet%s will get %s from Object.

%s
`, v.NameForFunctionName(), v.NameForFunctionName(), v.Description)
		f.NewFunction("MustGet"+v.NameForFunctionName()).
			WithReceiver("o", "*Object").
			AddResult("", v.Type.FullName("types")).
			AddBody(
				gg.S("o.stat()"),
				gg.Line(),
				gg.If(gg.S("o.bit & objectIndex%s == 0", pname)).
					AddBody(
						gg.S(`panic(fmt.Sprintf("object %s is not set"))`, v.Name),
					),
				gg.Return("o."+v.NameForStructField()),
			)

		f.AddLineComment(`Set%s will get %s into Object.

%s
`, v.NameForFunctionName(), v.NameForFunctionName(), v.Description)
		f.NewFunction("Set"+v.NameForFunctionName()).
			WithReceiver("o", "*Object").
			AddParameter("v", v.Type.FullName("types")).
			AddResult("", "*Object").
			AddBody(
				gg.S("o.%s = v", v.NameForStructField()),
				gg.S("o.bit |= objectIndex%s", pname),
				gg.Return("o"),
			)
	}
	fn := f.NewFunction("clone").
		WithReceiver("o", "*Object").
		AddParameter("xo", "*Object")
	for _, v := range om {
		fn.AddBody(gg.S("o.%s = xo.%s", v.NameForStructField(), v.NameForStructField()))
	}
	fn.AddBody(gg.S("o.bit = xo.bit"))

	err := g.WriteFile(path)
	if err != nil {
		log.Fatalf("generate to %s: %v", path, err)
	}
}
