/* Copyright 2023 Dual Tachyon
 * https://github.com/DualTachyon
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 */

#include <string.h>
#include "font.h"
#if !defined(ENABLE_OVERLAY)

#include "ARMCM0.h"

#endif
#ifdef ENABLE_FMRADIO
#include "app/fm.h"
#endif

#include "app/uart.h"
#include "board.h"
#include "bsp/dp32g030/dma.h"
#include "bsp/dp32g030/gpio.h"
#include "driver/aes.h"
#include "driver/backlight.h"
#include "driver/bk4819.h"
#include "driver/crc.h"
#include "driver/eeprom.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "functions.h"
#include "misc.h"
#include "settings.h"

#if defined(ENABLE_OVERLAY)
#include "sram-overlay.h"
#endif

#include "version.h"

#define DMA_INDEX(x, y) (((x) + (y)) % sizeof(UART_DMA_Buffer))

typedef struct {
    uint16_t ID;
    uint16_t Size;
} Header_t;
#ifdef ENABLE_DOCK
typedef struct {
		Header_t Header;
		uint8_t Key;
		uint8_t Padding;
		uint32_t Timestamp;
	} CMD_0801_t; // simulate key press
#endif
typedef struct {
    uint8_t Padding[2];
    uint16_t ID;
} Footer_t;

typedef struct {
    Header_t Header;
    uint32_t Timestamp;
} CMD_0514_t;

typedef struct {
    Header_t Header;
    struct {
        char Version[16];
        bool bHasCustomAesKey;
        bool bIsInLockScreen;
        uint8_t Padding[2];
        uint32_t Challenge[4];
    } Data;
} REPLY_0514_t;

typedef struct {
    Header_t Header;
    uint16_t Offset;
    uint8_t Size;
    uint8_t Padding;
    uint32_t Timestamp;
} CMD_051B_t;
typedef struct {
    Header_t Header;
    uint16_t Offset;
    uint8_t Size;
    uint8_t Padding;
    uint32_t Timestamp;
    uint8_t ADD[2];

} CMD_052B_t;
typedef struct {
    Header_t Header;
    struct {
        uint16_t Offset;
        uint8_t Size;
        uint8_t Padding;
        uint8_t Data[128];
    } Data;
} REPLY_051B_t;

typedef struct {
    Header_t Header;
    uint16_t Offset;
    uint8_t Size;
    bool bAllowPassword;
    uint32_t Timestamp;
    uint8_t Data[0];
} CMD_051D_t;

typedef struct {
    Header_t Header;
    struct {
        uint16_t Offset;
    } Data;
} REPLY_051D_t;

typedef struct {
    Header_t Header;
    struct {
        uint16_t RSSI;
        uint8_t ExNoiseIndicator;
        uint8_t GlitchIndicator;
    } Data;
} REPLY_0527_t;

typedef struct {
    Header_t Header;
    struct {
        uint16_t Voltage;
        uint16_t Current;
    } Data;
} REPLY_0529_t;

typedef struct {
    Header_t Header;
    uint32_t Response[4];
} CMD_052D_t;
#ifdef ENABLE_BLOCK
typedef struct {
    Header_t Header;
    struct {
        bool bIsLocked;
        uint8_t Padding[3];
    } Data;
} REPLY_052D_t;
#endif
typedef struct {
    Header_t Header;
    uint32_t Timestamp;
} CMD_052F_t;

static const uint8_t Obfuscation[16] =
        {
                0x16, 0x6C, 0x14, 0xE6, 0x2E, 0x91, 0x0D, 0x40, 0x21, 0x35, 0xD5, 0x40, 0x13, 0x03, 0xE9, 0x80
        };

static union {
    uint8_t Buffer[256];
    struct {
        Header_t Header;
        uint8_t Data[252];
    };
} UART_Command;

static uint32_t Timestamp;
static uint16_t gUART_WriteIndex;
static bool bIsEncrypted = true;

static void SendReply(void *pReply, uint16_t Size) {
    Header_t Header;
    Footer_t Footer;

    if (bIsEncrypted) {
        uint8_t *pBytes = (uint8_t *) pReply;
        unsigned int i;
        for (i = 0; i < Size; i++)
            pBytes[i] ^= Obfuscation[i % 16];
    }

    Header.ID = 0xCDAB;
    Header.Size = Size;
    UART_Send(&Header, sizeof(Header));
    UART_Send(pReply, Size);

    if (bIsEncrypted) {
        Footer.Padding[0] = Obfuscation[(Size + 0) % 16] ^ 0xFF;
        Footer.Padding[1] = Obfuscation[(Size + 1) % 16] ^ 0xFF;
    } else {
        Footer.Padding[0] = 0xFF;
        Footer.Padding[1] = 0xFF;
    }
    Footer.ID = 0xBADC;

    UART_Send(&Footer, sizeof(Footer));
}

static void SendVersion(void) {
    REPLY_0514_t Reply;

    Reply.Header.ID = 0x0515;
    Reply.Header.Size = sizeof(Reply.Data);
    strcpy(Reply.Data.Version, Version);
    Reply.Data.bHasCustomAesKey = bHasCustomAesKey;
    Reply.Data.bIsInLockScreen = bIsInLockScreen;
    Reply.Data.Challenge[0] = gChallenge[0];
    Reply.Data.Challenge[1] = gChallenge[1];
    Reply.Data.Challenge[2] = gChallenge[2];
    Reply.Data.Challenge[3] = gChallenge[3];

    SendReply(&Reply, sizeof(Reply));
}

static bool IsBadChallenge(const uint32_t *pKey, const uint32_t *pIn, const uint32_t *pResponse) {
    unsigned int i;
    uint32_t IV[4];

    IV[0] = 0;
    IV[1] = 0;
    IV[2] = 0;
    IV[3] = 0;

    AES_Encrypt(pKey, IV, pIn, IV, true);

    for (i = 0; i < 4; i++)
        if (IV[i] != pResponse[i])
            return true;

    return false;
}

static void CMD_0514(const uint8_t *pBuffer) {
    const CMD_0514_t *pCmd = (const CMD_0514_t *) pBuffer;

    Timestamp = pCmd->Timestamp;

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif

    gSerialConfigCountDown_500ms = 12; // 6 sec

    // turn the LCD backlight off
    BACKLIGHT_TurnOff();

    SendVersion();
}

static void CMD_051B(const uint8_t *pBuffer) {
    const CMD_051B_t *pCmd = (const CMD_051B_t *) pBuffer;
    REPLY_051B_t Reply;
#ifdef ENABLE_BLOCK

    bool bLocked = false;
#endif
    if (pCmd->Timestamp != Timestamp)
        return;

    gSerialConfigCountDown_500ms = 12; // 6 sec

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif

    memset(&Reply, 0, sizeof(Reply));
    Reply.Header.ID = 0x051C;
    Reply.Header.Size = pCmd->Size + 4;
    Reply.Data.Offset = pCmd->Offset;
    Reply.Data.Size = pCmd->Size;
#ifdef ENABLE_BLOCK

    if (bHasCustomAesKey)
        bLocked = gIsLocked;

    if (!bLocked)
#endif
        EEPROM_ReadBuffer(pCmd->Offset, Reply.Data.Data, pCmd->Size);

    SendReply(&Reply, pCmd->Size + 8);
}

static void CMD_051D(const uint8_t *pBuffer) {
    const CMD_051D_t *pCmd = (const CMD_051D_t *) pBuffer;
    REPLY_051D_t Reply;
    bool bReloadEeprom;
#ifdef ENABLE_BLOCK

    bool bIsLocked;
#endif
    if (pCmd->Timestamp != Timestamp)
        return;

    gSerialConfigCountDown_500ms = 12; // 6 sec

    bReloadEeprom = false;

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif

    Reply.Header.ID = 0x051E;
    Reply.Header.Size = sizeof(Reply.Data);
    Reply.Data.Offset = pCmd->Offset;
#ifdef ENABLE_BLOCK

    bIsLocked = bHasCustomAesKey ? gIsLocked : bHasCustomAesKey;
#endif
#ifdef ENABLE_BLOCK

    if (!bIsLocked) {
#endif

    unsigned int i;
        for (i = 0; i < (pCmd->Size / 8); i++) {
            const uint16_t Offset = pCmd->Offset + (i * 8U);
#ifdef ENABLE_BLOCK

            if (Offset >= 0x0F30 && Offset < 0x0F40)
                if (!gIsLocked)
#endif

            bReloadEeprom = true;
#ifdef ENABLE_BLOCK

            if ((Offset < 0x0E98 || Offset >= 0x0EA0) || !bIsInLockScreen || pCmd->bAllowPassword)
#endif

            EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U],8);

        }

        if (bReloadEeprom)
            SETTINGS_InitEEPROM();
#ifdef ENABLE_BLOCK

    }
#endif

    SendReply(&Reply, sizeof(Reply));
}

static void CMD_0527(void) {
    REPLY_0527_t Reply;

    Reply.Header.ID = 0x0528;
    Reply.Header.Size = sizeof(Reply.Data);
    Reply.Data.RSSI = BK4819_ReadRegister(BK4819_REG_67) & 0x01FF;
    Reply.Data.ExNoiseIndicator = BK4819_ReadRegister(BK4819_REG_65) & 0x007F;
    Reply.Data.GlitchIndicator = BK4819_ReadRegister(BK4819_REG_63);

    SendReply(&Reply, sizeof(Reply));
}

static void CMD_0529(void) {
    REPLY_0529_t Reply;

    Reply.Header.ID = 0x52A;
    Reply.Header.Size = sizeof(Reply.Data);

    // Original doesn't actually send current!
    BOARD_ADC_GetBatteryInfo(&Reply.Data.Voltage, &Reply.Data.Current);

    SendReply(&Reply, sizeof(Reply));
}
#ifdef ENABLE_BLOCK
static void CMD_052D(const uint8_t *pBuffer) {
    const CMD_052D_t *pCmd = (const CMD_052D_t *) pBuffer;
    REPLY_052D_t Reply;
    bool bIsLocked;

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif
    Reply.Header.ID = 0x052E;
    Reply.Header.Size = sizeof(Reply.Data);

    bIsLocked = bHasCustomAesKey;

    if (!bIsLocked)
        bIsLocked = IsBadChallenge(gCustomAesKey, gChallenge, pCmd->Response);

    if (!bIsLocked) {
        bIsLocked = IsBadChallenge(gDefaultAesKey, gChallenge, pCmd->Response);
        if (bIsLocked)
            gTryCount++;
    }

    if (gTryCount < 3) {
        if (!bIsLocked)
            gTryCount = 0;
    } else {
        gTryCount = 3;
        bIsLocked = true;
    }

    gIsLocked = bIsLocked;
    Reply.Data.bIsLocked = bIsLocked;

    SendReply(&Reply, sizeof(Reply));
}
#endif
// session init, sends back version info and state
// timestamp is a session id really
// this command also disables dual watch, crossband,
// DTMF side tones, freq reverse, PTT ID, DTMF decoding, frequency offset
// exits power save, sets main VFO to upper,
static void CMD_052F(const uint8_t *pBuffer) {
    const CMD_052F_t *pCmd = (const CMD_052F_t *) pBuffer;

    gEeprom.DUAL_WATCH = DUAL_WATCH_OFF;
    gEeprom.CROSS_BAND_RX_TX = CROSS_BAND_OFF;
    gEeprom.RX_VFO = 0;
    gEeprom.DTMF_SIDE_TONE = false;
    gEeprom.VfoInfo[0].FrequencyReverse = false;
    gEeprom.VfoInfo[0].pRX = &gEeprom.VfoInfo[0].freq_config_RX;
    gEeprom.VfoInfo[0].pTX = &gEeprom.VfoInfo[0].freq_config_TX;
    gEeprom.VfoInfo[0].TX_OFFSET_FREQUENCY_DIRECTION = TX_OFFSET_FREQUENCY_DIRECTION_OFF;
    gEeprom.VfoInfo[0].DTMF_PTT_ID_TX_MODE = PTT_ID_OFF;
#ifdef ENABLE_DTMF_CALLING
    gEeprom.VfoInfo[0].DTMF_DECODING_ENABLE          = false;
#endif

#ifdef ENABLE_NOAA
    gIsNoaaMode = false;
#endif

    if (gCurrentFunction == FUNCTION_POWER_SAVE)
        FUNCTION_Select(FUNCTION_FOREGROUND);

    gSerialConfigCountDown_500ms = 12; // 6 sec

    Timestamp = pCmd->Timestamp;

    // turn the LCD backlight off
    BACKLIGHT_TurnOff();

    SendVersion();
}

#ifdef ENABLE_UART_RW_BK_REGS
static void CMD_0601_ReadBK4819Reg(const uint8_t *pBuffer)
{
	typedef struct  __attribute__((__packed__)) {
		Header_t header;
		uint8_t reg;
	} CMD_0601_t;

	CMD_0601_t *cmd = (CMD_0601_t*) pBuffer;

	struct  __attribute__((__packed__)) {
		Header_t header;
	struct __attribute__((__packed__)) {
			uint8_t reg;
			uint16_t value;
		} data;
	} reply;

	reply.header.ID = 0x0601;
	reply.header.Size = sizeof(reply.data);
	reply.data.reg = cmd->reg;
	reply.data.value = BK4819_ReadRegister(cmd->reg);
	SendReply(&reply, sizeof(reply));
}

static void CMD_0602_WriteBK4819Reg(const uint8_t *pBuffer)
{
	typedef struct  __attribute__((__packed__)) {
		Header_t header;
		uint8_t reg;
		uint16_t value;
	} CMD_0602_t;

	CMD_0602_t *cmd = (CMD_0602_t*) pBuffer;
	BK4819_WriteRegister(cmd->reg, cmd->value);
}
#endif
bool UART_IsCommandAvailable(void) {
    uint16_t Index;
    uint16_t TailIndex;
    uint16_t Size;
    uint16_t CRC;
    uint16_t CommandLength;
    uint16_t DmaLength = DMA_CH0->ST & 0xFFFU;


    while (1) {
        if (gUART_WriteIndex == DmaLength)
            return false;

        while (gUART_WriteIndex != DmaLength && UART_DMA_Buffer[gUART_WriteIndex] != 0xABU)
            gUART_WriteIndex = DMA_INDEX(gUART_WriteIndex, 1);

        if (gUART_WriteIndex == DmaLength)
            return false;

        if (gUART_WriteIndex < DmaLength)
            CommandLength = DmaLength - gUART_WriteIndex;
        else
            CommandLength = (DmaLength + sizeof(UART_DMA_Buffer)) - gUART_WriteIndex;

        if (CommandLength < 8)
            return 0;

        if (UART_DMA_Buffer[DMA_INDEX(gUART_WriteIndex, 1)] == 0xCD)
            break;

        gUART_WriteIndex = DMA_INDEX(gUART_WriteIndex, 1);
    }

    Index = DMA_INDEX(gUART_WriteIndex, 2);
    Size = (UART_DMA_Buffer[DMA_INDEX(Index, 1)] << 8) | UART_DMA_Buffer[Index];

    if ((Size + 8u) > sizeof(UART_DMA_Buffer)) {
        gUART_WriteIndex = DmaLength;
        return false;
    }

    if (CommandLength < (Size + 8))
        return false;

    Index = DMA_INDEX(Index, 2);
    TailIndex = DMA_INDEX(Index, Size + 2);

    if (UART_DMA_Buffer[TailIndex] != 0xDC || UART_DMA_Buffer[DMA_INDEX(TailIndex, 1)] != 0xBA) {
        gUART_WriteIndex = DmaLength;
        return false;
    }

    if (TailIndex < Index) {
        const uint16_t ChunkSize = sizeof(UART_DMA_Buffer) - Index;
        memcpy(UART_Command.Buffer, UART_DMA_Buffer + Index, ChunkSize);
        memcpy(UART_Command.Buffer + ChunkSize, UART_DMA_Buffer, TailIndex);
    } else
        memcpy(UART_Command.Buffer, UART_DMA_Buffer + Index, TailIndex - Index);

    TailIndex = DMA_INDEX(TailIndex, 2);
    if (TailIndex < gUART_WriteIndex) {
        memset(UART_DMA_Buffer + gUART_WriteIndex, 0, sizeof(UART_DMA_Buffer) - gUART_WriteIndex);
        memset(UART_DMA_Buffer, 0, TailIndex);
    } else
        memset(UART_DMA_Buffer + gUART_WriteIndex, 0, TailIndex - gUART_WriteIndex);

    gUART_WriteIndex = TailIndex;

    if (UART_Command.Header.ID == 0x0514)
        bIsEncrypted = false;

    if (UART_Command.Header.ID == 0x6902)
        bIsEncrypted = true;

    if (bIsEncrypted) {
        unsigned int i;
        for (i = 0; i < (Size + 2u); i++)
            UART_Command.Buffer[i] ^= Obfuscation[i % 16];
    }

    CRC = UART_Command.Buffer[Size] | (UART_Command.Buffer[Size + 1] << 8);
//    char b[2]="3K";
//     uint8_t tmp[Size];
//    for (int i = 0; i < Size; i++) {
//        tmp[i]=UART_Command.Buffer[i];
//    }
    bool judge = (CRC_Calculate1(UART_Command.Buffer, Size) != CRC) ? false : true;

    return judge;
}

#if ENABLE_CHINESE_FULL==4

static void CMD_052B(const uint8_t *pBuffer)//read
{
    const CMD_052B_t *pCmd = (const CMD_052B_t *) pBuffer;
    REPLY_051B_t Reply;


    if (pCmd->Timestamp != Timestamp)
        return;

    gSerialConfigCountDown_500ms = 12; // 6 sec

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif

    memset(&Reply, 0, sizeof(Reply));
    Reply.Header.ID = 0x051C;
    Reply.Header.Size = pCmd->Size + 4;
    Reply.Data.Offset = pCmd->Offset;

    Reply.Data.Size = pCmd->Size;


        EEPROM_ReadBuffer(((pCmd->Offset) << 16) + ((pCmd->ADD[1]) << 8) + (pCmd->ADD[0]), Reply.Data.Data, pCmd->Size);

    SendReply(&Reply, pCmd->Size + 8);
}

static void CMD_0538(const uint8_t *pBuffer)//write
{
    const CMD_051D_t *pCmd = (const CMD_051D_t *) pBuffer;
    REPLY_051D_t Reply;
    bool bReloadEeprom;
    bool bIsLocked;

    if (pCmd->Timestamp != Timestamp)
        return;

    gSerialConfigCountDown_500ms = 12; // 6 sec

    bReloadEeprom = false;

#ifdef ENABLE_FMRADIO
    gFmRadioCountdown_500ms = fm_radio_countdown_500ms;
#endif

    Reply.Header.ID = 0x051E;
    Reply.Header.Size = sizeof(Reply.Data);
    Reply.Data.Offset = pCmd->Offset;

    bIsLocked = bHasCustomAesKey ? gIsLocked : bHasCustomAesKey;
    int add=((pCmd->Size) - 2)%8;
    if (!bIsLocked) {
        for ( int i = 0; i < ((pCmd->Size) - 2) / 8+(add==0?0:1); i++) {
            const uint32_t Offset = ((pCmd->Offset) << 16) + ((pCmd->Data[1]) << 8) + (pCmd->Data[0]) + (i * 8U);

            if (Offset >= 0x0F30 && Offset < 0x0F40)
                if (!gIsLocked)
                    bReloadEeprom = true;

            if ((Offset > 0xffff || Offset < 0x0E98 || (Offset <= 0xffff && Offset >= 0x0EA0)) || !bIsInLockScreen ||
                pCmd->bAllowPassword) {
                if(add&&i==((pCmd->Size) - 2) / 8+(add==0?0:1)-1)
                    EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U + 2], add);
                else
                EEPROM_WriteBuffer(Offset, &pCmd->Data[i * 8U + 2], 8);
//                uint8_t back[8];
//                EEPROM_ReadBuffer(Offset, back, 8);
//                UART_Send(back, 8);
            }
        }


        if (bReloadEeprom)
            SETTINGS_InitEEPROM();
    }

    SendReply(&Reply, sizeof(Reply));
}
#endif

#ifdef ENABLE_DOCK
static void CMD_0801(const uint8_t *pBuffer)
	{
		const CMD_0801_t *pCmd = (const CMD_0801_t *)pBuffer;
		const uint8_t key = pCmd->Key & 0x1f;
		const bool click = pCmd->Key & 32;
		if(key != KEY_INVALID)
		{
			gSimulateKey = key;
			gDebounceDefeat = 0;
		}
		gSimulateHold = click ? KEY_INVALID : key;
	}
#endif
void UART_HandleCommand(void) {
    switch (UART_Command.Header.ID) {
#if ENABLE_CHINESE_FULL==4
        case 0x052B://read
            CMD_052B(UART_Command.Buffer);
            break;
        case 0x0538://write
            CMD_0538(UART_Command.Buffer);
            break;
#endif
#ifdef ENABLE_DOCK
        case 0x0801:
            CMD_0801(UART_Command.Buffer);
            break;

#endif
        case 0x0514:
            CMD_0514(UART_Command.Buffer);
            break;

        case 0x051B:
            CMD_051B(UART_Command.Buffer);
            break;

        case 0x051D:
            CMD_051D(UART_Command.Buffer);
            break;



        case 0x0527:
            CMD_0527();
            break;

        case 0x0529:
            CMD_0529();
            break;
#ifdef ENABLE_BLOCK

        case 0x052D:
            CMD_052D(UART_Command.Buffer);
            break;
#endif
        case 0x052F:
            CMD_052F(UART_Command.Buffer);
            break;

        case 0x05DD:
#if defined(ENABLE_OVERLAY)
            overlay_FLASH_RebootToBootloader();
#else
            NVIC_SystemReset();
#endif
            break;
#ifdef ENABLE_UART_RW_BK_REGS
            case 0x0601:
			CMD_0601_ReadBK4819Reg(UART_Command.Buffer);
			break;

		case 0x0602:
			CMD_0602_WriteBK4819Reg(UART_Command.Buffer);
			break;
#endif
    }
}