////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia 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.
//
// Nestopia 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 Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include <cstring>
#include "../NstTimer.hpp"
#include "NstBoard.hpp"
#include "NstBoardBandai.hpp"

namespace Nes
{
	namespace Core
	{
		namespace Boards
		{
			namespace Bandai
			{
				#ifdef NST_MSVC_OPTIMIZE
				#pragma optimize("s", on)
				#endif

				Datach::Datach(const Context& c)
				:
				Lz93d50Ex     (c),
				barcodeReader (*c.cpu)
				{
				}

				Datach::Reader::Reader(Cpu& c)
				: cpu(c)
				{
					Reset( false );
				}

				Datach::Device Datach::QueryDevice(DeviceType devType)
				{
					if (devType == DEVICE_BARCODE_READER)
						return static_cast<BarcodeReader*>(&barcodeReader);
					else
						return Lz93d50Ex::QueryDevice( devType );
				}

				void Datach::SubReset(const bool hard)
				{
					Lz93d50Ex::SubReset( hard );

					barcodeReader.Reset();
					p6000 = cpu.Map( 0x6000 );

					for (uint i=0x6000; i < 0x8000; i += 0x100)
						Map( i, &Datach::Peek_6000 );
				}

				void Datach::SubLoad(State::Loader& state,const dword baseChunk)
				{
					if (baseChunk == AsciiId<'B','D','A'>::V)
					{
						while (const dword chunk = state.Begin())
						{
							if (chunk == AsciiId<'B','R','C'>::V)
								barcodeReader.LoadState( state );

							state.End();
						}
					}
					else
					{
						Lz93d50Ex::SubLoad( state, baseChunk );
					}
				}

				void Datach::SubSave(State::Saver& state) const
				{
					Lz93d50Ex::SubSave( state );

					state.Begin( AsciiId<'B','D','A'>::V );
					barcodeReader.SaveState( state, AsciiId<'B','R','C'>::V );
					state.End();
				}

				void Datach::Reader::Reset(bool initHook)
				{
					cycles = Cpu::CYCLE_MAX;
					output = 0x00;
					stream = data;
					std::memset( data, END, MAX_DATA_LENGTH );

					if (initHook)
						cpu.AddHook( Hook(this,&Reader::Hook_Fetcher) );
				}

				bool Datach::Reader::IsTransferring() const
				{
					return *stream != END;
				}

				bool Datach::Reader::IsDigitsSupported(uint count) const
				{
					return count == MIN_DIGITS || count == MAX_DIGITS;
				}

				void Datach::Reader::LoadState(State::Loader& state)
				{
					Reset( false );

					while (const dword chunk = state.Begin())
					{
						switch (chunk)
						{
							case AsciiId<'P','T','R'>::V:

								stream = data + (state.Read8() & (MAX_DATA_LENGTH-1));
								break;

							case AsciiId<'D','A','T'>::V:

								state.Uncompress( data );
								data[MAX_DATA_LENGTH-1] = END;
								break;

							case AsciiId<'C','Y','C'>::V:

								cycles = state.Read16();
								break;
						}

						state.End();
					}

					if (Reader::IsTransferring())
					{
						output = (stream != data) ? stream[-1] : 0x00;

						if (cycles > CC_INTERVAL)
							cycles = CC_INTERVAL;

						cycles = cpu.GetCycles() + cpu.GetClock() * cycles;
					}
					else
					{
						cycles = Cpu::CYCLE_MAX;
						output = 0x00;
					}
				}

				void Datach::Reader::SaveState(State::Saver& state,const dword baseChunk) const
				{
					if (Reader::IsTransferring())
					{
						NST_ASSERT( cycles != Cpu::CYCLE_MAX );

						state.Begin( baseChunk );

						state.Begin( AsciiId<'P','T','R'>::V ).Write8( stream - data ).End();
						state.Begin( AsciiId<'D','A','T'>::V ).Compress( data ).End();

						uint next;

						if (cycles > cpu.GetCycles())
							next = (cycles - cpu.GetCycles()) / cpu.GetClock();
						else
							next = 0;

						state.Begin( AsciiId<'C','Y','C'>::V ).Write16( next ).End();

						state.End();
					}
				}

				bool Datach::Reader::Transfer(cstring const string,const uint length)
				{
					Reset( false );

					if (!string || (length != MAX_DIGITS && length != MIN_DIGITS))
						return false;

					static const byte prefixParityType[10][6] =
					{
						{8,8,8,8,8,8}, {8,8,0,8,0,0},
						{8,8,0,0,8,0}, {8,8,0,0,0,8},
						{8,0,8,8,0,0}, {8,0,0,8,8,0},
						{8,0,0,0,8,8}, {8,0,8,0,8,0},
						{8,0,8,0,0,8}, {8,0,0,8,0,8}
					};

					static const byte dataLeftOdd[10][7] =
					{
						{8,8,8,0,0,8,0}, {8,8,0,0,8,8,0},
						{8,8,0,8,8,0,0}, {8,0,0,0,0,8,0},
						{8,0,8,8,8,0,0}, {8,0,0,8,8,8,0},
						{8,0,8,0,0,0,0}, {8,0,0,0,8,0,0},
						{8,0,0,8,0,0,0}, {8,8,8,0,8,0,0}
					};

					static const byte dataLeftEven[10][7] =
					{
						{8,0,8,8,0,0,0}, {8,0,0,8,8,0,0},
						{8,8,0,0,8,0,0}, {8,0,8,8,8,8,0},
						{8,8,0,0,0,8,0}, {8,0,0,0,8,8,0},
						{8,8,8,8,0,8,0}, {8,8,0,8,8,8,0},
						{8,8,8,0,8,8,0}, {8,8,0,8,0,0,0}
					};

					static const byte dataRight[10][7] =
					{
						{0,0,0,8,8,0,8}, {0,0,8,8,0,0,8},
						{0,0,8,0,0,8,8}, {0,8,8,8,8,0,8},
						{0,8,0,0,0,8,8}, {0,8,8,0,0,0,8},
						{0,8,0,8,8,8,8}, {0,8,8,8,0,8,8},
						{0,8,8,0,8,8,8}, {0,0,0,8,0,8,8}
					};

					byte code[16];

					for (uint i=0; i < length; ++i)
					{
						if (string[i] >= '0' && string[i] <= '9')
							code[i] = string[i] - '0';
						else
							return false;
					}

					byte* NST_RESTRICT output = data;

					for (uint i=0; i < 1+32; ++i)
						*output++ = 8;

					*output++ = 0;
					*output++ = 8;
					*output++ = 0;

					uint sum = 0;

					if (length == MAX_DIGITS)
					{
						for (uint i=0; i < 6; ++i)
						{
							if (prefixParityType[code[0]][i])
							{
								for (uint j=0; j < 7; ++j)
									*output++ = dataLeftOdd[code[i+1]][j];
							}
							else
							{
								for (uint j=0; j < 7; ++j)
									*output++ = dataLeftEven[code[i+1]][j];
							}
						}

						*output++ = 8;
						*output++ = 0;
						*output++ = 8;
						*output++ = 0;
						*output++ = 8;

						for (uint i=7; i < 12; ++i)
						{
							for (uint j=0; j < 7; ++j)
								*output++ = dataRight[code[i]][j];
						}

						for (uint i=0; i < 12; ++i)
							sum += (i & 1) ? (code[i] * 3) : (code[i] * 1);
					}
					else
					{
						for (uint i=0; i < 4; ++i)
						{
							for (uint j=0; j < 7; ++j)
								*output++ = dataLeftOdd[code[i]][j];
						}

						*output++ = 8;
						*output++ = 0;
						*output++ = 8;
						*output++ = 0;
						*output++ = 8;

						for (uint i=4; i < 7; ++i)
						{
							for (uint j=0; j < 7; ++j)
								*output++ = dataRight[code[i]][j];
						}

						for (uint i=0; i < 7; ++i)
							sum += (i & 1) ? (code[i] * 1) : (code[i] * 3);
					}

					sum = (10 - (sum % 10)) % 10;

					for (uint i=0; i < 7; ++i)
						*output++ = dataRight[sum][i];

					*output++ = 0;
					*output++ = 8;
					*output++ = 0;

					for (uint i=0; i < 32; ++i)
						*output++ = 8;

					cycles = cpu.GetCycles() + cpu.GetClock() * CC_INTERVAL;

					return true;
				}

				#ifdef NST_MSVC_OPTIMIZE
				#pragma optimize("", on)
				#endif

				NES_HOOK(Datach::Reader,Fetcher)
				{
					while (cycles <= cpu.GetCycles())
					{
						output = *stream;
						stream += (output != END);

						if (output != END)
						{
							cycles += cpu.GetClock() * CC_INTERVAL;
						}
						else
						{
							output = 0x00;
							cycles = Cpu::CYCLE_MAX;
							break;
						}
					}
				}

				inline uint Datach::Reader::GetOutput() const
				{
					return output;
				}

				NES_PEEK_A(Datach,6000)
				{
					return barcodeReader.GetOutput() | p6000.Peek( address );
				}

				inline void Datach::Reader::Sync()
				{
					if (cycles != Cpu::CYCLE_MAX)
					{
						if (cycles >= cpu.GetFrameCycles())
							cycles -= cpu.GetFrameCycles();
						else
							cycles = 0;
					}
				}

				void Datach::Sync(Event event,Input::Controllers* controllers)
				{
					if (event == EVENT_END_FRAME)
						barcodeReader.Sync();

					Lz93d50Ex::Sync( event, controllers );
				}
			}
		}
	}
}
