/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cache

import (
	"context"
	"errors"
	"sync"

	"github.com/karlseguin/ccache"
)

var errNilNode = errors.New("nil node")

type Tree struct {
	Config  *Config
	roots   *ccache.Cache
	filters []Filter
	lock    sync.RWMutex
}

func (t *Tree) AddFilter(fs ...Filter) *Tree {
	t.lock.Lock()
	t.filters = append(t.filters, fs...)
	t.lock.Unlock()
	return t
}

func (t *Tree) Get(ctx context.Context, ops ...Option) (node *Node, err error) {
	var op Option
	if len(ops) > 0 {
		op = ops[0]
	}

	var (
		parent *Node
		i      int
	)

	if !op.NoCache {
		if parent, err = t.getOrCreateRoot(ctx); parent == nil {
			return
		}
		i++
		// parent may be a temp root in concurrent scene
	}

	for ; i < len(t.filters); i++ {
		if op.Level > 0 && op.Level == i {
			break
		}
		if parent, err = t.getOrCreateNode(ctx, i, parent); parent == nil {
			break
		}
	}
	node = parent
	return
}

func (t *Tree) Remove(ctx context.Context) {
	if len(t.filters) == 0 {
		return
	}

	t.roots.Delete(t.filters[0].Name(ctx, nil))
}

func (t *Tree) getOrCreateRoot(ctx context.Context) (node *Node, err error) {
	if len(t.filters) == 0 {
		return
	}

	filter := t.filters[0]
	name := filter.Name(ctx, nil)
	item, err := t.roots.Fetch(name, t.Config.TTL(), func() (interface{}, error) {
		node, err := t.getOrCreateNode(ctx, 0, nil)
		if err != nil {
			return nil, err
		}
		if node == nil {
			return nil, errNilNode
		}
		return node, nil
	})
	switch err {
	case nil:
		node = item.Value().(*Node)
	case errNilNode:
		err = nil
	}
	return
}

func (t *Tree) getOrCreateNode(ctx context.Context, idx int, parent *Node) (node *Node, err error) {
	filter := t.filters[idx]
	name := t.nodeFullName(filter.Name(ctx, parent), parent)

	if parent == nil {
		// new a temp node
		return t.createNode(ctx, idx, name, parent)
	}

	item, err := parent.Childs.Fetch(name, func() (interface{}, error) {
		node, err := t.createNode(ctx, idx, name, parent)
		if err != nil {
			return nil, err
		}
		if node == nil {
			return nil, errNilNode
		}
		return node, nil
	})
	switch err {
	case nil:
		node = item.(*Node)
	case errNilNode:
		err = nil
	}
	return
}

func (t *Tree) nodeFullName(name string, parent *Node) string {
	if parent != nil {
		name = parent.Name + "." + name
	}
	return name
}

func (t *Tree) createNode(ctx context.Context, idx int, name string, parent *Node) (node *Node, err error) {
	node, err = t.filters[idx].Init(ctx, parent)
	if node == nil {
		return
	}
	node.Name = name
	node.Tree = t
	node.Level = idx
	return
}

func NewTree(cfg *Config) *Tree {
	return &Tree{
		Config: cfg,
		roots:  ccache.New(ccache.Configure().MaxSize(cfg.MaxSize())),
	}
}
