/**
 * \file    xmodem.c
 * \brief   XModem interface
 *
 * \par     Copyright (c) 2018 UWE Technologies Corp., Ltd. All Rights Reserved.
 * \author  James
 * \date    2018-03-05
 * \version 
 * 
 * \history 
 *  Date        Note
 *  2018-03-05  Original Creation
 * 
 */

#include "types.h"
#include "libc.h"
#include "drivers.h"
#include "crc16.h"

#define SOH  0x01
#define STX  0x02
#define EOT  0x04
#define ACK  0x06
#define NAK  0x15
#define CAN  0x18
#define CTRLZ 0x1A
#define DLY_1S 1000
#define MAXRETRANS 25
static int last_error = 0;

#define MAX_TRY_CNT         128
#define MAX_FLASH_WRITE_SZ  128

unsigned char xbuff[1030];

#define T_PORT_IN_TIMEOUT   1000000UL


int port_inbyte(unsigned int tn)
{
	sys_tick_t start = now();
    sys_tick_t timeout = tn * T_PORT_IN_TIMEOUT;
    last_error = 0;
    
	while(1)
	{
        if (DRV_REG_GET(UART_LSR_REG) & UART_LSR_REG_DR_FLDSET)
        {
            return DRV_REG_GET(UART_PBR_REG);
        }
		if ((now() - start) >= timeout)
		{
			break;
		}
    }
    
    last_error = 1;
    return 0;
}

void port_putbyte(int c)
{
    while(!(DRV_REG_GET(UART_LSR_REG) & UART_LSR_REG_THRE_FLDSET));
    DRV_REG_SET(UART_THR_REG, c);
}

static int check(int crc, const unsigned char *buf, int sz)
{
    unsigned short rcrc;
    unsigned short tcrc;
    int i;
    unsigned char cks;

    if (crc)
    {
        rcrc = crc16_ccitt(buf, sz);
        tcrc = (buf[sz]<<8)+buf[sz+1];
        if (rcrc == tcrc)
            return 1;
    }
    else
    {
        cks = 0;
        for (i = 0; i < sz; ++i)
        {
            cks += buf[i];
        }
        if (cks == buf[sz])
            return 1;
    }
    return 0;
}

long xmodem_rx(unsigned long dest, unsigned long destsz)
{
    unsigned char *p;
    int bufsz, crc = 0;
    int trychar = 'C';
    int packetno = 1;
    int c;
    int i, buff_cnt;
    long len = 0, count;
    int retry, retrans = MAXRETRANS;

    for(;;)
    {
        for( retry = 0; retry < MAX_TRY_CNT; ++retry)
        {
            if (trychar)
                port_putbyte(trychar);
            c = port_inbyte(2);
            if (last_error == 0)
            {
                switch (c)
                {
                case SOH:
                    bufsz = 128;
                    goto start_recv;
                case STX:
                    bufsz = 1024;
                    goto start_recv;
                case EOT:
                    port_putbyte(ACK);
                    return len;
                case CAN:
                    c = port_inbyte(1);
                    if (c == CAN)
                    {
                        port_putbyte(ACK);
                        return -1;
                    }
                    break;
                default:
                    break;
                }
            }
        }
        if (trychar == 'C')
        {
            trychar = NAK;
            continue;
        }
        port_putbyte(CAN);
        port_putbyte(CAN);
        port_putbyte(CAN);
        return -2;
start_recv:
        if (trychar == 'C') crc = 1;
        trychar = 0;
        p = xbuff;
        *p++ = c;
        for (i = 0; i < (bufsz+(crc?1:0)+3); ++i)
        {
            c = port_inbyte(1);
            if (last_error != 0)
                goto reject;
            *p++ = c;
        }
        if ((xbuff[1] == (unsigned char)(~xbuff[2])) &&
            ((xbuff[1] == packetno) || (xbuff[1] == (unsigned char)(packetno-1))) &&
            check(crc, &xbuff[3], bufsz))
        {
            if (xbuff[1] == packetno) 
            {
                buff_cnt = bufsz;
                count = destsz - len;
                if (count > bufsz)
                    count = bufsz;
                if (count > 0)
                {
                    i = 0;
                    while (buff_cnt > 0)
                    {
                    	drv_flash_write(dest+len+i*MAX_FLASH_WRITE_SZ, &xbuff[i*MAX_FLASH_WRITE_SZ+3], MAX_FLASH_WRITE_SZ);
                        //drv_flash_write2(dest+len+i*MAX_FLASH_WRITE_SZ, &xbuff[i*MAX_FLASH_WRITE_SZ+3], MAX_FLASH_WRITE_SZ);
                        buff_cnt -= MAX_FLASH_WRITE_SZ;
                        i++;
                    }
                    len += count;
                }
                ++packetno;
                retrans = MAXRETRANS+1;
            }
            if (--retrans <= 0)
            {
                port_putbyte(CAN);
                port_putbyte(CAN);
                port_putbyte(CAN);
                return -3;
            }
            port_putbyte(ACK);
            continue;
        }
reject:
        port_putbyte(NAK);
    }
}
