//
//  BlitterSequencer.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 19/08/2022.
//  Copyright © 2022 Thomas Harte. All rights reserved.
//

#ifndef BlitterSequencer_hpp
#define BlitterSequencer_hpp

#include <array>

namespace Amiga {

/*!
	Statefully provides the next access the Blitter should make.

	TODO: determine the actual logic here, rather than
	relying on tables.
*/
class BlitterSequencer {
	public:
		enum class Channel {
			/// Tells the caller to calculate and load a new piece of output
			/// into the output pipeline.
			///
			/// If any inputs are enabled then a one-slot output pipeline applies:
			/// output will rest in the pipeline for one write phase before being written.
			Write,
			/// Indicates that a write should occur if anything is in the pipeline, otherwise
			/// no activity should occur.
			FlushPipeline,
			/// The caller should read from channel C.
			C,
			/// The caller should read from channel B.
			B,
			/// The caller should read from channel A.
			A,
			/// Indicates an unused DMA slot.
			None
		};

		/// Sets the current control value, which indicates which
		/// channels are enabled.
		void set_control(int control) {
			control_ = control & 0xf;
			index_ = 0;	// TODO: this probably isn't accurate; case caught is a change
						// of control values during a blit.
		}

		/// Indicates that blitting should conclude after this step, i.e.
		/// whatever is being fetched now is part of the final set of input data;
		/// this is safe to call following a fetch request on any channel.
		void complete() {
			next_phase_ =
				(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
					Phase::PauseAndComplete : Phase::Complete;
		}

		/// Begins a blit operation.
		void begin() {
			phase_ = next_phase_ = Phase::Ongoing;
			index_ = loop_ = 0;
		}

		/// Provides the next channel to fetch from, or that a write is required,
		/// along with a count of complete channel iterations so far completed.
		std::pair<Channel, int> next() {
			switch(phase_) {
				default: break;

				case Phase::Complete:
				return std::make_pair(Channel::FlushPipeline, loop_);

				case Phase::PauseAndComplete:
					phase_ = Phase::Complete;
				return std::make_pair(Channel::None, loop_);
			}

			Channel next = Channel::None;

			switch(control_) {
				default: break;

				case 0: next = next_channel(pattern0); break;
				case 1: next = next_channel(pattern1); break;
				case 2: next = next_channel(pattern2); break;
				case 3: next = next_channel(pattern3); break;
				case 4: next = next_channel(pattern4); break;
				case 5: next = next_channel(pattern5); break;
				case 6: next = next_channel(pattern6); break;
				case 7: next = next_channel(pattern7); break;
				case 8: next = next_channel(pattern8); break;
				case 9: next = next_channel(pattern9); break;
				case 10: next = next_channel(patternA); break;
				case 11: next = next_channel(patternB); break;
				case 12: next = next_channel(patternC); break;
				case 13: next = next_channel(patternD); break;
				case 14: next = next_channel(patternE); break;
				case 15: next = next_channel(patternF); break;
			}

			return std::make_pair(next, loop_);
		}

		template <int channel> bool channel_enabled() {
			return control_ & (8 >> channel);
		}

	private:
		static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
		static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
		static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
		static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
		static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
		static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
		static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
		static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
		static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
		static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
		static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
		static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
		static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
		static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
		static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
		static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
		template <typename ArrayT> Channel next_channel(const ArrayT &list) {
			loop_ += index_ / list.size();
			index_ %= list.size();
			const Channel result = list[index_];
			++index_;
			if(index_ == list.size()) {
				phase_ = next_phase_;
			}
			return result;
		}

		// Current control flags, i.e. which channels are enabled.
		int control_ = 0;

		// Index into the pattern table for this blit.
		size_t index_ = 0;

		// Number of times the entire pattern table has been completed.
		int loop_ = 0;

		enum class Phase {
			/// Return the next thing in the pattern table and advance.
			/// If looping from the end of the pattern table to the start,
			/// set phase_ to next_phase_.
			Ongoing,
			/// Return a Channel::None and advancce to phase_ = Phase::Complete.
			PauseAndComplete,
			/// Return Channel::Write indefinitely.
			Complete
		};

		// Current sequencer pahse.
		Phase phase_ = Phase::Complete;
		// Phase to assume at the end of this iteration of the sequence table.
		Phase next_phase_ = Phase::Complete;
};

}

#endif /* BlitterSequencer_hpp */
