/*
	This file is part of Flycast.

    Flycast 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 2 of the License, or
    (at your option) any later version.

    Flycast 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 Flycast.  If not, see <https://www.gnu.org/licenses/>.
 */
#pragma once
#include "types.h"
#include "serialize.h"
#include <cmath>

struct MemChip
{
	u8* data;
	u32 size;
	u32 mask;

protected:
	u32 write_protect_size;
	std::string load_filename;

	MemChip(u32 size, u32 write_protect_size = 0)
	{
		this->data = new u8[size]();
		this->size = size;
		this->mask = size - 1; // must be power of 2
		this->write_protect_size = write_protect_size;
	}

public:
	virtual ~MemChip()
	{
		delete[] data;
	}

	virtual u8 Read8(u32 addr)
	{
		return data[addr & mask];
	}

	u32 Read(u32 addr, u32 sz)
	{
		addr &= mask;

		u32 rv = 0;

		for (u32 i = 0; i < sz; i++)
			rv |= Read8(addr + i) << (i * 8);

		return rv;
	}

	bool Load(const std::string& file);
	virtual bool Reload() { return true; }
	bool Load(const std::string &prefix, const std::string &names_ro,
			const std::string &title);
	void Load(const u8 *data, size_t size);
	void digest(u8 md5Digest[16]);

	virtual void Reset() {}
	virtual void Serialize(Serializer& ser) const { }
	virtual void Deserialize(Deserializer& deser) { }
};

struct WritableChip : MemChip
{
protected:
	WritableChip(u32 size, u32 write_protect_size = 0)
		: MemChip(size, write_protect_size) {}

public:
	virtual void Write(u32 addr, u32 data, u32 size) = 0;

	bool Reload() override
	{
		if (load_filename.empty())
			return false;
		return Load(this->load_filename);
	}
	void Save(const std::string& file);
	void Save(const std::string &prefix, const std::string &name_ro,
			const std::string &title);
};

struct RomChip : MemChip
{
	RomChip(u32 sz) : MemChip(sz) {}
};

struct SRamChip : WritableChip
{
	SRamChip(u32 sz, u32 write_protect_size = 0) : WritableChip(sz, write_protect_size) {}

	void Write(u32 addr, u32 val, u32 sz) override
	{
		addr &= mask;
		if (addr < write_protect_size)
			return;
		switch (sz)
		{
		case 1:
			data[addr] = (u8)val;
			return;
		case 2:
			*(u16 *)&data[addr] = (u16)val;
			return;
		case 4:
			*(u32 *)&data[addr] = val;
			return;
		default:
			die("invalid access size");
		}
	}

	void Serialize(Serializer& ser) const override
	{
		ser.serialize(&this->data[write_protect_size], size - write_protect_size);
	}
	void Deserialize(Deserializer& deser) override
	{
		deser.deserialize(&this->data[write_protect_size], size - write_protect_size);
	}
};

//
// Flash block handling code borrowed from redream (https://github.com/inolen/redream)
//

// magic cookie every block-allocated partition begins with
#define FLASH_MAGIC_COOKIE "KATANA_FLASH____"

#define FLASH_BLOCK_SIZE 0x40
// each bitmap is 64 bytes in length, and each byte can record the state of 8
// physical blocks (one per bit), therefore, each bitmap can represent up to
// 512 physical blocks. these 512 blocks are each 64-bytes in length, meaning
// each partition would need partition_size / 32768 bitmap blocks to represent
// all of its physical blocks
#define FLASH_BITMAP_BLOCKS (FLASH_BLOCK_SIZE * 8)
#define FLASH_BITMAP_BYTES (FLASH_BITMAP_BLOCKS * 64)

// flash partitions
#define FLASH_PT_FACTORY 0
#define FLASH_PT_RESERVED 1
#define FLASH_PT_USER 2
#define FLASH_PT_GAME 3
#define FLASH_PT_UNKNOWN 4
#define FLASH_PT_NUM 5

// flash logical blocks
#define FLASH_USER_SYSCFG 0x05
#define FLASH_USER_INET 0x80
#define FLASH_USER_ISP1 0xC0
#define FLASH_USER_ISP2 0xC6

struct flash_syscfg_block {
  u16 block_id;
  // last set time (seconds since 1/1/1950 00:00)
  u16 time_lo;
  u16 time_hi;
	// in 15 mins increment, from -48 (West) to +52 (East), unused
  int8_t time_zone;
  u8 lang;
  u8 mono;
  u8 autostart;
  u8 unknown2[4];
  u8 reserved[50];
};

struct flash_isp1_block {
	u16 block_id;
	u8 _unknown[4];
	char sega[4];
	char username[28];
	char password[16];
	char phone[8];
	u16 crc;
};

struct flash_isp2_block {
	u16 block_id;
	char sega[4];
	char username[28];
	char password[16];
	char phone[12];
	u16 crc;
};

// header block in block-allocated partition
struct flash_header_block {
  char magic[16];
  u8 part_id;
  u8 version;
  u8 reserved[46];
};

// user block in block-allocated partition
struct flash_user_block {
  u16 block_id;
  u8 data[60];
  u16 crc;
};

// Macronix 29LV160TMC
// AtomisWave uses a custom 29L001mc model
struct DCFlashChip : WritableChip
{
	DCFlashChip(u32 sz, u32 write_protect_size = 0): WritableChip(sz, write_protect_size), state(FS_Normal) { }

	enum FlashState
	{
		FS_Normal,
		FS_ReadAMDID1,
		FS_ReadAMDID2,
		FS_ByteProgram,
		FS_EraseAMD1,
		FS_EraseAMD2,
		FS_EraseAMD3,
		FS_SelectMode,
	};

	FlashState state;
	void Reset() override
	{
		//reset the flash chip state
		state = FS_Normal;
	}
	
	void Write(u32 addr,u32 val,u32 sz) override;

	u8 Read8(u32 addr) override
	{
		if (state == FS_SelectMode)
		{
			state = FS_Normal;
			switch (addr & 0x43)
			{
			case 0:	// manufacturer's code
				return 4;		// or 0x20 or 1
			case 1:	// device code
				return 0xb0;	// or 0x40 or 0x3e
			case 2:	// sector protection verification
				// sector protection
				DEBUG_LOG(FLASHROM, "Sector protection address %x", addr);
				return (addr & 0x1e000) == 0x1a000;
			default:
				WARN_LOG(FLASHROM, "SelectMode unknown address %x", addr);
				return 0;
			}
		}
		return MemChip::Read8(addr);
	}

	int WriteBlock(u32 part_id, u32 block_id, const void *data);
	int ReadBlock(u32 part_id, u32 block_id, void *data);

	void GetPartitionInfo(int part_id, int *offset, int *size)
	{
		switch (part_id)
		{
		case FLASH_PT_FACTORY:
			*offset = 0x1a000;
			*size = 8_KB;
			break;
		case FLASH_PT_RESERVED:
			*offset = 0x18000;
			*size = 8_KB;
			break;
		case FLASH_PT_USER:
			*offset = 0x1c000;
			*size = 16_KB;
			break;
		case FLASH_PT_GAME:
			*offset = 0x10000;
			*size = 32_KB;
			break;
		case FLASH_PT_UNKNOWN:
			*offset = 0x00000;
			*size = 64_KB;
			break;
		default:
			*offset = 0;
			*size = 0;
			die("unknown partition");
			break;
		}
	}

	void Validate();

private:
	int crc_block(struct flash_user_block *block)
	{
		const u8 *buf = (const u8 *)block;
		int size = 62;
		int n = 0xffff;

		for (int i = 0; i < size; i++) {
			n ^= (buf[i] << 8);

			for (int c = 0; c < 8; c++) {
				if (n & 0x8000) {
					n = (n << 1) ^ 4129;
				} else {
					n = (n << 1);
				}
			}
		}

		return (~n) & 0xffff;
	}

	int validate_crc(struct flash_user_block *user)
	{
		return user->crc == crc_block(user);
	}

	inline int num_physical_blocks(u32 size)
	{
		return size / FLASH_BLOCK_SIZE;
	}

	inline int num_bitmap_blocks(u32 size)
	{
		return (int)std::ceil(size / (float)FLASH_BITMAP_BYTES);
	}

	inline int num_user_blocks(u32 size)
	{
		return num_physical_blocks(size) - num_bitmap_blocks(size) - 1;
	}

	inline int is_allocated(const u8 *bitmap, u32 phys_id)
	{
		int index = (phys_id - 1) % FLASH_BITMAP_BLOCKS;
		return (bitmap[index / 8] & (0x80 >> (index % 8))) == 0x0;
	}

	inline void set_allocated(u8 *bitmap, u32 phys_id)
	{
		int index = (phys_id - 1) % FLASH_BITMAP_BLOCKS;
		bitmap[index / 8] &= ~(0x80 >> (index % 8));
	}

	void write_physical_block(u32 offset, u32 phys_id, const void *data)
	{
		memcpy(&this->data[offset + phys_id * FLASH_BLOCK_SIZE], data, FLASH_BLOCK_SIZE);
	}

	void read_physical_block(u32 offset, u32 phys_id, void *data)
	{
		memcpy(data, &this->data[offset + phys_id * FLASH_BLOCK_SIZE], FLASH_BLOCK_SIZE);
	}

	int validate_header(u32 offset, u32 part_id)
	{
		struct flash_header_block header;
		read_physical_block(offset, 0, &header);

		if (memcmp(header.magic, FLASH_MAGIC_COOKIE, sizeof(header.magic)) != 0)
			return 0;

		if (header.part_id != part_id)
			return 0;

		return 1;
	}

	int validate_header(u32 part_id)
	{
		int offset, size;
		GetPartitionInfo(part_id, &offset, &size);

		return validate_header(offset, part_id);
	}

	int alloc_block(u32 offset, u32 size);
	int lookup_block(u32 offset, u32 size, u32 block_id);

	void Serialize(Serializer& ser) const override
	{
		ser << state;
		ser.serialize(&this->data[write_protect_size], size - write_protect_size);
	}

	void Deserialize(Deserializer& deser) override
	{
		deser >> state;
		deser.deserialize(&this->data[write_protect_size], size - write_protect_size);
	}

	void erase_partition(u32 part_id)
	{
		int offset, size;
		GetPartitionInfo(part_id, &offset, &size);

		memset(data + offset, 0xFF, size);
	}

	void write_header(int part_id)
	{
		int offset, size;
		GetPartitionInfo(part_id, &offset, &size);

		struct flash_header_block header;
		memset(&header, 0xff, sizeof(header));
		memcpy(header.magic, FLASH_MAGIC_COOKIE, sizeof(header.magic));
		header.part_id = part_id;
		header.version = 0;

		write_physical_block(offset, 0, &header);
	}
};
