text: #include "memory.h"
  
  #include <cstdlib>
  #include <unordered_map>
  
  #include "logging.h"
  
  using namespace Retro;
  using namespace std;
  
  Endian Retro::reduce(Endian e) {
  	switch (e) {
  	case Endian::BIG:
  	case Endian::LITTLE:
  	case Endian::UNDEF:
  	case Endian::MIXED_BL:
  	case Endian::MIXED_LB:
  		return e;
  	case Endian::NATIVE:
  		return Endian::REAL_NATIVE;
  	case Endian::MIXED_BN:
  		return Endian::REAL_MIXED_BN;
  	case Endian::MIXED_LN:
  		return Endian::REAL_MIXED_LN;
  	}
  	return e;
  }
  
  bool Retro::reduceCompare(Endian a, Endian b) {
  	return reduce(a) == reduce(b);
  }
  
  DataType::DataType(const char* type)
  	: width(type[strlen(type) - 1] - '0')
  	, endian(
  		  type[0] == '=' ? Endian::NATIVE : type[0] == '>' ? (type[1] == '<' ? Endian::MIXED_BL : type[1] == '=' ? Endian::MIXED_BN : Endian::BIG) : type[0] == '<' ? (type[1] == '>' ? Endian::MIXED_LB : type[1] == '=' ? Endian::MIXED_LN : Endian::LITTLE) : Endian::UNDEF)
  	, repr(static_cast<Repr>(type[strlen(type) - 2]))
  	, type{ type[0], type[1], type[2], type[3] }
  	, maskLo(repr == Repr::LN_BCD || repr == Repr::BCD ? 0xF : 0xFF)
  	, maskHi(repr == Repr::BCD ? 0xF0 : 0x0)
  	, cvt(repr == Repr::BCD || repr == Repr::LN_BCD ? 10 : 256) {
  	uint64_t shiftInc =
  		repr == Repr::BCD ? 100 : repr == Repr::LN_BCD ? 10 : 256;
  
  	int baseLoc;
  	int baseEnd;
  	int halfLoc = -1;
  	int diff;
  
  	if (width > 8) {
  		throw std::out_of_range("Invalid DataType width");
  	}
  
  	switch (reduce(endian)) {
  	case Endian::LITTLE:
  	default:
  		baseLoc = 0;
  		baseEnd = width;
  		diff = 1;
  		break;
  	case Endian::BIG:
  		baseLoc = width - 1;
  		baseEnd = -1;
  		diff = -1;
  		break;
  	case Endian::MIXED_LB:
  		baseLoc = width / 2 - 1;
  		baseEnd = -1;
  		halfLoc = width - 1;
  		diff = -1;
  		break;
  	case Endian::MIXED_BL:
  		baseLoc = width / 2;
  		baseEnd = width;
  		halfLoc = 0;
  		diff = 1;
  		break;
  	}
  
  	uint64_t baseShift = 1;
  	for (int i = baseLoc; i != baseEnd; i += diff, baseShift *= shiftInc) {
  		shift[i] = baseShift;
  	}
  	if (halfLoc >= 0) {
  		for (int i = halfLoc; i != baseLoc; i += diff, baseShift *= shiftInc) {
  			shift[i] = baseShift;
  		}
  	}
  }
  
  DataType::DataType(const string& type)
  	: DataType(type.c_str()) {
  }
  
  Datum DataType::operator()(void* base) const {
  	return Datum(base, *this);
  }
  
  Datum DataType::operator()(void* base, size_t offset, const MemoryOverlay& overlay) const {
  	return Datum(base, offset, *this, overlay);
  }
  
  bool DataType::operator==(const DataType& other) const {
  	return width == other.width && endian == other.endian && repr == other.repr;
  }
  
  bool DataType::operator!=(const DataType& other) const {
  	return !(*this == other);
  }
  
  void DataType::encode(void* buffer, int64_t value) const {
  	for (size_t i = 0; i < width; ++i) {
  		uint64_t b = (uint64_t) value / shift[i];
  		b = b % cvt + b / cvt % cvt * (~maskHi + 1);
  		static_cast<uint8_t*>(buffer)[i] = b;
  	}
  }
  
  int64_t DataType::decode(const void* buffer) const {
  	int64_t datum = 0;
  	for (size_t i = 0; i < width; ++i) {
  		uint8_t b = static_cast<const uint8_t*>(buffer)[i];
  		datum += ((b & maskLo) % cvt + ((b & maskHi) >> 4) % cvt * 10) * shift[i];
  	}
  	if (repr == Repr::SIGNED) {
  		datum <<= 8 * (8 - width);
  		datum >>= 8 * (8 - width);
  	}
  	return datum;
  }
  
  size_t hash<DataType>::operator()(const DataType& type) const {
  	return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(type.type));
  }
  
  static constexpr char endianTag(Endian e) {
  	switch (e) {
  	case Endian::BIG:
  		return '>';
  	case Endian::LITTLE:
  		return '<';
  	default:
  	case Endian::UNDEF:
  		return '|';
  	case Endian::NATIVE:
  		return '=';
  	}
  }
  
  MemoryOverlay::MemoryOverlay(Endian backing, Endian real, size_t width)
  	: width(width)
  	, m_backing({ endianTag(backing), 'u', static_cast<char>('0' + width) })
  	, m_real({ endianTag(real), 'u', static_cast<char>('0' + width) }) {
  }
  
  MemoryOverlay::MemoryOverlay(char backing, char real, size_t width)
  	: width(width)
  	, m_backing({ backing, 'u', static_cast<char>('0' + width) })
  	, m_real({ real, 'u', static_cast<char>('0' + width) }) {
  }
  
  void* MemoryOverlay::parse(const void* in, size_t offset, void* out, size_t size) const {
  	size_t offsetEdge = offset & (width - 1);
  	uintptr_t base = reinterpret_cast<uintptr_t>(in);
  	base += offset & ~(width - 1);
  	size += offsetEdge;
  	uintptr_t outBase = reinterpret_cast<uintptr_t>(out);
  	for (size_t i = 0; i < size; i += width) {
  		int64_t val = m_backing.decode(reinterpret_cast<const void*>(base + i));
  		m_real.encode(reinterpret_cast<void*>(outBase + i), val);
  	}
  	return reinterpret_cast<void*>(outBase + offsetEdge);
  }
  
  void MemoryOverlay::unparse(void* out, size_t offset, const void* in, size_t size) const {
  	size_t offsetEdge = offset & (width - 1);
  	uintptr_t base = reinterpret_cast<uintptr_t>(out);
  	base += offset & ~(width - 1);
  	size += offsetEdge;
  	uintptr_t inBase = reinterpret_cast<uintptr_t>(in);
  	for (size_t i = 0; i < size; i += width) {
  		int64_t val = m_real.decode(reinterpret_cast<void*>(inBase + i));
  		m_backing.encode(reinterpret_cast<void*>(base + i), val);
  	}
  }
  
  Variant::Variant(int64_t i)
  	: m_type(Type::INT)
  	, m_vi(i) {
  }
  
  Variant::Variant(double d)
  	: m_type(Type::FLOAT)
  	, m_vf(d) {
  }
  
  Variant::Variant(bool b)
  	: m_type(Type::BOOL)
  	, m_vb(b) {
  }
  
  Variant::operator int64_t() const {
  	return cast<int64_t>();
  }
  
  Variant::operator int() const {
  	return cast<int>();
  }
  
  Variant::operator float() const {
  	return cast<float>();
  }
  
  Variant::operator double() const {
  	return cast<double>();
  }
  
  Variant::operator bool() const {
  	return cast<bool>();
  }
  
  void Variant::clear() {
  	m_type = Type::VOID;
  }
  
  Variant& Variant::operator=(int64_t v) {
  	m_type = Type::INT;
  	m_vi = v;
  	return *this;
  }
  
  Variant& Variant::operator=(double v) {
  	m_type = Type::FLOAT;
  	m_vf = v;
  	return *this;
  }
  
  Variant& Variant::operator=(bool v) {
  	m_type = Type::BOOL;
  	m_vb = v;
  	return *this;
  }
  
  Datum::Datum(void* base, const DataType& type)
  	: m_base(base)
  	, m_type(type) {
  }
  
  Datum::Datum(void* base, size_t offset, const DataType& type, const MemoryOverlay& overlay)
  	: m_base(base)
  	, m_offset(offset)
  	, m_type(type)
  	, m_overlay(overlay) {
  }
  
  Datum::Datum(void* base, const Variable& var, const MemoryOverlay& overlay)
  	: m_base(base)
  	, m_offset(var.address)
  	, m_type(var.type)
  	, m_mask(var.mask)
  	, m_overlay(overlay) {
  }
  
  Datum::Datum(Variant* variant)
  	: m_type("=i8")
  	, m_variant(variant) {
  }
  
  Datum& Datum::operator=(int64_t value) {
  	if (m_base) {
  		if (m_overlay.width > 1 || m_offset) {
  			uint8_t fakeBase[16]{};
  			m_type.encode(m_overlay.parse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width), value);
  			m_overlay.unparse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width);
  		} else {
  			m_type.encode(m_base, value);
  		}
  	} else if (m_variant) {
  		*m_variant = value;
  	}
  	return *this;
  }
  
  Datum::operator int64_t() const {
  	if (!m_base) {
  		if (m_variant) {
  			return *m_variant;
  		}
  		return 0;
  	}
  	int64_t value;
  	if (m_overlay.width > 1 || m_offset) {
  		uint8_t fakeBase[16]{};
  		value = m_type.decode(m_overlay.parse(m_base, m_offset, reinterpret_cast<void*>(fakeBase), m_type.width));
  	} else {
  		value = m_type.decode(m_base);
  	}
  	return value & m_mask;
  }
  
  Datum::operator Variant() const {
  	if (m_variant) {
  		return *m_variant;
  	}
  	return static_cast<int64_t>(*this);
  }
  
  DynamicMemoryView::DynamicMemoryView(void* buffer, size_t bytes, const DataType& dtype, const MemoryOverlay& overlay)
  	: dtype(dtype)
  	, overlay(overlay) {
  	m_mem.open(buffer, bytes);
  }
  
  Datum DynamicMemoryView::operator[](size_t offset) {
  	return dtype(m_mem.offset(0), offset, overlay);
  }
  
  int64_t DynamicMemoryView::operator[](size_t offset) const {
  	if (overlay.width > 1) {
  		uint8_t fakeBase[16]{};
  		return dtype.decode(overlay.parse(m_mem.offset(0), offset, reinterpret_cast<void*>(fakeBase), dtype.width));
  	}
  	return dtype.decode(m_mem.offset(offset));
  }
  
  const DataType AddressSpace::s_type{ "|u1" };
  
  void AddressSpace::addBlock(size_t offset, size_t size, void* data) {
  	if (data) {
  		m_blocks[offset].open(data, size);
  	} else {
  		m_blocks[offset].open(size);
  	}
  }
  
  void AddressSpace::addBlock(size_t offset, size_t size, const void* data) {
  	if (data) {
  		m_blocks[offset].clone(data, size);
  	} else {
  		m_blocks[offset].open(size);
  	}
  }
  
  void AddressSpace::addBlock(size_t offset, const MemoryView<>& base) {
  	m_blocks[offset].clone(base);
  }
  
  void AddressSpace::updateBlock(size_t offset, void* data) {
  	m_blocks[offset].open(data, m_blocks[offset].size());
  }
  
  void AddressSpace::updateBlock(size_t offset, const void* data) {
  	m_blocks[offset].clone(data, m_blocks[offset].size());
  }
  
  void AddressSpace::updateBlock(size_t offset, const MemoryView<>& base) {
  	m_blocks[offset].clone(base);
  }
  
  bool AddressSpace::hasBlock(size_t offset) const {
  	for (const auto& block : m_blocks) {
  		if (offset < block.first) {
  			continue;
  		}
  		if (offset < block.first + block.second.size()) {
  			return true;
  		}
  	}
  	return false;
  }
  
  const MemoryView<>& AddressSpace::block(size_t offset) const {
  	for (const auto& block : m_blocks) {
  		if (offset < block.first) {
  			continue;
  		}
  		if (offset < block.first + block.second.size()) {
  			return block.second;
  		}
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  MemoryView<>& AddressSpace::block(size_t offset) {
  	for (auto& block : m_blocks) {
  		if (offset < block.first) {
  			continue;
  		}
  		if (offset < block.first + block.second.size()) {
  			return block.second;
  		}
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  bool AddressSpace::ok() const {
  	return m_blocks.size() > 0;
  }
  
  void AddressSpace::reset() {
  	m_blocks.clear();
  }
  
  void AddressSpace::clone(const AddressSpace& as) {
  	m_blocks.clear();
  	m_overlay = make_unique<MemoryOverlay>(*as.m_overlay);
  	for (auto& kv : as.m_blocks) {
  		m_blocks[kv.first].clone(kv.second);
  	}
  }
  
  void AddressSpace::clone() {
  	for (auto& kv : m_blocks) {
  		kv.second.clone();
  	}
  }
  
  void AddressSpace::setOverlay(const MemoryOverlay& overlay) {
  	m_overlay = make_unique<MemoryOverlay>(overlay);
  }
  
  Datum AddressSpace::operator[](size_t offset) {
  	for (auto& kv : m_blocks) {
  		if (offset < kv.first) {
  			throw std::out_of_range("No known mapping");
  		}
  		if (offset - kv.first >= kv.second.size()) {
  			continue;
  		}
  		return Datum(kv.second.offset(0), offset - kv.first, s_type, *m_overlay);
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  Datum AddressSpace::operator[](const Variable& var) {
  	for (auto& kv : m_blocks) {
  		// ZLOG("var.address: %zx", var.address);
  		// ZLOG("kv.first: %zx", kv.first);
  		if (var.address < kv.first) {
  			// ZLOG("1 too small var.address", "");
  			throw std::out_of_range("No known mapping");
  		}
  		if (var.address - kv.first >= kv.second.size()) {
  			// ZLOG("2 too large var.address", "");
  			continue;
  		}
  		return Datum(kv.second.offset(0), Variable{ var.type, var.address - kv.first, var.mask }, *m_overlay);
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  uint8_t AddressSpace::operator[](size_t offset) const {
  	for (const auto& kv : m_blocks) {
  		if (offset < kv.first) {
  			throw std::out_of_range("No known mapping");
  		}
  		if (offset - kv.first >= kv.second.size()) {
  			continue;
  		}
  		uint8_t fakeBase[16]{};
  		return s_type.decode(m_overlay->parse(kv.second.offset(0), offset - kv.first, reinterpret_cast<void*>(fakeBase), s_type.width));
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  int64_t AddressSpace::operator[](const Variable& var) const {
  	for (const auto& kv : m_blocks) {
  		if (var.address < kv.first) {
  			throw std::out_of_range("No known mapping");
  		}
  		if (var.address - kv.first >= kv.second.size()) {
  			continue;
  		}
  		int64_t value;
  		if (m_overlay->width > 1) {
  			uint8_t fakeBase[16];
  			value = var.type.decode(m_overlay->parse(kv.second.offset(0), var.address - kv.first, reinterpret_cast<void*>(fakeBase), var.type.width));
  		} else {
  			value = var.type.decode(kv.second.offset(var.address - kv.first));
  		}
  		value &= var.mask;
  		return value;
  	}
  	throw std::out_of_range("No known mapping");
  }
  
  AddressSpace& AddressSpace::operator=(AddressSpace&& as) {
  	m_blocks.clear();
  	m_overlay = move(as.m_overlay);
  	for (auto& kv : as.m_blocks) {
  		m_blocks[kv.first] = move(as.m_blocks[kv.first]);
  	}
  	as.m_blocks.clear();
  	return *this;
  }
  
  int64_t Retro::toBcd(int64_t value) {
  	int64_t out = 0;
  	int shift = 0;
  	while (value) {
  		out |= (value % 10) << (shift * 4);
  		++shift;
  		value /= 10;
  	}
  	return out;
  }
  
  int64_t Retro::toLNBcd(int64_t value) {
  	int64_t out = 0;
  	int shift = 0;
  	while (value) {
  		out |= (value % 10) << (shift * 8);
  		++shift;
  		value /= 10;
  	}
  	return out;
  }
  
  bool Retro::isBcd(uint64_t value) {
  	uint64_t halfdigits = (value >> 1) & 0x7777777777777777;
  	return !((halfdigits + 0x3333333333333333) & 0x8888888888888888);
  }
  