/*
 * ECAL
 *
 * Copyright 2020 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the MIT
 * License, If a copy of the MIT License was not distributed with this
 * file, You can obtain one at https://opensource.org/licenses/MIT.
 */

package stdlib

import (
	"fmt"
	"math"
	"reflect"
	"strconv"
	"testing"

	"devt.de/krotik/common/errorutil"
	"devt.de/krotik/ecal/scope"
)

func TestECALFunctionAdapter(t *testing.T) {

	res, err := runAdapterTest(
		reflect.ValueOf(strconv.Atoi),
		[]interface{}{"1"},
	)

	if err != nil || res != float64(1) {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(strconv.ParseUint),
		[]interface{}{"123", float64(0), float64(0)},
	)

	if err != nil || res != float64(123) {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(strconv.ParseFloat),
		[]interface{}{"123.123", float64(0)},
	)

	if err != nil || res != float64(123.123) {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(fmt.Sprintf),
		[]interface{}{"foo %v", "bar"},
	)

	if err != nil || res != "foo bar" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(math.Float32bits),
		[]interface{}{float64(11)},
	)

	if r := fmt.Sprintf("%X", uint32(res.(float64))); err != nil || r != fmt.Sprintf("%X", math.Float32bits(11)) {
		t.Error("Unexpected result: ", r, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(math.Float32frombits),
		[]interface{}{float64(math.Float32bits(11))},
	)

	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
		t.Error("Unexpected result: ", r, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(math.Float32frombits),
		[]interface{}{math.Float32bits(11)}, // Giving the correct type also works
	)

	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
		t.Error("Unexpected result: ", r, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(math.Float64bits),
		[]interface{}{float64(11)},
	)

	if r := fmt.Sprintf("%X", uint64(res.(float64))); err != nil || r != fmt.Sprintf("%X", math.Float64bits(11)) {
		t.Error("Unexpected result: ", r, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(math.Float64frombits),
		[]interface{}{float64(math.Float64bits(11))},
	)

	if r := fmt.Sprintf("%v", res.(float64)); err != nil || r != "11" {
		t.Error("Unexpected result: ", r, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyUint),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyUint8),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyUint16),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyUintptr),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyInt8),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyInt16),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyInt32),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(dummyInt64),
		[]interface{}{float64(1)},
	)

	if err != nil || res != "1" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	// Test Error cases

	res, err = runAdapterTest(
		reflect.ValueOf(strconv.ParseFloat),
		[]interface{}{"123.123", 0, 0},
	)

	if err == nil || err.Error() != "Too many parameters - got 3 expected 2" {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(strconv.ParseFloat),
		[]interface{}{"Hans", 0},
	)

	if err == nil || err.Error() != `strconv.ParseFloat: parsing "Hans": invalid syntax` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	res, err = runAdapterTest(
		reflect.ValueOf(strconv.ParseFloat),
		[]interface{}{123, 0},
	)

	if err == nil || err.Error() != `Parameter 1 should be of type string but is of type int` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	// Make sure we are never panicing but just returning an error

	res, err = runAdapterTest(
		reflect.ValueOf(errorutil.AssertTrue),
		[]interface{}{false, "Some Panic Description"},
	)

	if err == nil || err.Error() != `Error: Some Panic Description` {
		t.Error("Unexpected result: ", res, err)
		return
	}

	// Get documentation

	afuncEcal := NewECALFunctionAdapter(reflect.ValueOf(fmt.Sprint), "test123")

	if s, err := afuncEcal.DocString(); s == "" || err != nil {
		t.Error("Docstring should return something")
		return
	}
}

func runAdapterTest(afunc reflect.Value, args []interface{}) (interface{}, error) {
	afuncEcal := &ECALFunctionAdapter{afunc, ""}
	return afuncEcal.Run("test", scope.NewScope(""), make(map[string]interface{}), args)

}

func dummyUint(v uint) string {
	return fmt.Sprint(v)
}

func dummyUint8(v uint8) string {
	return fmt.Sprint(v)
}

func dummyUint16(v uint16) string {
	return fmt.Sprint(v)
}

func dummyUintptr(v uintptr) string {
	return fmt.Sprint(v)
}

func dummyInt8(v int8) string {
	return fmt.Sprint(v)
}

func dummyInt16(v int16) string {
	return fmt.Sprint(v)
}

func dummyInt32(v int32) string {
	return fmt.Sprint(v)
}

func dummyInt64(v int64) string {
	return fmt.Sprint(v)
}
