# See comment in stm32w_flasher.py.
# Extraction and little adaptation performed by E.Duble (CNRS, LIG).

import os
import serial
import struct
import time
from messages import errorMessage
from messages import infoMessage


class Ymodem():
	ABORT1 = 65
	ABORT2 = 97
	ACK = 6
	CA = 24
	CRC16 = 67
	EOT = 4
	NAK = 21
	PACKET_1K_SIZE = 1024
	PACKET_HEADER = 3
	PACKET_OVERHEAD = 5
	PACKET_SIZE = 128
	PACKET_TRAILER = 2
	SOH = 1
	STATE_DONE = 6
	STATE_READY = 2
	STATE_SEND_EOT = 3
	STATE_SEND_SESSION_DONE = 4
	STATE_START_APPLICATION = 5
	STATE_WAITING_ACK = 1
	STATE_WAITING_CRC16 = 0
	STX = 2
	YMODEM_RX_TO_TX = 2
	YMODEM_TX_TO_RX = 0
	YMODEM_TX_TO_RX2 = 1
	def Crc16X(self, packet, count):
		j = 0
		crc = 0
		for j in range(count):
			crc = (crc ^ ((packet[j]) << 8))
			for i in range(8):
				if (crc & 32768):
					crc = ((crc << 1) ^ 4129)
					continue
				else:
					crc = (crc << 1)
					continue
			continue
		return crc

	def __init__(self, serialPort, port, updateAction=None):
		self.serialPort = serialPort
		self.updateAction = updateAction
		self.port = port
		return None

	def bootloaderInit(self):
		returnValue = False
		systemRestartPacket = [170, 1, 7, 85]
		self.serialPort.write(struct.pack(('B' * len(systemRestartPacket)), *systemRestartPacket))
		self.serialPort.close()
		time.sleep(5)
		self.serialPort = serial.Serial(port, 10, timeout=4)
		self.serialPort.flushInput()
		systemRestartPacket = [170, 1, 5, 85]
		self.serialPort.write(struct.pack(('B' * len(systemRestartPacket)), *systemRestartPacket))
		self.serialPort.read(4)
		for i in range(10):
			startTime = time.time()
			while (time.time() - startTime) < 0.5:
				if self.serialPort.inWaiting() > 0:
					c = self.serialPort.read(1)
					c = (struct.unpack('B', c)[0])
					if c == ord('C'):
						returnValue = True
						break
				time.sleep(0.01)
				continue
			continue
		return returnValue

	def getByte(self):
		data = None
		if self.serialPort.inWaiting() > 0:
			data = (struct.unpack('B', self.serialPort.read(1))[0])
		if data:
			pass
		return data

	def loadFile(self, name):
		returnValue = True
		state = self.STATE_WAITING_CRC16
		filename = os.path.basename(name)
		startTime = time.time()
		try:
			f = open(name, 'rb')
			infoMessage((('File ' + name) + ' opened\n'))
			f.seek(0, 2)
			file_size = f.tell()
			f.seek(0, 0)
			packet_number = 0
			loop_counter = 1
			size = file_size
			retry = 0
			packet = []
			prevState = self.STATE_READY
			while True:
				newState = state
				if state == self.STATE_WAITING_CRC16:
					if self.updateAction:
						self.updateAction((file_size - max(size, 0)), file_size)
					data = self.getByte()
					if data == self.CRC16:
						newState = self.STATE_READY
				else:
					if state == self.STATE_WAITING_ACK:
						if self.updateAction:
							self.updateAction((file_size - max(size, 0)), file_size)
						data = self.getByte()
						if data is not None:
							if data == self.ACK:
								if prevState == self.STATE_READY:
									retry = 0
									if loop_counter > 1:
										size = (size - packet_size)
									packet_number = ((packet_number + 1) % 256)
									loop_counter = (loop_counter + 1)
									packet_done = True
									if self.updateAction:
										self.updateAction((file_size - max(size, 0)), file_size)
									else:
										infoMessage(('Sent %05d/%05d\r' % ((file_size - max(size, 0)), file_size)))
									newState = self.STATE_READY
								else:
									if prevState == self.STATE_SEND_EOT:
										newState = self.STATE_SEND_SESSION_DONE
									else:
										if prevState == self.STATE_SEND_SESSION_DONE:
											newState = self.STATE_START_APPLICATION
							else:
								if data == self.CA:
									errorMessage('Transaction aborted by client\n')
									newState = self.STATE_DONE
								else:
									if data == self.CRC16:
										pass
									else:
										if prevState == self.STATE_READY:
											infoMessage('Retrying\n')
											retry = (retry + 1)
											if retry > 3:
												errorMessage('Too many retry exiting\n')
												newState = self.STATE_DONE
											else:
												newState = self.STATE_READY
					else:
						if state == self.STATE_READY:
							if size <= 0:
								newState = self.STATE_SEND_EOT
							else:
								if retry == 0:
									packet = []
									if loop_counter == 1:
										packet.extend(struct.unpack(('%uB' % len(filename)), filename))
										packet = (packet + [0])
										size_string = ('%d ' % file_size)
										packet.extend(struct.unpack(('%uB' % len(size_string)), size_string))
										packet_size = self.PACKET_SIZE
									else:
										packet_size = self.PACKET_1K_SIZE
										packet_string = f.read(packet_size)
										packet.extend(struct.unpack(('%uB' % len(packet_string)), packet_string))
									packet = (packet + ([0] * (packet_size - len(packet))))
								if self.sendYModemPacket(packet, packet_number):
									newState = self.STATE_DONE
								else:
									newState = self.STATE_WAITING_ACK
						else:
							if state == self.STATE_SEND_EOT:
								if self.updateAction:
									self.updateAction((file_size - max(size, 0)), file_size)
								else:
									infoMessage(('Sent %05d/%05d\r' % ((file_size - max(size, 0)), file_size)))
								self.sendByte(self.EOT)
								newState = self.STATE_WAITING_ACK
							else:
								if state == self.STATE_SEND_SESSION_DONE:
									self.sendYModemPacket(([0] * 128), 0)
									newState = self.STATE_WAITING_ACK
								else:
									if state == self.STATE_START_APPLICATION:
										self.startApplication()
										newState = self.STATE_DONE
									else:
										if state == self.STATE_DONE:
											returnValue = False
											endTime = time.time()
											infoMessage('\nDone\n')
											break
										else:
											errorMessage(('Unknonw state = %d' % state))
											newState = self.STATE_DONE
				if state != newState:
					prevState = state
					state = newState
					continue
				else:
					continue
			return returnValue
		except:
			errorMessage((('File ' + name) + ' open failed\n'))
			if self.updateAction:
				self.updateAction(0, 0)
			return returnValue

	def sendByte(self, byte):
		self.serialPort.write(struct.pack('B', byte))
		return None

	def sendYModemPacket(self, myPacket, packet_number):
		returnValue = 0
		packet = myPacket[:]
		packet_crc = self.Crc16X(packet, len(packet))
		packet.append(((packet_crc >> 8) & 255))
		packet.append((packet_crc & 255))
		packet.insert(0, (255 - packet_number))
		packet.insert(0, packet_number)
		if len(myPacket) == self.PACKET_SIZE:
			packet.insert(0, self.SOH)
		else:
			packet.insert(0, self.STX)
		self.serialPort.write(struct.pack(('B' * len(packet)), *packet))
		return returnValue

	def startApplication(self):
		return None