/*
 * This file is part of the Aaru Remote Server.
 * Copyright (c) 2019-2026 Natalia Portillo.
 *
 * This program 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, version 3.
 *
 * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <windows.h>

#include "win32.h"
#include "../aaruremote.h"
#include "ntioctl.h"

int32_t SendSdhciCommand(void*     device_ctx,
                         uint8_t   command,
                         uint8_t   write,
                         uint8_t   application,
                         uint32_t  flags,
                         uint32_t  argument,
                         uint32_t  block_size,
                         uint32_t  blocks,
                         char*     buffer,
                         uint32_t  buf_len,
                         uint32_t  timeout,
                         uint32_t* response,
                         uint32_t* duration,
                         uint32_t* sense)
{
    DeviceContext*               ctx = device_ctx;
    DWORD                        cmdbuf_len;
    PCHAR                        cmdbuf;
    PSFFDISK_DEVICE_COMMAND_DATA cmd_data;
    PSDCMD_DESCRIPTOR            cmd_descriptor;
    PCHAR                        data_buf;
    DWORD                        error = 0;
    DWORD                        k     = 0;
    LARGE_INTEGER                frequency;
    LARGE_INTEGER                start;
    LARGE_INTEGER                end;
    DOUBLE                       interval;

    if(!ctx) return -1;

    cmdbuf_len = sizeof(SFFDISK_DEVICE_COMMAND_DATA) + sizeof(SDCMD_DESCRIPTOR) + buf_len;

    cmdbuf = malloc(cmdbuf_len);

    if(!cmdbuf) return -1;

    memset(cmdbuf, 0, cmdbuf_len);
    cmd_data       = (PSFFDISK_DEVICE_COMMAND_DATA)cmdbuf;
    cmd_descriptor = (PSDCMD_DESCRIPTOR)(cmdbuf + sizeof(SFFDISK_DEVICE_COMMAND_DATA));
    data_buf       = cmdbuf + sizeof(SFFDISK_DEVICE_COMMAND_DATA) + sizeof(SDCMD_DESCRIPTOR);

    memcpy(data_buf, buffer, buf_len);

    cmd_data->HeaderSize              = sizeof(SFFDISK_DEVICE_COMMAND_DATA);
    cmd_data->Command                 = SFFDISK_DC_DEVICE_COMMAND;
    cmd_data->ProtocolArgumentSize    = sizeof(SDCMD_DESCRIPTOR);
    cmd_data->DeviceDataBufferSize    = buf_len;
    cmd_descriptor->Cmd               = command;
    cmd_descriptor->CmdClass          = application ? SDCC_APP_CMD : SDCC_STANDARD;
    cmd_descriptor->TransferDirection = write ? SDTD_WRITE : SDTD_READ;
    cmd_descriptor->ResponseType      = 0;
    cmd_descriptor->TransferType =
        (flags & AARUREMOTE_MMC_COMMAND_ADTC) ? (command == 18) ? SDTT_MULTI_BLOCK : SDTT_SINGLE_BLOCK : SDTT_CMD_ONLY;

    if((flags & AARUREMOTE_MMC_RESPONSE_R1) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R1))
        cmd_descriptor->ResponseType = SDRT_1;
    if((flags & AARUREMOTE_MMC_RESPONSE_R1B) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R1B))
        cmd_descriptor->ResponseType = SDRT_1B;
    if((flags & AARUREMOTE_MMC_RESPONSE_R2) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R2))
        cmd_descriptor->ResponseType = SDRT_2;
    if((flags & AARUREMOTE_MMC_RESPONSE_R3) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R3))
        cmd_descriptor->ResponseType = SDRT_3;
    if((flags & AARUREMOTE_MMC_RESPONSE_R4) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R4))
        cmd_descriptor->ResponseType = SDRT_4;
    if((flags & AARUREMOTE_MMC_RESPONSE_R5) || (flags & AARUREMOTE_MMC_RESPONSE_SPI_R5))
        cmd_descriptor->ResponseType = SDRT_5;
    if((flags & AARUREMOTE_MMC_RESPONSE_R6)) cmd_descriptor->ResponseType = SDRT_6;

    QueryPerformanceFrequency(&frequency);
    QueryPerformanceCounter(&start);
    *sense =
        !DeviceIoControl(ctx->handle, IOCTL_SFFDISK_DEVICE_COMMAND, cmdbuf, cmdbuf_len, cmdbuf, cmdbuf_len, &k, NULL);
    QueryPerformanceCounter(&end);

    interval  = (DOUBLE)(end.QuadPart - start.QuadPart) / frequency.QuadPart;
    *duration = (uint32_t)(interval * 1000.0);

    if(*sense) error = GetLastError();

    memcpy(buffer, data_buf, buf_len);

    free(cmdbuf);
    return error;
}

BOOL GuidEquals(GUID a, GUID b) { return memcmp(&a, &b, 16) == 0; }

BOOL IsSdhci(HANDLE handle)
{
    SFFDISK_QUERY_DEVICE_PROTOCOL_DATA query;
    DWORD                              k       = 0;
    GUID                               sdGuid  = GUID_SFF_PROTOCOL_SD;
    GUID                               mmcGuid = GUID_SFF_PROTOCOL_MMC;

    DeviceIoControl(handle,
                    IOCTL_SFFDISK_QUERY_DEVICE_PROTOCOL,
                    NULL,
                    0,
                    &query,
                    sizeof(SFFDISK_QUERY_DEVICE_PROTOCOL_DATA),
                    &k,
                    NULL);

    return GuidEquals(query.ProtocolGUID, sdGuid) || GuidEquals(query.ProtocolGUID, mmcGuid);
}

int32_t GetSdhciRegisters(void*     device_ctx,
                          char**    csd,
                          char**    cid,
                          char**    ocr,
                          char**    scr,
                          uint32_t* csd_len,
                          uint32_t* cid_len,
                          uint32_t* ocr_len,
                          uint32_t* scr_len)
{
    DeviceContext* ctx = device_ctx;
    uint32_t       duration;
    uint32_t       sense;

    if(!ctx) return -1;

    if(!IsSdhci(ctx->handle)) return -1;

    *csd = malloc(16);
    if(*csd)
    {
        *csd_len = 16;
        SendSdhciCommand(device_ctx,
                         9,
                         0,
                         0,
                         AARUREMOTE_MMC_RESPONSE_SPI_R2 | AARUREMOTE_MMC_RESPONSE_R2 | AARUREMOTE_MMC_COMMAND_AC,
                         0,
                         16,
                         1,
                         *csd,
                         *csd_len,
                         1000,
                         NULL,
                         &duration,
                         &sense);

        if(sense)
        {
            *csd_len = 0;
            free(*csd);
        }
    }

    *cid = malloc(16);
    if(*cid)
    {
        *cid_len = 16;
        SendSdhciCommand(device_ctx,
                         10,
                         0,
                         0,
                         AARUREMOTE_MMC_RESPONSE_SPI_R2 | AARUREMOTE_MMC_RESPONSE_R2 | AARUREMOTE_MMC_COMMAND_AC,
                         0,
                         16,
                         1,
                         *cid,
                         *cid_len,
                         1000,
                         NULL,
                         &duration,
                         &sense);

        if(sense)
        {
            *cid_len = 0;
            free(*cid);
        }
    }

    *scr = malloc(8);
    if(*scr)
    {
        *scr_len = 8;
        SendSdhciCommand(device_ctx,
                         10,
                         0,
                         0,
                         AARUREMOTE_MMC_RESPONSE_SPI_R1 | AARUREMOTE_MMC_RESPONSE_R1 | AARUREMOTE_MMC_COMMAND_ADTC,
                         0,
                         8,
                         1,
                         *scr,
                         *scr_len,
                         1000,
                         NULL,
                         &duration,
                         &sense);

        if(sense)
        {
            *scr_len = 0;
            free(*scr);
        }
    }

    *ocr = malloc(4);
    if(*ocr)
    {
        *ocr_len = 4;
        SendSdhciCommand(device_ctx,
                         *scr_len > 0 ? 41 : 1,
                         0,
                         0,
                         AARUREMOTE_MMC_RESPONSE_SPI_R1 | AARUREMOTE_MMC_RESPONSE_R1 | AARUREMOTE_MMC_COMMAND_ADTC,
                         0,
                         4,
                         1,
                         *ocr,
                         *ocr_len,
                         1000,
                         NULL,
                         &duration,
                         &sense);

        if(sense)
        {
            *ocr_len = 0;
            free(*ocr);
        }
    }

    return *csd_len > 0 || *cid_len > 0 || *scr_len > 0 || *ocr_len > 0;
}

int32_t SendMultiSdhciCommand(void*            device_ctx,
                              uint64_t         count,
                              MmcSingleCommand commands[],
                              uint32_t*        duration,
                              uint32_t*        sense)
{
    DeviceContext* ctx = device_ctx;
    *duration          = 0;
    *sense             = 0;
    uint64_t i;
    int32_t  error = 0, single_error;
    uint32_t single_sense, single_duration;

    if(!ctx) return -1;

    if(count == 3 && commands[0].command == 16 && // SET_BLOCK_LEN
       commands[1].command == 18 &&               // READ_MULTIPLE_BLOCK
       commands[2].command == 12)                 // STOP_TRANSMISSION
        return SendSdhciCommand(device_ctx,
                                commands[1].command,
                                commands[1].write,
                                commands[1].application,
                                commands[1].flags,
                                commands[1].argument,
                                commands[1].block_size,
                                commands[1].blocks,
                                commands[1].buffer,
                                commands[1].buf_len,
                                0,
                                commands[1].response,
                                duration,
                                sense);

    for(i = 0; i < count; i++)
    {
        single_error = SendSdhciCommand(device_ctx,
                                        commands[i].command,
                                        commands[i].write,
                                        commands[i].application,
                                        commands[i].flags,
                                        commands[i].argument,
                                        commands[i].block_size,
                                        commands[i].blocks,
                                        commands[i].buffer,
                                        commands[i].buf_len,
                                        0,
                                        commands[i].response,
                                        &single_duration,
                                        &single_sense);
        if(error == 0 && single_error != 0) error = single_error;

        *duration += single_duration;

        if(single_sense) *sense = TRUE;
    }

    return error;
}