// Copyright (c) 2021 - 2026 kio@little-bat.de
// BSD 2-clause license
// https://spdx.org/licenses/BSD-2-Clause.html

#pragma once
#include "BlockDevice.h"
#include "SerialDevice.h"
#include "common/cdefs.h"
#include "common/standard_types.h"
#include "internal/CSD.h"
//#include <hardware/spi.h>


namespace kilipili
{

struct CID
{						// note: MSB first!
	uint8 mid	 = 0;	// Manufacturer ID
	uint8 oid[2] = {0}; // OEM/Application ID
	char  pnm[5] = {0}; // Product name: ascii[5]
	uint8 prv	 = 0;	// Product revision: bcd_digits[2] -> x.y
	uint8 psn[4] = {0}; // Product serial number: uint32
	uint8 mdt[2] = {0}; // reserved nibble + Manufacturing date: hex_nibbles[3] -> yy.m
	uint8 crc	 = 0;	// crc7 << 1 | 0x01
};
static_assert(sizeof(CID) == 16, "");


constexpr char CRC_ERROR[]				 = "CRC error";
constexpr char DEVICE_INVALID_RESPONSE[] = "Device illegal response";
constexpr char CONTROLLER_FAILURE[]		 = "Controller failure";
constexpr char ILLEGAL_COMMAND[]		 = "Illegal command";
constexpr char ADDRESS_ERROR[]			 = "Address error";
constexpr char ERASE_CMD_ERROR[]		 = "Erase cmd error";
constexpr char DEVICE_NOT_SUPPORTED[]	 = "Device not supported";


class SDCard : public BlockDevice
{
	static_assert(sizeof(LBA) == sizeof(uint32), "");
	static_assert(sizeof(SIZE) == sizeof(SIZE), "");

public:
	enum CardType : uint8 {
		SD_unknown,
		SD_v1,	 // standard capacity SD card (16MB..2GB)
		SD_v2,	 // standard capacity SD card (16MB..2GB)
		SDHC_v2, // SDHC high capacity /2GB..32GB) or SDXC extended capacity (32GB..2TB)
		SDUC_v3, // SDUC ultra capacity SD card (2TB..128TB) (no SPI support!)
		MMC,	 // need 400kHz for initialization, different CSD and CID
	};

	CardType card_type = SD_unknown;
	uint8	 cs_pin;				 // gpio pin number of the CSn pin
	uint8	 erased_byte	= 0xff;	 //
	bool	 ccs		: 1 = false; // 1 = SDHC or SDXC: uses sector addresses
	bool	 no_crc		: 1 = false;
	bool	 _more_bits : 6 = 0;
	CSD		 csd;
	CID		 cid;
	uint32	 ocr = 0;

	static SDCard* getInstance(int instance = 0);

	//virtual void read (ADDR q, uint8* bu, SIZE) override;
	//virtual void write (ADDR z, const uint8* bu, SIZE) override;
	//virtual void copy_blocks (ADDR z, ADDR q, SIZE sz) override { copy_hd(z,q,sz); }
	virtual void   readSectors(LBA, void* bu, SIZE) throws override;
	virtual void   writeSectors(LBA, const void* bu, SIZE) throws override;
	virtual uint32 ioctl(IoCtl cmd, void* arg1 = nullptr, void* arg2 = nullptr) throws override;

	void printSCR(SerialDevice*, bool v = 1);
	void printCID(SerialDevice*, bool v = 1);
	void printCSD(SerialDevice*, bool v = 1);
	void printOCR(SerialDevice*, bool v = 1);
	void printCardInfo(SerialDevice*, bool v = 1);

private:
	SDCard(uint8 cs) noexcept;
	void init_spi() noexcept;

	void connect() throws;
	void disconnect() noexcept;

	inline void		   select() const noexcept;
	inline void		   deselect() const noexcept;
	static inline void read_spi(uint8*, uint32) noexcept;
	static inline void write_spi(const uint8*, uint32) noexcept;
	static inline void write_read_spi(const uint8*, uint8*, uint32) noexcept;

	uint8  read_byte() noexcept;
	uint8  receive_byte_or_throw(int timeout_us) throws;
	void   wait_ready_or_throw() throws;
	void   stop_transmission() noexcept;					 // CMD12
	uint16 read_status(bool keep_selected = false) noexcept; // CMD13

	enum { no_deselect = 1, f_acmd = 2, f_idle = 4 };
	uint8 send_cmd(uint8 cmd, uint32 args = 0, uint flags = 0);
	uint8 send_acmd(uint8 cmd, uint32 args = 0, uint flags = 0) { return send_cmd(cmd, args, flags | f_acmd); }

	void initialize_card_and_wait_ready();
	void read_ocr();				// CMD58
	void read_scr();				// ACMD51
	void read_card_info(uint8 cmd); // CMD9+10: CSD+CID
	void write_csd();				// CMD27  TODO
	void set_blocklen(uint);		// CMD16

	void read_single_block(LBA blkidx, uint8* data) throws;			// CMD17
	void write_single_block(LBA blkidx, const uint8* data) throws;	// CMD24
	void read_sectors(LBA blkidx, uint8* data, SIZE) throws;		// CMD18
	void write_sectors(LBA blkidx, const uint8* data, SIZE) throws; // CMD25
	void erase_sectors(LBA blkidx, SIZE) throws;					// CMD22, 33, 38

	void __attribute__((noreturn)) throwDeviceNotResponding();
	void __attribute__((noreturn)) throwWriteDataErrorToken(uint8 n);
	void __attribute__((noreturn)) throwReadDataErrorToken(uint8 n);
	void __attribute__((noreturn)) throwR1ResponseError(uint8 r1);
};

} // namespace kilipili

/*



















*/
