// Copyright 2020 CUE Authors
//
// 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 export

import (
	"cuelang.org/go/cue/ast"
	"cuelang.org/go/internal/core/adt"
	"github.com/cockroachdb/apd/v3"
)

// boundSimplifier simplifies bound values into predeclared identifiers, if
// possible.
type boundSimplifier struct {
	e *exporter

	isInt  bool
	min    *adt.BoundValue
	minNum *adt.Num
	max    *adt.BoundValue
	maxNum *adt.Num
}

func (s *boundSimplifier) add(v adt.Value) (used bool) {
	switch x := v.(type) {
	case *adt.BasicType:
		switch x.K & adt.ScalarKinds {
		case adt.IntKind:
			s.isInt = true
			return true
		}

	case *adt.BoundValue:
		if adt.IsConcrete(x.Value) && x.Kind() == adt.IntKind {
			s.isInt = true
		}
		switch x.Op {
		case adt.GreaterThanOp:
			if n, ok := x.Value.(*adt.Num); ok {
				if s.min == nil || s.minNum.X.Cmp(&n.X) != 1 {
					s.min = x
					s.minNum = n
				}
				return true
			}

		case adt.GreaterEqualOp:
			if n, ok := x.Value.(*adt.Num); ok {
				if s.min == nil || s.minNum.X.Cmp(&n.X) == -1 {
					s.min = x
					s.minNum = n
				}
				return true
			}

		case adt.LessThanOp:
			if n, ok := x.Value.(*adt.Num); ok {
				if s.max == nil || s.maxNum.X.Cmp(&n.X) != -1 {
					s.max = x
					s.maxNum = n
				}
				return true
			}

		case adt.LessEqualOp:
			if n, ok := x.Value.(*adt.Num); ok {
				if s.max == nil || s.maxNum.X.Cmp(&n.X) == 1 {
					s.max = x
					s.maxNum = n
				}
				return true
			}
		}
	}

	return false
}

type builtinRange struct {
	typ string
	lo  *apd.Decimal
	hi  *apd.Decimal
}

func makeDec(s string) *apd.Decimal {
	d, _, err := apd.NewFromString(s)
	if err != nil {
		panic(err)
	}
	return d
}

func (s *boundSimplifier) expr(ctx *adt.OpContext) (e ast.Expr) {
	if s.min == nil || s.max == nil {
		return nil
	}
	switch {
	case s.isInt:
		t := s.matchRange(intRanges)
		if t != "" {
			e = ast.NewIdent(t)
			break
		}
		if sign := s.minNum.X.Sign(); sign == -1 {
			e = ast.NewIdent("int")

		} else {
			e = ast.NewIdent("uint")
			if sign == 0 && s.min.Op == adt.GreaterEqualOp {
				s.min = nil
				break
			}
		}
		fallthrough
	default:
		t := s.matchRange(floatRanges)
		if t != "" {
			e = wrapBin(e, ast.NewIdent(t), adt.AndOp)
		}
	}

	if s.min != nil {
		e = wrapBin(e, s.e.expr(nil, s.min), adt.AndOp)
	}
	if s.max != nil {
		e = wrapBin(e, s.e.expr(nil, s.max), adt.AndOp)
	}
	return e
}

func (s *boundSimplifier) matchRange(ranges []builtinRange) (t string) {
	for _, r := range ranges {
		if !s.minNum.X.IsZero() && s.min.Op == adt.GreaterEqualOp && s.minNum.X.Cmp(r.lo) == 0 {
			switch s.maxNum.X.Cmp(r.hi) {
			case 0:
				if s.max.Op == adt.LessEqualOp {
					s.max = nil
				}
				s.min = nil
				return r.typ
			case -1:
				if !s.minNum.X.IsZero() {
					s.min = nil
					return r.typ
				}
			case 1:
			}
		} else if s.max.Op == adt.LessEqualOp && s.maxNum.X.Cmp(r.hi) == 0 {
			switch s.minNum.X.Cmp(r.lo) {
			case -1:
			case 0:
				if s.min.Op == adt.GreaterEqualOp {
					s.min = nil
				}
				fallthrough
			case 1:
				s.max = nil
				return r.typ
			}
		}
	}
	return ""
}

var intRanges = []builtinRange{
	{"int8", makeDec("-128"), makeDec("127")},
	{"int16", makeDec("-32768"), makeDec("32767")},
	{"int32", makeDec("-2147483648"), makeDec("2147483647")},
	{"int64", makeDec("-9223372036854775808"), makeDec("9223372036854775807")},
	{"int128", makeDec("-170141183460469231731687303715884105728"),
		makeDec("170141183460469231731687303715884105727")},

	{"uint8", makeDec("0"), makeDec("255")},
	{"uint16", makeDec("0"), makeDec("65535")},
	{"uint32", makeDec("0"), makeDec("4294967295")},
	{"uint64", makeDec("0"), makeDec("18446744073709551615")},
	{"uint128", makeDec("0"), makeDec("340282366920938463463374607431768211455")},

	// {"rune", makeDec("0"), makeDec(strconv.Itoa(0x10FFFF))},
}

var floatRanges = []builtinRange{
	// 2**127 * (2**24 - 1) / 2**23
	{"float32",
		makeDec("-3.40282346638528859811704183484516925440e+38"),
		makeDec("3.40282346638528859811704183484516925440e+38")},

	// 2**1023 * (2**53 - 1) / 2**52
	{"float64",
		makeDec("-1.797693134862315708145274237317043567981e+308"),
		makeDec("1.797693134862315708145274237317043567981e+308")},
}

func wrapBin(a, b ast.Expr, op adt.Op) ast.Expr {
	if a == nil {
		return b
	}
	if b == nil {
		return a
	}
	return ast.NewBinExpr(op.Token(), a, b)
}
