# Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative 'framer'
require 'securerandom'

module Protocol
	module WebSocket
		# Wraps a framer and implements for implementing connection specific interactions like reading and writing text.
		class Connection
			# @parameter mask [String] 4-byte mask to be used for frames generated by this connection.
			def initialize(framer, mask: nil, **options)
				@framer = framer
				@mask = mask
				
				@state = :open
				@frames = []
				
				@reserved = Frame::RESERVED
				
				@reader = self
				@writer = self
			end
			
			# The framer which is used for reading and writing frames.
			attr :framer
			
			# The (optional) mask which is used when generating frames.
			attr :mask
			
			# The allowed reserved bits:
			attr :reserved
			
			# Buffered frames which form part of a complete message.
			attr_accessor :frames
			
			attr_accessor :reader
			attr_accessor :writer
			
			def reserve!(bit)
				if (@reserved & bit).zero?
					raise "Unable to use #{bit}!"
				end
				
				@reserved  &= ~bit
				
				return true
			end
			
			def flush
				@framer.flush
			end
			
			def closed?
				@state == :closed
			end
			
			def close(code = Error::NO_ERROR, reason = "")
				send_close(code, reason) unless closed?
				
				@framer.close
			end
			
			def read_frame
				return nil if closed?
				
				frame = @framer.read_frame
				
				unless (frame.flags & @reserved).zero?
					raise ProtocolError, "Received frame with reserved flags set!"
				end
				
				yield frame if block_given?
				
				frame.apply(self)
				
				return frame
			rescue ProtocolError => error
				send_close(error.code, error.message)
				
				raise
			rescue
				send_close(Error::PROTOCOL_ERROR, $!.message)
				
				raise
			end
			
			def write_frame(frame)
				@framer.write_frame(frame)
				
				return frame
			end
			
			def receive_text(frame)
				if @frames.empty?
					@frames << frame
				else
					raise ProtocolError, "Received text, but expecting continuation!"
				end
			end
			
			def receive_binary(frame)
				if @frames.empty?
					@frames << frame
				else
					raise ProtocolError, "Received binary, but expecting continuation!"
				end
			end
			
			def receive_continuation(frame)
				if @frames.any?
					@frames << frame
				else
					raise ProtocolError, "Received unexpected continuation!"
				end
			end
					
			def receive_close(frame)
				@state = :closed
				
				code, reason = frame.unpack
				
				send_close(code, reason)
				
				if code and code != Error::NO_ERROR
					raise ClosedError.new reason, code
				end
			end
			
			def send_ping(data = "")
				if @state != :closed
					frame = PingFrame.new(mask: @mask)
					frame.pack(data)
					
					write_frame(frame)
				else
					raise ProtocolError, "Cannot send ping in state #{@state}"
				end
			end
			
			def open!
				@state = :open
				
				return self
			end
			
			def receive_ping(frame)
				if @state != :closed
					write_frame(frame.reply(mask: @mask))
				else
					raise ProtocolError, "Cannot receive ping in state #{@state}"
				end
			end
			
			def receive_pong(frame)
				# Ignore.
			end
			
			def receive_frame(frame)
				warn "Unhandled frame #{frame.inspect}"
			end
			
			def pack_text_frame(buffer, **options)
				frame = TextFrame.new(mask: @mask)
				frame.pack(buffer)
				
				return frame
			end
			
			def send_text(buffer, **options)
				write_frame(@writer.pack_text_frame(buffer, **options))
			end
			
			def pack_binary_frame(buffer, **options)
				frame = BinaryFrame.new(mask: @mask)
				frame.pack(buffer)
				
				return frame
			end
			
			def send_binary(buffer, **options)
				write_frame(@writer.pack_binary_frame(buffer, **options))
			end
			
			def send_close(code = Error::NO_ERROR, reason = "")
				frame = CloseFrame.new(mask: @mask)
				frame.pack(code, reason)
				
				self.write_frame(frame)
				self.flush
				
				@state = :closed
			end
			
			# Write a message to the connection.
			# @parameter message [Message] The message to send.
			def write(message, **options)
				# This is a compatibility shim for the previous implementation. We may want to eventually deprecate this use case... or maybe it's convenient enough to leave it around.
				if message.is_a?(String)
					if message.encoding == Encoding::UTF_8
						return send_text(message, **options)
					else
						return send_binary(message, **options)
					end
				end
				
				message.send(self, **options)
			end
			
			# The default implementation for reading a message buffer.
			def unpack_frames(frames)
				frames.map(&:unpack).join("")
			end
			
			# Read a message from the connection.
			# @returns message [Message] The received message.
			def read(**options)
				@framer.flush
				
				while read_frame
					if @frames.last&.finished?
						frames = @frames
						@frames = []
						
						buffer = @reader.unpack_frames(frames, **options)
						return frames.first.read_message(buffer)
					end
				end
			rescue ProtocolError => error
				send_close(error.code, error.message)
				
				raise
			end
		end
	end
end
