// Copyright 2020 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 stmtsummary

import (
	"fmt"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"

	"github.com/pingcap/errors"
	"github.com/pingcap/tidb/config"
)

const (
	typeEnable = iota
	typeEnableInternalQuery
	typeRefreshInterval
	typeHistorySize
	typeMaxStmtCount
	typeMaxSQLLength
	typesNum
)

type systemVars struct {
	sync.Mutex
	// This array itself won't be modified once created. Only its elements may be modified.
	variables []variable
}

type variable struct {
	sessionValue string
	globalValue  string
	finalValue   int64
}

func newSysVars() *systemVars {
	s := &systemVars{
		variables: make([]variable, typesNum),
	}
	// Initialize these configurations by values in the config file.
	// They may be overwritten by system variables later.
	for varType := range s.variables {
		atomic.StoreInt64(&s.variables[varType].finalValue, getConfigValue(varType))
	}
	return s
}

func (s *systemVars) getVariable(varType int) int64 {
	return atomic.LoadInt64(&s.variables[varType].finalValue)
}

func (s *systemVars) setVariable(varType int, valueStr string, isSession bool) error {
	s.Lock()
	defer s.Unlock()

	v := &s.variables[varType]
	if isSession {
		v.sessionValue = valueStr
	} else {
		v.globalValue = valueStr
	}
	sessionValue := v.sessionValue
	globalValue := v.globalValue

	var valueInt int64
	switch varType {
	case typeEnable, typeEnableInternalQuery:
		valueInt = getBoolFinalVariable(varType, sessionValue, globalValue)
	case typeHistorySize, typeMaxSQLLength:
		valueInt = getIntFinalVariable(varType, sessionValue, globalValue, 0)
	case typeRefreshInterval, typeMaxStmtCount:
		valueInt = getIntFinalVariable(varType, sessionValue, globalValue, 1)
	default:
		return errors.New(fmt.Sprintf("no such type of variable: %d", varType))
	}
	atomic.StoreInt64(&v.finalValue, valueInt)
	return nil
}

func getBoolFinalVariable(varType int, sessionValue, globalValue string) int64 {
	var valueInt int64
	if len(sessionValue) > 0 {
		valueInt = normalizeEnableValue(sessionValue)
	} else if len(globalValue) > 0 {
		valueInt = normalizeEnableValue(globalValue)
	} else {
		valueInt = getConfigValue(varType)
	}
	return valueInt
}

// normalizeEnableValue converts 'ON' or '1' to 1 and 'OFF' or '0' to 0.
func normalizeEnableValue(value string) int64 {
	switch {
	case strings.EqualFold(value, "ON"):
		return 1
	case value == "1":
		return 1
	default:
		return 0
	}
}

func getIntFinalVariable(varType int, sessionValue, globalValue string, minValue int64) int64 {
	valueInt := minValue - 1
	var err error
	if len(sessionValue) > 0 {
		valueInt, err = strconv.ParseInt(sessionValue, 10, 64)
		if err != nil {
			valueInt = minValue - 1
		}
	}
	if valueInt < minValue {
		valueInt, err = strconv.ParseInt(globalValue, 10, 64)
		if err != nil {
			valueInt = minValue - 1
		}
	}
	// If session and global variables are both '', use the value in config.
	if valueInt < minValue {
		valueInt = getConfigValue(varType)
	}
	return valueInt
}

func getConfigValue(varType int) int64 {
	var valueInt int64
	stmtSummaryConfig := config.GetGlobalConfig().StmtSummary
	switch varType {
	case typeEnable:
		if stmtSummaryConfig.Enable {
			valueInt = 1
		}
	case typeEnableInternalQuery:
		if stmtSummaryConfig.EnableInternalQuery {
			valueInt = 1
		}
	case typeRefreshInterval:
		valueInt = int64(stmtSummaryConfig.RefreshInterval)
	case typeHistorySize:
		valueInt = int64(stmtSummaryConfig.HistorySize)
	case typeMaxStmtCount:
		valueInt = int64(stmtSummaryConfig.MaxStmtCount)
	case typeMaxSQLLength:
		valueInt = int64(stmtSummaryConfig.MaxSQLLength)
	default:
		panic(fmt.Sprintf("No such type of variable: %d", varType))
	}
	return valueInt
}
