/*
    Copyright 2016-2024 melonDS team

    This file is part of melonDS.

    melonDS is free software: you can redistribute it and/or modify it under
    the terms of the GNU General Public License as published by the Free
    Software Foundation, either version 3 of the License, or (at your option)
    any later version.

    melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with melonDS. If not, see http://www.gnu.org/licenses/.
*/

#include "SPI_Firmware.h"
#include "SPI.h"
#include "Platform.h"

namespace melonDS
{
using Platform::Log;
using Platform::LogLevel;

#include <string.h>

constexpr u8 BBINIT[0x69]
{
    0x03, 0x17, 0x40, 0x00, 0x1B, 0x6C, 0x48, 0x80, 0x38, 0x00, 0x35, 0x07, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC7, 0xBB, 0x01, 0x24, 0x7F,
    0x5A, 0x01, 0x3F, 0x01, 0x3F, 0x36, 0x1D, 0x00, 0x78, 0x35, 0x55, 0x12, 0x34, 0x1C, 0x00, 0x01,
    0x0E, 0x38, 0x03, 0x70, 0xC5, 0x2A, 0x0A, 0x08, 0x04, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFE,
    0xFE, 0xFE, 0xFE, 0xFC, 0xFC, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xF8, 0xF8, 0xF6, 0x00, 0x12, 0x14,
    0x12, 0x41, 0x23, 0x03, 0x04, 0x70, 0x35, 0x0E, 0x2C, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x12, 0x28, 0x1C
};

constexpr u8 RFINIT[0x29]
{
    0x31, 0x4C, 0x4F, 0x21, 0x00, 0x10, 0xB0, 0x08, 0xFA, 0x15, 0x26, 0xE6, 0xC1, 0x01, 0x0E, 0x50,
    0x05, 0x00, 0x6D, 0x12, 0x00, 0x00, 0x01, 0xFF, 0x0E, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x06,
    0x06, 0x00, 0x00, 0x00, 0x18, 0x00, 0x02, 0x00, 0x00
};

constexpr u8 CHANDATA[0x3C]
{
    0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x16,
    0x26, 0x1C, 0x1C, 0x1C, 0x1D, 0x1D, 0x1D, 0x1E, 0x1E, 0x1E, 0x1E, 0x1F, 0x1E, 0x1F, 0x18,
    0x01, 0x4B, 0x4B, 0x4B, 0x4B, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4D, 0x4D, 0x4D,
    0x02, 0x6C, 0x71, 0x76, 0x5B, 0x40, 0x45, 0x4A, 0x2F, 0x34, 0x39, 0x3E, 0x03, 0x08, 0x14
};

constexpr u8 DEFAULT_UNUSED3[6] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 };

Firmware::WifiAccessPoint::WifiAccessPoint()
{
    memset(Bytes, 0, sizeof(Bytes));
    Status = AccessPointStatus::NotConfigured;
    ConnectionConfigured = 0x01;
    UpdateChecksum();
}

Firmware::WifiAccessPoint::WifiAccessPoint(int consoletype)
{
    memset(Bytes, 0, sizeof(Bytes));
    strncpy(SSID, DEFAULT_SSID, sizeof(SSID));
    if (consoletype == 1) Mtu = 1400;
    Status = AccessPointStatus::Normal;
    ConnectionConfigured = 0x01;
    UpdateChecksum();
}

void Firmware::WifiAccessPoint::UpdateChecksum()
{
    Checksum = CRC16(Bytes, 0xFE, 0x0000);
}

Firmware::ExtendedWifiAccessPoint::ExtendedWifiAccessPoint()
{
    memset(Bytes, 0, sizeof(Bytes));
    Data.Base = WifiAccessPoint();

    UpdateChecksum();
}

void Firmware::ExtendedWifiAccessPoint::UpdateChecksum()
{
    Data.Base.UpdateChecksum();
    Data.ExtendedChecksum = CRC16(&Bytes[0x100], 0xFE, 0x0000);
}

Firmware::FirmwareHeader::FirmwareHeader(int consoletype)
{
    memset(Bytes, 0, sizeof(Bytes));
    if (consoletype == 1)
    {
        ConsoleType = FirmwareConsoleType::DSi;
        WifiVersion = WifiVersion::W015;
        WifiBoard = WifiBoard::W015;
        WifiFlash = 0x20;

        // these need to be zero (part of the stage2 firmware signature!)
        memset(&Bytes[0x22], 0, 8);
    }
    else
    {
        ConsoleType = FirmwareConsoleType::DSLite; // TODO: make configurable?
        WifiVersion = WifiVersion::W006;
    }
    Identifier = GENERATED_FIRMWARE_IDENTIFIER;


    // wifi calibration
    WifiConfigLength = 0x138;
    Unused1 = 0;
    memcpy(&Unused3, DEFAULT_UNUSED3, sizeof(DEFAULT_UNUSED3));
    MacAddr = DEFAULT_MAC;
    EnabledChannels = 0x3FFE;
    memset(&Unknown2, 0xFF, sizeof(Unknown2));
    RFChipType = RFChipType::Type3;
    RFBitsPerEntry = 0x94;
    RFEntries = 0x29;
    Unknown3 = 0x02;
    *(u16*)&Bytes[0x44] = 0x0002;
    *(u16*)&Bytes[0x46] = 0x0017;
    *(u16*)&Bytes[0x48] = 0x0026;
    *(u16*)&Bytes[0x4A] = 0x1818;
    *(u16*)&Bytes[0x4C] = 0x0048;
    *(u16*)&Bytes[0x4E] = 0x4840;
    *(u16*)&Bytes[0x50] = 0x0058;
    *(u16*)&Bytes[0x52] = 0x0042;
    *(u16*)&Bytes[0x54] = 0x0146;
    *(u16*)&Bytes[0x56] = 0x8064;
    *(u16*)&Bytes[0x58] = 0xE6E6;
    *(u16*)&Bytes[0x5A] = 0x2443;
    *(u16*)&Bytes[0x5C] = 0x000E;
    *(u16*)&Bytes[0x5E] = 0x0001;
    *(u16*)&Bytes[0x60] = 0x0001;
    *(u16*)&Bytes[0x62] = 0x0402;
    memcpy(&InitialBBValues, BBINIT, sizeof(BBINIT));
    Unused4 = 0;
    memcpy(&Type3Config.InitialRFValues, RFINIT, sizeof(RFINIT));
    Type3Config.BBIndicesPerChannel = 0x02;
    memcpy(&Bytes[0xF8], CHANDATA, sizeof(CHANDATA));
    memset(&Type3Config.Unused0, 0xFF, sizeof(Type3Config.Unused0));

    UpdateChecksum();
}


void Firmware::FirmwareHeader::UpdateChecksum()
{
    WifiConfigChecksum = CRC16(&Bytes[0x2C], WifiConfigLength, 0x0000);
}

Firmware::UserData::UserData()
{
    memset(Bytes, 0, sizeof(Bytes));
    Version = 5;
    BirthdayMonth = 1;
    BirthdayDay = 1;
    Settings = Language::English | BacklightLevel::Max; // NOLINT(*-suspicious-enum-usage)
    memcpy(Nickname, DEFAULT_USERNAME.data(), DEFAULT_USERNAME.size() * sizeof(std::u16string_view::value_type));
    NameLength = DEFAULT_USERNAME.size();
    Checksum = CRC16(Bytes, 0x70, 0xFFFF);
}

void Firmware::UserData::UpdateChecksum()
{
    Checksum = CRC16(Bytes, 0x70, 0xFFFF);
    if (ExtendedSettings.Unknown0 == 0x01)
    {
        ExtendedSettings.Checksum = CRC16(&Bytes[0x74], 0x8A, 0xFFFF);
    }
}

u32 Firmware::FixFirmwareLength(u32 originalLength)
{
    if (originalLength != 0x20000 && originalLength != 0x40000 && originalLength != 0x80000)
    {
        Log(LogLevel::Warn, "Bad firmware size %d, ", originalLength);

        // pick the nearest power-of-two length
        originalLength |= (originalLength >> 1);
        originalLength |= (originalLength >> 2);
        originalLength |= (originalLength >> 4);
        originalLength |= (originalLength >> 8);
        originalLength |= (originalLength >> 16);
        originalLength++;

        // ensure it's a sane length
        if (originalLength > 0x80000) originalLength = 0x80000;
        else if (originalLength < 0x20000) originalLength = 0x20000;

        Log(LogLevel::Debug, "assuming %d\n", originalLength);
    }
    return originalLength;
}

Firmware::Firmware(int consoletype)
{
    FirmwareBufferLength = DEFAULT_FIRMWARE_LENGTH;
    FirmwareBuffer = new u8[FirmwareBufferLength];
    memset(FirmwareBuffer, 0xFF, FirmwareBufferLength);
    FirmwareMask = FirmwareBufferLength - 1;

    memset(FirmwareBuffer, 0, 0x1D);
    FirmwareHeader& header = *reinterpret_cast<FirmwareHeader*>(FirmwareBuffer);
    header = FirmwareHeader(consoletype);
    FirmwareBuffer[0x2FF] = 0x80; // boot0: use NAND as stage2 medium

    // user data
    header.UserSettingsOffset = (0x7FE00 & FirmwareMask) >> 3;

    std::array<UserData, 2>& settings = *reinterpret_cast<std::array<UserData, 2>*>(GetUserDataPosition());
    settings = {
        UserData(),
        UserData(),
    };

    // wifi access points
    // TODO: WFC ID??

    std::array<WifiAccessPoint, 3>& accesspoints = *reinterpret_cast<std::array<WifiAccessPoint, 3>*>(GetWifiAccessPointPosition());

    accesspoints = {
        WifiAccessPoint(consoletype),
        WifiAccessPoint(),
        WifiAccessPoint(),
    };

    if (consoletype == 1)
    {
        std::array<ExtendedWifiAccessPoint, 3>& extendedaccesspoints = *reinterpret_cast<std::array<ExtendedWifiAccessPoint, 3>*>(GetExtendedAccessPointPosition());

        extendedaccesspoints = {
            ExtendedWifiAccessPoint(),
            ExtendedWifiAccessPoint(),
            ExtendedWifiAccessPoint(),
        };
    }
}

Firmware::Firmware(Platform::FileHandle* file) : FirmwareBuffer(nullptr), FirmwareBufferLength(0), FirmwareMask(0)
{
    if (file)
    {
        u64 length = Platform::FileLength(file);
        if (length > 0)
        {
            FirmwareBufferLength = FixFirmwareLength(length);
            FirmwareBuffer = new u8[FirmwareBufferLength];
            FirmwareMask = FirmwareBufferLength - 1;

            memset(FirmwareBuffer, 0, FirmwareBufferLength);
            Platform::FileRewind(file);
            if (!Platform::FileRead(FirmwareBuffer, length, 1, file))
            {
                delete[] FirmwareBuffer;
                FirmwareBuffer = nullptr;
                FirmwareBufferLength = 0;
                FirmwareMask = 0;
            }
        }

        Platform::FileRewind(file);
    }
}

Firmware::Firmware(const u8* data, u32 length) : FirmwareBuffer(nullptr), FirmwareBufferLength(FixFirmwareLength(length))
{
    if (data)
    {
        FirmwareBuffer = new u8[FirmwareBufferLength];
        memset(FirmwareBuffer, 0, FirmwareBufferLength);
        memcpy(FirmwareBuffer, data, std::min(length, FirmwareBufferLength));
        FirmwareMask = FirmwareBufferLength - 1;
    }
}

Firmware::Firmware(const Firmware& other) : FirmwareBuffer(nullptr), FirmwareBufferLength(other.FirmwareBufferLength)
{
    FirmwareBuffer = new u8[FirmwareBufferLength];
    memcpy(FirmwareBuffer, other.FirmwareBuffer, FirmwareBufferLength);
    FirmwareMask = other.FirmwareMask;
}

Firmware::Firmware(Firmware&& other) noexcept
{
    FirmwareBuffer = other.FirmwareBuffer;
    FirmwareBufferLength = other.FirmwareBufferLength;
    FirmwareMask = other.FirmwareMask;
    other.FirmwareBuffer = nullptr;
    other.FirmwareBufferLength = 0;
    other.FirmwareMask = 0;
}

Firmware& Firmware::operator=(const Firmware& other)
{
    if (this != &other)
    {
        delete[] FirmwareBuffer;
        FirmwareBufferLength = other.FirmwareBufferLength;
        FirmwareBuffer = new u8[other.FirmwareBufferLength];
        memcpy(FirmwareBuffer, other.FirmwareBuffer, other.FirmwareBufferLength);
        FirmwareMask = other.FirmwareMask;
    }

    return *this;
}

Firmware& Firmware::operator=(Firmware&& other) noexcept
{
    if (this != &other)
    {
        delete[] FirmwareBuffer;
        FirmwareBuffer = other.FirmwareBuffer;
        FirmwareBufferLength = other.FirmwareBufferLength;
        FirmwareMask = other.FirmwareMask;
        other.FirmwareBuffer = nullptr;
        other.FirmwareBufferLength = 0;
        other.FirmwareMask = 0;
    }

    return *this;
}

Firmware::~Firmware()
{
    delete[] FirmwareBuffer;
}

bool Firmware::IsBootable() const
{
    return
        FirmwareBufferLength != DEFAULT_FIRMWARE_LENGTH &&
        GetHeader().Identifier != GENERATED_FIRMWARE_IDENTIFIER
    ;
}

const Firmware::UserData& Firmware::GetEffectiveUserData() const {
    const std::array<union UserData, 2>& userdata = GetUserData();
    bool userdata0ChecksumOk = userdata[0].ChecksumValid();
    bool userdata1ChecksumOk = userdata[1].ChecksumValid();

    if (userdata0ChecksumOk && userdata1ChecksumOk)
    {
        return userdata[0].UpdateCounter >= userdata[1].UpdateCounter ? userdata[0] : userdata[1];
    }
    else if (userdata0ChecksumOk)
    {
        return userdata[0];
    }
    else if (userdata1ChecksumOk)
    {
        return userdata[1];
    }
    else
    {
        return userdata[0];
    }
}

Firmware::UserData& Firmware::GetEffectiveUserData() {
    std::array<union UserData, 2>& userdata = GetUserData();
    bool userdata0ChecksumOk = userdata[0].ChecksumValid();
    bool userdata1ChecksumOk = userdata[1].ChecksumValid();

    if (userdata0ChecksumOk && userdata1ChecksumOk)
    {
        return userdata[0].UpdateCounter >= userdata[1].UpdateCounter ? userdata[0] : userdata[1];
    }
    else if (userdata0ChecksumOk)
    {
        return userdata[0];
    }
    else if (userdata1ChecksumOk)
    {
        return userdata[1];
    }
    else
    {
        return userdata[0];
    }
}

void Firmware::UpdateChecksums()
{
    GetHeader().UpdateChecksum();

    for (auto& ap : GetAccessPoints())
    {
        ap.UpdateChecksum();
    }

    if (GetHeader().ConsoleType == FirmwareConsoleType::DSi)
    {
        for (auto& eap : GetExtendedAccessPoints())
        {
            eap.UpdateChecksum();
        }
    }

    for (auto& u : GetUserData())
    {
        u.UpdateChecksum();
    }
}

}