//
//  Tape.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 18/01/2016.
//  Copyright © 2016 Thomas Harte. All rights reserved.
//

#include "Tape.hpp"
#include "../../NumberTheory/Factors.hpp"

using namespace Storage::Tape;

#pragma mark - Lifecycle

TapePlayer::TapePlayer(unsigned int input_clock_rate) :
	TimedEventLoop(input_clock_rate)
{}

#pragma mark - Seeking

void Storage::Tape::Tape::seek(Time &seek_time) {
	Time next_time(0);
	reset();
	while(next_time <= seek_time) {
		get_next_pulse();
		next_time += pulse_.length;
	}
}

Storage::Time Tape::get_current_time() {
	Time time(0);
	uint64_t steps = get_offset();
	reset();
	while(steps--) {
		get_next_pulse();
		time += pulse_.length;
	}
	return time;
}

void Storage::Tape::Tape::reset() {
	offset_ = 0;
	virtual_reset();
}

Tape::Pulse Tape::get_next_pulse() {
	pulse_ = virtual_get_next_pulse();
	offset_++;
	return pulse_;
}

uint64_t Tape::get_offset() {
	return offset_;
}

void Tape::set_offset(uint64_t offset) {
	if(offset == offset_) return;
	if(offset < offset_) {
		reset();
	}
	offset -= offset_;
	while(offset--) get_next_pulse();
}

#pragma mark - Player

bool TapePlayer::is_sleeping() {
	return !tape_ || tape_->is_at_end();
}

void TapePlayer::set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
	tape_ = tape;
	reset_timer();
	get_next_pulse();
	update_sleep_observer();
}

std::shared_ptr<Storage::Tape::Tape> TapePlayer::get_tape() {
	return tape_;
}

bool TapePlayer::has_tape() {
	return (bool)tape_;
}

void TapePlayer::get_next_pulse() {
	// get the new pulse
	if(tape_) {
		current_pulse_ = tape_->get_next_pulse();
		if(tape_->is_at_end()) update_sleep_observer();
	}
	else {
		current_pulse_.length.length = 1;
		current_pulse_.length.clock_rate = 1;
		current_pulse_.type = Tape::Pulse::Zero;
	}

	set_next_event_time_interval(current_pulse_.length);
}

void TapePlayer::run_for(const Cycles cycles) {
	if(has_tape()) {
		TimedEventLoop::run_for(cycles);
	}
}

void TapePlayer::run_for_input_pulse() {
	jump_to_next_event();
}

void TapePlayer::process_next_event() {
	process_input_pulse(current_pulse_);
	get_next_pulse();
}

#pragma mark - Binary Player

BinaryTapePlayer::BinaryTapePlayer(unsigned int input_clock_rate) :
	TapePlayer(input_clock_rate), motor_is_running_(false), input_level_(false), delegate_(nullptr)
{}

bool BinaryTapePlayer::is_sleeping() {
	return !motor_is_running_ || TapePlayer::is_sleeping();
}

void BinaryTapePlayer::set_motor_control(bool enabled) {
	if(motor_is_running_ != enabled) {
		motor_is_running_ = enabled;
		update_sleep_observer();
	}
}

void BinaryTapePlayer::set_tape_output(bool set) {
	// TODO
}

bool BinaryTapePlayer::get_input() {
	return motor_is_running_ && input_level_;
}

void BinaryTapePlayer::run_for(const Cycles cycles) {
	if(motor_is_running_) TapePlayer::run_for(cycles);
}

void BinaryTapePlayer::set_delegate(Delegate *delegate) {
	delegate_ = delegate;
}

void BinaryTapePlayer::process_input_pulse(const Storage::Tape::Tape::Pulse &pulse) {
	bool new_input_level = pulse.type == Tape::Pulse::High;
	if(input_level_ != new_input_level) {
		input_level_ = new_input_level;
		if(delegate_) delegate_->tape_did_change_input(this);
	}
}
