//	Altirra - Atari 800/800XL/5200 emulator
//	Copyright (C) 2009-2016 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#include <stdafx.h>
#include <at/atcore/scheduler.h>
#include "riot.h"

ATRIOT6532Emulator::ATRIOT6532Emulator() {
}

ATRIOT6532Emulator::~ATRIOT6532Emulator() {
	Shutdown();
}

void ATRIOT6532Emulator::Init(ATScheduler *sch) {
	mpScheduler = sch;
}

void ATRIOT6532Emulator::Shutdown() {
}

void ATRIOT6532Emulator::Reset() {
	mTimerDeadline = mpScheduler->GetTick64() - 1;
	mbTimerIrqPending = false;
	mTimerPrescalerShift = 0;

	mDDRA = 0;
	mORA = 0;
	mDDRB = 0;
	mORB = 0;
	mbEdgePositive = false;
	mbEdgeIrqActive = false;
}

void ATRIOT6532Emulator::SetInputA(uint8 value, uint8 mask) {
	value = ((mInputA ^ value) & mask) ^ mInputA;

	if (mInputA == value)
		return;

	// Check if PA7 is being toggled and is not pulled down, in which case
	// we might have an edge transition.
	if ((mInputA ^ value) & 0x80) {
		// Check for correct polarity.
		if (mbEdgePositive ? (value & 0x80) : !(value & 0x80)) {
			// Check if not being held down by output.
			if (!(mDDRA & 0x80) || (mORA & 0x80))
				mbEdgeIrqActive = true;
		}
	}

	mInputA = value;
}

void ATRIOT6532Emulator::SetInputB(uint8 value, uint8 mask) {
	mInputB ^= ((mInputB ^ value) & mask);
}

uint8 ATRIOT6532Emulator::DebugReadByte(uint8 address) const {
	switch(address & 7) {
		case 0:		// read DRA (outputs merge with inputs)
		default:
			return (mORA | ~mDDRA) & mInputA;

		case 1:		// read DDRA
			return mDDRA;

		case 2:		// read DRB (outputs override inputs)
			return ((mInputB ^ mORB) & mDDRB) ^ mInputB;

		case 3:		// read DDRB
			return mDDRB;

		case 4:		// read timer
		case 6: {
			const uint64 delta = mTimerDeadline - mpScheduler->GetTick64();

			return delta < (UINT64_C(1) << 63) ? (uint8)(delta >> mTimerPrescalerShift) : (uint8)delta;
		}

		case 5:		// read interrupt flag
		case 7: {
			uint8 v = 0;

			if (mbTimerIrqPending && mpScheduler->GetTick64() - mTimerDeadline < (UINT64_C(1) << 63))
				v += 0x80;

			if (mbEdgeIrqActive)
				v += 0x40;

			return v;
		}
	}
}

uint8 ATRIOT6532Emulator::ReadByte(uint8 address) {
	switch(address & 7) {
		case 0:		// read DRA (outputs merge with inputs)
		default:
			return (mORA | ~mDDRA) & mInputA;

		case 1:		// read DDRA
			return mDDRA;

		case 2:		// read DRB (outputs override inputs)
			return ((mInputB ^ mORB) & mDDRB) ^ mInputB;

		case 3:		// read DDRB
			return mDDRB;

		case 4:		// read timer (also clears timer interrupt)
		case 6: {
			const uint64 delta = mTimerDeadline - mpScheduler->GetTick64();

			if (mbTimerIrqPending) {
				// wrap(t > deadline) -> wrap(deadline < t) -> wrap(deadline - t < 0)
				if (delta >= (UINT64_C(1) << 63))
					mbTimerIrqPending = false;
			}

			return delta < (UINT64_C(1) << 63) ? (uint8)(delta >> mTimerPrescalerShift) : (uint8)delta;
		}

		case 5:		// read interrupt flag (also clears PA7 interrupt)
		case 7: {
			uint8 v = 0;

			if (mbTimerIrqPending) {
				const uint64 t = mpScheduler->GetTick64();
				
				if (t - mTimerDeadline < (UINT64_C(1) << 63))
					v += 0x80;
			}

			if (mbEdgeIrqActive) {
				v += 0x40;
				mbEdgeIrqActive = false;
			}

			return v;
		}
	}
}

void ATRIOT6532Emulator::WriteByte(uint8 address, uint8 value) {
	if (address & 4) {
		if (address & 16) {
			// Write timer
			static const int kPrescalerShifts[4] = {
				0,		// 1T
				3,		// 8T
				6,		// 64T
				10		// 1024T (*not* 512T!).
			};

			mTimerPrescalerShift = kPrescalerShifts[address & 3];
			const uint64 t = mpScheduler->GetTick64();
			mTimerDeadline = t + ((value ? value : 256U) << mTimerPrescalerShift);
			mbTimerIrqPending = true;
		} else {
			// Write edge detect control. We don't support IRQs, so the enable
			// flag is ignored.
			mbEdgePositive = (address & 1) != 0;
		}
	} else {
		switch(address & 3) {
			case 0:
			default:
				mORA = value;
				break;

			case 1:
				mDDRA = value;
				break;

			case 2:
				mORB = value;
				break;

			case 3:
				mDDRB = value;
				break;
		}
	}
}
