//
//   Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
//
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License version 2 as
//   published by the Free Software Foundation.
//
//   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 version 2 for more details.
//
//   You should have received a copy of the GNU General Public License
//   version 2 along with this program; if not, write to the
//   Free Software Foundation, Inc.,
//   51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
//

#ifndef VIDEO_H
#define VIDEO_H

#include "interruptrequester.h"
#include "minkeeper.h"
#include "osd_element.h"
#include "scoped_ptr.h"
#include "video/lyc_irq.h"
#include "video/mstat_irq.h"
#include "video/next_m0_time.h"
#include "video/ppu.h"

namespace gambatte {

class VideoInterruptRequester {
public:
	explicit VideoInterruptRequester(InterruptRequester &intreq)
	: intreq_(intreq)
	{
	}

	void flagHdmaReq() const;
	void flagIrq(unsigned) const;
	void flagIrq(unsigned, unsigned long) const;
	void setNextEventTime(unsigned long) const;

private:
	InterruptRequester &intreq_;
};

class LCD {
public:
	LCD(unsigned char const*, unsigned char const*,
		VideoInterruptRequester);
	void reset(unsigned char const*, unsigned char const*, bool);
	void setStatePtrs(SaveState&);
	void saveState(SaveState&) const;
	void loadState(SaveState const&, unsigned char const*);
	void setCgbColorCorrection(unsigned);
	void setDmgPaletteColor(unsigned, unsigned, unsigned long);
	void setVideoBuffer(uint_least32_t*, std::ptrdiff_t);
	void setOsdElement(transfer_ptr<OsdElement>);

	void dmgBgPaletteChange(unsigned, unsigned long);
	void dmgSpPalette1Change(unsigned, unsigned long);
	void dmgSpPalette2Change(unsigned, unsigned long);
	void cgbBgColorChange(unsigned, unsigned, unsigned long);
	void cgbSpColorChange(unsigned, unsigned, unsigned long);
	unsigned cgbBgColorRead(unsigned, unsigned long);
	unsigned cgbSpColorRead(unsigned, unsigned long);

	void updateScreen(bool, unsigned long);
	void resetCc(unsigned long, unsigned long);
	void speedChange(unsigned long);
	bool vramReadable(unsigned long);
	bool vramWritable(unsigned long);
	bool oamReadable(unsigned long);
	bool oamWritable(unsigned long);
	void wxChange(unsigned, unsigned long);
	void wyChange(unsigned, unsigned long);
	void oamChange(unsigned long);
	void oamChange(const unsigned char*, unsigned long);
	void scxChange(unsigned, unsigned long);
	void scyChange(unsigned, unsigned long);
	void vramChange(unsigned long);
	unsigned getStat(unsigned, unsigned long);

	unsigned getLyReg(unsigned long const);

	unsigned long nextMode1IrqTime() const;
	void lcdcChange(unsigned, unsigned long);
	void lcdstatChange(unsigned, unsigned long);
	void lycRegChange(unsigned, unsigned long);
	void enableHdma(unsigned long);
	void disableHdma(unsigned long);
	bool isHdmaPeriod(unsigned long);
	bool hdmaIsEnabled() const;
	void update(unsigned long);
	bool isCgb() const;
	bool isDoubleSpeed() const;

private:
	enum Event { event_mem,
	             event_ly, event_last = event_ly };

	enum MemEvent { memevent_oneshot_statirq,
	                memevent_oneshot_updatewy2,
	                memevent_m1irq,
	                memevent_lycirq,
	                memevent_spritemap,
	                memevent_hdma,
	                memevent_m2irq,
	                memevent_m0irq, memevent_last = memevent_m0irq };

	enum { num_events = event_last + 1 };
	enum { num_memevents = memevent_last + 1 };

	class EventTimes {
	public:
		explicit EventTimes(VideoInterruptRequester memEventRequester)
		: eventMin_(disabled_time)
		, memEventMin_(disabled_time)
		, memEventRequester_(memEventRequester)
		{
		}

		Event nextEvent() const;
		unsigned long nextEventTime() const;
		unsigned long operator()(Event) const;
		template<Event> void set(unsigned long);
		void set(Event, unsigned long);

		MemEvent nextMemEvent() const;
		unsigned long nextMemEventTime() const;
		unsigned long operator()(MemEvent) const;

		template<MemEvent> void setm(unsigned long);
		void set(MemEvent, unsigned long);

		void flagIrq(unsigned);
		void flagIrq(unsigned, unsigned long);
		void flagHdmaReq();

	private:
		MinKeeper<num_events> eventMin_;
		MinKeeper<num_memevents> memEventMin_;
		VideoInterruptRequester memEventRequester_;

		void setMemEvent();
	};

	PPU ppu_;
	unsigned long dmgColorsRgb32_[3][num_palette_entries];
	unsigned char  bgpData_[2 * max_num_palettes * num_palette_entries];
	unsigned char objpData_[2 * max_num_palettes * num_palette_entries];
	EventTimes eventTimes_;
	MStatIrqEvent mstatIrq_;
	LycIrq lycIrq_;
	NextM0Time nextM0Time_;
	scoped_ptr<OsdElement> osdElement_;
	unsigned char statReg_;
	unsigned cgbColorCorrection;

	static void setDmgPalette(unsigned long[], unsigned long const[],
	                          unsigned);
	void refreshPalettes();
	void setDBuffer();
	void doCgbColorChange(unsigned char*, unsigned long*, unsigned,
		unsigned);
	void doMode2IrqEvent();
	void event();
	unsigned long m0TimeOfCurrentLine(unsigned long);
	unsigned long gbcToRgb32(unsigned const);
	bool cgbpAccessible(unsigned long);
	bool lycRegChangeStatTriggerBlockedByM0OrM1Irq(unsigned, unsigned long);
	bool lycRegChangeTriggersStatIrq(unsigned, unsigned, unsigned long);
	bool statChangeTriggersM0LycOrM1StatIrqCgb(unsigned, unsigned, bool, unsigned long);
	bool statChangeTriggersStatIrqCgb(unsigned, unsigned, unsigned long);
	bool statChangeTriggersStatIrqDmg(unsigned, unsigned long);
	bool statChangeTriggersStatIrq(unsigned, unsigned, unsigned long);
	void mode3CyclesChange();
	void doCgbBgColorChange(unsigned, unsigned, unsigned long);
	void doCgbSpColorChange(unsigned, unsigned, unsigned long);
};

}

#endif
