// 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,
// 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 expression

import (
	"context"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/pkg/meta/model"
	"github.com/pingcap/tidb/pkg/parser"
	"github.com/pingcap/tidb/pkg/parser/ast"
	"github.com/pingcap/tidb/pkg/types"
	"github.com/pingcap/tidb/pkg/util"
	"github.com/pingcap/tidb/pkg/util/intest"
	"github.com/pingcap/tidb/pkg/util/sqlexec"
)

// ParseSimpleExprWithTableInfo now is only used by some external repo like tiflow to make sure they are not break.
// Deprecated: please use ParseSimpleExpr instead.
func ParseSimpleExprWithTableInfo(ctx BuildContext, exprStr string, tableInfo *model.TableInfo) (Expression, error) {
	return ParseSimpleExpr(ctx, exprStr, WithTableInfo("", tableInfo))
}

// ParseSimpleExpr parses simple expression string to Expression.
// The expression string must only reference the column in table Info.
func ParseSimpleExpr(ctx BuildContext, exprStr string, opts ...BuildOption) (Expression, error) {
	if exprStr == "" {
		intest.Assert(false)
		// This should never happen. Return a clear error message in case we have some unexpected bugs.
		return nil, errors.New("expression should not be an empty string")
	}
	exprStr = "select " + exprStr
	var stmts []ast.StmtNode
	var err error
	var warns []error
	if p, ok := ctx.(sqlexec.SQLParser); ok {
		stmts, warns, err = p.ParseSQL(context.Background(), exprStr)
	} else {
		stmts, warns, err = parser.New().ParseSQL(exprStr)
	}
	for _, warn := range warns {
		ctx.GetEvalCtx().AppendWarning(util.SyntaxWarn(warn))
	}

	if err != nil {
		return nil, errors.Trace(err)
	}
	expr := stmts[0].(*ast.SelectStmt).Fields.Fields[0].Expr
	return BuildSimpleExpr(ctx, expr, opts...)
}

// FindFieldName finds the column name from NameSlice.
func FindFieldName(names types.NameSlice, astCol *ast.ColumnName) (int, error) {
	dbName, tblName, colName := astCol.Schema.L, astCol.Table.L, astCol.Name.L
	idx := -1

	for i, name := range names {
		// Early continue if not usable or column name doesn't match
		if name.NotExplicitUsable || name.ColName.L != colName {
			continue
		}

		// Check database and table name match
		if (dbName == "" || dbName == name.DBName.L) &&
			(tblName == "" || tblName == name.TblName.L) {
			if idx != -1 {
				// Do not allow multiple non-redundant columns with the same name.
				if names[idx].Redundant || name.Redundant {
					if !name.Redundant {
						idx = i
					}
					continue
				}
				return -1, errNonUniq.GenWithStackByArgs(astCol.String(), "field list")
			}
			idx = i
		}
	}

	return idx, nil
}

// FindFieldNameIdxByColName finds the index of corresponding name in the given slice. -1 for not found.
func FindFieldNameIdxByColName(names []*types.FieldName, colName string) int {
	for i, name := range names {
		if name.ColName.L == colName {
			return i
		}
	}
	return -1
}
