/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package expr

import (
	"math"
)

type builtInFunc struct {
	minArgs, maxArgs int
	call             func([]interface{}) interface{}
}

func fnMin(args []interface{}) interface{} {
	res := args[0]
	for _, arg := range args[1:] {
		switch v1 := res.(type) {
		case int64:
			switch v2 := arg.(type) {
			case int64:
				if v2 < v1 {
					res = v2
				}
			case float64:
				res = math.Min(float64(v1), v2)
			default:
				panic(ErrorUnsupportedDataType)
			}
		case float64:
			switch v2 := arg.(type) {
			case int64:
				res = math.Min(v1, float64(v2))
			case float64:
				res = math.Min(v1, v2)
			default:
				panic(ErrorUnsupportedDataType)
			}
		default:
			panic(ErrorUnsupportedDataType)
		}
	}
	return res
}

func fnMax(args []interface{}) interface{} {
	res := args[0]
	for _, arg := range args[1:] {
		switch v1 := res.(type) {
		case int64:
			switch v2 := arg.(type) {
			case int64:
				if v2 > v1 {
					res = v2
				}
			case float64:
				res = math.Max(float64(v1), v2)
			default:
				panic(ErrorUnsupportedDataType)
			}
		case float64:
			switch v2 := arg.(type) {
			case int64:
				res = math.Max(v1, float64(v2))
			case float64:
				res = math.Max(v1, v2)
			default:
				panic(ErrorUnsupportedDataType)
			}
		default:
			panic(ErrorUnsupportedDataType)
		}
	}
	return res
}

func fnSum(args []interface{}) interface{} {
	res := float64(0)
	for _, arg := range args {
		switch v := arg.(type) {
		case int64:
			res += float64(v)
		case float64:
			res += v
		default:
			panic(ErrorUnsupportedDataType)
		}
	}
	return res
}

func fnAvg(args []interface{}) interface{} {
	return fnSum(args).(float64) / float64(len(args))
}

func fnSqrt(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return math.Sqrt(float64(v))
	case float64:
		return math.Sqrt(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnFloor(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return v
	case float64:
		return math.Floor(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnCeil(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return v
	case float64:
		return math.Ceil(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnRound(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return v
	case float64:
		return math.Round(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnLog(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return math.Log(float64(v))
	case float64:
		return math.Log(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnLog10(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		return math.Log10(float64(v))
	case float64:
		return math.Log10(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnAbs(args []interface{}) interface{} {
	switch v := args[0].(type) {
	case int64:
		if v < 0 {
			return -v
		}
		return v
	case float64:
		return math.Abs(v)
	default:
		panic(ErrorUnsupportedDataType)
	}
}

func fnIf(args []interface{}) interface{} {
	v, ok := args[0].(bool)
	if !ok {
		panic(ErrorUnsupportedDataType)
	}
	if v {
		return args[1]
	} else {
		return args[2]
	}
}

var funcs = map[string]builtInFunc{
	"min":   builtInFunc{minArgs: 1, maxArgs: -1, call: fnMin},
	"max":   builtInFunc{minArgs: 1, maxArgs: -1, call: fnMax},
	"sum":   builtInFunc{minArgs: 1, maxArgs: -1, call: fnSum},
	"avg":   builtInFunc{minArgs: 1, maxArgs: -1, call: fnAvg},
	"sqrt":  builtInFunc{minArgs: 1, maxArgs: 1, call: fnSqrt},
	"ceil":  builtInFunc{minArgs: 1, maxArgs: 1, call: fnCeil},
	"floor": builtInFunc{minArgs: 1, maxArgs: 1, call: fnFloor},
	"round": builtInFunc{minArgs: 1, maxArgs: 1, call: fnRound},
	"log":   builtInFunc{minArgs: 1, maxArgs: 1, call: fnLog},
	"log10": builtInFunc{minArgs: 1, maxArgs: 1, call: fnLog10},
	"abs":   builtInFunc{minArgs: 1, maxArgs: 1, call: fnAbs},
	"if":    builtInFunc{minArgs: 3, maxArgs: 3, call: fnIf},
}
