/*
 * File      : sz.c
 * the implemention of sending files to the remote computers
 * through the zmodem protocol.
 * Change Logs:
 * Date           Author       Notes
 * 2011-03-29     itspy
 */

#include <rtthread.h>
#include <finsh.h>
#include <shell.h>
#include <rtdef.h>
#include <dfs.h>
#include <dfs_file.h>
#include <dfs_posix.h>
#include "zdef.h"


static rt_uint8_t TX_BUFFER[TX_BUFFER_SIZE];         /* sender buffer */
static rt_uint8_t file_cnt = 0;                      /* count of number of files opened */
static rt_uint8_t Rxflags  = 0;                      /* rx parameter flags */
static rt_uint8_t ZF2_OP;                            /* file transfer option */

void zs_start(char *path);
static void zsend_init(void);
static rt_err_t zsend_files(struct zfile *zf);
static rt_err_t zsend_file(struct zfile *zf, rt_uint8_t *buf, rt_uint16_t len);
static rt_err_t zsend_file_data(struct zfile *zf);
static rt_uint16_t zfill_buffer(struct zfile *zf, rt_uint8_t *buf, rt_uint16_t size);
static rt_err_t zget_sync(void);
static void zsay_bibi(void);




/* start zmodem send process */
void zs_start(char *path)
{
    struct zfile *zf;
    rt_err_t res = RT_ERROR;
    char *p,*q;
    zf = rt_malloc(sizeof(struct zfile));
    if (zf == RT_NULL)
    {
        rt_kprintf("zf: out of memory\r\n");
        return;
    }
    rt_kprintf("\r\nsz: ready...\r\n");    /* here ready to send things */
    memset(zf, 0, sizeof(struct zfile));
    zf->fname = path;
    zf->fd = -1;
    res = zsend_files(zf);
    p = zf->fname;
    for (;;)
    {
        q = strstr(p,"/");
        if (q == RT_NULL)  break;
        p = q+1;
    }
    if (res == RT_EOK)
    {
        rt_kprintf("\r\nfile: %s \r\nsize: %ld bytes\r\nsend completed.\r\n",
                  p,zf->bytes_received);
    }
    else
    {
        rt_kprintf("\r\nfile: %s \r\nsize: 0 bytes\r\nsend failed.\r\n",p);
    }
    rt_free(zf);

    return;
}

/* init the parameters */
static void zsend_init(void)
{
    rt_err_t res = -RT_ERROR;

    zinit_parameter();
    for(;;)          /* wait ZPAD */
    {
        res = zread_line(800);
        if (res == ZPAD) break;
    }
    for (;;)
    {
        res = zget_header(rx_header);
        if (res == ZRINIT) break;
    }
    if ((rx_header[ZF1] & ZRQNVH))
    {
        zput_pos(0x80L);    /* Show we can var header */
        zsend_hex_header(ZRQINIT, tx_header);
    }
    Rxflags = rx_header[ZF0] & 0377;
    if (Rxflags & CANFC32) Txfcs32 = 1;    /* used 32bits CRC check */

    if (ZF2_OP == ZTRLE && (Rxflags & CANRLE))    /* for RLE packet */
         Txfcs32 = 2;
    else
        ZF2_OP = 0;
    /* send SINIT cmd */
    return;
}

/* send files */
static rt_err_t zsend_files(struct zfile *zf)
{
    char *p,*q;
    char *str = "/";
    struct stat finfo;
    rt_err_t res = -RT_ERROR;

    if (zf->fname == RT_NULL)
    {
        rt_kprintf("\r\nerror: no file to be send.\r\n");
        return res;
    }
    if ((zf->fd=open(zf->fname, DFS_O_RDONLY,0)) <0)
    {
        rt_kprintf("\r\ncan not open file:%s\r\n",zf->fname+1);
        return res;
    }

    zf->file_end = 0;
    ++file_cnt;
    /* extract file name */
    p = zf->fname;
    for (;;)
    {
        q = strstr(p,str);
        if (q == RT_NULL)  break;
        p = q+1;
    }
    q = (char*)TX_BUFFER;
    for (;;)
    {
        *q++ = *p++;
        if (*p == 0) break;
    }
    *q++ = 0;
    p=q;
    while (q < (char*)(TX_BUFFER + 1024))
        *q++ = 0;
    /* get file attributes */
    fstat(zf->fd,&finfo);
    Left_sizes += finfo.st_size;
    rt_sprintf(p, "%lu %lo %o 3 %d %ld", (long)finfo.st_size, finfo.st_mtime,
              finfo.st_mode, file_cnt, Left_sizes);
    Left_sizes -= finfo.st_size;
    TX_BUFFER[127] = (finfo.st_size + 127) >>7;
    TX_BUFFER[126] = (finfo.st_size + 127) >>15;

    zsend_init();
    /* start sending files */
    res = zsend_file(zf,TX_BUFFER, (p-(char*)TX_BUFFER)+strlen(p)+1);
    zsay_bibi();
    close(zf->fd);

    return res;
}

/* send file name and related info */
static rt_err_t zsend_file(struct zfile *zf, rt_uint8_t *buf, rt_uint16_t len)
{
    rt_uint8_t cnt;
    rt_err_t res = -RT_ERROR;

    for (cnt=0;cnt<5;cnt++)
    {
        tx_header[ZF0] = ZF0_CMD;               /* file conversion option */
        tx_header[ZF1] = ZF1_CMD;               /* file management option */
        tx_header[ZF2] = (ZF3_CMD|ZF2_OP);      /* file transfer option   */
        tx_header[ZF3] = ZF3_CMD;
        zsend_bin_header(ZFILE, tx_header);
        zsend_bin_data(buf, len, ZCRCW);
loop:
        res = zget_header(rx_header);
        switch (res)
        {
        case ZRINIT:
             while ((res = zread_line(50)) > 0)
             {
                 if (res == ZPAD)
                 {
                    goto loop;
                 }
             }
             break;
        case ZCAN:
        case TIMEOUT:
        case ZABORT:
        case ZFIN:
             break;
        case -RT_ERROR:
        case ZNAK:
             break;
        case ZCRC:                           /* no CRC request */
             goto loop;
        case ZFERR:
        case ZSKIP:
             break;
        case ZRPOS:                          /* here we want */
             zget_pos(Rxpos);
             Txpos = Rxpos;
             return(zsend_file_data(zf));
        default:
             break;
        }
    }

    return res;
}

/* send the file data */
static rt_err_t zsend_file_data(struct zfile *zf)
{
    rt_int16_t cnt;
    rt_uint8_t cmd;
    rt_err_t res = -RT_ERROR;
    /* send ZDATA packet, start to send data */
start_send:
    zput_pos(Txpos);
    zsend_bin_header(ZDATA, tx_header);
    do
    {
        cnt = zfill_buffer(zf,TX_BUFFER,RX_BUFFER_SIZE);
        if (cnt < RX_BUFFER_SIZE )
            cmd = ZCRCE;
        else
            cmd = ZCRCG;
        zsend_bin_data(TX_BUFFER, cnt, cmd);
        zf->bytes_received= Txpos += cnt;
        if (cmd == ZCRCW)
            goto get_syn1;
    } while (cnt == RX_BUFFER_SIZE);
    for (;;)                         /*  get ack and check if send finish */
    {
        zput_pos(Txpos);
        zsend_bin_header(ZEOF, tx_header);
get_syn1:
        res = zget_sync();
        switch (res)
        {
        case ZACK:
             goto get_syn1;
        case ZNAK:
             continue;
        case ZRPOS:                     /* resend here */
             lseek(zf->fd,Txpos,0);
             goto start_send;
        case ZRINIT:                   /*  send finish,then begin to send next file */
             return RT_EOK;
        case ZSKIP:
        case -RT_ERROR:
             return res;
        default:
             return res;
        }
    }
}

/* fill file data to buffer*/
static rt_uint16_t zfill_buffer(struct zfile *zf, rt_uint8_t *buf, rt_uint16_t size)
{
    return (read(zf->fd,buf,size));
}

/* wait sync(ack) from the receiver */
static rt_err_t zget_sync(void)
{
    rt_err_t res = -RT_ERROR;

    for (;;)
    {
        res = zget_header(rx_header);
        switch (res)
        {
        case ZCAN:
        case ZABORT:
        case ZFIN:
        case TIMEOUT:
             return -RT_ERROR;
        case ZRPOS:                  /* get pos, need to resend */
             zget_pos(Rxpos);
             Txpos = Rxpos;
             return res;
        case ZACK:
             return res;
        case ZRINIT:                 /* get ZRINIT indicate that the prev file send completed */
             return res;
        case ZSKIP:
             return res;
        case -RT_ERROR:
        default:
             zsend_bin_header(ZNAK, tx_header);
             continue;
        }
    }
}

/* say "bibi" to the receiver */
static void zsay_bibi(void)
{
    for (;;)
    {
        zput_pos(0L);                         /* reninit position of next file*/
        zsend_hex_header(ZFIN, tx_header);    /* send finished session cmd */
        switch (zget_header(rx_header))
        {
        case ZFIN:
            zsend_line('O');
            zsend_line('O');
        case ZCAN:
        case TIMEOUT:
            return;
        }
    }
}
/* end of sz.c */
