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

package memory

import (
	"context"
	"fmt"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/id"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/logger"
	"code.forgejo.org/f3/gof3/v3/options"
	options_logger "code.forgejo.org/f3/gof3/v3/options/logger"
	"code.forgejo.org/f3/gof3/v3/path"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
)

type IDAllocatorInterface interface {
	allocateID(id string) string
	isNull() bool
}

type idAllocatorGenerator struct {
	prefix string
	lastID rune
}

func NewIDAllocatorGenerator(prefix string) IDAllocatorInterface {
	return &idAllocatorGenerator{
		prefix: prefix,
		lastID: 'A',
	}
}

func (o *idAllocatorGenerator) allocateID(string) string {
	r := fmt.Sprintf("%s-%c", o.prefix, o.lastID)
	o.lastID++
	return r
}

func (o *idAllocatorGenerator) isNull() bool { return false }

type idAllocatorNull struct{}

func (o *idAllocatorNull) allocateID(id string) string { return id }

func (o *idAllocatorNull) isNull() bool { return true }

func NewIDAllocatorNull() IDAllocatorInterface {
	return &idAllocatorNull{}
}

type memoryOptions struct {
	options.Options
	options_logger.OptionsLogger

	IDAllocator IDAllocatorInterface
}

func NewOptions(idAllocator IDAllocatorInterface) options.Interface {
	opts := &memoryOptions{}
	opts.IDAllocator = idAllocator
	opts.SetName("memory")
	l := logger.NewLogger()
	l.SetLevel(logger.Trace)
	opts.SetLogger(l)
	return opts
}

type memoryStorage struct {
	idAllocator IDAllocatorInterface
	root        *memoryStorageNode
}

func newmemoryStorage(opts options.Interface) *memoryStorage {
	return &memoryStorage{
		idAllocator: opts.(*memoryOptions).IDAllocator,
	}
}

func (o *memoryStorage) newStorageNode(id string) *memoryStorageNode {
	return &memoryStorageNode{
		storage:  o,
		f:        NewFormat(o.idAllocator.allocateID(id)),
		children: make(map[string]*memoryStorageNode),
	}
}

func (o *memoryStorage) Find(path path.PathString) *memoryStorageNode {
	p := path.Elements()
	fmt.Printf("memoryStorage Find %s\n", path.Join())
	current := o.root
	for {
		fmt.Printf("  memoryStorage Find lookup '%s'\n", current.f.GetID())
		if current.f.GetID() != p[0] {
			panic("")
		}
		p = p[1:]
		if len(p) == 0 {
			return current
		}
		if next, ok := current.children[p[0]]; ok {
			current = next
		} else {
			return nil
		}
	}
}

type memoryStorageNode struct {
	storage  *memoryStorage
	f        *FormatMemory
	children map[string]*memoryStorageNode
}

type treeDriver struct {
	generic.NullTreeDriver
	storage    *memoryStorage
	allocateID bool
}

func newTreeDriver(opts options.Interface) *treeDriver {
	tree := &treeDriver{
		storage:    newmemoryStorage(opts),
		allocateID: !opts.(*memoryOptions).IDAllocator.isNull(),
	}
	tree.Init()
	return tree
}

func (o *treeDriver) AllocateID() bool { return o.allocateID }

func (o *treeDriver) Factory(ctx context.Context, kind kind.Kind) generic.NodeDriverInterface {
	return &Driver{}
}

type Driver struct {
	generic.NullDriver
	f *FormatMemory
}

type FormatMemory struct {
	f3.Common
	Ref     *f3.Reference
	Content string
}

func NewFormat(id string) *FormatMemory {
	f := &FormatMemory{}
	f.Ref = f3.NewReference("")
	f.Content = "????"
	f.SetID(id)
	return f
}

func (o *FormatMemory) GetReferences() f3.References {
	references := o.Common.GetReferences()
	if o.Ref.Get() != "" {
		references = append(references, o.Ref)
	}
	return references
}

func (o *Driver) NewFormat() f3.Interface {
	return &FormatMemory{}
}

func (o *Driver) ToFormat() f3.Interface {
	if o == nil || o.f == nil {
		return o.NewFormat()
	}
	return &FormatMemory{
		Common:  o.f.Common,
		Ref:     o.f.Ref,
		Content: o.f.Content,
	}
}

func (o *Driver) FromFormat(f f3.Interface) {
	m := f.(*FormatMemory)
	o.f = &FormatMemory{
		Common:  m.Common,
		Ref:     m.Ref,
		Content: m.Content,
	}
}

func (o *Driver) Equals(ctx context.Context, other generic.NodeInterface) bool {
	switch i := other.GetDriver().(type) {
	case *Driver:
		return o.f.Content == i.f.Content
	default:
		return false
	}
}

func (o *Driver) String() string {
	if o.f == nil {
		return "<nil>"
	}
	return o.f.Content
}

func (o *Driver) Get(ctx context.Context) bool {
	node := o.GetNode()
	o.Trace("id '%s' '%s'", node.GetID(), node.GetCurrentPath().ReadableString())
	storage := o.getStorage(node, node.GetCurrentPath())
	if storage != nil {
		o.f = storage.f
	}
	node.List(ctx)
	return true
}

func (o *Driver) Put(ctx context.Context) id.NodeID {
	return o.upsert(ctx)
}

func (o *Driver) Patch(ctx context.Context) {
	o.upsert(ctx)
}

func (o *Driver) upsert(ctx context.Context) id.NodeID {
	node := o.GetNode()
	path := node.GetParent().GetCurrentPath()
	storageParent := o.getStorage(node, path)
	i := node.GetID()
	o.Trace("node id '%s'", i)
	if existing, ok := storageParent.children[i.String()]; ok {
		o.Trace("update %s content=%s ref=%s", node.GetCurrentPath().ReadableString(), o.f.Content, o.f.Ref)
		existing.f = o.f
	} else {
		storageChild := storageParent.storage.newStorageNode(i.String())
		storageID := storageChild.f.GetID()
		storageParent.children[storageID] = storageChild
		i = id.NewNodeID(storageID)
		if o.f == nil {
			o.f = storageChild.f
		} else {
			o.f.SetID(storageChild.f.GetID())
		}
		o.Trace("create '%s' '%s'", node.GetCurrentPath().ReadableString(), i)
	}
	return i
}

func (o *Driver) Delete(ctx context.Context) {
	node := o.GetNode()
	storage := o.getStorage(node, node.GetParent().GetCurrentPath())
	if storage != nil && storage.children != nil {
		id := node.GetID().String()
		delete(storage.children, id)
	}
}

func (o *Driver) getStorage(node generic.NodeInterface, path path.Path) *memoryStorageNode {
	storage := node.GetTree().GetDriver().(*treeDriver).storage
	return storage.Find(path.PathString())
}

func (o *Driver) ListPage(ctx context.Context, page int) generic.ChildrenSlice {
	node := o.GetNode()
	o.Trace("'%s'", node.GetCurrentPath().ReadableString())
	storage := o.getStorage(node, node.GetCurrentPath())

	children := generic.NewChildrenSlice(0)
	if storage != nil {
		for i := range storage.children {
			node := node.CreateChild(context.Background())
			childID := id.NewNodeID(i)
			node.SetID(childID)
			children = append(children, node)
		}
	}
	return children
}

func (o *Driver) GetIDFromName(ctx context.Context, content string) id.NodeID {
	node := o.GetNode()
	o.Trace("'%s'", node.GetCurrentPath().ReadableString())
	storage := o.getStorage(node, node.GetCurrentPath())

	if storage != nil {
		for i, child := range storage.children {
			if child.f.Content == content {
				o.Trace("found '%s'", i)
				return id.NewNodeID(i)
			}
		}
	}
	return id.NilID
}

type treeMemory struct {
	generic.Tree
}

func newTreeMemory(ctx context.Context, opts options.Interface) generic.TreeInterface {
	t := &treeMemory{}
	t.Init(t, opts)
	t.GetLogger().SetLevel(logger.Trace)
	t.Register(kind.KindNil, func(ctx context.Context, kind kind.Kind) generic.NodeInterface {
		return generic.NewNode()
	})

	treeDriver := newTreeDriver(opts)
	t.SetDriver(treeDriver)

	f := NewFormat("")
	f.Content = "ROOT"

	storage := treeDriver.storage
	storage.root = &memoryStorageNode{
		storage:  storage,
		f:        f,
		children: make(map[string]*memoryStorageNode),
	}
	root := t.Factory(ctx, kind.KindRoot)
	root.FromFormat(f)
	t.SetRoot(root)

	return t
}

func SetContent(node generic.NodeInterface, content string) {
	f := node.GetDriver().ToFormat().(*FormatMemory)
	f.Content = content
	node.FromFormat(f)
}

func GetContent(node generic.NodeInterface) string {
	return node.GetDriver().ToFormat().(*FormatMemory).Content
}

func SetRef(node generic.NodeInterface, ref string) {
	f := node.GetDriver().ToFormat().(*FormatMemory)
	f.Ref = f3.NewReference(ref)
	node.FromFormat(f)
}

func GetRef(node generic.NodeInterface) string {
	return node.GetDriver().ToFormat().(*FormatMemory).Ref.Get()
}

func init() {
	generic.RegisterFactory("memory", newTreeMemory)
}
