//
//  RealTimeClock.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 07/05/2019.
//  Copyright © 2019 Thomas Harte. All rights reserved.
//

#ifndef Apple_RealTimeClock_hpp
#define Apple_RealTimeClock_hpp

#include <array>

namespace Apple::Clock {

/*!
	Models Apple's real-time clocks, as contained in the Macintosh and IIgs.

	Since tracking of time is pushed to this class, it is assumed
	that whomever is translating real time into emulated time
	will also signal interrupts — this is just the storage and time counting.
*/
class ClockStorage {
	public:
		ClockStorage() {}

		/*!
			Advances the clock by 1 second.

			The caller should also signal an interrupt if applicable.
		*/
		void update() {
			for(size_t c = 0; c < 4; ++c) {
				++seconds_[c];
				if(seconds_[c]) break;
			}
		}

		/*!
			Sets the current [P/B]RAM contents.
		*/
		template <typename CollectionT> void set_data(const CollectionT &collection) {
			set_data(collection.begin(), collection.end());
		}

		template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
			size_t c = 0;
			while(begin != end && c < 256) {
				data_[c] = *begin;
				++begin;
				++c;
			}
		}

	protected:
		static constexpr uint16_t NoResult = 0x100;
		static constexpr uint16_t DidComplete = 0x101;
		uint16_t perform(uint8_t command) {
			/*
				Documented commands:

					z0000001		Seconds register 0 (lowest order byte)
					z0000101		Seconds register 1
					z0001001		Seconds register 2
					z0001101		Seconds register 3
					00110001		Test register (write only)
					00110101		Write-protect register (write only)
					z010aa01		RAM addresses 0x10 - 0x13
					z1aaaa01		RAM addresses 0x00 – 0x0f

					z0111abc, followed by 0defgh00
									RAM address abcdefgh

					z = 1 => a read; z = 0 => a write.

				The top bit of the write-protect register enables (0) or disables (1)
				writes to other locations.

				All the documentation says about the test register is to set the top
				two bits to 0 for normal operation. Abnormal operation is undefined.
			*/
			switch(phase_) {
				case Phase::Command:
					// Decode an address.
					switch(command & 0x70) {
						default:
							if(command & 0x40) {
								// RAM addresses 0x00 – 0x0f.
								address_ = (command >> 2) & 0xf;
							} else return DidComplete;	// Unrecognised.
						break;

						case 0x00:
							// A time access.
							address_ = SecondsBuffer + ((command >> 2)&3);
						break;
						case 0x30:
							// Either a register access or an extended instruction.
							if(command & 0x08) {
								address_ = unsigned((command & 0x7) << 5);
								phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
								return NoResult;
							} else {
								address_ = (command & 4) ? RegisterWriteProtect : RegisterTest;
							}
						break;
						case 0x20:
							// RAM addresses 0x10 – 0x13.
							address_ = 0x10 + ((command >> 2) & 0x3);
						break;
					}

					// If this is a read, return a result; otherwise prepare to write.
					if(command & 0x80) {
						// The two registers are write-only.
						if(address_ == RegisterTest || address_ == RegisterWriteProtect) {
							return DidComplete;
						}
						return (address_ >= SecondsBuffer) ? seconds_[address_ & 0xff] : data_[address_];
					}
					phase_ = Phase::WriteData;
				return NoResult;

				case Phase::SecondAddressByteRead:
				case Phase::SecondAddressByteWrite:
					if(command & 0x83) {
						return DidComplete;
					}
					address_ |= command >> 2;

					if(phase_ == Phase::SecondAddressByteRead) {
						phase_ = Phase::Command;
						return data_[address_];	// Only RAM accesses can get this far.
					} else {
						phase_ = Phase::WriteData;
					}
				return NoResult;

				case Phase::WriteData:
					// First test: is this to the write-protect register?
					if(address_ == RegisterWriteProtect) {
						write_protect_ = command;
						return DidComplete;
					}

					if(address_ == RegisterTest) {
						// No documentation here.
						return DidComplete;
					}

					// No other writing is permitted if the write protect
					// register won't allow it.
					if(!(write_protect_ & 0x80)) {
						if(address_ >= SecondsBuffer) {
							seconds_[address_ & 0xff] = command;
						} else {
							data_[address_] = command;
						}
					}

					phase_ = Phase::Command;
				return DidComplete;
			}

			return NoResult;
		}


	private:
		std::array<uint8_t, 256> data_{0xff};
		std::array<uint8_t, 4> seconds_{};
		uint8_t write_protect_ = 0;
		unsigned int address_ = 0;

		static constexpr int SecondsBuffer = 0x100;
		static constexpr int RegisterTest = 0x200;
		static constexpr int RegisterWriteProtect = 0x201;

		enum class Phase {
			Command,
			SecondAddressByteRead,
			SecondAddressByteWrite,
			WriteData
		};
		Phase phase_ = Phase::Command;

};

/*!
	Provides the serial interface implemented by the Macintosh.
*/
class SerialClock: public ClockStorage {
	public:
		/*!
			Sets the current clock and data inputs to the clock.
		*/
		void set_input(bool clock, bool data) {
			// The data line is valid when the clock transitions to level 0.
			if(clock && !previous_clock_) {
				// Shift into the command_ register, no matter what.
				command_ = uint16_t((command_ << 1) | (data ? 1 : 0));
				result_ <<= 1;

				// Increment phase.
				++phase_;

				// If a whole byte has been collected, push it onwards.
				if(!(phase_&7)) {
					// Begin pessimistically.
					const auto effect = perform(uint8_t(command_));

					switch(effect) {
						case ClockStorage::NoResult:
						break;
						default:
							result_ = uint8_t(effect);
						break;
						case ClockStorage::DidComplete:
							abort();
						break;
					}
				}
			}

			previous_clock_ = clock;
		}

		/*!
			Reads the current data output level from the clock.
		*/
		bool get_data() {
			return !!(result_ & 0x80);
		}

		/*!
			Announces that a serial command has been aborted.
		*/
		void abort() {
			result_ = 0;
			phase_ = 0;
			command_ = 0;
		}

	private:
		int phase_ = 0;
		uint16_t command_;
		uint8_t result_ = 0;

		bool previous_clock_ = false;
};

/*!
	Provides the parallel interface implemented by the IIgs.
*/
class ParallelClock: public ClockStorage {
	public:
		void set_control(uint8_t control) {
			if(!(control&0x80)) return;

			if(control & 0x40) {
				// Read from the RTC.
				// A no-op for now.
			} else {
				// Write to the RTC. Which in this implementation also sets up a future read.
				const auto result = perform(data_);
				if(result < 0x100) {
					data_ = uint8_t(result);
				}
			}

			// MAGIC! The transaction took 0 seconds.
			// TODO: no magic.
			control_ = control & 0x7f;

			// Bit 5 is also meant to be 1 or 0 to indicate the final byte.
		}

		uint8_t get_control() {
			return control_;
		}

		void set_data(uint8_t data) {
			data_ = data;
		}

		uint8_t get_data() {
			return data_;
		}

	private:
		uint8_t data_;
		uint8_t control_;
};

}

#endif /* Apple_RealTimeClock_hpp */
