// Package gen contains the libraries and algorithms used to generate the
// code for the vocab package.
package gen

import (
	"bytes"
	"fmt"
	"github.com/go-fed/activity/tools/defs"
	"go/format"
	"strings"
)

const (
	objectName                    = "Object"
	linkName                      = "Link"
	ObjectTypeName                = "ObjectType"
	LinkTypeName                  = "LinkType"
	typePropertyName              = "type"
	resolveLinkName               = "resolveLink"
	resolveObjectName             = "resolveObject"
	unknownValueDeserializeFnName = "unknownValueDeserialize"
	unknownValueSerializeFnName   = "unknownValueSerialize"
)

type File struct {
	Name    string
	Content []byte
}

func GenerateImplementations(types []*defs.Type, properties []*defs.PropertyType, values []*defs.ValueType) (f []*File, err error) {
	// Validate inputs
	err = validateDomains(properties)
	if err != nil {
		return
	}
	err = validateProperties(types)
	if err != nil {
		return
	}
	err = validateValues(values, properties)
	if err != nil {
		return
	}

	p := generatePackageDefinition()
	p.Defs = append(p.Defs, generateUnknownType())

	// Add ValueType serialize & deserialize functions
	for _, v := range values {
		p.F = append(p.F, v.DeserializeFn, v.SerializeFn)
	}
	p.F = append(p.F, defs.IRIFuncs()...)
	p.F = append(p.F, generateHasTypeFuncs(types)...)
	p.I = append(p.I, generateTyperInterface())

	// Add functions to resolve string 'name' into concrete types
	p.F = append(p.F, generateResolveObjectFunction(types))
	p.F = append(p.F, generateResolveLinkFunction(types))
	unknown := generateUnknownValueType()
	p.F = append(p.F, unknown.DeserializeFn, unknown.SerializeFn)

	var b []byte
	b, err = format.Source([]byte(p.Generate()))
	if err != nil {
		return
	}
	f = append(f, &File{
		Name:    "gen_vocab.go",
		Content: b,
	})

	// ActivityStream Types
	m := make(map[*defs.PropertyType]*intermedDef)
	for _, t := range types {
		p := &defs.PackageDef{
			Name: "vocab",
		}
		funcs, defs, interfaces, imports := generateDefinitions(t, m)
		for i, _ := range imports {
			p.Imports = append(p.Imports, i)
		}
		p.F = append(p.F, funcs...)
		p.Defs = append(p.Defs, defs...)
		p.I = append(p.I, interfaces...)

		var b []byte
		b, err = format.Source([]byte(p.Generate()))
		if err != nil {
			return
		}
		f = append(f, &File{
			Name:    fmt.Sprintf("gen_%s.go", strings.ToLower(t.Name)),
			Content: b,
		})
	}

	// Intermediate definitions
	p = &defs.PackageDef{
		Name:    "vocab",
		Imports: []string{"fmt", "net/url", "time"},
	}
	for _, v := range m {
		p.F = append(p.F, v.F...)
		p.Defs = append(p.Defs, v.S)
	}
	b, err = format.Source([]byte(p.Generate()))
	if err != nil {
		return
	}
	f = append(f, &File{
		Name:    fmt.Sprintf("gen_intermediate.go"),
		Content: b,
	})
	return
}

func generateTyperInterface() *defs.InterfaceDef {
	return &defs.InterfaceDef{
		Typename: "Typer",
		Comment:  "Typer supports common functions for determining an ActivityStream type",
		F: []*defs.FunctionDef{
			{
				Name:   "TypeLen",
				Return: []*defs.FunctionVarDef{{Name: "l", Type: "int"}},
			},
			{
				Name:   "GetType",
				Args:   []*defs.FunctionVarDef{{Name: "index", Type: "int"}},
				Return: []*defs.FunctionVarDef{{Name: "v", Type: "interface{}"}},
			},
		},
	}
}

func generateHasTypeFuncs(types []*defs.Type) (f []*defs.FunctionDef) {
	var activityTypes []string
	for _, t := range types {
		t := t
		if defs.IsActivity(t) {
			activityTypes = append(activityTypes, t.Name)
		}
		f = append(f, &defs.FunctionDef{
			Name:    fmt.Sprintf("HasType%s", t.Name),
			Comment: fmt.Sprintf("HasType%s returns true if the Typer has a type of %s.", t.Name, t.Name),
			Args:    []*defs.FunctionVarDef{{Name: "t", Type: "Typer"}},
			Return:  []*defs.FunctionVarDef{{Name: "b", Type: "bool"}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("for i := 0; i < t.TypeLen(); i++ {\n")
				b.WriteString("v := t.GetType(i)\n")
				b.WriteString("if s, ok := v.(string); ok {\n")
				b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", t.Name))
				b.WriteString("return true\n")
				b.WriteString("}\n")
				b.WriteString("}\n")
				b.WriteString("}\n")
				b.WriteString("return false\n")
				return b.String()
			},
		})
	}
	f = append(f, &defs.FunctionDef{
		Name:    "IsActivityType",
		Comment: "Returns true if the provided Typer is an Activity.",
		Args:    []*defs.FunctionVarDef{{Name: "t", Type: "Typer"}},
		Return:  []*defs.FunctionVarDef{{Name: "b", Type: "bool"}},
		Body: func() string {
			var b bytes.Buffer
			b.WriteString(fmt.Sprintf("var activityTypes = []string{\"%s\"}\n", strings.Join(activityTypes, "\", \"")))
			b.WriteString("hasType := make(map[string]bool, 1)\n")
			b.WriteString("for i := 0; i < t.TypeLen(); i++ {\n")
			b.WriteString("v := t.GetType(i)\n")
			b.WriteString("if s, ok := v.(string); ok {\n")
			b.WriteString("hasType[s] = true\n")
			b.WriteString("}\n")
			b.WriteString("}\n")
			b.WriteString("for _, t := range activityTypes {\n")
			b.WriteString("if hasType[t] {\n")
			b.WriteString("return true\n")
			b.WriteString("}\n")
			b.WriteString("}\n")
			b.WriteString("return false\n")
			return b.String()
		},
	})
	return
}

func generatePackageDefinition() *defs.PackageDef {
	return &defs.PackageDef{
		Name:    "vocab",
		Comment: "Package vocab provides an implementation of serializing and deserializing activity streams into native golang structs without relying on reflection. This package is code-generated from the vocabulary specification available at https://www.w3.org/TR/activitystreams-vocabulary and by design forgoes full resolution of raw JSON-LD data. However, custom extensions of the vocabulary are supported by modifying the data definitions in the generation tool and rerunning it. Do not modify this package directly.",
		Imports: []string{"fmt", "time", "net/url", "regexp", "strconv", "math"},
		I: []*defs.InterfaceDef{
			{
				Typename: "Serializer",
				Comment:  "Serializer implementations can serialize themselves to a generic map form.",
				F: []*defs.FunctionDef{{
					Name:   "Serialize",
					Return: []*defs.FunctionVarDef{{"m", "map[string]interface{}"}, {"e", "error"}},
				}},
			},
			{
				Typename: "Deserializer",
				Comment:  "Deserializer implementations can deserialize themselves from a generic map form.",
				F: []*defs.FunctionDef{{
					Name:   "Deserialize",
					Args:   []*defs.FunctionVarDef{{"m", "map[string]interface{}"}},
					Return: []*defs.FunctionVarDef{{"e", "error"}},
				}},
			},
		},
	}
}

func generateUnknownValueType() *defs.ValueType {
	return &defs.ValueType{
		Name:           "Unknown Value",
		DefinitionType: "interface{}",
		Zero:           "nil",
		DeserializeFn: &defs.FunctionDef{
			Name:    unknownValueDeserializeFnName,
			Comment: "unknownValueDeserialize transparently stores the object.",
			Args:    []*defs.FunctionVarDef{{"v", "interface{}"}},
			Return:  []*defs.FunctionVarDef{{"o", "interface{}"}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("o = v\n")
				b.WriteString("return\n")
				return b.String()
			},
		},
		SerializeFn: &defs.FunctionDef{
			Name:    unknownValueSerializeFnName,
			Comment: "unknownValueSerialize transparently returns the object.",
			Args:    []*defs.FunctionVarDef{{"v", "interface{}"}},
			Return:  []*defs.FunctionVarDef{{"o", "interface{}"}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("o = v\n")
				b.WriteString("return\n")
				return b.String()
			},
		},
	}
}

func generateUnknownType() *defs.StructDef {
	u := &defs.StructDef{
		Typename: "Unknown",
		Comment:  "Unknown is an entry whose root type is unknown.",
		M: []*defs.StructMember{{
			Name:    "u",
			Type:    "map[string]interface{}",
			Comment: "Raw unknown, untyped values",
		}},
	}
	u.F = append(u.F, []*defs.MemberFunctionDef{
		{
			Name:    "Serialize",
			Comment: "Serialize turns this object into a map[string]interface{}. Note that for the Unknown type, the \"type\" property is NOT populated with anything special during this process.",
			P:       u,
			Return:  []*defs.FunctionVarDef{{"m", "map[string]interface{}"}, {"err", "error"}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("m = t.u\n")
				b.WriteString("return\n")
				return b.String()
			},
		},
		{
			Name:    "Deserialize",
			Comment: "Deserialize populates this object from a map[string]interface{}",
			P:       u,
			Args:    []*defs.FunctionVarDef{{"m", "map[string]interface{}"}},
			Return:  []*defs.FunctionVarDef{{"err", "error"}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("t.u = m\n")
				b.WriteString("return\n")
				return b.String()
			},
		},
		{
			Name:    "HasField",
			Comment: "HasField determines whether the call to GetField is safe with the specified field",
			P:       u,
			Args:    []*defs.FunctionVarDef{{"f", "string"}},
			Return:  []*defs.FunctionVarDef{{"ok", "bool"}},
			Body: func() string {
				return "return t.u != nil && t.u[f] != nil\n"
			},
		},
		{
			Name:    "GetField",
			Comment: "GetField returns the unknown field value",
			P:       u,
			Args:    []*defs.FunctionVarDef{{"f", "string"}},
			Return:  []*defs.FunctionVarDef{{"v", "interface{}"}},
			Body: func() string {
				return "return t.u[f]"
			},
		},
		{
			Name:    "SetField",
			Comment: "SetField sets the unknown field value",
			P:       u,
			Args:    []*defs.FunctionVarDef{{"f", "string"}, {"i", "interface{}"}},
			Return:  []*defs.FunctionVarDef{{"this", "*" + u.Typename}},
			Body: func() string {
				var b bytes.Buffer
				b.WriteString("if t.u == nil {\n")
				b.WriteString("t.u = make(map[string]interface{})\n")
				b.WriteString("}\n")
				b.WriteString("t.u[f] = i\n")
				b.WriteString("return t\n")
				return b.String()
			},
		},
	}...)
	return u
}

func generateResolveObjectFunction(types []*defs.Type) *defs.FunctionDef {
	return &defs.FunctionDef{
		Name:    resolveObjectName,
		Comment: fmt.Sprintf("%s turns a string type that extends Object into a concrete type.", resolveObjectName),
		Args:    []*defs.FunctionVarDef{{"s", "string"}},
		Return:  []*defs.FunctionVarDef{{"i", "interface{}"}},
		Body: func() string {
			var b bytes.Buffer
			for _, r := range types {
				if isAnObjectType(r) {
					b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", r.Name))
					b.WriteString(fmt.Sprintf("return &%s{}\n", r.Name))
					b.WriteString("}\n")
				}
			}
			b.WriteString("return nil\n")
			return b.String()
		},
	}
}

func generateResolveLinkFunction(types []*defs.Type) *defs.FunctionDef {
	return &defs.FunctionDef{
		Name:    resolveLinkName,
		Comment: fmt.Sprintf("%s turns a string type that extends Link into a concrete type.", resolveLinkName),
		Args:    []*defs.FunctionVarDef{{"s", "string"}},
		Return:  []*defs.FunctionVarDef{{"i", "interface{}"}},
		Body: func() string {
			var b bytes.Buffer
			for _, r := range types {
				if isALinkType(r) {
					b.WriteString(fmt.Sprintf("if s == \"%s\" {\n", r.Name))
					b.WriteString(fmt.Sprintf("return &%s{}\n", r.Name))
					b.WriteString("}\n")
				}
			}
			b.WriteString("return nil\n")
			return b.String()
		},
	}
}

func isAnObjectType(t *defs.Type) bool {
	obj, _ := getIsAType(t)
	return obj
}

func isALinkType(t *defs.Type) bool {
	_, link := getIsAType(t)
	return link
}

func getIsAType(t *defs.Type) (obj, link bool) {
	if t.Name == objectName {
		return true, false
	} else if t.Name == linkName {
		return false, true
	}
	for _, e := range t.Extends {
		if obj, link = getIsAType(e); obj || link {
			return
		}
	}
	panic("Unknown root is-a-type")
}
