// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package generic

import (
	"context"

	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/logger"
	"code.forgejo.org/f3/gof3/v3/options"
	"code.forgejo.org/f3/gof3/v3/path"
)

type FactoryFun func(ctx context.Context, kind kind.Kind) NodeInterface

type kindMap map[kind.Kind]FactoryFun

type Tree struct {
	logger.Logger

	opts   options.Interface
	self   TreeInterface
	driver TreeDriverInterface
	root   NodeInterface
	kind   kindMap
}

func NewTree(opts options.Interface) TreeInterface {
	tree := &Tree{}
	return tree.Init(tree, opts)
}

func (o *Tree) Init(self TreeInterface, opts options.Interface) TreeInterface {
	o.self = self
	o.kind = make(kindMap)
	o.SetDriver(NewNullTreeDriver())
	o.SetOptions(opts)
	o.SetLogger(opts.(options.LoggerInterface).GetLogger())
	return o
}

func (o *Tree) GetOptions() options.Interface     { return o.opts }
func (o *Tree) SetOptions(opts options.Interface) { o.opts = opts }

func (o *Tree) GetSelf() TreeInterface     { return o.self }
func (o *Tree) SetSelf(self TreeInterface) { o.self = self }

func (o *Tree) GetRoot() NodeInterface     { return o.root }
func (o *Tree) SetRoot(root NodeInterface) { o.root = root }

func (o *Tree) GetDriver() TreeDriverInterface { return o.driver }
func (o *Tree) SetDriver(driver TreeDriverInterface) {
	driver.SetTree(o.GetSelf())
	o.driver = driver
}

func (o *Tree) GetChildrenKind(parentKind kind.Kind) kind.Kind {
	return kind.KindNil
}

func (o *Tree) GetPageSize() int { return o.GetDriver().GetPageSize() }

func (o *Tree) AllocateID() bool { return o.GetDriver().AllocateID() }

func (o *Tree) Walk(ctx context.Context, options *WalkOptions) {
	o.GetRoot().Walk(ctx, path.NewPath(), options)
}

func (o *Tree) WalkAndGet(ctx context.Context, options *WalkOptions) {
	o.GetRoot().WalkAndGet(ctx, path.NewPath(), options)
}

func (o *Tree) Clear(ctx context.Context) {
	rootKind := o.GetRoot().GetKind()
	o.SetRoot(o.Factory(ctx, rootKind))
}

func (o *Tree) Exists(ctx context.Context, path path.Path) bool {
	return o.Find(path) != NilNode
}

func (o *Tree) MustFind(path path.Path) NodeInterface {
	return o.GetRoot().MustFind(path.RemoveFirst())
}

func (o *Tree) Find(path path.Path) NodeInterface {
	return o.GetRoot().Find(path.RemoveFirst())
}

func (o *Tree) FindAndGet(ctx context.Context, path path.Path) NodeInterface {
	return o.GetRoot().FindAndGet(ctx, path.RemoveFirst())
}

func (o *Tree) Diff(a, b NodeInterface) string {
	return o.GetDriver().Diff(a.GetDriver(), b.GetDriver())
}

func (o *Tree) Apply(ctx context.Context, p path.Path, options *ApplyOptions) bool {
	if p.Empty() {
		return true
	}
	return o.GetRoot().Apply(ctx, path.NewPath(), p.RemoveFirst(), options)
}

func (o *Tree) ApplyAndGet(ctx context.Context, path path.Path, options *ApplyOptions) bool {
	if path.Empty() {
		return true
	}
	return o.GetRoot().ApplyAndGet(ctx, path.RemoveFirst(), options)
}

func (o *Tree) Factory(ctx context.Context, kind kind.Kind) NodeInterface {
	var node NodeInterface
	if factory, ok := o.kind[kind]; ok {
		node = factory(ctx, kind)
	} else {
		node = NewNode()
	}
	node.SetIsNil(false)
	node.SetKind(kind)
	node.SetTree(o.GetSelf())
	if o.GetDriver() != nil {
		nodeDriver := o.GetDriver().Factory(ctx, kind)
		nodeDriver.SetTreeDriver(o.GetDriver())
		node.SetDriver(nodeDriver)
	}
	return node
}

func (o *Tree) Register(kind kind.Kind, factory FactoryFun) {
	o.kind[kind] = factory
}
