package models

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

const ModelGongFileTemplate = `// generated by ModelGongFileTemplate
package models

import "sort"

type __void struct{}

var __member __void

type StageStruct struct { 
	AllModelsStructCreateCallback AllModelsStructCreateInterface

	AllModelsStructDeleteCallback AllModelsStructDeleteInterface

	BackRepo BackRepoInterface

	OnInitCommitCallback OnInitCommitInterface
}

type OnInitCommitInterface interface {
	BeforeCommit(stage *StageStruct)
}

type BackRepoInterface interface {
	Commit(stage *StageStruct)
	Checkout(stage *StageStruct)
	Backup(stage *StageStruct, dirPath string)
	Restore(stage *StageStruct, dirPath string)
	BackupXL(stage *StageStruct, dirPath string)
	RestoreXL(stage *StageStruct, dirPath string)

	GetLastCommitNb() uint
	GetLastPushFromFrontNb() uint
}

var Stage StageStruct = StageStruct{ 

}

func (stage *StageStruct) Commit() {
	if stage.BackRepo != nil {
		stage.BackRepo.Commit(stage)
	}
}

func (stage *StageStruct) Checkout() {
	if stage.BackRepo != nil {
		stage.BackRepo.Checkout(stage)
	}
}

func (stage *StageStruct) Backup(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.Backup(stage, dirPath)
	}
}

func (stage *StageStruct) Restore(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.Restore(stage, dirPath)
	}
}

func (stage *StageStruct) BackupXL(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.BackupXL(stage, dirPath)
	}
}

func (stage *StageStruct) RestoreXL(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.RestoreXL(stage, dirPath)
	}
}

type AllModelsStructCreateInterface interface { 
}

type AllModelsStructDeleteInterface interface { 
}

func (stage *StageStruct) Reset() { 
}

func (stage *StageStruct) Nil() { 
}
`

type ModelGongInsertionPoints int

const (
	ModelGongInsertionCommitCheckoutSignature ModelGongInsertionPoints = iota
	ModelGongInsertionStageFunctions
	ModelGongInsertionCreateCallback
	ModelGongInsertionDeleteCallback
	ModelGongInsertionArrayDefintion
	ModelGongInsertionArrayInitialisation
	ModelGongInsertionArrayReset
	ModelGongInsertionArrayNil
	ModelGongInsertionsNb
)

type ModelGongSubTemplate int

const (
	ModelGongCommitCheckout ModelGongSubTemplate = iota
	ModelGongStageFunction
	ModelGongStructCreateCallback
	ModelGongStructDeleteCallback
	ModelGongStructArrayDefintion
	ModelGongStructArrayInitialisation
	ModelGongStructArrayReset
	ModelGongStructArrayNil
)

var ModelGongSubTemplateCode map[ModelGongSubTemplate]string = 
map[ModelGongSubTemplate]string{
	ModelGongCommitCheckout: `
	Commit{{Structname}}({{structname}} *{{Structname}})
	Checkout{{Structname}}({{structname}} *{{Structname}})`,

	ModelGongStageFunction: `
func (stage *StageStruct) get{{Structname}}OrderedStructWithNameField() []*{{Structname}} {

	{{structname}}Ordered := []*{{Structname}}{}
	for {{structname}} := range stage.{{Structname}}s {
		{{structname}}Ordered = append({{structname}}Ordered, {{structname}})
	}
	sort.Slice({{structname}}Ordered[:], func(i, j int) bool {
		return {{structname}}Ordered[i].Name < {{structname}}Ordered[j].Name
	})
	return {{structname}}Ordered
}

func ({{structname}} *{{Structname}}) Stage() *{{Structname}} {
	Stage.{{Structname}}s[{{structname}}] = __member
	Stage.{{Structname}}s_mapString[{{structname}}.Name] = {{structname}}

	return {{structname}}
}

func ({{structname}} *{{Structname}}) Unstage() *{{Structname}} {
	delete(Stage.{{Structname}}s, {{structname}})
	delete(Stage.{{Structname}}s_mapString, {{structname}}.Name)
	return {{structname}}
}

func ({{structname}} *{{Structname}}) Commit() *{{Structname}} {
	if _, ok := Stage.{{Structname}}s[{{structname}}]; ok {
		if Stage.BackRepo != nil {
			Stage.BackRepo.Commit{{Structname}}({{structname}})
		}
	}
	return {{structname}}
}

func ({{structname}} *{{Structname}}) Checkout() *{{Structname}} {
	if _, ok := Stage.{{Structname}}s[{{structname}}]; ok {
		if Stage.BackRepo != nil {
			Stage.BackRepo.Checkout{{Structname}}({{structname}})
		}
	}
	return {{structname}}
}

func ({{structname}} *{{Structname}}) StageCopy() *{{Structname}} {
	_{{structname}} := new({{Structname}})
	*_{{structname}} = *{{structname}}
	_{{structname}}.Stage()
	return _{{structname}}
}

func ({{structname}} *{{Structname}}) StageAndCommit() *{{Structname}} {
	{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
	return {{structname}}
}

func ({{structname}} *{{Structname}}) DeleteStageAndCommit() *{{Structname}} {
	{{structname}}.Unstage()
	DeleteORM{{Structname}}({{structname}})
	return {{structname}}
}

func ({{structname}} *{{Structname}}) StageCopyAndCommit() *{{Structname}} {
	_{{structname}} := new({{Structname}})
	*_{{structname}} = *{{structname}}
	_{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
	return _{{structname}}
}

func CreateORM{{Structname}}({{structname}} *{{Structname}}) {
	{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
}

func DeleteORM{{Structname}}({{structname}} *{{Structname}}) {
	{{structname}}.Unstage()
	if Stage.AllModelsStructDeleteCallback != nil {
		Stage.AllModelsStructDeleteCallback.DeleteORM{{Structname}}({{structname}})
	}
}
`,

	ModelGongStructCreateCallback: `
	CreateORM{{Structname}}({{Structname}} *{{Structname}})`,

	ModelGongStructDeleteCallback: `
	DeleteORM{{Structname}}({{Structname}} *{{Structname}})`,

	ModelGongStructArrayDefintion: `
	{{Structname}}s           map[*{{Structname}}]struct{}
	{{Structname}}s_mapString map[string]*{{Structname}}
`,

	ModelGongStructArrayInitialisation: `
	{{Structname}}s:           make(map[*{{Structname}}]struct{}),
	{{Structname}}s_mapString: make(map[string]*{{Structname}}),
`,

	ModelGongStructArrayReset: `
	stage.{{Structname}}s = make(map[*{{Structname}}]struct{})
	stage.{{Structname}}s_mapString = make(map[string]*{{Structname}})
`,

	ModelGongStructArrayNil: `
	stage.{{Structname}}s = nil
	stage.{{Structname}}s_mapString = nil
`,
}

var ModelGongSubSubTemplateCode map[string]string = 
map[string]string{}

var ModelGongSubSubToSubMap map[string]string = 
map[string]string{}

func CodeGeneratorModelGong(
	mdlPkg *ModelPkg,
	pkgName string,
	pkgPath string) {

	codeGO := ModelGongFileTemplate

	insertions := make(map[ModelGongInsertionPoints]string)
	for insertion := ModelGongInsertionPoints(0); insertion < ModelGongInsertionsNb; insertion++ {
		insertions[insertion] = ""
	}

	subCodes := make(map[ModelGongSubTemplate]string)
	for subTemplate := range ModelGongSubTemplateCode {
		subCodes[subTemplate] = ""
	}

	gongStructs := []*GongStruct{}
	for _, _struct := range mdlPkg.GongStructs {
		gongStructs = append(gongStructs, _struct)
	}
	sort.Slice(gongStructs[:], func(i, j int) bool {
		return gongStructs[i].Name < gongStructs[j].Name
	})
	for _, gongStruct := range gongStructs {

		if gongStruct.HasNameField() {
			for subTemplate := range ModelGongSubTemplateCode {
				subCodes[subTemplate] += Replace2(ModelGongSubTemplateCode[subTemplate],
					"{{structname}}", strings.ToLower(gongStruct.Name),
					"{{Structname}}", gongStruct.Name)
			}
		}
	}

	for insertion := ModelGongInsertionPoints(0); insertion < ModelGongInsertionsNb; insertion++ {

		insertions[insertion] = subCodes[ModelGongSubTemplate(insertion)]

		toReplace := "{{" + string(rune(insertion)) + "}}"
		codeGO = strings.ReplaceAll(codeGO, toReplace, insertions[insertion])
	}

	codeGO = Replace3(codeGO,
		"{{PkgName}}", pkgName,
		"{{TitlePkgName}}", strings.Title(pkgName),
		"{{pkgname}}", strings.ToLower(pkgName))

	file, err := os.Create(filepath.Join(pkgPath, "gong.go"))
	if err != nil {
		log.Panic(err)
	}
	defer file.Close()
	fmt.Fprint(file, codeGO)

}
package models

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

const ModelGongFileTemplate = `// generated by ModelGongFileTemplate
package models

import "sort"

// swagger:ignore
type __void struct{}

// needed for creating set of instances in the stage
var __member __void

// StageStruct enables storage of staged instances
// swagger:ignore
type StageStruct struct { // insertion point for definition of arrays registering instances{{` + string(rune(ModelGongStructArrayDefintion)) + `}}
	AllModelsStructCreateCallback AllModelsStructCreateInterface

	AllModelsStructDeleteCallback AllModelsStructDeleteInterface

	BackRepo BackRepoInterface

	// if set will be called before each commit to the back repo
	OnInitCommitCallback OnInitCommitInterface
}

type OnInitCommitInterface interface {
	BeforeCommit(stage *StageStruct)
}

type BackRepoInterface interface {
	Commit(stage *StageStruct)
	Checkout(stage *StageStruct)
	Backup(stage *StageStruct, dirPath string)
	Restore(stage *StageStruct, dirPath string)
	BackupXL(stage *StageStruct, dirPath string)
	RestoreXL(stage *StageStruct, dirPath string)
	// insertion point for Commit and Checkout signatures{{` + string(rune(ModelGongInsertionCommitCheckoutSignature)) + `}}
	GetLastCommitNb() uint
	GetLastPushFromFrontNb() uint
}

// swagger:ignore instructs the gong compiler (gongc) to avoid this particular struct
var Stage StageStruct = StageStruct{ // insertion point for array initiatialisation{{` + string(rune(ModelGongInsertionArrayInitialisation)) + `}}
	// end of insertion point
}

func (stage *StageStruct) Commit() {
	if stage.BackRepo != nil {
		stage.BackRepo.Commit(stage)
	}
}

func (stage *StageStruct) Checkout() {
	if stage.BackRepo != nil {
		stage.BackRepo.Checkout(stage)
	}
}

// backup generates backup files in the dirPath
func (stage *StageStruct) Backup(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.Backup(stage, dirPath)
	}
}

// Restore resets Stage & BackRepo and restores their content from the restore files in dirPath
func (stage *StageStruct) Restore(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.Restore(stage, dirPath)
	}
}

// backup generates backup files in the dirPath
func (stage *StageStruct) BackupXL(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.BackupXL(stage, dirPath)
	}
}

// Restore resets Stage & BackRepo and restores their content from the restore files in dirPath
func (stage *StageStruct) RestoreXL(dirPath string) {
	if stage.BackRepo != nil {
		stage.BackRepo.RestoreXL(stage, dirPath)
	}
}

// insertion point for cumulative sub template with model space calls{{` + string(rune(ModelGongInsertionStageFunctions)) + `}}
// swagger:ignore
type AllModelsStructCreateInterface interface { // insertion point for Callbacks on creation{{` + string(rune(ModelGongInsertionCreateCallback)) + `}}
}

type AllModelsStructDeleteInterface interface { // insertion point for Callbacks on deletion{{` + string(rune(ModelGongInsertionDeleteCallback)) + `}}
}

func (stage *StageStruct) Reset() { // insertion point for array reset{{` + string(rune(ModelGongInsertionArrayReset)) + `}}
}

func (stage *StageStruct) Nil() { // insertion point for array nil{{` + string(rune(ModelGongInsertionArrayNil)) + `}}
}
`

// insertion points
type ModelGongInsertionPoints int

const (
	ModelGongInsertionCommitCheckoutSignature ModelGongInsertionPoints = iota
	ModelGongInsertionStageFunctions
	ModelGongInsertionCreateCallback
	ModelGongInsertionDeleteCallback
	ModelGongInsertionArrayDefintion
	ModelGongInsertionArrayInitialisation
	ModelGongInsertionArrayReset
	ModelGongInsertionArrayNil
	ModelGongInsertionsNb
)

type ModelGongSubTemplate int

const (
	ModelGongCommitCheckout ModelGongSubTemplate = iota
	ModelGongStageFunction
	ModelGongStructCreateCallback
	ModelGongStructDeleteCallback
	ModelGongStructArrayDefintion
	ModelGongStructArrayInitialisation
	ModelGongStructArrayReset
	ModelGongStructArrayNil
)

var ModelGongSubTemplateCode map[ModelGongSubTemplate]string = // new line
map[ModelGongSubTemplate]string{
	ModelGongCommitCheckout: `
	Commit{{Structname}}({{structname}} *{{Structname}})
	Checkout{{Structname}}({{structname}} *{{Structname}})`,

	ModelGongStageFunction: `
func (stage *StageStruct) get{{Structname}}OrderedStructWithNameField() []*{{Structname}} {
	// have alphabetical order generation
	{{structname}}Ordered := []*{{Structname}}{}
	for {{structname}} := range stage.{{Structname}}s {
		{{structname}}Ordered = append({{structname}}Ordered, {{structname}})
	}
	sort.Slice({{structname}}Ordered[:], func(i, j int) bool {
		return {{structname}}Ordered[i].Name < {{structname}}Ordered[j].Name
	})
	return {{structname}}Ordered
}

// Stage puts {{structname}} to the model stage
func ({{structname}} *{{Structname}}) Stage() *{{Structname}} {
	Stage.{{Structname}}s[{{structname}}] = __member
	Stage.{{Structname}}s_mapString[{{structname}}.Name] = {{structname}}

	return {{structname}}
}

// Unstage removes {{structname}} off the model stage
func ({{structname}} *{{Structname}}) Unstage() *{{Structname}} {
	delete(Stage.{{Structname}}s, {{structname}})
	delete(Stage.{{Structname}}s_mapString, {{structname}}.Name)
	return {{structname}}
}

// commit {{structname}} to the back repo (if it is already staged)
func ({{structname}} *{{Structname}}) Commit() *{{Structname}} {
	if _, ok := Stage.{{Structname}}s[{{structname}}]; ok {
		if Stage.BackRepo != nil {
			Stage.BackRepo.Commit{{Structname}}({{structname}})
		}
	}
	return {{structname}}
}

// Checkout {{structname}} to the back repo (if it is already staged)
func ({{structname}} *{{Structname}}) Checkout() *{{Structname}} {
	if _, ok := Stage.{{Structname}}s[{{structname}}]; ok {
		if Stage.BackRepo != nil {
			Stage.BackRepo.Checkout{{Structname}}({{structname}})
		}
	}
	return {{structname}}
}

//
// Legacy, to be deleted
//

// StageCopy appends a copy of {{structname}} to the model stage
func ({{structname}} *{{Structname}}) StageCopy() *{{Structname}} {
	_{{structname}} := new({{Structname}})
	*_{{structname}} = *{{structname}}
	_{{structname}}.Stage()
	return _{{structname}}
}

// StageAndCommit appends {{structname}} to the model stage and commit to the orm repo
func ({{structname}} *{{Structname}}) StageAndCommit() *{{Structname}} {
	{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
	return {{structname}}
}

// DeleteStageAndCommit appends {{structname}} to the model stage and commit to the orm repo
func ({{structname}} *{{Structname}}) DeleteStageAndCommit() *{{Structname}} {
	{{structname}}.Unstage()
	DeleteORM{{Structname}}({{structname}})
	return {{structname}}
}

// StageCopyAndCommit appends a copy of {{structname}} to the model stage and commit to the orm repo
func ({{structname}} *{{Structname}}) StageCopyAndCommit() *{{Structname}} {
	_{{structname}} := new({{Structname}})
	*_{{structname}} = *{{structname}}
	_{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
	return _{{structname}}
}

// CreateORM{{Structname}} enables dynamic staging of a {{Structname}} instance
func CreateORM{{Structname}}({{structname}} *{{Structname}}) {
	{{structname}}.Stage()
	if Stage.AllModelsStructCreateCallback != nil {
		Stage.AllModelsStructCreateCallback.CreateORM{{Structname}}({{structname}})
	}
}

// DeleteORM{{Structname}} enables dynamic staging of a {{Structname}} instance
func DeleteORM{{Structname}}({{structname}} *{{Structname}}) {
	{{structname}}.Unstage()
	if Stage.AllModelsStructDeleteCallback != nil {
		Stage.AllModelsStructDeleteCallback.DeleteORM{{Structname}}({{structname}})
	}
}
`,

	ModelGongStructCreateCallback: `
	CreateORM{{Structname}}({{Structname}} *{{Structname}})`,

	ModelGongStructDeleteCallback: `
	DeleteORM{{Structname}}({{Structname}} *{{Structname}})`,

	ModelGongStructArrayDefintion: `
	{{Structname}}s           map[*{{Structname}}]struct{}
	{{Structname}}s_mapString map[string]*{{Structname}}
`,

	ModelGongStructArrayInitialisation: `
	{{Structname}}s:           make(map[*{{Structname}}]struct{}),
	{{Structname}}s_mapString: make(map[string]*{{Structname}}),
`,

	ModelGongStructArrayReset: `
	stage.{{Structname}}s = make(map[*{{Structname}}]struct{})
	stage.{{Structname}}s_mapString = make(map[string]*{{Structname}})
`,

	ModelGongStructArrayNil: `
	stage.{{Structname}}s = nil
	stage.{{Structname}}s_mapString = nil
`,
}

var ModelGongSubSubTemplateCode map[string]string = // new line
map[string]string{}

var ModelGongSubSubToSubMap map[string]string = //
map[string]string{}

func CodeGeneratorModelGong(
	mdlPkg *ModelPkg,
	pkgName string,
	pkgPath string) {

	// generate the typescript file
	codeGO := ModelGongFileTemplate

	insertions := make(map[ModelGongInsertionPoints]string)
	for insertion := ModelGongInsertionPoints(0); insertion < ModelGongInsertionsNb; insertion++ {
		insertions[insertion] = ""
	}

	subCodes := make(map[ModelGongSubTemplate]string)
	for subTemplate := range ModelGongSubTemplateCode {
		subCodes[subTemplate] = ""
	}

	gongStructs := []*GongStruct{}
	for _, _struct := range mdlPkg.GongStructs {
		gongStructs = append(gongStructs, _struct)
	}
	sort.Slice(gongStructs[:], func(i, j int) bool {
		return gongStructs[i].Name < gongStructs[j].Name
	})
	for _, gongStruct := range gongStructs {

		if gongStruct.HasNameField() {
			for subTemplate := range ModelGongSubTemplateCode {
				subCodes[subTemplate] += Replace2(ModelGongSubTemplateCode[subTemplate],
					"{{structname}}", strings.ToLower(gongStruct.Name),
					"{{Structname}}", gongStruct.Name)
			}
		}
	}

	// substitutes {{<<insertion points>>}} stuff with generated code
	for insertion := ModelGongInsertionPoints(0); insertion < ModelGongInsertionsNb; insertion++ {

		// compute insertion
		insertions[insertion] = subCodes[ModelGongSubTemplate(insertion)]

		toReplace := "{{" + string(rune(insertion)) + "}}"
		codeGO = strings.ReplaceAll(codeGO, toReplace, insertions[insertion])
	}

	codeGO = Replace3(codeGO,
		"{{PkgName}}", pkgName,
		"{{TitlePkgName}}", strings.Title(pkgName),
		"{{pkgname}}", strings.ToLower(pkgName))

	file, err := os.Create(filepath.Join(pkgPath, "gong.go"))
	if err != nil {
		log.Panic(err)
	}
	defer file.Close()
	fmt.Fprint(file, codeGO)

}
