#include "OneWireSlave.h"

// uncomment this line to enable sending messages along with errors (but takes more program memory)
//#define ERROR_MESSAGES

#ifdef ERROR_MESSAGES
#define ERROR(msg) error_(msg)
#else
#define ERROR(msg) error_(0)
#endif

namespace
{
	const unsigned long ResetMinDuration = 480;
	const unsigned long ResetMaxDuration = 900;

	const unsigned long PresenceWaitDuration = 15;
	const unsigned long PresenceDuration = 200;

	const unsigned long ReadBitSamplingTime = 25;

	const unsigned long SendBitDuration = 35;

	const byte ReceiveCommand = (byte)-1;

	void(*timerEvent)() = 0;
}

OneWireSlave OWSlave;

byte OneWireSlave::rom_[8];
byte OneWireSlave::scratchpad_[8];
Pin OneWireSlave::pin_;

unsigned long OneWireSlave::resetStart_;
unsigned long OneWireSlave::lastReset_;

void(*OneWireSlave::receiveBitCallback_)(bool bit, bool error);
void(*OneWireSlave::bitSentCallback_)(bool error);
void(*OneWireSlave::clientReceiveCallback_)(ReceiveEvent evt, byte data);
void(*OneWireSlave::clientReceiveBitCallback_)(bool bit);

byte OneWireSlave::receivingByte_;

byte OneWireSlave::searchRomBytePos_;
byte OneWireSlave::searchRomBitPos_;
bool OneWireSlave::searchRomInverse_;
bool OneWireSlave::resumeCommandFlag_;
bool OneWireSlave::alarmedFlag_;

const byte* OneWireSlave::sendBuffer_;
byte* OneWireSlave::recvBuffer_;
short OneWireSlave::bufferLength_;
byte OneWireSlave::bufferBitPos_;
short OneWireSlave::bufferPos_;
void(*OneWireSlave::receiveBytesCallback_)(bool error);
void(*OneWireSlave::sendBytesCallback_)(bool error);

volatile bool OneWireSlave::waitingSynchronousWriteToComplete_;
volatile bool OneWireSlave::synchronousWriteError_;

bool OneWireSlave::sendingClientBytes_;

bool OneWireSlave::singleBit_;
bool OneWireSlave::singleBitRepeat_;
void(*OneWireSlave::singleBitSentCallback_)(bool error);

void(*OneWireSlave::logCallback_)(const char* message);


ISR(USERTIMER_COMPA_vect) // timer1 interrupt
{
	UserTimer_Stop(); // disable clock
	void(*event)() = timerEvent;
	timerEvent = 0;
	event();
}

void OneWireSlave::begin(const byte* rom, byte pinNumber)
{
	pin_ = Pin(pinNumber);
	resetStart_ = (unsigned long)-1;
	lastReset_ = 0;

	memcpy(rom_, rom, 7);
	rom_[7] = crc8(rom_, 7);

	resumeCommandFlag_ = false;
	alarmedFlag_       = false;

	clientReceiveBitCallback_ = 0;
	sendingClientBytes_ = false;

	// log("Enabling 1-wire library")

	cli(); // disable interrupts
	pin_.inputMode();
	pin_.writeLow(); // make sure the internal pull-up resistor is disabled

	// prepare hardware timer
	UserTimer_Init();

	// start 1-wire activity
	beginWaitReset_();
	sei(); // enable interrupts
}

void OneWireSlave::end()
{
	// log("Disabling 1-wire library");

	cli();
	disableTimer_();
	pin_.detachInterrupt();
	releaseBus_();
	sei();
}

bool OneWireSlave::write(const byte* bytes, short numBytes)
{
	// TODO: put the arduino to sleep between interrupts to save power?
	waitingSynchronousWriteToComplete_ = true;
	beginWrite(bytes, numBytes, &OneWireSlave::onSynchronousWriteComplete_);
	while (waitingSynchronousWriteToComplete_)
		delay(1);
	return !synchronousWriteError_;
}

void OneWireSlave::onSynchronousWriteComplete_(bool error)
{
	synchronousWriteError_ = error;
	waitingSynchronousWriteToComplete_ = false;
}

void OneWireSlave::beginWrite(const byte* bytes, short numBytes, void(*complete)(bool error))
{
	cli();
	endWrite_(true);
	sendingClientBytes_ = true;
	beginWriteBytes_(bytes, numBytes, complete == 0 ? noOpCallback_ : complete);
	sei();
}

void OneWireSlave::endWrite_(bool error, bool resetInterrupts)
{
	if(resetInterrupts)
		beginWaitReset_();

	if (sendingClientBytes_)
	{
		sendingClientBytes_ = false;
		if (sendBytesCallback_ != 0)
		{
			void(*callback)(bool error) = sendBytesCallback_;
			sendBytesCallback_ = noOpCallback_;
			callback(error);
		}
	}
	else if (singleBitSentCallback_ != 0)
	{
		void(*callback)(bool) = singleBitSentCallback_;
		singleBitSentCallback_ = 0;
		callback(error);
	}
}

bool OneWireSlave::writeBit(bool value)
{
	// TODO: put the arduino to sleep between interrupts to save power?
	waitingSynchronousWriteToComplete_ = true;
	beginWriteBit(value, false, &OneWireSlave::onSynchronousWriteComplete_);
	while (waitingSynchronousWriteToComplete_)
		delay(1);
	return !synchronousWriteError_;
}

void OneWireSlave::beginWriteBit(bool value, bool repeat, void(*bitSent)(bool))
{
	cli();
	endWrite_(true);

	singleBit_ = value;
	singleBitRepeat_ = repeat;
	singleBitSentCallback_ = bitSent;
	beginSendBit_(value, &OneWireSlave::onSingleBitSent_);
	sei();
}

void OneWireSlave::onSingleBitSent_(bool error)
{
	if (!error && singleBitRepeat_)
	{
		beginSendBit_(singleBit_, &OneWireSlave::onSingleBitSent_);
	}
	else
	{
		beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
	}

	if (singleBitSentCallback_ != 0)
	{
		void(*callback)(bool) = singleBitSentCallback_;
		singleBitSentCallback_ = 0;
		callback(error);
	}
}

void OneWireSlave::stopWrite()
{
	beginWrite(0, 0, 0);
}

void OneWireSlave::alarmed(bool value)
{
	alarmedFlag_ = value;
}

byte OneWireSlave::crc8(const byte* data, short numBytes)
{
	byte crc = 0;

	while (numBytes--) {
		byte inbyte = *data++;
		for (byte i = 8; i; i--) {
			byte mix = (crc ^ inbyte) & 0x01;
			crc >>= 1;
			if (mix) crc ^= 0x8C;
			inbyte >>= 1;
		}
	}
	return crc;
}

void OneWireSlave::setTimerEvent_(short delayMicroSeconds, void(*handler)())
{
	delayMicroSeconds -= 10; // remove overhead (tuned on Arduino Uno)

	short skipTicks = (delayMicroSeconds - 3) / 4; // round the micro seconds delay to a number of ticks to skip (4us per tick, so 4us must skip 0 tick, 8us must skip 1 tick, etc.)
	if (skipTicks < 1) skipTicks = 1;
	timerEvent = handler;
	UserTimer_Run(skipTicks);
}

void OneWireSlave::disableTimer_()
{
	UserTimer_Stop();
}

void OneWireSlave::onEnterInterrupt_()
{
}

void OneWireSlave::onLeaveInterrupt_()
{
}

void OneWireSlave::error_(const char* message)
{
	if (logCallback_ != 0)
		logCallback_(message);
	endWrite_(true);
	if (clientReceiveCallback_ != 0)
		clientReceiveCallback_(RE_Error, 0);
}

void OneWireSlave::pullLow_()
{
	pin_.outputMode();
	pin_.writeLow();
}

void OneWireSlave::releaseBus_()
{
	pin_.inputMode();
}

void OneWireSlave::beginResetDetection_()
{
	setTimerEvent_(ResetMinDuration - 50, &OneWireSlave::resetCheck_);
	resetStart_ = micros() - 50;
}

void OneWireSlave::beginResetDetectionSendZero_()
{
	setTimerEvent_(ResetMinDuration - SendBitDuration - 50, &OneWireSlave::resetCheck_);
	resetStart_ = micros() - SendBitDuration - 50;
}

void OneWireSlave::cancelResetDetection_()
{
	disableTimer_();
	resetStart_ = (unsigned long)-1;
}

void OneWireSlave::resetCheck_()
{
	onEnterInterrupt_();
	if (!pin_.read())
	{
		pin_.attachInterrupt(&OneWireSlave::waitReset_, CHANGE);
		// log("Reset detected during another operation");
	}
	onLeaveInterrupt_();
}

void OneWireSlave::beginReceiveBit_(void(*completeCallback)(bool bit, bool error))
{
	receiveBitCallback_ = completeCallback;
	pin_.attachInterrupt(&OneWireSlave::receive_, FALLING);
}

void OneWireSlave::receive_()
{
	onEnterInterrupt_();

	pin_.detachInterrupt();
	setTimerEvent_(ReadBitSamplingTime, &OneWireSlave::readBit_);

	onLeaveInterrupt_();
}

void OneWireSlave::readBit_()
{
	onEnterInterrupt_();

	bool bit = pin_.read();
	if (bit)
		cancelResetDetection_();
	else
		beginResetDetection_();
	receiveBitCallback_(bit, false);
	//dbgOutput.writeLow();
	//dbgOutput.writeHigh();

	onLeaveInterrupt_();
}

void OneWireSlave::beginSendBit_(bool bit, void(*completeCallback)(bool error))
{
	bitSentCallback_ = completeCallback;
	if (bit)
	{
		pin_.attachInterrupt(&OneWireSlave::sendBitOne_, FALLING);
	}
	else
	{
		pin_.attachInterrupt(&OneWireSlave::sendBitZero_, FALLING);
	}
}

void OneWireSlave::sendBitOne_()
{
	onEnterInterrupt_();

	beginResetDetection_();
	bitSentCallback_(false);

	onLeaveInterrupt_();
}

void OneWireSlave::sendBitZero_()
{
	pullLow_(); // this must be executed first because the timing is very tight with some master devices

	onEnterInterrupt_();

	pin_.detachInterrupt();
	setTimerEvent_(SendBitDuration, &OneWireSlave::endSendBitZero_);

	onLeaveInterrupt_();
}

void OneWireSlave::endSendBitZero_()
{
	onEnterInterrupt_();

	releaseBus_();
	beginResetDetectionSendZero_();
	bitSentCallback_(false);

	onLeaveInterrupt_();
}

void OneWireSlave::beginWaitReset_()
{
	disableTimer_();
	pin_.inputMode();
	pin_.attachInterrupt(&OneWireSlave::waitReset_, CHANGE);
	resetStart_ = (unsigned int)-1;
}

void OneWireSlave::waitReset_()
{
	onEnterInterrupt_();
	bool state = pin_.read();
	unsigned long now = micros();
	if (state)
	{
		if (resetStart_ == (unsigned int)-1)
		{
			onLeaveInterrupt_();
			return;
		}

		unsigned long resetDuration = now - resetStart_;
		resetStart_ = (unsigned int)-1;
		if (resetDuration >= ResetMinDuration)
		{
			if (resetDuration > ResetMaxDuration)
			{
				ERROR("Reset too long");
				onLeaveInterrupt_();
				return;
			}

			lastReset_ = now;
			pin_.detachInterrupt();
			unsigned long alreadyElapsedTime = micros() - now;
			setTimerEvent_(alreadyElapsedTime < PresenceWaitDuration ? PresenceWaitDuration - alreadyElapsedTime : 0, &OneWireSlave::beginPresence_);
			endWrite_(true, false);
			if (clientReceiveCallback_ != 0)
				clientReceiveCallback_(RE_Reset, 0);
		}
	}
	else
	{
		resetStart_ = now;
	}
	onLeaveInterrupt_();
}

void OneWireSlave::beginPresence_()
{
	pullLow_();
	setTimerEvent_(PresenceDuration, &OneWireSlave::endPresence_);
}

void OneWireSlave::endPresence_()
{
	releaseBus_();

	beginWaitCommand_();
}

void OneWireSlave::beginWaitCommand_()
{
	bufferPos_ = ReceiveCommand;
	beginReceive_();
}

void OneWireSlave::beginReceive_()
{
	receivingByte_ = 0;
	bufferBitPos_ = 0;
	beginReceiveBit_(&OneWireSlave::onBitReceived_);
}

void OneWireSlave::onBitReceived_(bool bit, bool error)
{
	if (error)
	{
		ERROR("Invalid bit");
		if (bufferPos_ >= 0)
			receiveBytesCallback_(true);
		return;
	}

	receivingByte_ |= ((bit ? 1 : 0) << bufferBitPos_);
	++bufferBitPos_;

	if (clientReceiveBitCallback_ != 0 && bufferPos_ != ReceiveCommand)
		clientReceiveBitCallback_(bit);

	if (bufferBitPos_ == 8)
	{
		// log("received byte", (long)receivingByte_);

		if (bufferPos_ == ReceiveCommand)
		{
			bufferPos_ = 0;
			switch (receivingByte_)
			{
			case 0xF0: // SEARCH ROM
				resumeCommandFlag_ = false;
				beginSearchRom_();
				return;
			case 0xEC: // CONDITIONAL SEARCH ROM
				resumeCommandFlag_ = false;
				if (alarmedFlag_)
				{
					beginSearchRom_();
				}
				else
				{
					beginWaitReset_();
				}
				return;
			case 0x33: // READ ROM
				resumeCommandFlag_ = false;
				beginWriteBytes_(rom_, 8, &OneWireSlave::noOpCallback_);
				return;
			case 0x55: // MATCH ROM
				resumeCommandFlag_ = false;
				beginReceiveBytes_(scratchpad_, 8, &OneWireSlave::matchRomBytesReceived_);
				return;
			case 0xCC: // SKIP ROM
				resumeCommandFlag_ = false;
				beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
				return;
			case 0xA5: // RESUME
				if (resumeCommandFlag_)
				{
					beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
				}
				else
				{
					beginWaitReset_();
				}
				return;
			default:
				ERROR("Unknown command");
				return;
			}
		}
		else
		{
			recvBuffer_[bufferPos_++] = receivingByte_;
			receivingByte_ = 0;
			bufferBitPos_ = 0;
			if (bufferPos_ == bufferLength_)
			{
				beginWaitReset_();
				receiveBytesCallback_(false);
				return;
			}
		}
	}
	
	beginReceiveBit_(&OneWireSlave::onBitReceived_);
}

void OneWireSlave::beginSearchRom_()
{
	searchRomBytePos_ = 0;
	searchRomBitPos_ = 0;
	searchRomInverse_ = false;

	beginSearchRomSendBit_();
}

void OneWireSlave::beginSearchRomSendBit_()
{
	byte currentByte = rom_[searchRomBytePos_];
	bool currentBit = bitRead(currentByte, searchRomBitPos_);
	bool bitToSend = searchRomInverse_ ? !currentBit : currentBit;

	beginSendBit_(bitToSend, &OneWireSlave::continueSearchRom_);
}

void OneWireSlave::continueSearchRom_(bool error)
{
	if (error)
	{
		ERROR("Failed to send bit");
		return;
	}

	searchRomInverse_ = !searchRomInverse_;
	if (searchRomInverse_)
	{
		beginSearchRomSendBit_();
	}
	else
	{
		beginReceiveBit_(&OneWireSlave::searchRomOnBitReceived_);
	}
}

void OneWireSlave::searchRomOnBitReceived_(bool bit, bool error)
{
	if (error)
	{
		ERROR("Bit read error during ROM search");
		return;
	}

	byte currentByte = rom_[searchRomBytePos_];
	bool currentBit = bitRead(currentByte, searchRomBitPos_);

	if (bit == currentBit)
	{
		++searchRomBitPos_;
		if (searchRomBitPos_ == 8)
		{
			searchRomBitPos_ = 0;
			++searchRomBytePos_;
		}

		if (searchRomBytePos_ == 8)
		{
			// log("ROM sent entirely");

			beginWaitReset_();
		}
		else
		{
			beginSearchRomSendBit_();
		}
	}
	else
	{
		// log("Leaving ROM search");
		beginWaitReset_();
	}
}

void OneWireSlave::beginWriteBytes_(const byte* data, short numBytes, void(*complete)(bool error))
{
	sendBuffer_ = data;
	bufferLength_ = numBytes;
	bufferPos_ = 0;
	bufferBitPos_ = 0;
	sendBytesCallback_ = complete;

	if (sendBuffer_ != 0 && bufferLength_ > 0)
	{
		bool bit = bitRead(sendBuffer_[0], 0);
		beginSendBit_(bit, &OneWireSlave::bitSent_);
	}
	else
	{
		endWrite_(true);
		beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
	}
}

void OneWireSlave::bitSent_(bool error)
{
	if (error)
	{
		ERROR("error sending a bit");
		sendBytesCallback_(true);
		return;
	}

	++bufferBitPos_;
	if (bufferBitPos_ == 8)
	{
		bufferBitPos_ = 0;
		++bufferPos_;
	}

	if (bufferPos_ == bufferLength_)
	{
		endWrite_(false);
		sendBytesCallback_(false);
		return;
	}

	bool bit = bitRead(sendBuffer_[bufferPos_], bufferBitPos_);
	beginSendBit_(bit, &OneWireSlave::bitSent_);
}

void OneWireSlave::beginReceiveBytes_(byte* buffer, short numBytes, void(*complete)(bool error))
{
	recvBuffer_ = buffer;
	bufferLength_ = numBytes;
	bufferPos_ = 0;
	receiveBytesCallback_ = complete;
	beginReceive_();
}

void OneWireSlave::noOpCallback_(bool error)
{
	if (error)
		ERROR("error during an internal 1-wire operation");
}

void OneWireSlave::matchRomBytesReceived_(bool error)
{
	if (error)
	{
		resumeCommandFlag_ = false;
		ERROR("error receiving match rom bytes");
		return;
	}

	if (memcmp(rom_, scratchpad_, 8) == 0)
	{
		// log("ROM matched");
		resumeCommandFlag_ = true;
		beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
	}
	else
	{
		// log("ROM not matched");
		resumeCommandFlag_ = false;
		beginWaitReset_();
	}
}

void OneWireSlave::notifyClientByteReceived_(bool error)
{
	if (error)
	{
		if (clientReceiveCallback_ != 0)
			clientReceiveCallback_(RE_Error, 0);
		ERROR("error receiving custom bytes");
		return;
	}

	beginReceiveBytes_(scratchpad_, 1, &OneWireSlave::notifyClientByteReceived_);
	if (clientReceiveCallback_ != 0)
		clientReceiveCallback_(RE_Byte, scratchpad_[0]);
}