/*
	run threads inside goroutine and each thread will be blocked by
	`C.pthread_join`.
*/
func run_in_block_with_pthread_join(number_to_run int) ([]int, []string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	_values_collected := make([]int, 0)
	_thread_ids := make([]string, 0)

	tp := threadpool.NewThreadPool(number_to_run)
	for i := 0; i < number_to_run; i++ {
		log.Debug("> [%5d]", i)

		tid := tp.Start(
			threadpool.ThreadCallback(func(args unsafe.Pointer) {
				_args := *(**([1]interface{}))(unsafe.Pointer(&args))
				_v, _ := _args[0].(int)

				// run go function
				_f := Fibonacci(_v)
				_values_collected = append(_values_collected, _f)

				_tid := get_thread_id()
				_thread_ids = append(_thread_ids, _tid)

				log.Debug("  [%5d] tid=%p, Fibonacci=%-7d, args=(%T, %T)", _v, _tid, _f, _v)
			}),
			int(i),
		)
		tp.Join(tid)
		log.Debug("< [%5d]", i)
	}

	return _values_collected, _thread_ids
}
/*
	run threads inside goroutine and each thread will be blocked by
	`C.pthread_join` and wait the end of execution with `channel`
*/
func run_in_goroutine_with_pthread_join_and_channel(number_to_run int) ([]int, []string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	_values_collected := make([]int, 0)
	_thread_ids := make([]string, 0)

	_channel_collect_seq := make(chan int)

	/*
		if you increase the `number_to_run` too high, you may face the
		segfault, it's totally up to your system.
	*/
	tp := threadpool.NewThreadPool(number_to_run)

	for i := 0; i < number_to_run; i++ {
		go func(j int, ch chan int) {
			lock.Lock()
			defer lock.Unlock()

			tid := tp.Start(
				threadpool.ThreadCallback(func(args unsafe.Pointer) {
					_args := *(**([2]interface{}))(unsafe.Pointer(&args))

					_v := _args[0].(int)
					_ch := _args[1].(chan int)

					// run go function
					_f := Fibonacci(_v)
					_values_collected = append(_values_collected, _f)
					_tid := get_thread_id()
					_thread_ids = append(_thread_ids, _tid)

					log.Debug("result: [%5d] tid=%p, Fibonacci=%-7d, args=(%T, %T)", _v, _tid, _f, _v, _ch)
					_ch <- _v
				}),
				j,
				ch,
			)
			tp.Join(tid)
		}(int(i), _channel_collect_seq)
	}

	_done := make(chan bool, 1)
	_got := []int{}
	for _v := range _channel_collect_seq {
		_got = append(_got, _v)
		if len(_got) == number_to_run {
			_done <- true
			break
		}
	}
	<-_done
	close(_channel_collect_seq)
	close(_done)

	return _values_collected, _thread_ids
}
/*
	run threads inside goroutine and each thread will be blocked by
	`sync.WaitGroup` and wait the end of execution with `WaitGroup`
*/
func run_in_goroutine_without_pthread_join_with_WaitGroup(number_to_run int) ([]int, []string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	_values_collected := make([]int, 0)
	_thread_ids := make([]string, 0)

	var wg sync.WaitGroup
	wg.Add(number_to_run)

	tp := threadpool.NewThreadPool(number_to_run)

	for i := 0; i < number_to_run; i++ {
		go func(j int) {
			lock.Lock()
			defer lock.Unlock()

			var wg_thread sync.WaitGroup
			wg_thread.Add(1)
			tp.Start(
				threadpool.ThreadCallback(func(args unsafe.Pointer) {
					_args := *(**([2]interface{}))(unsafe.Pointer(&args))

					_v := _args[0].(int)

					// run go function
					_f := Fibonacci(_v)
					_values_collected = append(_values_collected, _f)

					_tid := get_thread_id()
					_thread_ids = append(_thread_ids, _tid)

					log.Debug("result: [%5d] tid=%p, Fibonacci=%-7d, args=(%T)", _v, _tid, _f, _v)
					wg_thread.Done()
				}),
				j,
			)
			wg_thread.Wait()
			wg.Done()
		}(int(i))
	}

	wg.Wait()

	return _values_collected, _thread_ids
}
/*
	run threads in golang and each thread will be blocked by `channel`.
*/
func run_in_block_with_channel(number_to_run int) ([]int, []string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	_values_collected := make([]int, 0)
	_thread_ids := make([]string, 0)

	tp := threadpool.NewThreadPool(number_to_run + 1)
	for i := 0; i < number_to_run; i++ {
		j := int(i)

		log.Debug("> [%5d]", j)
		_chan := make(chan bool)
		tp.Start(
			threadpool.ThreadCallback(func(args unsafe.Pointer) {
				_args := *(**([2]interface{}))(unsafe.Pointer(&args))
				_v, _ := _args[0].(int)
				_ch, _ := _args[1].(chan bool)

				// run go function
				_f := Fibonacci(_v)
				_values_collected = append(_values_collected, _f)

				_tid := get_thread_id()
				_thread_ids = append(_thread_ids, _tid)

				log.Debug("  [%5d], tid=%p, Fibonacci=%-7d, args=(%T, %T)", _v, _tid, _f, _v, _ch)
				_ch <- true
			}),
			j,
			_chan,
		)
		<-_chan
		close(_chan)
		log.Debug("< [%5d]", j)
	}

	return _values_collected, _thread_ids
}
/*
	run threads in golang and each thread will be blocked by `sync.WaitGroup`.
*/
func run_in_block_with_WaitGroup(number_to_run int) ([]int, []string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	_values_collected := make([]int, 0)
	_thread_ids := make([]string, 0)

	tp := threadpool.NewThreadPool(number_to_run + 1)
	for i := 0; i < number_to_run; i++ {
		j := int(i)

		log.Debug("> [%5d]", j)
		var wg sync.WaitGroup
		wg.Add(1)
		tp.Start(
			threadpool.ThreadCallback(func(args unsafe.Pointer) {
				_args := *(**([2]interface{}))(unsafe.Pointer(&args))
				_v, _ := _args[0].(int)

				// run go function
				_f := Fibonacci(_v)
				_values_collected = append(_values_collected, _f)
				_tid := get_thread_id()
				_thread_ids = append(_thread_ids, _tid)

				log.Debug("  [%5d], tid=%p, Fibonacci=%-7d, args=(%T)", _v, _tid, _f, _v)
				wg.Done()
			}),
			j,
		)
		wg.Wait()
		log.Debug("< [%5d]", j)
	}

	return _values_collected, _thread_ids
}