// 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 (
	plannercore "github.com/pingcap/tidb/planner/core"
)

// Operand is the node of a pattern tree, it represents a logical expression operator.
// Different from logical plan operator which holds the full information about an expression
// operator, Operand only stores the type information.
// An Operand may correspond to a concrete logical plan operator, or it can has special meaning,
// e.g, a placeholder for any logical plan operator.
type Operand int

const (
	// OperandAny is a placeholder for any Operand.
	OperandAny Operand = iota
	// OperandJoin is the operand for LogicalJoin.
	OperandJoin
	// OperandAggregation is the operand for LogicalAggregation.
	OperandAggregation
	// OperandProjection is the operand for LogicalProjection.
	OperandProjection
	// OperandSelection is the operand for LogicalSelection.
	OperandSelection
	// OperandApply is the operand for LogicalApply.
	OperandApply
	// OperandMaxOneRow is the operand for LogicalMaxOneRow.
	OperandMaxOneRow
	// OperandTableDual is the operand for LogicalTableDual.
	OperandTableDual
	// OperandDataSource is the operand for DataSource.
	OperandDataSource
	// OperandUnionScan is the operand for LogicalUnionScan.
	OperandUnionScan
	// OperandUnionAll is the operand for LogicalUnionAll.
	OperandUnionAll
	// OperandSort is the operand for LogicalSort.
	OperandSort
	// OperandTopN is the operand for LogicalTopN.
	OperandTopN
	// OperandLock is the operand for LogicalLock.
	OperandLock
	// OperandLimit is the operand for LogicalLimit.
	OperandLimit
	// OperandTiKVSingleGather is the operand for TiKVSingleGather.
	OperandTiKVSingleGather
	// OperandMemTableScan is the operand for MemTableScan.
	OperandMemTableScan
	// OperandTableScan is the operand for TableScan.
	OperandTableScan
	// OperandIndexScan is the operand for IndexScan.
	OperandIndexScan
	// OperandShow is the operand for Show.
	OperandShow
	// OperandWindow is the operand for window function.
	OperandWindow
	// OperandUnsupported is the operand for unsupported operators.
	OperandUnsupported
)

// GetOperand maps logical plan operator to Operand.
func GetOperand(p plannercore.LogicalPlan) Operand {
	switch p.(type) {
	case *plannercore.LogicalApply:
		return OperandApply
	case *plannercore.LogicalJoin:
		return OperandJoin
	case *plannercore.LogicalAggregation:
		return OperandAggregation
	case *plannercore.LogicalProjection:
		return OperandProjection
	case *plannercore.LogicalSelection:
		return OperandSelection
	case *plannercore.LogicalMaxOneRow:
		return OperandMaxOneRow
	case *plannercore.LogicalTableDual:
		return OperandTableDual
	case *plannercore.DataSource:
		return OperandDataSource
	case *plannercore.LogicalUnionScan:
		return OperandUnionScan
	case *plannercore.LogicalUnionAll:
		return OperandUnionAll
	case *plannercore.LogicalSort:
		return OperandSort
	case *plannercore.LogicalTopN:
		return OperandTopN
	case *plannercore.LogicalLock:
		return OperandLock
	case *plannercore.LogicalLimit:
		return OperandLimit
	case *plannercore.TiKVSingleGather:
		return OperandTiKVSingleGather
	case *plannercore.LogicalTableScan:
		return OperandTableScan
	case *plannercore.LogicalMemTable:
		return OperandMemTableScan
	case *plannercore.LogicalIndexScan:
		return OperandIndexScan
	case *plannercore.LogicalShow:
		return OperandShow
	case *plannercore.LogicalWindow:
		return OperandWindow
	default:
		return OperandUnsupported
	}
}

// Match checks if current Operand matches specified one.
func (o Operand) Match(t Operand) bool {
	if o == OperandAny || t == OperandAny {
		return true
	}
	if o == t {
		return true
	}
	return false
}

// Pattern defines the match pattern for a rule. It's a tree-like structure
// which is a piece of a logical expression. Each node in the Pattern tree is
// defined by an Operand and EngineType pair.
type Pattern struct {
	Operand
	EngineTypeSet
	Children []*Pattern
}

// Match checks whether the EngineTypeSet contains the given EngineType
// and whether the two Operands match.
func (p *Pattern) Match(o Operand, e EngineType) bool {
	return p.EngineTypeSet.Contains(e) && p.Operand.Match(o)
}

// MatchOperandAny checks whether the pattern's Operand is OperandAny
// and the EngineTypeSet contains the given EngineType.
func (p *Pattern) MatchOperandAny(e EngineType) bool {
	return p.EngineTypeSet.Contains(e) && p.Operand == OperandAny
}

// NewPattern creates a pattern node according to the Operand and EngineType.
func NewPattern(operand Operand, engineTypeSet EngineTypeSet) *Pattern {
	return &Pattern{Operand: operand, EngineTypeSet: engineTypeSet}
}

// SetChildren sets the Children information for a pattern node.
func (p *Pattern) SetChildren(children ...*Pattern) {
	p.Children = children
}

// BuildPattern builds a Pattern from Operand, EngineType and child Patterns.
// Used in GetPattern() of Transformation interface to generate a Pattern.
func BuildPattern(operand Operand, engineTypeSet EngineTypeSet, children ...*Pattern) *Pattern {
	p := &Pattern{Operand: operand, EngineTypeSet: engineTypeSet}
	p.Children = children
	return p
}
