cdef extern from "unistd.h":
	int read(int fileno, char *buffer, int nbytes)
	int write(int fileno, char *buffer, int nbytes)

cdef extern from "malloc.h":
	char *malloc(int length)
	void free(char *buffer)

import sys
import exceptions

cdef class Ring_buffer:
	# private instance variables
	cdef int begin
	cdef int end
	cdef int buffer_size
	cdef char *buffer

	def __init__(self, buffer_size):
		'''Constructor'''
		# the first byte we're using
		self.begin = 0
		# one byte past the last byte we're using - like a python slice
		self.end = 0
		# we can actually store this many bytes-1... because otherwise we couldn't distinguish between empty and 100% full
		self.buffer_size = buffer_size
		self.buffer = malloc(self.buffer_size)
		maybe_null = <int>self.buffer
		assert maybe_null != 0

	def __dealloc__(self):
		'''Deallocate the buffer'''
		free(self.buffer)

	cpdef empty(self):
		'''Return true if the buffer is empty'''
		if self.begin == self.end:
			return True
		else:
			return False

	cpdef full(self):
		'''Return true if the buffer is full'''
		if self.begin == (self.end + 1) % self.buffer_size:
			return True
		else:
			return False

	def __len__(self):
		'''Return the number of bytes in the buffer'''
		if self.begin == self.end:
			amount_in_use = 0
		elif self.begin < self.end:
			# begin is to the left of end, that's simple
			amount_in_use = self.end - self.begin
		else:
			# begin is after the end; it's in a wrapped state
			amount_in_use = self.end + self.buffer_size - self.begin
		return amount_in_use
		
	cpdef would_overflow(self, length):
		'''Return true if adding a string of length would cause the buffer to overflow'''
		# compute how much remains in the buffer, then compare to length
		amount_in_use = len(self)
		if amount_in_use + length >= self.buffer_size:
			return True
		else:
			return False
	
	cpdef would_underflow(self, length):
		'''Return true if removing a string of length would cause the buffer to underflow'''
		# compute how much is in the buffer, then compare to length
		amount_in_use = len(self)
		sys.stdout.flush()
		if amount_in_use < length:
			return True
		else:
			return False

	cdef would_add_wrap(self, length):
		'''Return true if adding a string of length would wrap from the end of the buffer around to the beginning'''
		if self.end + length >= self.buffer_size:
			return True
		else:
			return False

	cdef would_subtract_wrap(self, length):
		'''Return true if subtracting a string of length would wrap from the end of the buffer around to the beginning'''
		if self.begin + length >= self.buffer_size:
			return True
		else:
			return False

	cdef split_to_two_add(self, length):
		'''Return the lengths of the two pieces at the end and beginning of the buffer; assumes we're going to wrap'''
		first_begin = self.end
		first_end = self.buffer_size - 1
		first_length = first_end - first_begin

		second_begin = 0
		second_end = self.buffer_size - first_length
		second_length = second_end - second_begin

		return first_length, second_length

	cdef split_to_two_subtract(self, length):
		'''Return the lengths of the two pieces at the end and beginning of the buffer; assumes we're going to wrap'''
		first_length = self.buffer_size - self.begin
		second_length = length - first_length

		return first_length, second_length

	cpdef add_from_fileno(self, fileno, length_to_add):
		'''Add to the buffer length_to_add bytes from fileno - private'''
		if self.would_overflow(length_to_add):
			raise exceptions.BufferError, "Buffer overflow"
		# FIXME: We need to do something about EINTR on these 3 read's!
		if self.would_add_wrap(length_to_add):
			# do the read in two pieces
			first_length, second_length = self.split_to_two_add(length_to_add)
			length_added = read(fileno, &self.buffer[self.end], first_length)
			assert length_added == first_length, "length_added != first_length"
			length_added = read(fileno, self.buffer, second_length)
			assert length_added == second_length, "length_added != second_length"
			self.end = second_length
		else:
			# do the read in one piece
			length_added = read(fileno, &self.buffer[self.end], length_to_add)
			assert length_added == length_to_add, "length_added != length_to_add"
			self.end += length_to_add

	cpdef add_from_string(self, string_):
		'''Add string_ to the buffer'''
		cdef int string_index
		cdef int buffer_index
		length_to_add = len(string_)
		if self.would_overflow(length_to_add):
			raise exceptions.BufferError, "Buffer overflow"
		if self.would_add_wrap(length_to_add):
			# do the "read" in two pieces
			first_length, second_length = self.split_to_two_add(length_to_add)
			for string_index in range(length_to_add):
				buffer_index = (self.end + string_index) % self.buffer_size
				self.buffer[buffer_index] = ord(string_[string_index])
			self.end = (self.end + length_to_add) % self.buffer_size
		else:
			# do the "read" in one piece
			string_index = 0
			for buffer_index in range(self.end, self.end + length_to_add):
				self.buffer[buffer_index] = ord(string_[string_index])
				string_index += 1
			self.end += length_to_add

	cpdef subtract_to_fileno(self, fileno, length_to_subtract=None):
		'''Remove from the buffer, putting the extracted bytes into fileno'''
		if length_to_subtract == None:
			length_to_subtract = len(self)
		if self.would_underflow(length_to_subtract):
			raise exceptions.BufferError, "Buffer underflow"
		if self.would_subtract_wrap(length_to_subtract):
			# there's wrap - do the write in two pieces
			first_length, second_length = self.split_to_two_subtract(length_to_subtract)
			length_subtracted = write(fileno, &self.buffer[self.begin], first_length)
			assert length_subtracted == first_length
			length_subtracted = write(fileno, self.buffer, second_length)
			assert length_subtracted == second_length
		else:
			# no wrap - do the write in one piece
			print 'subtract_to_fileno: length_to_subtract is %d' % length_to_subtract
			length_subtracted = write(fileno, &self.buffer[self.begin], length_to_subtract)
			assert length_subtracted == length_to_subtract
			self.begin += length_to_subtract

	cpdef peek_to_string(self, length_to_peek=None):
		'''Peek at length_to_peek bytes in the buffer - don't subtract them'''
		if length_to_peek == None:
			length_to_peek = len(self)
		return self.subtract_to_string(length_to_peek, True)

	cpdef subtract_to_string(self, length_to_subtract, peek=False):
		'''Pull length_to_subtract bytes from the buffer'''
		if length_to_subtract == None:
			length_to_subtract = len(self)
		if self.would_underflow(length_to_subtract):
			raise exceptions.BufferError, "Buffer underflow"
		if self.would_subtract_wrap(length_to_subtract):
			# do the "write" in two pieces
			first_length, second_length = self.split_to_two_subtract(length_to_subtract)
			list_ = []
			for index in xrange(self.begin, self.begin+first_length):
				list_.append(self.buffer[index])
			for index in xrange(second_length):
				list_.append(self.buffer[index])
			list_of_strings = [ chr(byte) for byte in list_ ]
			result = ''.join(list_of_strings)
			if not peek:
				self.begin = (self.begin + length_to_subtract) % self.buffer_size
			assert len(result) == length_to_subtract
			return result
		else:
			# do the "write" in one piece
			list_ = []
			for index in xrange(self.begin, self.begin+length_to_subtract):
				list_.append(chr(self.buffer[index]))
			result = ''.join(list_)
			assert len(result) == length_to_subtract
			if not peek:
				self.begin = (self.begin + length_to_subtract) % self.buffer_size
			return result

	def __iter__(self):
		'''Return ourself - so we can iterate over ourself'''
		return self   

	# The Cython doc says you must define __next__ and must not define next, but it seems in reality __next__ is ignored and next works.
	def __next__(self):
		'''Toy iterator example - will be removed eventually'''
		retval = self.value
		self.value *= 2
		if retval >= 10e200:
			raise exceptions.StopIteration
		else:
			return retval