#define SEND_RECIEVE_TIMEOUT 5000

#include <stdlib.h>
#include "ltravrapi.h"
#include "ltr010api.h"
#include "ltrmodule.h"


typedef struct {
    DWORD sign;
    INT flast_size;
    INT page_size;
    INT fuse_bytes_cnt;
} t_avr_params;

/* время, которое следует ожидать, для завершения операции в мс */
#define LTRAVR_TIME_WD_FUSE   5 /* 4.5 */
#define LTRAVR_TIME_WD_FLASH  5 /* 4.5 */
#define LTRAVR_TIME_WD_EEPROM 9
#define LTRAVR_TIME_WD_ERASE  9


static t_avr_params f_avr_param_tbl[] = {
    {ATMEGA8515_SIGN_CODE, ATMEGA8515_FLASH_SIZE, ATMEGA8515_PAGE_SIZE, 2},
    {ATMEGA128_SIGN_CODE,  ATMEGA128_FLASH_SIZE,  ATMEGA128_PAGE_SIZE, 3}
};

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTRAVR_ERROR_RECV_PRG_DATA_ECHO,  "Ошибка приема эхо ответа данных для программирования."},
    {LTRAVR_ERROR_SEND_PRG_DATA,       "Ошибка отправки данных команды програмирования avr."},
    {LTRAVR_ERROR_RECV_PRG_ENABLE_ACK, "Ошибка приема подтверждения команды входа в режим программирования."},
    {LTRAVR_ERROR_SEND_PRG_ENB_CMD,    "Ошибка отправки команды входа в режим программирования."},
    {LTRAVR_ERROR_CHIP_ERASE,          "Ошибка стирания flash команд avr."},
    {LTRAVR_ERROR_READ_PRG_MEM,        "Ошибка считывания flash команд avr."},
    {LTRAVR_ERROR_WRITE_PRG_MEM,       "Ошибка программирования flash команд avr."},
    {LTRAVR_ERROR_READ_FUSE_BITS,      "Ошибка считывания fuse витов avr."},
    {LTRAVR_ERROR_WRITE_FUSE_BITS,     "Ошибка программирования fuse витов avr."},
    {LTRAVR_ERROR_READ_SIGN,           "Ошибка считывания сигнатуры avr."},
    {LTRAVR_ERROR_UNKNOWN_SIGN,        "Неизвестная сигнатура avr-контроллера"}
};

#ifdef _WIN32
    int ___CPPdebugHook = 0;
#endif


//-----------------------------------------------------------------------------
//                функции программирования модуля
//-----------------------------------------------------------------------------

/* получить запись таблицы с параметрами AVR, установленного в модуль */
static INT f_get_avr_param_tbl(TLTRAVR *module, t_avr_params **tbl) {
    DWORD SignCode;
    /* читаем сигнатуру модуля */
    INT res = LTRAVR_GetSignCode(module, &SignCode);
    if (res == LTRAVR_OK) {
        t_avr_params *fnd;
        unsigned i;
        /* ищем в таблице параметров поддерживаем контроллеров AVR запись
           с прочитанной сигнатурой */
        for (i=0, fnd=NULL; (fnd==NULL)
             && (i < sizeof(f_avr_param_tbl)/sizeof(f_avr_param_tbl[0])); i++) {
            if (f_avr_param_tbl[i].sign == SignCode) {
                fnd = &f_avr_param_tbl[i];
            }
        }

        if (fnd==NULL) {
            res = LTRAVR_ERROR_UNKNOWN_SIGN;
        } else if (tbl!=NULL) {
            *tbl = fnd;
        }
    }
    return res;
}

static INT f_avr_spi_cmd_exchange(TLTRAVR *module, WORD *xbuf, DWORD xsize) {
    DWORD *buf= malloc (xsize * sizeof(buf[0]));
    INT res = LTR_OK;

    if (buf==NULL) {
        res = LTR_ERROR_MEMORY_ALLOC;
    } else {
        DWORD i;
        /* Из переданного массива команд по SPI формируем массив
           инструкций PROGR в формате LTR для осуществления указанных
           циклов по SPI */
        for(i=0; i<xsize; i++)
            buf[i]=LTR010CMD_PROGR|(xbuf[i]<<16);
        res = ltr_module_send_cmd(&module->ltr, buf, xsize);
        if (res==LTR_OK) {
            if (module->low_speed_flag==FALSE) {
                /* в нормальном режиме получаем ответ */
                res = ltr_module_recv_cmd_resp_tout(&module->ltr, buf, xsize,
                                                    LTR_MODULE_CMD_RECV_TIMEOUT + xsize/4);
                if (res==LTR_OK) {
                    /* из принятых команд выделяем часть, соответствующую
                       данным, прочитанным по SPI интерфейсу */
                    for (i=0; i < xsize; i++)
                        xbuf[i]=buf[i]>>16;
                }
            } else {
                /* при медленной скорости не используем подтверждение */
                LTRAPI_SLEEP_MS(100);
            }
        }

        free(buf);
    }
    return res;
}


/**
  Функция для передачи в AVR команд программирования по SPI-интерфейсу
  */
LTRAVRAPI_DllExport(INT) LTRAVR_Exchange(TLTRAVR *module, WORD *xbuf, DWORD xsize) {
    INT res = module==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&module->ltr);
    if(res==LTR_OK) {
        /* комада авру "разрешить программирование" */
        WORD pg_en[] = {0xAC53, 0x0};
        res = f_avr_spi_cmd_exchange(module, pg_en, sizeof(pg_en)/sizeof(pg_en[0]));

        /* для низкой стандартного режима проверяем ответ на команду
            разрешения программирования (в третьем слове должно
            быть возвращено 0x53)*/
        if ((res==LTR_OK) && (module->low_speed_flag==FALSE) && (pg_en[1]!=0x5300)) {
            res = LTRAVR_ERROR_RECV_PRG_ENABLE_ACK;
        }

        if (res==LTR_OK) {
            res = f_avr_spi_cmd_exchange(module, xbuf, xsize);
        }
    }
    return res;
}


//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_Init(TLTRAVR *module) {
    INT res = module==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res==LTR_OK) {
        module->low_speed_flag=FALSE;
        res=LTR_Init(&module->ltr);
    }
    return res;
}


//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_Open(TLTRAVR *module, DWORD saddr, WORD sport, const CHAR *csn, WORD cc) {
    INT res, warning;
    DWORD flags = LTR_MOPEN_INFLAGS_DEF_SEC_STOP;

    res = ltr_module_open(&module->ltr, saddr, sport, csn, cc, 0, &flags, NULL, &warning);
    if (res==LTR_OK) {
        LTRAPI_SLEEP_MS(200);
    }

    return res==LTR_OK ? warning : res;
}
//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_Close(TLTRAVR *module) {
    DWORD cmds[] = {LTR010CMD_STOP,LTR010CMD_RESET,LTR010CMD_STOP};
    INT res, closeres;

    res = ltr_module_stop(&module->ltr, cmds, sizeof(cmds)/sizeof(cmds[0]), LTR010CMD_RESET, 0, 0, NULL);
    closeres = LTR_Close(&module->ltr);

    return res == LTR_OK ? closeres : res;
}
//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_ChipErase(TLTRAVR *module) {
    WORD xbuf[2]={0xAC80, 0xAC80};
    INT err = LTRAVR_Exchange(module, xbuf, 2);
    LTRAPI_SLEEP_MS(LTRAVR_TIME_WD_ERASE);
    return err;
}


//-----------------------------------------------------------------------------
//получить код сигнатуры в виде переменной DWORD с нулевым старшим байтом
LTRAVRAPI_DllExport(INT) LTRAVR_GetSignCode(TLTRAVR *module, DWORD *SignCode) {
    INT res;
    res = LTRAVR_ReadSignature(module, (BYTE*)SignCode);
    if (res!=LTRAVR_OK)
        return res;
    *SignCode &= 0xFFFFFF; //значимы только младшие 3 байта
    return res;
}





//-----------------------------------------------------------------------------
//получение размера памяти и ее страницы AVR 
LTRAVRAPI_DllExport(INT) LTRAVR_ReadFlashParam(TLTRAVR *module, INT *FlashSize, INT* PageSize) {
    t_avr_params *params;
    INT res = f_get_avr_param_tbl(module, &params);
    if (res==LTR_OK) {
        if (FlashSize!=NULL)
            *FlashSize = params->flast_size;
        if (PageSize!=NULL)
            *PageSize = params->page_size;
    }
    return res;
}
//-----------------------------------------------------------------------------

LTRAVRAPI_DllExport(INT) LTRAVR_ReadProgrammMemory(TLTRAVR *module, WORD *buf,
                                                   DWORD size, DWORD addr) {
    INT FlashSize;
    INT res;
    DWORD i;
    WORD *xbuf = NULL;


    res = LTRAVR_ReadFlashParam(module, &FlashSize, NULL);
    if (res==LTR_OK) {
        /* корректируем параметры, если они находятся в не пределах памяти.
           Вообще логичнее возыращать ошибку, но для обратной совместимости
           оставлено так как было */
        if (size > (DWORD)FlashSize)
            size = FlashSize;
        if (addr > (FlashSize-size))
            addr = FlashSize-size;

        xbuf = malloc(4*size*sizeof(xbuf[0]));
        if (xbuf==NULL)
            res = LTR_ERROR_MEMORY_ALLOC;
    }

    if (res==LTR_OK) {
        /* по каждому адресу читаем по два байта (сперва младший потом старший) */
        for(i=0; i<size; i++) {
            xbuf[4*i+0]=(0x2000|(addr>>8))&0xFFFF;
            xbuf[4*i+1]=(addr<<8)&0xFFFF;
            xbuf[4*i+2]=(0x2800|(addr>>8))&0xFFFF;
            xbuf[4*i+3]=(addr<<8)&0xFFFF;
            addr++;
        }
        res=LTRAVR_Exchange(module, xbuf, 4*size);
    }

    if(res==LTR_OK) {
        for(i=0; i<size; i++)
            buf[i]=(xbuf[4*i+1]&0xFF)|((xbuf[4*i+3]<<8) & 0xFF00);
    }

    free(xbuf);

    return res;
}

//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_WriteProgrammMemory(TLTRAVR *module, const WORD *buf,
                                                    DWORD size, DWORD addr) {
    return LTRAVR_WriteProgrammMemoryCb(module, buf, size, addr, NULL, NULL);
}


LTRAVRAPI_DllExport(INT) LTRAVR_WriteProgrammMemoryCb(TLTRAVR *module, const WORD *buf,
                                                      DWORD size, DWORD addr,
                                                      TLTRAVR_PROGR_CB cb, void *cb_data) {
    INT PageSize;
    INT FlashSize;
    INT res;
    WORD *xbuf = NULL;
    DWORD total_size = size;

    res = LTRAVR_ReadFlashParam(module, &FlashSize, &PageSize);
    if (res == LTR_OK) {
        if (size > (DWORD)FlashSize)
            size = FlashSize;
        if (addr > (FlashSize-size))
            addr = FlashSize-size;

        xbuf = malloc((4*PageSize+2)*sizeof(xbuf[0]));
        if (xbuf==NULL)
            res = LTR_ERROR_MEMORY_ALLOC;
    }

    while ((res==LTR_OK) && (size!=0)) {
        /* выбираем размер записи блока. Блок должен находится внутри одной
           страницы */
        WORD i;
        WORD sz = (WORD)(PageSize-(addr&(PageSize-1)));
        if (sz > size)
            sz = (WORD)size;

        for (i=0; i<sz; i++) {
            WORD _addr=(WORD)((addr+i)&(PageSize-1));
            WORD _data=buf[i];
            /* сперва выполняется запись младшего байта, затем старшего */
            xbuf[4*i+0]=0x4000;
            xbuf[4*i+1]=(_addr<<8)|((_data>>0)&0xFF);
            xbuf[4*i+2]=0x4800;
            xbuf[4*i+3]=(_addr<<8)|((_data>>8)&0xFF);

        }
        /* последней инструкцией идет запись страницы в AVR */
        xbuf[4*sz+0]=(0x4C00|(addr>>8)) & 0xFFFF;
        xbuf[4*sz+1]=((addr&0xE0)<<8);

        res=LTRAVR_Exchange(module, xbuf, 4*sz+2);
        if(res==LTR_OK) {
            buf+=sz;
            addr+=sz;
            size-=sz;
            /* после записи страницы необходимо выдержать паузу перед посылкой
             * следующей команды */
            LTRAPI_SLEEP_MS(LTRAVR_TIME_WD_FLASH);

            if (cb!=NULL) {
                cb(cb_data, module, total_size-size, total_size);
            }
        }
    }

    free(xbuf);

    return res;
}
//-----------------------------------------------------------------------------
//Чтение fuse
//для ATMEGA8515 - buf[0]= Fuse Low Byte, buf[1] - Fuse High Byte
//для ATMEGA128  - buf[0]= Fuse Low Byte, buf[1] - Fuse High Byte, buf[2] - Fuse Extended Byte
LTRAVRAPI_DllExport(INT) LTRAVR_ReadFuseBits(TLTRAVR *module, BYTE *buf) {
    t_avr_params *params;
    WORD xbuf[6]={0x5000, 0x0000, 0x5808, 0x0000, 0x5008, 0x0000};
    /* для определения количества байт с fuse bits читаем параметры AVR */
    INT res = f_get_avr_param_tbl(module, &params);
    if (res==LTR_OK) {
        INT i;
        /* каждый байт читается двумя инструкциями, результат в последнем байте
          ответа на вторую */
        res=LTRAVR_Exchange(module, xbuf, 2*params->fuse_bytes_cnt);
        for (i=0; i < params->fuse_bytes_cnt; i++)
            buf[i] = xbuf[2*i+1]&0xFF;
    }
    return res;
}

/* Функция аналогична LTRAVR_WriteFuseBits(), но позволяет явно указать размер
 * байт в массиве buf. Если size = 0, то действие функции аналогично LTRAVR_WriteFuseBits()
 * (идет попытка автоматически понять кол-во байт в buf), если size !=0, то
 * используется размер байт в buf из size.
 * Явное указание size необходимо в частности для записи при низкой скорости
 * для LTR114, т.к. мы не можем автоматически определить, что массив должен быть
 * из 3 байт и старший байт останется неизмененным */
LTRAVRAPI_DllExport(INT) LTRAVR_WriteFuseBitsEx(TLTRAVR *module, const BYTE *buf, INT size) {
    DWORD sbuf[] = {LTR010CMD_STOP, LTR010CMD_RESET};
    INT fuse_bytes=2;
    INT res = module==NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (res==LTR_OK) {
        if (module->low_speed_flag) {
            /* в режиме низкой скорости мы не можем ждать подтверждение, т.к. крейт ничего не шлет */
            res = ltr_module_send_cmd(&module->ltr, sbuf, sizeof(sbuf)/sizeof(sbuf[0]));
        } else {
            res = ltr_module_stop(&module->ltr, sbuf, sizeof(sbuf)/sizeof(sbuf[0]), LTR010CMD_RESET,0, 0, NULL);
        }
    }

    if (res==LTR_OK) {
        LTRAPI_SLEEP_MS(100);

        if (size != 0) {
            fuse_bytes = size;
        } else if (!module->low_speed_flag) {
            /* для штатного режима можем спокойно определить что за AVR по
               сигнатуре */
            t_avr_params *params;
            res = f_get_avr_param_tbl(module, &params);
            if (res==LTR_OK)
                fuse_bytes = params->fuse_bytes_cnt;
        }
    }


    if (res==LTR_OK) {
        static WORD f_avr_wr_fuse_cmds[] = {0xACA0, 0xACA8, 0xACA4};
        INT i;

        for (i=0; (res==LTR_OK) && (i < fuse_bytes); i++) {
            WORD xbuf[2];
            xbuf[0] = f_avr_wr_fuse_cmds[i];
            xbuf[1] = buf[i];
            res=LTRAVR_Exchange(module, xbuf, sizeof(xbuf)/sizeof(xbuf[0]));
        }
    }

    return res;

}

//-----------------------------------------------------------------------------
/*Запись fuse (так как при первой записи нет возможности считать сигнатуру из-за того, что клок AVR возможно не установлен
  * то в медленном режиме для всех AVR пишется 3 байта fuse (для ATMEGA8515 третий - пустая команда)
  * для ATMEGA8515 - buf[0]= Fuse Low Byte, buf[1] - Fuse High Byte
  * для ATMEGA128 - buf[0]= Fuse Low Byte, buf[1] - Fuse High Byte, buf[2] - Fuse Extended Byte
  *
  * Данная функция не работает корректно в низкой скорсти для LTR114, т.к. мы не
  * можем определить, что это ATMEGA128 и решить о размере fuse-битов в buf.
  * Для этого случая необходимо использовать LTRAVR_WriteFuseBitsEx() с явным
  * указанием размера */
LTRAVRAPI_DllExport(INT) LTRAVR_WriteFuseBits(TLTRAVR *module, const BYTE *buf) {    
    return LTRAVR_WriteFuseBitsEx(module, buf, 0);
}
//-----------------------------------------------------------------------------
/* Чтение 4-х байт сигнатуры, по которым можно определить, с каким контроллером
   AVR идет работа. Функция оставлена для совместимости со старой библиотекой.
   Удобнее использовать LTRAVR_ReadSignCode, которая возвращает код в виде слова */
LTRAVRAPI_DllExport(INT) LTRAVR_ReadSignature(TLTRAVR *module, BYTE *buf) {
    WORD xbuf[8]={0x3000, 0x0000, 0x3000, 0x0100, 0x3000, 0x0200, 0x3000, 0x0300};
    INT res = module == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res==LTR_OK)
        res=LTRAVR_Exchange(module, xbuf, 8);
    if(res==LTR_OK) {
        int i;
        for (i=0; i<4; i++)
            buf[i]=xbuf[2*i+1]&0xFF;
    }
    return res;
}
//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(INT) LTRAVR_SetSpeedFlag(TLTRAVR *module, BOOL sflag) {
    INT res = module == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&module->ltr);
    if (res==LTR_OK) {
        TLTR010 control;
        LTR010_Init(&control);
        res=LTR010_Open(&control, module->ltr.saddr, module->ltr.sport, module->ltr.csn);
        if (res == LTR_OK) {
            WORD buf=0;
            res = LTR010_PutArray(&control, (BYTE*)&buf, 2, SEL_AVR_DM|0x8006);
            if(res==LTR_OK) {
                buf= sflag ? (1<<1) : (0<<1);
                res=LTR010_PutArray(&control, (BYTE*)&buf, 1, SEL_AVR_DM|0x8002);
                if(res==LTR_OK)
                    module->low_speed_flag=sflag;
            }
            LTR010_Close(&control);
        }
    }
    return res;
}
//-----------------------------------------------------------------------------
LTRAVRAPI_DllExport(LPCSTR) LTRAVR_GetErrorString(INT error) {
    size_t i;
    for (i = 0; i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0]); i++) {
        if (f_err_tbl[i].code == error)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(error);
}
//-----------------------------------------------------------------------------

#ifdef _WIN32
int WINAPI DllEntryPoint(HINSTANCE hinst,  unsigned long reason, void* lpReserved) {
    return 1;
}
#endif
