#include "ltr22api.h"
#include "ltrmodule.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#define LTR22_FIRMWARE_SIZE         8192
#define LTR22_CALIBR_SIZE           4800
#define LTR22_DESCR_ADDR            (LTR22_FIRMWARE_SIZE-LTR22_DESCR_SIZE)
#define LTR22_CALIBR_ADDR           (LTR22_DESCR_ADDR-LTR22_CALIBR_SIZE)
#define LTR22_DESCR_SIZE             128

#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

#define ATMEGA8515_FLASH_SIZE  (4*1024)
#define ATMEGA8515_PAGE_SIZE   (32)
#define ATMEGA8515_EEPROM_SIZE (512)

static INT f_reset_module(TLTR22 *module) {
    DWORD Command = LTR010CMD_RESET;
    INT res = ltr_module_stop(&module->Channel, &Command, 1, Command, 0, 0, NULL);
    if (res == LTR_OK) {
        /* ждем пока АЦП настроится, иначе данные отсылаемые в модуль пропадают */
        LTRAPI_SLEEP_MS(240);
    } else {
        printf("reset error!\n");
    }
    return res;
}

static INT f_stop_module(TLTR22 *module) {
    DWORD Command = LTR010CMD_STOP;
    return ltr_module_send_cmd(&module->Channel, &Command, 1);
}

static int f_reconnect_module(TLTR22 *module) {
    int res=f_stop_module(module);;
    if (res==LTR_OK) {
        res=f_reset_module(module);
    }
    if (res==LTR_OK) {
        res=f_stop_module(module);
    }
    if (res==LTR_OK) {
        res=f_stop_module(module);
    }
    return res;
}





static INT f_avr_spi_cmd_exchange(TLTR *channel, 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(channel, buf, xsize);
        if (res==LTR_OK) {
            /* в нормальном режиме получаем ответ */
            res = ltr_module_recv_cmd_resp_tout(channel, 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 {
                printf("exchange err!!\n");
            }
        }

        free(buf);
    }
    return res;
}

static INT  f_avr_exchange(TLTR *channel, WORD *xbuf, DWORD xsize) {
    /* комада авру "разрешить программирование" */
    WORD pg_en[] = {0xAC53, 0x0};
    INT res;

    res = f_avr_spi_cmd_exchange(channel, pg_en, sizeof(pg_en)/sizeof(pg_en[0]));

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

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



static INT  f_avr_chip_erase(TLTR *channel) {
    WORD xbuf[2]={0xAC80, 0xAC80};
    INT err = f_avr_exchange(channel, xbuf, 2);
    LTRAPI_SLEEP_MS(LTRAVR_TIME_WD_ERASE);
    return err;
}


static INT f_avr_read_programm_memory(TLTR *channel, WORD *buf, DWORD size, DWORD addr) {
    INT res = LTR_OK;
    DWORD i;
    WORD *xbuf = NULL;


    /* корректируем параметры, если они находятся в не пределах памяти.
       Вообще логичнее возыращать ошибку, но для обратной совместимости
       оставлено так как было */
    if (size > ATMEGA8515_FLASH_SIZE)
        size = ATMEGA8515_FLASH_SIZE;
    if (addr > (ATMEGA8515_FLASH_SIZE-size))
        addr = ATMEGA8515_FLASH_SIZE-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=f_avr_exchange(channel, 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;
}


static INT f_avr_write_programm_memory(TLTR *channel, const WORD *buf,
                                       DWORD size, DWORD addr) {
    INT res = LTR_OK;
    WORD *xbuf = NULL;



    if (size > (DWORD)ATMEGA8515_FLASH_SIZE)
        size = ATMEGA8515_FLASH_SIZE;
    if (addr > (ATMEGA8515_FLASH_SIZE-size))
        addr = ATMEGA8515_FLASH_SIZE-size;

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

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

        for (i=0; i<sz; i++) {
            WORD _addr=(WORD)((addr+i)&(ATMEGA8515_PAGE_SIZE-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 = f_avr_exchange(channel, xbuf, 4*sz+2);
        if(res==LTR_OK) {
            buf+=sz;
            addr+=sz;
            size-=sz;
            /* после записи страницы необходимо выдержать паузу перед посылкой
             * следующей команды */
            LTRAPI_SLEEP_MS(LTRAVR_TIME_WD_FLASH);
        }
    }

    free(xbuf);

    return res;
}


static INT WriteModuleMemory(TLTR22 *module, int BeginPageNumber, BYTE * Data, DWORD size, BOOLEAN MadeChipErase) {
    INT stopres, res = LTR_OK;

    res = f_reconnect_module(module);
    if ((res == LTR_OK) && MadeChipErase)
        res = f_avr_chip_erase(&module->Channel);
    if (res == LTR_OK) {
        res = f_avr_write_programm_memory(&module->Channel, (WORD*)Data, size/2, BeginPageNumber * ATMEGA8515_PAGE_SIZE);
    }
    stopres = f_reconnect_module(module);
    if (res == LTR_OK)
        res = stopres;
    return res;
}

/* Важно. В данной функции используются страницы по 256 слов (512 байт), а не по 32 слова, как
 * при записи и как в памяти AVR. Почему так сделано не понятно, но необходимо для обратной
 * совместимости */
static int ReadModuleMemory(TLTR22 *module, int BeginPageNumber, BYTE * Data, DWORD Pages) {
    INT stopres, res = LTR_OK;

    res = f_reconnect_module(module);
    if (res == LTR_OK) {
        res = f_avr_read_programm_memory(&module->Channel, (WORD*)Data,
                                         Pages * 256,
                                         BeginPageNumber * 256);
    }
    stopres = f_reconnect_module(module);
    if (res == LTR_OK)
        res = stopres;
    return res;
}


LTR22API_DllExport(INT) LTR22_ReadAVRFirmware(TLTR22 *module, BYTE *Data, DWORD size, DWORD BeginPage, DWORD PageNumbers) {
    int res = (size < LTR22_FIRMWARE_SIZE) ? LTR_ERROR_PARAMETERS : LTR22_IsOpened(module);
    if (res==LTR_OK)
        res = ReadModuleMemory(module, BeginPage,Data, PageNumbers);
    return res;
}


/*
-----------------------------------------------------------------------------------
Запись прошивки с параметрами калибровки и конфигурации модуля из
структуры модуля
*/
LTR22API_DllExport(INT) LTR22_WriteAVRFirmvare(TLTR22 *module, BYTE *Data, DWORD size,
                                               BOOLEAN WriteCalibrAndDescription, BOOLEAN ProgrammAVR) {

    int res = (size < LTR22_FIRMWARE_SIZE) ? LTR_ERROR_PARAMETERS : LTR22_IsOpened(module);
    if (res == LTR_OK) {
        if (!WriteCalibrAndDescription) {
            // заполняем текущими значениями параметры модуля
            res = LTR22_GetModuleDescription(module);
            if (res == LTR_OK)
                res = LTR22_GetCalibrCoeffs(module);
        }
    }

    if(res == LTR_OK) {
        /// Заполняем прошивку информацией о модуле
        BYTE TempDescArray[LTR22_DESCR_SIZE];
        int DescriptionArrayCounter=0;
        DWORD SizeCopy=0;
        int i;
        DWORD Clock;
        WORD CRC;

        BYTE TempConfArray2[LTR22_CALIBR_SIZE];
        int CalibrArrayCounter=0;
        int ADCCount;


        // имя компании
        SizeCopy=sizeof(module->ModuleInfo.Description.CompanyName);
        memcpy(TempDescArray+DescriptionArrayCounter, module->ModuleInfo.Description.CompanyName, SizeCopy);
        DescriptionArrayCounter+=SizeCopy;
        // имя устройства
        SizeCopy=sizeof(module->ModuleInfo.Description.DeviceName);
        memcpy(TempDescArray+DescriptionArrayCounter,module->ModuleInfo.Description.DeviceName,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;
        // имя серийный номер
        SizeCopy=sizeof(module->ModuleInfo.Description.SerialNumber);
        memcpy(TempDescArray+DescriptionArrayCounter,module->ModuleInfo.Description.SerialNumber,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;
        // имя название CPU
        SizeCopy=sizeof(module->ModuleInfo.CPU.Name);
        memcpy(TempDescArray+DescriptionArrayCounter,module->ModuleInfo.CPU.Name,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;

        // CPU Clock
        Clock=(DWORD)module->ModuleInfo.CPU.ClockRate;
        SizeCopy=sizeof(Clock);
        memcpy(TempDescArray+DescriptionArrayCounter,(BYTE*)&Clock,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;

        // FirmwareVersion
        SizeCopy=sizeof(module->ModuleInfo.CPU.FirmwareVersion);
        memcpy(TempDescArray+DescriptionArrayCounter,(BYTE*)&module->ModuleInfo.CPU.FirmwareVersion,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;

        // DeviceRevision
        SizeCopy=sizeof(module->ModuleInfo.Description.Revision);
        TempDescArray[DescriptionArrayCounter]=module->ModuleInfo.Description.Revision;
        DescriptionArrayCounter+=SizeCopy;

        // Комментарий
        SizeCopy=53;
        memcpy(TempDescArray+DescriptionArrayCounter,module->ModuleInfo.Description.Comment,SizeCopy);
        DescriptionArrayCounter+=SizeCopy;

        // CRC
        CRC=0xffff;
        SizeCopy=sizeof(CRC);
        memcpy(TempDescArray+DescriptionArrayCounter,(BYTE*)&CRC,SizeCopy);

        // запись новых значений описания модуля
        for (i = 0; i < LTR22_DESCR_SIZE; i++) {
            Data[LTR22_DESCR_ADDR+i]=TempDescArray[i];
        }
        /// Заполняем прошивку калибровками

        // проверка на соответствие принятых значений тому, что должно быть
        // и преобразование их байтовый массив
        for (ADCCount = 0; ADCCount < LTR22_CHANNEL_CNT; ADCCount++) {
            int FreqCount;
            for (FreqCount = 0; FreqCount < LTR22_ADC_FREQ_CNT; FreqCount++) {
                int RangeCount;
                // занесение данных в калибровочную структуру
                for(RangeCount = 0; RangeCount < LTR22_RANGE_CNT; RangeCount++) {
                    BYTE *pbyte = (BYTE*)&module->ADCCalibration[ADCCount][FreqCount].FactoryCalibOffset[RangeCount];

                    for (i=0; i < 4; i++, CalibrArrayCounter++) {
                        TempConfArray2[CalibrArrayCounter]=*pbyte++;
                    }

                    pbyte=(BYTE*)&module->ADCCalibration[ADCCount][FreqCount].FactoryCalibScale[RangeCount];

                    for (i=0; i<4; i++,CalibrArrayCounter++) {
                        TempConfArray2[CalibrArrayCounter]=*pbyte++;
                    }
                }
            }
        }


        // запись новых значений описания модуля
        for (i=0; i < LTR22_CALIBR_SIZE; i++) {
            Data[LTR22_CALIBR_ADDR+i] = TempConfArray2[i];
        }

        if (ProgrammAVR) {
            res = WriteModuleMemory(module, 0, Data, LTR22_FIRMWARE_SIZE, TRUE);
            if (res == LTR_OK) {
                BYTE *rddata = malloc(LTR22_FIRMWARE_SIZE);
                if (rddata == NULL) {
                    res = LTR_ERROR_MEMORY_ALLOC;
                } else {
                    res = ReadModuleMemory(module, 0, rddata, 16);
                    if (res == LTR_OK) {
                        if (memcmp(Data, rddata, LTR22_FIRMWARE_SIZE) != 0) {
                            res = LTR22_ERROR_AVR_MEMORY_COMPARE;
                        }
                    }
                    free(rddata);
                }
            }

        }
    }

    if (res == LTR_OK) {
        LTR22_Close(module);
        res = LTR22_Open(module, module->Channel.saddr,module->Channel.sport,module->Channel.csn, module->Channel.cc);
    }
    return res;
}

/***************************************************************************************************
Тестовые функции
****************************************************************************************************/
/*
    Получение прошивки
*/


