// Copyright 2018 PingCAP, Inc.
//
// Licensed 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package memo

import (
	"container/list"
)

// ExprIter enumerates all the equivalent expressions in the Group according to
// the expression pattern.
type ExprIter struct {
	// Group and Element solely identify a Group expression.
	*Group
	*list.Element

	// matched indicates whether the current Group expression binded by the
	// iterator matches the pattern after the creation or iteration.
	matched bool

	// Operand is the node of the pattern tree. The Operand type of the Group
	// expression must be matched with it.
	Operand

	// Children is used to iterate the child expressions.
	Children []*ExprIter
}

// Next returns the next Group expression matches the pattern.
func (iter *ExprIter) Next() (found bool) {
	defer func() {
		iter.matched = found
	}()

	// Iterate child firstly.
	for i := len(iter.Children) - 1; i >= 0; i-- {
		if !iter.Children[i].Next() {
			continue
		}

		for j := i + 1; j < len(iter.Children); j++ {
			iter.Children[j].Reset()
		}
		return true
	}

	// It's root node.
	if iter.Group == nil {
		return false
	}

	// Otherwise, iterate itself to find more matched equivalent expressions.
	for elem := iter.Element.Next(); elem != nil; elem = elem.Next() {
		expr := elem.Value.(*GroupExpr)
		exprOperand := GetOperand(expr.ExprNode)

		if !iter.Operand.Match(exprOperand) {
			// All the Equivalents which have the same Operand are continuously
			// stored in the list. Once the current equivalent can not Match
			// the Operand, the rest can not, either.
			return false
		}

		if len(iter.Children) != len(expr.Children) {
			continue
		}

		allMatched := true
		for i := range iter.Children {
			iter.Children[i].Group = expr.Children[i]
			if !iter.Children[i].Reset() {
				allMatched = false
				break
			}
		}

		if allMatched {
			iter.Element = elem
			return true
		}
	}
	return false
}

// Matched returns whether the iterator founds a Group expression matches the
// pattern.
func (iter *ExprIter) Matched() bool {
	return iter.matched
}

// Reset resets the iterator to the first matched Group expression.
func (iter *ExprIter) Reset() (findMatch bool) {
	defer func() { iter.matched = findMatch }()

	for elem := iter.Group.GetFirstElem(iter.Operand); elem != nil; elem = elem.Next() {
		expr := elem.Value.(*GroupExpr)
		exprOperand := GetOperand(expr.ExprNode)
		if !iter.Operand.Match(exprOperand) {
			break
		}

		if len(expr.Children) != len(iter.Children) {
			continue
		}

		allMatched := true
		for i := range iter.Children {
			iter.Children[i].Group = expr.Children[i]
			if !iter.Children[i].Reset() {
				allMatched = false
				break
			}
		}
		if allMatched {
			iter.Element = elem
			return true
		}
	}
	return false
}

// NewExprIterFromGroupElem creates the iterator on the Group Element.
func NewExprIterFromGroupElem(elem *list.Element, p *Pattern) *ExprIter {
	expr := elem.Value.(*GroupExpr)
	if !p.Operand.Match(GetOperand(expr.ExprNode)) {
		return nil
	}
	iter := newExprIterFromGroupExpr(expr, p)
	if iter != nil {
		iter.Element = elem
	}
	return iter
}

// newExprIterFromGroupExpr creates the iterator on the Group expression.
func newExprIterFromGroupExpr(expr *GroupExpr, p *Pattern) *ExprIter {
	if len(p.Children) != len(expr.Children) {
		return nil
	}

	iter := &ExprIter{Operand: p.Operand, matched: true}
	for i := range p.Children {
		childIter := newExprIterFromGroup(expr.Children[i], p.Children[i])
		if childIter == nil {
			return nil
		}
		iter.Children = append(iter.Children, childIter)
	}
	return iter
}

// newExprIterFromGroup creates the iterator on the Group.
func newExprIterFromGroup(g *Group, p *Pattern) *ExprIter {
	for elem := g.GetFirstElem(p.Operand); elem != nil; elem = elem.Next() {
		expr := elem.Value.(*GroupExpr)
		if !p.Operand.Match(GetOperand(expr.ExprNode)) {
			return nil
		}
		iter := newExprIterFromGroupExpr(expr, p)
		if iter != nil {
			iter.Group, iter.Element = g, elem
			return iter
		}
	}
	return nil
}
