package gval_test

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/PaesslerAG/gval"
	"github.com/PaesslerAG/jsonpath"
)

func Example() {

	vars := map[string]interface{}{"name": "World"}

	value, err := gval.Evaluate(`"Hello " + name + "!"`, vars)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// Hello World!
}

func ExampleEvaluate() {

	value, err := gval.Evaluate("foo > 0", map[string]interface{}{
		"foo": -1.,
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// false
}

func ExampleEvaluate_nestedParameter() {

	value, err := gval.Evaluate("foo.bar > 0", map[string]interface{}{
		"foo": map[string]interface{}{"bar": -1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// false
}

func ExampleEvaluate_array() {

	value, err := gval.Evaluate("foo[0]", map[string]interface{}{
		"foo": []interface{}{-1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// -1
}

func ExampleEvaluate_complexAccessor() {

	value, err := gval.Evaluate(`foo["b" + "a" + "r"]`, map[string]interface{}{
		"foo": map[string]interface{}{"bar": -1.},
	})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// -1
}

func ExampleEvaluate_arithmetic() {

	value, err := gval.Evaluate("(requests_made * requests_succeeded / 100) >= 90",
		map[string]interface{}{
			"requests_made":      100,
			"requests_succeeded": 80,
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// false
}

func ExampleEvaluate_string() {

	value, err := gval.Evaluate(`http_response_body == "service is ok"`,
		map[string]interface{}{
			"http_response_body": "service is ok",
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// true
}

func ExampleEvaluate_float64() {

	value, err := gval.Evaluate("(mem_used / total_mem) * 100",
		map[string]interface{}{
			"total_mem": 1024,
			"mem_used":  512,
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// 50
}

func ExampleEvaluate_dateComparison() {

	value, err := gval.Evaluate("date(`2014-01-02`) > date(`2014-01-01 23:59:59`)",
		nil,
		// define Date comparison because it is not part expression language gval
		gval.InfixOperator(">", func(a, b interface{}) (interface{}, error) {
			date1, ok1 := a.(time.Time)
			date2, ok2 := b.(time.Time)

			if ok1 && ok2 {
				return date1.After(date2), nil
			}
			return nil, fmt.Errorf("unexpected operands types (%T) > (%T)", a, b)
		}),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// true
}

func ExampleEvaluable() {
	eval, err := gval.Full(gval.Constant("maximum_time", 52)).
		NewEvaluable("response_time <= maximum_time")
	if err != nil {
		fmt.Println(err)
	}

	for i := 50; i < 55; i++ {
		value, err := eval(context.Background(), map[string]interface{}{
			"response_time": i,
		})
		if err != nil {
			fmt.Println(err)

		}

		fmt.Println(value)
	}

	// Output:
	// true
	// true
	// true
	// false
	// false
}

func ExampleEvaluate_strlen() {

	value, err := gval.Evaluate(`strlen("someReallyLongInputString") <= 16`,
		nil,
		gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
			length := len(args[0].(string))
			return (float64)(length), nil
		}))
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// false
}

func ExampleEvaluate_encoding() {

	value, err := gval.Evaluate(`(7 < "47" == true ? "hello world!\n\u263a" : "good bye\n")`+" + ` more text`",
		nil,
		gval.Function("strlen", func(args ...interface{}) (interface{}, error) {
			length := len(args[0].(string))
			return (float64)(length), nil
		}))
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// hello world!
	// ☺ more text
}

type exampleType struct {
	Hello string
}

func (e exampleType) World() string {
	return "world"
}

func ExampleEvaluate_accessor() {

	value, err := gval.Evaluate(`foo.Hello + foo.World()`,
		map[string]interface{}{
			"foo": exampleType{Hello: "hello "},
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// hello world
}

func ExampleEvaluate_flatAccessor() {

	value, err := gval.Evaluate(`Hello + World()`,
		exampleType{Hello: "hello "},
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// hello world
}

func ExampleEvaluate_nestedAccessor() {

	value, err := gval.Evaluate(`foo.Bar.Hello + foo.Bar.World()`,
		map[string]interface{}{
			"foo": struct{ Bar exampleType }{
				Bar: exampleType{Hello: "hello "},
			},
		})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// hello world
}

func ExampleVariableSelector() {
	value, err := gval.Evaluate(`hello.world`,
		"!",
		gval.VariableSelector(func(path gval.Evaluables) gval.Evaluable {
			return func(c context.Context, v interface{}) (interface{}, error) {
				keys, err := path.EvalStrings(c, v)
				if err != nil {
					return nil, err
				}
				return fmt.Sprintf("%s%s", strings.Join(keys, " "), v), nil
			}
		}),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// hello world!
}

func ExampleEvaluable_EvalInt() {
	eval, err := gval.Full().NewEvaluable("1 + x")
	if err != nil {
		fmt.Println(err)
		return
	}

	value, err := eval.EvalInt(context.Background(), map[string]interface{}{"x": 5})
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// 6
}

func ExampleEvaluable_EvalBool() {
	eval, err := gval.Full().NewEvaluable("1 == x")
	if err != nil {
		fmt.Println(err)
		return
	}

	value, err := eval.EvalBool(context.Background(), map[string]interface{}{"x": 1})
	if err != nil {
		fmt.Println(err)
	}

	if value {
		fmt.Print("yeah")
	}

	// Output:
	// yeah
}

func ExampleEvaluate_jsonpath() {

	value, err := gval.Evaluate(`$["response-time"]`,
		map[string]interface{}{
			"response-time": 100,
		},
		jsonpath.Language(),
	)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Print(value)

	// Output:
	// 100
}

func ExampleLanguage() {
	lang := gval.NewLanguage(gval.JSON(), gval.Arithmetic(),
		//pipe operator
		gval.PostfixOperator("|", func(c context.Context, p *gval.Parser, pre gval.Evaluable) (gval.Evaluable, error) {
			post, err := p.ParseExpression(c)
			if err != nil {
				return nil, err
			}
			return func(c context.Context, v interface{}) (interface{}, error) {
				v, err := pre(c, v)
				if err != nil {
					return nil, err
				}
				return post(c, v)
			}, nil
		}))

	eval, err := lang.NewEvaluable(`{"foobar": 50} | foobar + 100`)
	if err != nil {
		fmt.Println(err)
	}

	value, err := eval(context.Background(), nil)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

	// Output:
	// 150
}

type exampleCustomSelector struct{ hidden string }

var _ gval.Selector = &exampleCustomSelector{}

func (s *exampleCustomSelector) SelectGVal(ctx context.Context, k string) (interface{}, error) {
	if k == "hidden" {
		return s.hidden, nil
	}

	return nil, nil
}

func ExampleSelector() {
	lang := gval.Base()
	value, err := lang.Evaluate(
		"myStruct.hidden",
		map[string]interface{}{"myStruct": &exampleCustomSelector{hidden: "hello world"}},
	)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

	// Output:
	// hello world
}

func parseSub(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) {
	return p.ParseSublanguage(ctx, subLang)
}

var (
	superLang = gval.NewLanguage(
		gval.PrefixExtension('$', parseSub),
	)
	subLang = gval.NewLanguage(
		gval.Init(func(ctx context.Context, p *gval.Parser) (gval.Evaluable, error) { return p.Const("hello world"), nil }),
	)
)

func ExampleParser_ParseSublanguage() {
	value, err := superLang.Evaluate("$", nil)

	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(value)

	// Output:
	// hello world
}
