package try

import (
	"fmt"
	"reflect"
	"runtime/debug"
)

type TryCatch struct {
	TryFn       interface{}
	TryParams   []interface{}
	CatchFn     interface{}
	CatchParams []interface{}
}

type TryInterface interface {
	Run()
	SetFn(interface{})
	SetCatchFn(fn interface{})
	SetTryParams(params []interface{})
	SetCatchParams(params []interface{})
}

func (t *TryCatch) SetFn(fn interface{}) {
	t.TryFn = fn
}

func (t *TryCatch) SetCatchFn(fn interface{}) {
	t.CatchFn = fn
}

func (t *TryCatch) SetTryParams(params []interface{}) {
	t.TryParams = params
}

func (t *TryCatch) SetCatchParams(params []interface{}) {
	t.CatchParams = params
}

func catchFunc(err interface{}) {
	fmt.Println(fmt.Sprintf("panic : %s\n", err))
	fmt.Println(fmt.Sprint(string(debug.Stack())))
}

func (t *TryCatch) getParams(params []interface{}) []reflect.Value {
	realParams := make([]reflect.Value, len(params))
	for i, param := range params {
		realParams[i] = reflect.ValueOf(param)
	}
	return realParams
}

func (t *TryCatch) Run() {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				defer func() {
					if err = recover(); err != nil {
						catchFunc(err)
					}
				}()
				if t.CatchFn != nil {
					realCatchFunc := reflect.ValueOf(t.CatchFn)
					errParams := []reflect.Value{}
					errParams = append(errParams, reflect.ValueOf(err))
					for _, param := range t.CatchParams {
						errParams = append(errParams, reflect.ValueOf(param))
					}
					realCatchFunc.Call(errParams)
				} else {
					catchFunc(err)
				}
			}
		}()
		realFunc := reflect.ValueOf(t.TryFn)
		realFunc.Call(t.getParams(t.TryParams))
	}()
}

func Go(tryFunc func(), catchFunc func(interface{})) {
	go func(tryFunc func(), catchFunc func(interface{})) {
		defer func() {
			if err := recover(); err != nil {
				catchFunc(err)
			}
		}()
		tryFunc()
	}(tryFunc, catchFunc)
}

/**
 * tryFunc go func
 * catchFunc catch func
 * tryFunc params
 */
func GoParams(tryFunc interface{}, catchFunc func(interface{}), fnParams ...interface{}) {
	realFunc := reflect.ValueOf(tryFunc)
	realParams := make([]reflect.Value, len(fnParams))
	for i, param := range fnParams {
		realParams[i] = reflect.ValueOf(param)
	}
	go func(realFunc reflect.Value, catchFunc func(interface{}), realParams []reflect.Value) {
		defer func() {
			if err := recover(); err != nil {
				catchFunc(err)
			}
		}()
		realFunc.Call(realParams)
	}(realFunc, catchFunc, realParams)
}
