#!/usr/bin/env python

import exceptions
import serial
import sys
import StringIO

def check_line_termination(response):
	if response.endswith("\x0A"):
		response = response[ : -1]

	if response.endswith("\x0D"):
		response = response[ : -1]

	return response


def strip_prefix(text, prefix):
	if not text.startswith(prefix):
		print >>sys.stderr, "oops! text was \"%s\", expected to start with \"%s\"" % (text.replace("\r", "\\r"), prefix)
		
	assert(text.startswith(prefix))
	return text[len(prefix) : ]

class CMSError(exceptions.Exception):
	def __init__(self, code):
		exceptions.Exception.__init__(self, "CMSError %d" % code)
		self.code = code

def Samsung_readline(serial_1):
	result = StringIO.StringIO()
	while True:
		c = serial_1.read(1)
		result.write(c)
		if c == chr(0xD):
			break

	result = result.getvalue()
				
	# Samsung "+CMS ERROR:\x0D" is missing the "\x0A" newline.
	if result.startswith("+CMS ERROR:"):
		result = result
	else:
		result = result + serial_1.readline() # overkill to read the 0A character or whatever.
	#print "=>", result
	return result

def UTF8_extract_length(first_character):
	if ord(first_character) < 0x80:
		return 1
	elif ord(first_character) < 0xE0:
		return 2
	elif ord(first_character) < 0xF0:
		return 3
	elif ord(first_character) < 0xF8:
		return 4
	elif ord(first_character) < 0xFC:
		return 5
	elif ord(first_character) < 0xFE:
		return 6

def CMGR_transfer_body(CMGR_line, serial_1, destination):
	assert(CMGR_line.startswith("+CMGR:"))
	CRLF = serial_1.read(2)
	assert(CRLF == "\x0D\x0A")
	body_length = int(CMGR_line.split(",")[-1]) # in characters, not bytes, so it's not that useful, actually.
	while body_length > 0:
		f = serial_1.read(1)
		destination.write(f)
		# assumes UTF-8
		for i in range(UTF8_extract_length(f) - 1):
			c = serial_1.read(1)
			destination.write(c)

		body_length = body_length - 1			

	#body = serial_1.read(body_length) nope.
	#CRLF = serial_1.read(2)
	#destination.write(CRLF)
	
class Connection(object):
	def __init__(self, port = 4):
		self.serial_1 = serial.Serial(port = port, baudrate = 115200, bytesize = 8, parity = 'N', stopbits = 1, timeout = 1005, writeTimeout = 1500, rtscts = 1, dsrdtr = 1, xonxoff = 0)

	def close(self):
		self.serial_1.close()
		
	def execute(self, command, strip_response_prefix = None):
		#print ">", command #, ">", ord(("X" + command)[-1])
		text = command + "\x0D" # "\x0A"
		
		#print ">%r" % text
		for c in text:
			self.serial_1.write(c)
			#while self.serial_1.read(1) == "":
			#	continue

		#time.sleep(100)
		#sys.exit(0)			

		text = text.replace("\x0D", "\x0D\x0D\x0A") # strangely enough, the response contains it twice.
		#self.serial_1.readline()
		echo = self.serial_1.read(len(text))
		assert(echo == text)
		
		responses = StringIO.StringIO()
		response = "+"
		while response.startswith("+"):
			response = Samsung_readline(self.serial_1)
			#print "<%r>" % response
			#response = check_line_termination(response)
			if response.startswith("+"):
				responses.write(response)
				#print "??", response
				if response.startswith("+CMS ERROR:"): # Samsung bug workaround where there's nothing written after "+CMS ERROR: blah\x0D" AT ALL.
					# so make sure this will not hang.
					raise CMSError(int(response[len("+CMS ERROR:"):].strip()))
					break
				elif response.startswith("+CMGR:"):
					CMGR_transfer_body(response, self.serial_1, responses)
					trailer = self.serial_1.readline() # skip ",538648645,1"
					responses.write(trailer)
			elif response == "\x0D\x0A": # and responses.getvalue().startswith("+CMGR:"): # Samsung "+CMGR:" has an extra newline after "+..." but just before "OK".
				response = self.serial_1.readline()
				break

		#print "real response", response				
		if response.rstrip() in [ "OK", "ERROR" ]: # workaround for weird codes that have no response except 
			status = response.rstrip()
		else:
			print "X", response
			status = "UNKNOWN"

		#print >>sys.stderr, "status was %r" % status
		assert(status == "OK")
		
		return responses.getvalue()

	def execute_and_strip(self, command, strip_response_prefix = None):
		response = self.execute(command)
		if strip_response_prefix:
			try:
				stripped_response = strip_prefix(response, strip_response_prefix)
			except:
				print >>sys.stderr, "info: command was \"%s\"" % command.replace("\x0A", "\\x0A").replace("\x0D", "\\x0D")
				raise
		else:
			stripped_response = response
		
		return check_line_termination(stripped_response)
	
	def send_simple_SMS(self, destination, text):
		self.execute("AT+CMGF=1") # text mode
		assert(destination.find("\n") == -1)
		assert(destination.find("\r") == -1)
		assert(destination.find("\"") == -1)
		command = "AT+CMGS=\"%s\"\x0D" % destination
		self.serial_1.write(command)
		command = command + "\x0D\x0A"
		assert(self.serial_1.read(len(command)) == command)
		assert(self.serial_1.read(1) == ">") # not error
		assert(self.serial_1.read(1) == " ") # not error
		self.serial_1.write(text + chr(26))
		self.serial_1.read(len(text + chr(26))) # echo
		data = self.serial_1.read(2)
		assert(data == "\x0D\x0A")
		response = Samsung_readline(self.serial_1)
		assert(response.startswith("+CMGS:")) # "+CMGS:7"newline
