//
//  Spectrum.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 07/03/2021.
//  Copyright © 2021 Thomas Harte. All rights reserved.
//

#ifndef Storage_Tape_Parsers_Spectrum_hpp
#define Storage_Tape_Parsers_Spectrum_hpp

#include "TapeParser.hpp"

#include <array>
#include <optional>
#include <vector>

namespace Storage {
namespace Tape {
namespace ZXSpectrum {

enum class WaveType {
	// All references to 't-states' below are cycles relative to the
	// ZX Spectrum's 3.5Mhz processor.

	Pilot,	// Nominally 2168 t-states.
	Zero,	// 855 t-states.
	One,	// 1710 t-states.
	Gap,
};

// Formally, there are two other types of wave:
//
//	Sync1,	// 667 t-states.
//	Sync2,	// 735 t-states.
//
// Non-Spectrum machines often just output a plain zero symbol instead of
// a two-step sync; this parser treats anything close enough to a zero
// as a sync.

enum class SymbolType {
	Zero,
	One,
	Pilot,
	Gap,
};

/// A block is anything that follows a period of pilot tone; on a Spectrum that might be a
/// file header or the file contents; on a CPC it might be a file header or a single chunk providing
/// partial file contents. The Enterprise seems broadly to follow the Spectrum but the internal
/// byte structure differs.
struct Block {
	uint8_t type = 0;
};

class Parser: public Storage::Tape::PulseClassificationParser<WaveType, SymbolType> {
	public:
		enum class MachineType {
			ZXSpectrum,
			Enterprise,
			SAMCoupe,
			AmstradCPC
		};
		Parser(MachineType);

		/*!
			Calibrates the expected data speed using a value in the CPC's native tape-speed measurement scale.
		*/
		void set_cpc_read_speed(uint8_t);

		/*!
			Finds the next block from the tape, if any.

			Following this call the tape will be positioned immediately after the byte that indicated the block type —
			in Spectrum-world this seems to be called the flag byte. This call can therefore be followed up with one
			of the get_ methods.
		*/
		std::optional<Block> find_block(const std::shared_ptr<Storage::Tape::Tape> &tape);

		/*!
			Reads the contents of the rest of this block, until the next gap.
		*/
		std::vector<uint8_t> get_block_body(const std::shared_ptr<Storage::Tape::Tape> &tape);

		/*!
			Reads a single byte from the tape, if there is one left, updating the internal checksum.

			The checksum is computed as an exclusive OR of all bytes read.
		*/
		std::optional<uint8_t> get_byte(const std::shared_ptr<Storage::Tape::Tape> &tape);

		/*!
			Seeds the internal checksum.
		*/
		void seed_checksum(uint8_t value = 0x00);

		/*!
			Push a pulse; primarily provided for Storage::Tape::PulseClassificationParser but also potentially useful
			for picking up fast loading from an ongoing tape.
		*/
		void process_pulse(const Storage::Tape::Tape::Pulse &pulse) override;

	private:
		const MachineType machine_type_;
		constexpr bool should_flip_bytes() {
			return machine_type_ == MachineType::Enterprise;
		}
		constexpr bool should_detect_speed() {
			return machine_type_ != MachineType::ZXSpectrum;
		}

		void inspect_waves(const std::vector<WaveType> &waves) override;

		uint8_t checksum_ = 0;

		enum class SpeedDetectionPhase {
			WaitingForGap,
			WaitingForPilot,
			CalibratingPilot,
			Done
		} speed_phase_ = SpeedDetectionPhase::Done;

		float too_long_ = 2600.0f;
		float too_short_ = 600.0f;
		float is_pilot_ = 1939.0f;
		float is_one_ = 1282.0f;

		std::array<float, 8> calibration_pulses_;
		size_t calibration_pulse_pointer_ = 0;

		void set_cpc_one_zero_boundary(float);
};

}
}
}

#endif /* Spectrum_hpp */
