//
//  Copper.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 16/09/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#ifndef NDEBUG
#define NDEBUG
#endif

#define LOG_PREFIX "[Copper] "
#include "../../Outputs/Log.hpp"

#include "Chipset.hpp"
#include "Copper.hpp"

using namespace Amiga;

namespace {

bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) {
	// Return immediately if: (i) wait-for-Blitter is not disabled; and (ii) the Blitter is busy.
	if(!(instruction[1] & 0x8000) && (blitter_status & 0x4000)) {
		return false;
	}

	// Otherwise, test the raster position against the instruction's value and mask.
	const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
	return (position & mask) >= (instruction[0] & mask);
}

}

//
// Quick notes on the Copper:
//
// There are three instructions: move, wait and skip. All are two words in length.
//
// Move writes a value to one of the Chipset registers; it is encoded as:
//
//		First word:
//			b0:		0
//			b1–b8:	register address
//			b9+:	unused ("should be set to 0")
//
//		Second word:
//			b0–b15:	value to move.
//
//
// Wait waits until the raster gets to at least a certain position, and
// optionally until the Blitter has finished. It is encoded as:
//
//		First word:
//			b0:		1
//			b1–b7:	horizontal beam position
//			b8+:	vertical beam position
//
//		Second word:
//			b0:		0
//			b1–b7:	horizontal beam comparison mask
//			b8–b14:	vertical beam comparison mask
//			b15:	1 => don't also wait for the Blitter to be finished; 0 => wait.
//
//
// Skip skips the next instruction if the raster has already reached a certain
// position, and optionally only if the Blitter has finished, and only if the
// next instruction is a move.
//
//		First word:
//			b0:		1
//			b1–b7:	horizontal beam position
//			b8+:	vertical beam position
//
//		Second word:
//			b0:		1
//			b1–b7:	horizontal beam comparison mask
//			b8–b14:	vertical beam comparison mask
//			b15:	1 => don't also test whether the Blitter is finished; 0 => test.
//
bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) {
	switch(state_) {
		default: return false;

		case State::Waiting:
			if(satisfies_raster(position, blitter_status, instruction_)) {
				LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << PADHEX(4) << position << " with mask " << PADHEX(4) << (instruction_[1] & 0x7ffe));
				state_ = State::FetchFirstWord;
			}
		return false;

		case State::FetchFirstWord:
			instruction_[0] = ram_[address_ & ram_mask_];
			++address_;
			state_ = State::FetchSecondWord;
			LOG("First word fetch at " << PADHEX(4) << position);
		break;

		case State::FetchSecondWord: {
			// Get and reset the should-skip-next flag.
			const bool should_skip_move = skip_next_;
			skip_next_ = false;

			// Read in the second instruction word.
			instruction_[1] = ram_[address_ & ram_mask_];
			++address_;
			LOG("Second word fetch at " << PADHEX(4) << position);

			// Check for a MOVE.
			if(!(instruction_[0] & 1)) {
				if(!should_skip_move) {
					// Stop if this move would be a privilege violation.
					instruction_[0] &= 0x1fe;
					if((instruction_[0] < 0x10) || (instruction_[0] < 0x20 && !(control_&1))) {
						LOG("Invalid MOVE to " << PADHEX(4) << instruction_[0] << "; stopping");
						state_ = State::Stopped;
						break;
					}

					chipset_.write(instruction_[0], instruction_[1]);
				}

				// Roll onto the next command.
				state_ = State::FetchFirstWord;
				break;
			}

			// Got to here => this is a WAIT or a SKIP.

			if(!(instruction_[1] & 1)) {
				// A WAIT. The wait-for-start-of-next PAL wait of
				// $FFDF,$FFFE seems to suggest evaluation will happen
				// in the next cycle rather than this one.
				state_ = State::Waiting;
				break;
			}

			// Neither a WAIT nor a MOVE => a SKIP.

			skip_next_ = satisfies_raster(position, blitter_status, instruction_);
			state_ = State::FetchFirstWord;
		} break;
	}

	return true;
}
