// Copyright 2019 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 implementation

import (
	plannercore "github.com/pingcap/tidb/planner/core"
	"github.com/pingcap/tidb/planner/memo"
)

// ProjectionImpl is the implementation of PhysicalProjection.
type ProjectionImpl struct {
	baseImpl
}

// NewProjectionImpl creates a new projection Implementation.
func NewProjectionImpl(proj *plannercore.PhysicalProjection) *ProjectionImpl {
	return &ProjectionImpl{baseImpl{plan: proj}}
}

// ShowImpl is the Implementation of PhysicalShow.
type ShowImpl struct {
	baseImpl
}

// NewShowImpl creates a new ShowImpl.
func NewShowImpl(show *plannercore.PhysicalShow) *ShowImpl {
	return &ShowImpl{baseImpl: baseImpl{plan: show}}
}

// TiDBSelectionImpl is the implementation of PhysicalSelection in TiDB layer.
type TiDBSelectionImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (sel *TiDBSelectionImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	sel.cost = children[0].GetPlan().Stats().RowCount*sel.plan.SCtx().GetSessionVars().CPUFactor + children[0].GetCost()
	return sel.cost
}

// NewTiDBSelectionImpl creates a new TiDBSelectionImpl.
func NewTiDBSelectionImpl(sel *plannercore.PhysicalSelection) *TiDBSelectionImpl {
	return &TiDBSelectionImpl{baseImpl{plan: sel}}
}

// TiKVSelectionImpl is the implementation of PhysicalSelection in TiKV layer.
type TiKVSelectionImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (sel *TiKVSelectionImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	sel.cost = children[0].GetPlan().Stats().RowCount*sel.plan.SCtx().GetSessionVars().CopCPUFactor + children[0].GetCost()
	return sel.cost
}

// NewTiKVSelectionImpl creates a new TiKVSelectionImpl.
func NewTiKVSelectionImpl(sel *plannercore.PhysicalSelection) *TiKVSelectionImpl {
	return &TiKVSelectionImpl{baseImpl{plan: sel}}
}

// TiDBHashAggImpl is the implementation of PhysicalHashAgg in TiDB layer.
type TiDBHashAggImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (agg *TiDBHashAggImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	hashAgg := agg.plan.(*plannercore.PhysicalHashAgg)
	selfCost := hashAgg.GetCost(children[0].GetPlan().Stats().RowCount, true)
	agg.cost = selfCost + children[0].GetCost()
	return agg.cost
}

// AttachChildren implements Implementation AttachChildren interface.
func (agg *TiDBHashAggImpl) AttachChildren(children ...memo.Implementation) memo.Implementation {
	hashAgg := agg.plan.(*plannercore.PhysicalHashAgg)
	hashAgg.SetChildren(children[0].GetPlan())
	// Inject extraProjection if the AggFuncs or GroupByItems contain ScalarFunction.
	plannercore.InjectProjBelowAgg(hashAgg, hashAgg.AggFuncs, hashAgg.GroupByItems)
	return agg
}

// NewTiDBHashAggImpl creates a new TiDBHashAggImpl.
func NewTiDBHashAggImpl(agg *plannercore.PhysicalHashAgg) *TiDBHashAggImpl {
	return &TiDBHashAggImpl{baseImpl{plan: agg}}
}

// TiKVHashAggImpl is the implementation of PhysicalHashAgg in TiKV layer.
type TiKVHashAggImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (agg *TiKVHashAggImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	hashAgg := agg.plan.(*plannercore.PhysicalHashAgg)
	selfCost := hashAgg.GetCost(children[0].GetPlan().Stats().RowCount, false)
	agg.cost = selfCost + children[0].GetCost()
	return agg.cost
}

// NewTiKVHashAggImpl creates a new TiKVHashAggImpl.
func NewTiKVHashAggImpl(agg *plannercore.PhysicalHashAgg) *TiKVHashAggImpl {
	return &TiKVHashAggImpl{baseImpl{plan: agg}}
}

// LimitImpl is the implementation of PhysicalLimit. Since PhysicalLimit on different
// engines have the same behavior, and we don't calculate the cost of `Limit`, we only
// have one Implementation for it.
type LimitImpl struct {
	baseImpl
}

// NewLimitImpl creates a new LimitImpl.
func NewLimitImpl(limit *plannercore.PhysicalLimit) *LimitImpl {
	return &LimitImpl{baseImpl{plan: limit}}
}

// TiDBTopNImpl is the implementation of PhysicalTopN in TiDB layer.
type TiDBTopNImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (impl *TiDBTopNImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	topN := impl.plan.(*plannercore.PhysicalTopN)
	childCount := children[0].GetPlan().Stats().RowCount
	impl.cost = topN.GetCost(childCount, true) + children[0].GetCost()
	return impl.cost
}

// NewTiDBTopNImpl creates a new TiDBTopNImpl.
func NewTiDBTopNImpl(topN *plannercore.PhysicalTopN) *TiDBTopNImpl {
	return &TiDBTopNImpl{baseImpl{plan: topN}}
}

// TiKVTopNImpl is the implementation of PhysicalTopN in TiKV layer.
type TiKVTopNImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (impl *TiKVTopNImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	topN := impl.plan.(*plannercore.PhysicalTopN)
	childCount := children[0].GetPlan().Stats().RowCount
	impl.cost = topN.GetCost(childCount, false) + children[0].GetCost()
	return impl.cost
}

// NewTiKVTopNImpl creates a new TiKVTopNImpl.
func NewTiKVTopNImpl(topN *plannercore.PhysicalTopN) *TiKVTopNImpl {
	return &TiKVTopNImpl{baseImpl{plan: topN}}
}

// UnionAllImpl is the implementation of PhysicalUnionAll.
type UnionAllImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (impl *UnionAllImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	var childMaxCost float64
	for _, child := range children {
		childCost := child.GetCost()
		if childCost > childMaxCost {
			childMaxCost = childCost
		}
	}
	selfCost := float64(1+len(children)) * impl.plan.SCtx().GetSessionVars().ConcurrencyFactor
	// Children of UnionAll are executed in parallel.
	impl.cost = selfCost + childMaxCost
	return impl.cost
}

// GetCostLimit implements Implementation interface.
func (impl *UnionAllImpl) GetCostLimit(costLimit float64, children ...memo.Implementation) float64 {
	return costLimit
}

// NewUnionAllImpl creates a new UnionAllImpl.
func NewUnionAllImpl(union *plannercore.PhysicalUnionAll) *UnionAllImpl {
	return &UnionAllImpl{baseImpl{plan: union}}
}

// ApplyImpl is the implementation of PhysicalApply.
type ApplyImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (impl *ApplyImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	apply := impl.plan.(*plannercore.PhysicalApply)
	impl.cost = apply.GetCost(
		children[0].GetPlan().Stats().RowCount,
		children[1].GetPlan().Stats().RowCount,
		children[0].GetCost(),
		children[1].GetCost())
	return impl.cost
}

// GetCostLimit implements Implementation GetCostLimit interface.
func (impl *ApplyImpl) GetCostLimit(costLimit float64, children ...memo.Implementation) float64 {
	if len(children) == 0 {
		return costLimit
	}
	// The Cost of Apply is: selfCost + leftCost + leftCount * rightCost.
	// If we have implemented the leftChild, the costLimit for the right
	// side should be (costLimit - selfCost - leftCost)/leftCount. Since
	// we haven't implement the rightChild, we cannot calculate the `selfCost`.
	// So we just use (costLimit - leftCost)/leftCount here.
	leftCount, leftCost := children[0].GetPlan().Stats().RowCount, children[0].GetCost()
	apply := impl.plan.(*plannercore.PhysicalApply)
	if len(apply.LeftConditions) > 0 {
		leftCount *= plannercore.SelectionFactor
	}
	return (costLimit - leftCost) / leftCount
}

// NewApplyImpl creates a new ApplyImpl.
func NewApplyImpl(apply *plannercore.PhysicalApply) *ApplyImpl {
	return &ApplyImpl{baseImpl{plan: apply}}
}

// MaxOneRowImpl is the implementation of PhysicalApply.
type MaxOneRowImpl struct {
	baseImpl
}

// CalcCost implements Implementation CalcCost interface.
func (impl *MaxOneRowImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	impl.cost = children[0].GetCost()
	return impl.cost
}

// NewMaxOneRowImpl creates a new MaxOneRowImpl.
func NewMaxOneRowImpl(maxOneRow *plannercore.PhysicalMaxOneRow) *MaxOneRowImpl {
	return &MaxOneRowImpl{baseImpl{plan: maxOneRow}}
}

// WindowImpl is the implementation of PhysicalWindow.
type WindowImpl struct {
	baseImpl
}

// NewWindowImpl creates a new WindowImpl.
func NewWindowImpl(window *plannercore.PhysicalWindow) *WindowImpl {
	return &WindowImpl{baseImpl{plan: window}}
}

// CalcCost implements Implementation CalcCost interface.
func (impl *WindowImpl) CalcCost(outCount float64, children ...memo.Implementation) float64 {
	impl.cost = children[0].GetCost()
	return impl.cost
}
