package sync

import "core:mem"
import "core:time"
import "core:intrinsics"
import "core:math/rand"

_, _ :: time, rand

Channel_Direction :: enum i8 {
	Both =  0,
	Send = +1,
	Recv = -1,
}

Channel :: struct($T: typeid, $Direction := Channel_Direction.Both) {
	using _internal: ^Raw_Channel,
}

channel_init :: proc(ch: ^$C/Channel($T, $D), cap := 0, allocator := context.allocator) {
	context.allocator = allocator
	ch._internal = raw_channel_create(size_of(T), align_of(T), cap)
	return
}

channel_make :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Both)) {
	context.allocator = allocator
	ch._internal = raw_channel_create(size_of(T), align_of(T), cap)
	return
}

channel_make_send :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Send)) {
	context.allocator = allocator
	ch._internal = raw_channel_create(size_of(T), align_of(T), cap)
	return
}
channel_make_recv :: proc($T: typeid, cap := 0, allocator := context.allocator) -> (ch: Channel(T, .Recv)) {
	context.allocator = allocator
	ch._internal = raw_channel_create(size_of(T), align_of(T), cap)
	return
}

channel_destroy :: proc(ch: $C/Channel($T, $D)) {
	raw_channel_destroy(ch._internal)
}

channel_as_send :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Send)) {
	res._internal = ch._internal
	return
}

channel_as_recv :: proc(ch: $C/Channel($T, .Both)) -> (res: Channel(T, .Recv)) {
	res._internal = ch._internal
	return
}


channel_len :: proc(ch: $C/Channel($T, $D)) -> int {
	return ch._internal.len if ch._internal != nil else 0
}
channel_cap :: proc(ch: $C/Channel($T, $D)) -> int {
	return ch._internal.cap if ch._internal != nil else 0
}


channel_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) where D >= .Both {
	msg := msg
	_ = raw_channel_send_impl(ch._internal, &msg, /*block*/true, loc)
}
channel_try_send :: proc(ch: $C/Channel($T, $D), msg: T, loc := #caller_location) -> bool where D >= .Both {
	msg := msg
	return raw_channel_send_impl(ch._internal, &msg, /*block*/false, loc)
}

channel_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T) where D <= .Both {
	c := ch._internal
	if c == nil {
		panic(message="cannot recv message; channel is nil", loc=loc)
	}
	mutex_lock(&c.mutex)
	raw_channel_recv_impl(c, &msg, loc)
	mutex_unlock(&c.mutex)
	return
}
channel_try_recv :: proc(ch: $C/Channel($T, $D), loc := #caller_location) -> (msg: T, ok: bool) where D <= .Both {
	c := ch._internal
	if c != nil && mutex_try_lock(&c.mutex) {
		if c.len > 0 {
			raw_channel_recv_impl(c, &msg, loc)
			ok = true
		}
		mutex_unlock(&c.mutex)
	}
	return
}
channel_try_recv_ptr :: proc(ch: $C/Channel($T, $D), msg: ^T, loc := #caller_location) -> (ok: bool) where D <= .Both {
	res: T
	res, ok = channel_try_recv(ch, loc)
	if ok && msg != nil {
		msg^ = res
	}
	return
}


channel_is_nil :: proc(ch: $C/Channel($T, $D)) -> bool {
	return ch._internal == nil
}
channel_is_open :: proc(ch: $C/Channel($T, $D)) -> bool {
	c := ch._internal
	return c != nil && !c.closed
}


channel_eq :: proc(a, b: $C/Channel($T, $D)) -> bool {
	return a._internal == b._internal
}
channel_ne :: proc(a, b: $C/Channel($T, $D)) -> bool {
	return a._internal != b._internal
}


channel_can_send :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D >= .Both {
	return raw_channel_can_send(ch._internal)
}
channel_can_recv :: proc(ch: $C/Channel($T, $D)) -> (ok: bool) where D <= .Both {
	return raw_channel_can_recv(ch._internal)
}


channel_peek :: proc(ch: $C/Channel($T, $D)) -> int {
	c := ch._internal
	if c == nil {
		return -1
	}
	if intrinsics.atomic_load(&c.closed) {
		return -1
	}
	return intrinsics.atomic_load(&c.len)
}


channel_close :: proc(ch: $C/Channel($T, $D), loc := #caller_location) {
	raw_channel_close(ch._internal, loc)
}


channel_iterator :: proc(ch: $C/Channel($T, $D)) -> (msg: T, ok: bool) where D <= .Both {
	c := ch._internal
	if c == nil {
		return
	}

	if !c.closed || c.len > 0 {
		msg, ok = channel_recv(ch), true
	}
	return
}
channel_drain :: proc(ch: $C/Channel($T, $D)) where D >= .Both {
	raw_channel_drain(ch._internal)
}


channel_move :: proc(dst: $C1/Channel($T, $D1) src: $C2/Channel(T, $D2)) where D1 <= .Both, D2 >= .Both {
	for msg in channel_iterator(src) {
		channel_send(dst, msg)
	}
}


Raw_Channel_Wait_Queue :: struct {
	next: ^Raw_Channel_Wait_Queue,
	state: ^uintptr,
}


Raw_Channel :: struct {
	closed:      bool,
	ready:       bool, // ready to recv
	data_offset: u16,  // data is stored at the end of this data structure
	elem_size:   u32,
	len, cap:    int,
	read, write: int,
	mutex:       Mutex,
	cond:        Condition,
	allocator:   mem.Allocator,

	sendq: ^Raw_Channel_Wait_Queue,
	recvq: ^Raw_Channel_Wait_Queue,
}

raw_channel_wait_queue_insert :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
	val.next = head^
	head^ = val
}
raw_channel_wait_queue_remove :: proc(head: ^^Raw_Channel_Wait_Queue, val: ^Raw_Channel_Wait_Queue) {
	p := head
	for p^ != nil && p^ != val {
		p = &p^.next
	}
	if p != nil {
		p^ = p^.next
	}
}


raw_channel_create :: proc(elem_size, elem_align: int, cap := 0) -> ^Raw_Channel {
	assert(int(u32(elem_size)) == elem_size)

	s := size_of(Raw_Channel)
	s = mem.align_forward_int(s, elem_align)
	data_offset := uintptr(s)
	s += elem_size * max(cap, 1)

	a := max(elem_align, align_of(Raw_Channel))

	c := (^Raw_Channel)(mem.alloc(s, a))
	if c == nil {
		return nil
	}

	c.data_offset = u16(data_offset)
	c.elem_size = u32(elem_size)
	c.len, c.cap = 0, max(cap, 0)
	c.read, c.write = 0, 0
	mutex_init(&c.mutex)
	condition_init(&c.cond, &c.mutex)
	c.allocator = context.allocator
	c.closed = false

	return c
}


raw_channel_destroy :: proc(c: ^Raw_Channel) {
	if c == nil {
		return
	}
	context.allocator = c.allocator
	intrinsics.atomic_store(&c.closed, true)

	condition_destroy(&c.cond)
	mutex_destroy(&c.mutex)
	free(c)
}

raw_channel_close :: proc(c: ^Raw_Channel, loc := #caller_location) {
	if c == nil {
		panic(message="cannot close nil channel", loc=loc)
	}
	mutex_lock(&c.mutex)
	defer mutex_unlock(&c.mutex)
	intrinsics.atomic_store(&c.closed, true)

	// Release readers and writers
	raw_channel_wait_queue_broadcast(c.recvq)
	raw_channel_wait_queue_broadcast(c.sendq)
	condition_broadcast(&c.cond)
}



raw_channel_send_impl :: proc(c: ^Raw_Channel, msg: rawptr, block: bool, loc := #caller_location) -> bool {
	send :: proc(c: ^Raw_Channel, src: rawptr) {
		data := uintptr(c) + uintptr(c.data_offset)
		dst := data + uintptr(c.write * int(c.elem_size))
		mem.copy(rawptr(dst), src, int(c.elem_size))
		c.len += 1
		c.write = (c.write + 1) % max(c.cap, 1)
	}

	switch {
	case c == nil:
		panic(message="cannot send message; channel is nil", loc=loc)
	case c.closed:
		panic(message="cannot send message; channel is closed", loc=loc)
	}

	mutex_lock(&c.mutex)
	defer mutex_unlock(&c.mutex)

	if c.cap > 0 {
		if !block && c.len >= c.cap {
			return false
		}

		for c.len >= c.cap {
			condition_wait_for(&c.cond)
		}
	} else if c.len > 0 { // TODO(bill): determine correct behaviour
		if !block {
			return false
		}
		condition_wait_for(&c.cond)
	} else if c.len == 0 && !block {
		return false
	}

	send(c, msg)
	condition_signal(&c.cond)
	raw_channel_wait_queue_signal(c.recvq)

	return true
}

raw_channel_recv_impl :: proc(c: ^Raw_Channel, res: rawptr, loc := #caller_location) {
	recv :: proc(c: ^Raw_Channel, dst: rawptr, loc := #caller_location) {
		if c.len < 1 {
			panic(message="cannot recv message; channel is empty", loc=loc)
		}
		c.len -= 1

		data := uintptr(c) + uintptr(c.data_offset)
		src := data + uintptr(c.read * int(c.elem_size))
		mem.copy(dst, rawptr(src), int(c.elem_size))
		c.read = (c.read + 1) % max(c.cap, 1)
	}

	if c == nil {
		panic(message="cannot recv message; channel is nil", loc=loc)
	}
	intrinsics.atomic_store(&c.ready, true)
	for c.len < 1 {
		raw_channel_wait_queue_signal(c.sendq)
		condition_wait_for(&c.cond)
	}
	intrinsics.atomic_store(&c.ready, false)
	recv(c, res, loc)
	if c.cap > 0 {
		if c.len == c.cap - 1 {
			// NOTE(bill): Only signal on the last one
			condition_signal(&c.cond)
		}
	} else {
		condition_signal(&c.cond)
	}
}


raw_channel_can_send :: proc(c: ^Raw_Channel) -> (ok: bool) {
	if c == nil {
		return false
	}
	mutex_lock(&c.mutex)
	switch {
	case c.closed:
		ok = false
	case c.cap > 0:
		ok = c.ready && c.len < c.cap
	case:
		ok = c.ready && c.len == 0
	}
	mutex_unlock(&c.mutex)
	return
}
raw_channel_can_recv :: proc(c: ^Raw_Channel) -> (ok: bool) {
	if c == nil {
		return false
	}
	mutex_lock(&c.mutex)
	ok = c.len > 0
	mutex_unlock(&c.mutex)
	return
}


raw_channel_drain :: proc(c: ^Raw_Channel) {
	if c == nil {
		return
	}
	mutex_lock(&c.mutex)
	c.len   = 0
	c.read  = 0
	c.write = 0
	mutex_unlock(&c.mutex)
}



MAX_SELECT_CHANNELS :: 64
SELECT_MAX_TIMEOUT :: max(time.Duration)

Select_Command :: enum {
	Recv,
	Send,
}

Select_Channel :: struct {
	channel: ^Raw_Channel,
	command: Select_Command,
}



select :: proc(channels: ..Select_Channel) -> (index: int) {
	return select_timeout(SELECT_MAX_TIMEOUT, ..channels)
}
select_timeout :: proc(timeout: time.Duration, channels: ..Select_Channel) -> (index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)

	backing: [MAX_SELECT_CHANNELS]int
	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue
	candidates := backing[:]
	cap := len(channels)
	candidates = candidates[:cap]

	count := u32(0)
	for c, i in channels {
		if c.channel == nil {
			continue
		}
		switch c.command {
		case .Recv:
			if raw_channel_can_recv(c.channel) {
				candidates[count] = i
				count += 1
			}
		case .Send:
			if raw_channel_can_send(c.channel) {
				candidates[count] = i
				count += 1
			}
		}
	}

	if count == 0 {
		wait_state: uintptr = 0
		for _, i in channels {
			q := &queues[i]
			q.state = &wait_state
		}

		for c, i in channels {
			if c.channel == nil {
				continue
			}
			q := &queues[i]
			switch c.command {
			case .Recv: raw_channel_wait_queue_insert(&c.channel.recvq, q)
			case .Send: raw_channel_wait_queue_insert(&c.channel.sendq, q)
			}
		}
		raw_channel_wait_queue_wait_on(&wait_state, timeout)
		for c, i in channels {
			if c.channel == nil {
				continue
			}
			q := &queues[i]
			switch c.command {
			case .Recv: raw_channel_wait_queue_remove(&c.channel.recvq, q)
			case .Send: raw_channel_wait_queue_remove(&c.channel.sendq, q)
			}
		}

		for c, i in channels {
			switch c.command {
			case .Recv:
				if raw_channel_can_recv(c.channel) {
					candidates[count] = i
					count += 1
				}
			case .Send:
				if raw_channel_can_send(c.channel) {
					candidates[count] = i
					count += 1
				}
			}
		}
		if count == 0 && timeout == SELECT_MAX_TIMEOUT {
			index = -1
			return
		}

		assert(count != 0)
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}

select_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)

	backing: [MAX_SELECT_CHANNELS]int
	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue
	candidates := backing[:]
	cap := len(channels)
	candidates = candidates[:cap]

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_recv(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		state: uintptr
		for c, i in channels {
			q := &queues[i]
			q.state = &state
			raw_channel_wait_queue_insert(&c.recvq, q)
		}
		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT)
		for c, i in channels {
			q := &queues[i]
			raw_channel_wait_queue_remove(&c.recvq, q)
		}

		for c, i in channels {
			if raw_channel_can_recv(c) {
				candidates[count] = i
				count += 1
			}
		}
		assert(count != 0)
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}

select_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)

	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue
	candidates: [MAX_SELECT_CHANNELS]int

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_recv(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		state: uintptr
		for c, i in channels {
			q := &queues[i]
			q.state = &state
			raw_channel_wait_queue_insert(&c.recvq, q)
		}
		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT)
		for c, i in channels {
			q := &queues[i]
			raw_channel_wait_queue_remove(&c.recvq, q)
		}

		for c, i in channels {
			if raw_channel_can_recv(c) {
				candidates[count] = i
				count += 1
			}
		}
		assert(count != 0)
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	msg = channel_recv(channels[index])

	return
}

select_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)

	backing: [MAX_SELECT_CHANNELS]int
	queues:  [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue
	candidates := backing[:]
	cap := len(channels)
	candidates = candidates[:cap]

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_recv(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		state: uintptr
		for c, i in channels {
			q := &queues[i]
			q.state = &state
			raw_channel_wait_queue_insert(&c.recvq, q)
		}
		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT)
		for c, i in channels {
			q := &queues[i]
			raw_channel_wait_queue_remove(&c.recvq, q)
		}

		for c, i in channels {
			if raw_channel_can_recv(c) {
				candidates[count] = i
				count += 1
			}
		}
		assert(count != 0)
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]

	if msg != nil {
		channel_send(channels[index], msg)
	}

	return
}

select_send :: proc(channels: ..^Raw_Channel) -> (index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)
	candidates: [MAX_SELECT_CHANNELS]int
	queues: [MAX_SELECT_CHANNELS]Raw_Channel_Wait_Queue

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_send(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		state: uintptr
		for c, i in channels {
			q := &queues[i]
			q.state = &state
			raw_channel_wait_queue_insert(&c.sendq, q)
		}
		raw_channel_wait_queue_wait_on(&state, SELECT_MAX_TIMEOUT)
		for c, i in channels {
			q := &queues[i]
			raw_channel_wait_queue_remove(&c.sendq, q)
		}

		for c, i in channels {
			if raw_channel_can_send(c) {
				candidates[count] = i
				count += 1
			}
		}
		assert(count != 0)
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}

select_try :: proc(channels: ..Select_Channel) -> (index: int) {
	switch len(channels) {
	case 0:
		panic("sync: select with no channels")
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)

	backing: [MAX_SELECT_CHANNELS]int
	candidates := backing[:]
	cap := len(channels)
	candidates = candidates[:cap]

	count := u32(0)
	for c, i in channels {
		switch c.command {
		case .Recv:
			if raw_channel_can_recv(c.channel) {
				candidates[count] = i
				count += 1
			}
		case .Send:
			if raw_channel_can_send(c.channel) {
				candidates[count] = i
				count += 1
			}
		}
	}

	if count == 0 {
		index = -1
		return
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}


select_try_recv :: proc(channels: ..^Raw_Channel) -> (index: int) {
	switch len(channels) {
	case 0:
		index = -1
		return
	case 1:
		index = -1
		if raw_channel_can_recv(channels[0]) {
			index = 0
		}
		return
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)
	candidates: [MAX_SELECT_CHANNELS]int

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_recv(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		index = -1
		return
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}


select_try_send :: proc(channels: ..^Raw_Channel) -> (index: int) #no_bounds_check {
	switch len(channels) {
	case 0:
		return -1
	case 1:
		if raw_channel_can_send(channels[0]) {
			return 0
		}
		return -1
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)
	candidates: [MAX_SELECT_CHANNELS]int

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_send(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		index = -1
		return
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	return
}

select_try_recv_msg :: proc(channels: ..$C/Channel($T, $D)) -> (msg: T, index: int) {
	switch len(channels) {
	case 0:
		index = -1
		return
	case 1:
		ok: bool
		if msg, ok = channel_try_recv(channels[0]); ok {
			index = 0
		}
		return
	}

	assert(len(channels) <= MAX_SELECT_CHANNELS)
	candidates: [MAX_SELECT_CHANNELS]int

	count := u32(0)
	for c, i in channels {
		if channel_can_recv(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		index = -1
		return
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	msg = channel_recv(channels[index])
	return
}

select_try_send_msg :: proc(msg: $T, channels: ..$C/Channel(T, $D)) -> (index: int) {
	index = -1
	switch len(channels) {
	case 0:
		return
	case 1:
		if channel_try_send(channels[0], msg) {
			index = 0
		}
		return
	}


	assert(len(channels) <= MAX_SELECT_CHANNELS)
	candidates: [MAX_SELECT_CHANNELS]int

	count := u32(0)
	for c, i in channels {
		if raw_channel_can_send(c) {
			candidates[count] = i
			count += 1
		}
	}

	if count == 0 {
		index = -1
		return
	}

	t := time.now()
	r := rand.create(transmute(u64)t)
	i := rand.uint32(&r)

	index = candidates[i % count]
	channel_send(channels[index], msg)
	return
}

