#!/usr/bin/env python3
import click
#from timeout_decorator import TimeoutError
import ISPProgrammer

BAUDRATES = (
    9600,
    19200,
    38400,
    57600,
    115200,
    230400,
    460800
)
@click.group()
@click.option('--device', '-d', default='/dev/ttyUSB0', help='Serial device')
@click.option('--baud', '-b', type=int, default=BAUDRATES[0], help='Baudrate')
@click.option('--crystal_frequency', '-c', type=int, default=12000,
              help="Crystal frequency of chip in khz")
@click.option('--config_file', '-f', default='/etc/lpctools_parts.def',
              help='Parts definition file')
@click.option('--echo', is_flag=True)
@click.option('--no_sync', is_flag=True)
@click.option('--sleep_time', '-s', type=float, default=0.25, help='Sleep time between commands')
@click.pass_context
def gr1(ctx, **kwargs):
    ctx.ensure_object(dict)
    ctx.obj.update(kwargs)
    ISPProgrammer.ISPChip.SetEcho(kwargs['echo'])

    if ISPProgrammer.ISPChip.GetEcho():
      print("Echoing received data with [...], and sent as <...>")

def ReadChipFile(fname: str) -> list:
    data = []
    with open(fname, 'r') as f:
        for line in f:
            if not line.strip() or line.strip()[0] == '#':
                continue
            data.append(line.split(","))
    return data

def GetPartDescriptor(fname: str, partid: int) -> str:
    entries = ReadChipFile(fname)
    for entry in entries:
        if partid == int(entry[0], 0):
            return entry
    raise UserWarning("PartId {0x%x} not found in {}".format(partid, fname))

def SetupChip(baudrate: int, device: str, crystal_frequency: int, chip_file: str, no_sync: bool = False, sleep_time : float = 1) -> ISPProgrammer.NXPChip:
    #print(baudrate, device, crystal_frequency, chip_file) 
    if(not no_sync):
        kStartingBaudRate = BAUDRATES[0]
    else:
        kStartingBaudRate = baudrate

    iodevice = ISPProgrammer.UartDevice(device, baudrate=kStartingBaudRate)
    chip = ISPProgrammer.NXPChip(iodevice)
    chip.kSleepTime = sleep_time
    if(not no_sync):
        chip.InitConnection()

    chip.Echo(False)
    '''
    try:
        chip.ReadLine()
        chip.Flush()
        chip.ClearBuffer()
    except TimeoutError:
        pass
    '''

    while True:
        try:
            part_id = chip.ReadPartID()
            break
        except TimeoutError:
            pass
    descriptor = GetPartDescriptor(chip_file, part_id)
    if descriptor is None:
        raise UserWarning("Warning chip %s not found in file %s"%(hex(part_id), chip_file))

    print(part_id, descriptor[1])
    chip.CrystalFrequency = crystal_frequency#12000#khz == 30MHz
    chip.SectorCount = int(descriptor[4])
    chip.RAMSize = int(descriptor[7], 0)

    ramstart = int(descriptor[6], 0)
    chip.RAMRange = (ramstart, ramstart + chip.RAMSize - 1)

    flashstart = int(descriptor[2], 0)
    flashsize = int(descriptor[3], 0)
    chip.FlashRange = (flashstart, flashstart + flashsize - 1)

    ram_buffer_offset = int(descriptor[8], 0)
    chip.RAMStartWrite = ramstart + ram_buffer_offset
    chip.kCheckSumLocation = 7 #0x0000001c

    if(chip.RAMRange[1]-chip.RAMRange[0] != chip.RAMSize - 1):
        raise ValueError("RAM size for {0x%x} is wrong".format(part_id))
    print("Setting new baudrate %d"%baudrate)
    chip.ChangeBaudRate(baudrate)
    return chip

@gr1.command()
@click.pass_context
def QueryChip(ctx):
    SetupChip(ctx.obj['baud'], ctx.obj['device'], ctx.obj['crystal_frequency'], ctx.obj['config_file'], ctx.obj['no_sync'], ctx.obj['sleep_time'])

@gr1.command()
@click.pass_context
def MassErase(ctx):
    chip = SetupChip(ctx.obj['baud'], ctx.obj['device'], ctx.obj['crystal_frequency'], ctx.obj['config_file'], ctx.obj['no_sync'], ctx.obj['sleep_time'])
    chip.MassErase()
    print("Mass Erase Successful")

@click.option('--start_sector', type=int, default=0, required=True, help='Sector to write to')
@click.option('--imagein', type=str, required=True, help='Location of hex file to program')
@gr1.command()
@click.pass_context
def WriteFlash(ctx, imagein, start_sector):
    chip = SetupChip(ctx.obj['baud'], ctx.obj['device'], ctx.obj['crystal_frequency'], ctx.obj['config_file'], ctx.obj['no_sync'], ctx.obj['sleep_time'])
    try:
        chip.WriteBinaryToFlash(imagein, start_sector)
    except UserWarning as e:
        print(e)
        chip.WriteBinaryToFlash(imagein, start_sector)

@click.option('--imagein', type=str, required=True, help='Location of hex file to program')
@gr1.command()
@click.pass_context
def WriteImage(ctx, imagein):
    chip = SetupChip(ctx.obj['baud'], ctx.obj['device'], ctx.obj['crystal_frequency'], ctx.obj['config_file'], ctx.obj['no_sync'], ctx.obj['sleep_time'])
    try:
        chip.WriteImage(imagein)
    except UserWarning as e:
        print(e)
        chip.WriteImage(imagein)

    #chip.Go(0, ThumbMode=False)
    chip.Go(0)

@click.option('--imageout', type=str, required=True, help='Name of hex file output')
@gr1.command()
@click.pass_context
def ReadImage(ctx, imageout: str):
    chip = SetupChip(ctx.obj['baud'], ctx.obj['device'], ctx.obj['crystal_frequency'], ctx.obj['config_file'], ctx.obj['no_sync'], ctx.obj['sleep_time'])
    chip.ReadImage(imageout)

if __name__ == "__main__":
    gr1()
