import sys
import platform
import time
import socket
import re
import base64

import usb.core
import usb.util
import struct
import Operations as op
from copy import deepcopy
import time
import numpy as np

import binascii
import array
DEVICE = None
DEVICE_TYPE = None
epBulkWriter = None
epBulkReader = None
VID = '0x2ab9'
PID = '0xffff'

class bootloaderMonsoon(object):
    def __init__(self,*args, **kwargs):
        pass    

    def setup_usb(self):
        """Sets up the USB connection."""
        global DEVICE
        global epBulkWriter
        global epBulkReader
        global VID
        global PID

        DEVICE = usb.core.find(idVendor=0x2AB9,idProduct=0xFFFF)
        if DEVICE is None:#If not a LVPM, look for an HVPM.
            DEVICE = usb.core.find(idVendor=0x04d8,idProduct=0x000b)
            VID = '0x4d8'
            PID = '0xb'
        if "Linux" == platform.system():
            try:
                DEVICE.detach_kernel_driver(0)
            except:
                pass # already unregistered  
        DEVICE.set_configuration()

        cfg = DEVICE.get_active_configuration()
        intf = cfg[(0,0)]

        epBulkWriter = usb.util.find_descriptor(
            intf,
            custom_match = \
                lambda e: \
                usb.util.endpoint_direction(e.bEndpointAddress) == \
                usb.util.ENDPOINT_OUT)
        epBulkReader = usb.util.find_descriptor(
            intf,
            custom_match = \
                lambda e: \
                usb.util.endpoint_direction(e.bEndpointAddress) == \
                usb.util.ENDPOINT_IN)

    def __bootCommand(self,Command,length,address,data):
        """Sends boot command."""
        sendData = []
        sendData.append(Command)
        sendData.append(length)
        sendData.append(address[2])
        sendData.append(address[1])
        sendData.append(address[0])
        for i in range(0,len(data)):
            sendData.append(data[i])
        for i in range(len(data),length):
            sendData.append(0)
        a = epBulkWriter.write(sendData,timeout=10000)
        ret = epBulkReader.read(length+5,timeout=10000)
        return ret
        



    def writeFlash(self, hex):
        """Writes a hex file to the Power Monitor's PIC.  Uses Intel HEX file format."""
        Flash, EEPROM,IDlocs,Config  = self.__formatHex(hex)
        print("Erasing Flash...")
        self.__writeRegion(op.BootloaderMemoryRegions.Flash,op.BootloaderCommands.EraseFlash,0x0800,Flash,None)
        print("Writing Flash...")
        if(self.__writeRegion(op.BootloaderMemoryRegions.Flash,op.BootloaderCommands.WriteFlash,0x0800,Flash,op.BootloaderCommands.ReadFlash)):
            print("Flash written OK")
        #Don't actually erase the EEPROM, this would wipe out all of the calibration data.
        #if(self.writeRegion(op.BootloaderMemoryRegions.EEPROM,op.BootloaderCommands.WriteEEPROM,0x0000,EEPROM,op.BootloaderCommands.ReadEEPROM)):
        #    print("EEPROM written OK")   
        if(self.__writeChunk(op.BootloaderMemoryRegions.IDLocs,op.BootloaderCommands.WriteFlash,0x0000,IDlocs,op.BootloaderCommands.ReadFlash)):
            print("IDLocs written OK")
        if(self.__writeChunk(op.BootloaderMemoryRegions.Config,op.BootloaderCommands.WriteConfig,0x0000,Config,op.BootloaderCommands.ReadConfig)):
            print("Config written OK")


    def __writeRegion(self, memoryRegion,command,addressStart,regionData,errorCheckCommand):
        """Writes information to a memory region."""
        address = [0 for x in range(3)]
        data = [0 for x in range(16)]
        result = True
        progressThresholds = [x*10 for x in range(11)]
        progressindex = 0
        test = len(regionData)
        for i in range(addressStart, len(regionData), 16):
            memoryIndex = struct.unpack("BBBB",struct.pack('I', i))
            address[0] = memoryRegion
            address[1] = memoryIndex[1]
            address[2] = memoryIndex[0]
            data = regionData[i:i+16]
            #self.bootCommand(op.BootloaderCommands.EraseFlash,16,address,[])
            self.__bootCommand(command,len(data),address,data)
            if(errorCheckCommand != None):
                dataout = self.__bootCommand(errorCheckCommand,16,address,[])
                dataout = dataout[5:len(dataout)]
                if not self.__compare(data,dataout):
                    result = False
                    print("Write error")
            percentComplete = (i*1.0 / len(regionData)) * 100
            if(progressThresholds[progressindex] < percentComplete):
                print('%.0f percent complete' % percentComplete)
                progressindex += 1
        return result

    def __writeChunk(self, memoryRegion,command,addressStart,regionData,errorCheckCommand):
        result = True
        address = [0 for x in range(3)]
        address[0] = memoryRegion
        address[1] = 0
        address[2] = 0
        data = regionData
        if(memoryRegion != op.BootloaderMemoryRegions.Config):
            self.__bootCommand(op.BootloaderCommands.EraseFlash,16,address,[])
        self.__bootCommand(command,len(data),address,data)
        #dataout = self.bootCommand(errorCheckCommand,16,address,[])
        #dataout = dataout[5:len(dataout)]
        #if not self.compare(data,dataout):
        #    result = False
        #    print("Reflash Write error")
        return result

    def __compare(self,data,dataout):
        """Compare read data to the data we think we wrote."""
        if(data == None or dataout == None):
            return False
        if(len(data) != len(dataout)):
            return False
        for i in range(len(data)):
            if(data[i] != dataout[i]):
                return False
        return True
    def __byteLine(self, line):
        """Translate a HEX file line into address, linetype, data, and checksum"""
        output = []
        for offset in range(1,len(line)-1,2):
            output.append(struct.unpack("B",struct.pack('B',np.int(line[offset:offset+2],16))[0])[0])
        address = []
        length = output[0]
        address.append(output[1])
        address.append(output[2])
        type = output[3]
        Data = output[4:4+length]
        checksum = output[len(output)-1]
        return address, type, Data, checksum


    def getHeaderFromFWM(self, filename):
        """Strips the header from a Monsoon FWM file, returns the HEX file and the formatted header.
        Header format [VID,PID,Rev,Model]"""
        f = open(filename,'r')
        hex = f.read()
        f.close()

        headerEnd = hex.find(':')
        header = hex[0:headerEnd]
        offset = 7
        count = array.array('B', header[offset])[0]
        offset += 1
        hex = hex[headerEnd:len(hex)]
        outHeader = [0 for x in range(4)]
        headers = []
        i = 0
        for i in range(count):
            outHeader[0] = array.array('H', header[offset:offset+2])[0] #VID
            offset += 2
            outHeader[1] = array.array('H', header[offset:offset+2])[0] #PID
            offset += 2
            outHeader[2] = array.array('H', header[offset:offset+2])[0] #Rev
            offset += 2
            outHeader[3] = array.array('H', header[offset:offset+2])[0] #Model
            offset += 2
            test = deepcopy(outHeader)

            headers.append(test)
            i+= 1
        return headers, hex

    def getHexFile(self, filename):
        """Reads an Intel HEX file."""
        f = open(filename,'r')
        hex = f.read()
        f.close()
        return hex

    def __formatHex(self,hex):
        """Takes raw hex input, and turns it into an array of hex lines."""
        output = []
        lineEnd = hex.find('\n')
        while lineEnd > 0:
            output.append(hex[0:lineEnd])
            hex = hex[lineEnd+1:len(hex)]
            lineEnd = hex.find('\n')
        Flash, EEPROM,IDlocs,Config = self.__formatAsPICFlash(output)
        return  Flash, EEPROM,IDlocs,Config 

    def __formatAsPICFlash(self, hex):
        """Formats an array of hex lines as PIC memory regions."""
        flash = [0xff for x in range(32768)]
        EEPROM = [0xff for x in range(256)]
        IDlocs = [0xff for x in range(16)]
        Config  = [0xff for x in range(14)]
        addressMSB = 0
        for line in hex:
            address, type, Data, checksum = self.__byteLine(line)
            intAddress = struct.unpack("h",struct.pack("BB", address[1],address[0]))[0]
            if(type == op.hexLineType.ExtendedLinearAddress):
                addressMSB = Data[1]
            if(type == op.hexLineType.Data):
                if(addressMSB == op.BootloaderMemoryRegions.Flash):
                    for byte in Data:
                        flash[intAddress] = byte
                        intAddress += 1
                if(addressMSB == op.BootloaderMemoryRegions.EEPROM):
                    intAddress = address[1]
                    for byte in Data:
                        EEPROM[intAddress] = byte
                        intAddress += 1
                if(addressMSB == op.BootloaderMemoryRegions.IDLocs):
                    intAddress = address[1]
                    for byte in Data:
                        IDlocs[intAddress] = byte
                        intAddress += 1
                if(addressMSB == op.BootloaderMemoryRegions.Config):
                    intAddress = address[1]
                    for byte in Data:
                        Config[intAddress] = byte
                        intAddress += 1
        return flash, EEPROM, IDlocs, Config

    def verifyHeader(self, headers):
        """Verifies the header matches the physical hardware being reflashed."""
        for head in headers:
            if(hex(head[0]) == VID and hex(head[1]) == PID):
                return True
        return False