////////////////////////////////////////////////////////////////////////////////////////
//
// 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 "../NstMachine.hpp"
#include "../NstVideoRenderer.hpp"
#include "NstApiVideo.hpp"

namespace Nes
{
	namespace Core
	{
		namespace Video
		{
			Output::Locker Output::lockCallback;
			Output::Unlocker Output::unlockCallback;
		}
	}

	namespace Api
	{
		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Result Video::EnableUnlimSprites(bool state) throw()
		{
			if (emulator.tracker.IsLocked( true ))
				return RESULT_ERR_NOT_READY;

			if (!emulator.ppu.HasSpriteLimit() != state)
			{
				emulator.ppu.EnableSpriteLimit( !state );
				emulator.tracker.Resync( true );
				return RESULT_OK;
			}

			return RESULT_NOP;
		}

		bool Video::AreUnlimSpritesEnabled() const throw()
		{
			return !emulator.ppu.HasSpriteLimit();
		}

		int Video::GetBrightness() const throw()
		{
			return emulator.renderer.GetBrightness();
		}

		int Video::GetSaturation() const throw()
		{
			return emulator.renderer.GetSaturation();
		}

		int Video::GetContrast() const throw()
		{
			return emulator.renderer.GetContrast();
		}

		int Video::GetSharpness() const throw()
		{
			return emulator.renderer.GetSharpness();
		}

		int Video::GetColorResolution() const throw()
		{
			return emulator.renderer.GetColorResolution();
		}

		int Video::GetColorBleed() const throw()
		{
			return emulator.renderer.GetColorBleed();
		}

		int Video::GetColorArtifacts() const throw()
		{
			return emulator.renderer.GetColorArtifacts();
		}

		int Video::GetColorFringing() const throw()
		{
			return emulator.renderer.GetColorFringing();
		}

		int Video::GetHue() const throw()
		{
			return emulator.renderer.GetHue();
		}
		
		bool Video::GetBlend() const throw()
		{
			return emulator.renderer.GetBlend() != 0;
		}

		int Video::GetCornerRounding() const throw()
		{
			return emulator.renderer.GetCornerRounding();
		}

		Result Video::SetBrightness(int value) throw()
		{
			return emulator.renderer.SetBrightness( value );
		}

		Result Video::SetSaturation(int value) throw()
		{
			return emulator.renderer.SetSaturation( value );
		}

		Result Video::SetContrast(int value) throw()
		{
			return emulator.renderer.SetContrast( value );
		}

		Result Video::SetSharpness(int value) throw()
		{
			return emulator.renderer.SetSharpness( value );
		}

		Result Video::SetColorResolution(int value) throw()
		{
			return emulator.renderer.SetColorResolution( value );
		}

		Result Video::SetColorBleed(int value) throw()
		{
			return emulator.renderer.SetColorBleed( value );
		}

		Result Video::SetColorArtifacts(int value) throw()
		{
			return emulator.renderer.SetColorArtifacts( value );
		}

		Result Video::SetColorFringing(int value) throw()
		{
			return emulator.renderer.SetColorFringing( value );
		}

		Result Video::SetHue(int value) throw()
		{
			return emulator.renderer.SetHue( value );
		}
		
		Result Video::SetBlend(bool value) throw()
		{
			return emulator.renderer.SetBlend(value);
		}

		Result Video::SetCornerRounding(int value) throw()
		{
			return emulator.renderer.SetCornerRounding(value);
		}

		void Video::ClearFilterUpdateFlag() throw()
		{
			emulator.renderer.ClearFilterUpdateFlag();
		}

		void Video::EnableFieldMerging(bool state) throw()
		{
			emulator.renderer.EnableFieldMerging( state );
		}

		bool Video::IsFieldMergingEnabled() const throw()
		{
			return emulator.renderer.IsFieldMergingEnabled();
		}

		Result Video::SetRenderState(const RenderState& state) throw()
		{
			const Result result = emulator.renderer.SetState( state );

			if (NES_SUCCEEDED(result) && result != RESULT_NOP)
				emulator.UpdateColorMode();

			return result;
		}

		Result Video::GetRenderState(RenderState& state) const throw()
		{
			return emulator.renderer.GetState( state );
		}

		Result Video::Blit(Output& output) throw()
		{
			if (emulator.renderer.IsReady())
			{
				emulator.renderer.Blit( output, emulator.ppu.GetScreen(), emulator.ppu.GetBurstPhase() );
				return RESULT_OK;
			}

			return RESULT_ERR_NOT_READY;
		}

		Video::RenderState::RenderState() throw()
		:
		width  (0),
		height (0),
		filter (FILTER_NONE)
		{
			bits.count = 0;
			bits.mask.r = 0;
			bits.mask.g = 0;
			bits.mask.b = 0;
		}

		Video::Decoder::Decoder(DecoderPreset preset) throw()
		{
			switch (preset)
			{
				case DECODER_CONSUMER:

					axes[0].angle = 105;
					axes[0].gain  = 0.78f;
					axes[1].angle = 236;
					axes[1].gain  = 0.33f;
					axes[2].angle = 0;
					axes[2].gain  = 1.0f;
					boostYellow   = false;
					break;

				case DECODER_ALTERNATIVE:

					axes[0].angle = 105;
					axes[0].gain  = 0.570f;
					axes[1].angle = 251;
					axes[1].gain  = 0.351f;
					axes[2].angle = 15;
					axes[2].gain  = 1.015f;
					boostYellow   = true;
					break;

				default:

					axes[0].angle = 105;
					axes[0].gain  = 0.570f;
					axes[1].angle = 251;
					axes[1].gain  = 0.351f;
					axes[2].angle = 15;
					axes[2].gain  = 1.015f;
					boostYellow   = false;
					break;
			}
		}

		bool Video::Decoder::operator == (const Decoder& decoder) const throw()
		{
			for (uint i=0; i < NUM_AXES; ++i)
			{
				if (axes[i].angle != decoder.axes[i].angle || axes[i].gain != decoder.axes[i].gain)
					return false;
			}

			if (boostYellow != decoder.boostYellow)
				return false;

			return true;
		}

		bool Video::Decoder::operator != (const Decoder& decoder) const throw()
		{
			return !(*this == decoder);
		}

		Result Video::SetDecoder(const Decoder& decoder) throw()
		{
			return emulator.renderer.SetDecoder( decoder );
		}

		const Video::Decoder& Video::GetDecoder() const throw()
		{
			return emulator.renderer.GetDecoder();
		}

		Result Video::Palette::SetMode(const Mode mode) throw()
		{
			return emulator.UpdateColorMode
			(
				mode == MODE_RGB    ? Core::Machine::COLORMODE_RGB :
				mode == MODE_CUSTOM ? Core::Machine::COLORMODE_CUSTOM :
                                      Core::Machine::COLORMODE_YUV
			);
		}

		Video::Palette::Mode Video::Palette::GetMode() const throw()
		{
			if (emulator.renderer.GetPaletteType() == Core::Video::Renderer::PALETTE_YUV)
			{
				return MODE_YUV;
			}
			else if (emulator.renderer.GetPaletteType() == Core::Video::Renderer::PALETTE_CUSTOM)
			{
				return MODE_CUSTOM;
			}

			return MODE_RGB;
		}

		Video::Palette::Mode Video::Palette::GetDefaultMode() const throw()
		{
			NST_COMPILE_ASSERT( Core::Video::Renderer::NST_DEFAULT_PALETTE - Core::Video::Renderer::PALETTE_YUV == 0 );

			return MODE_YUV;
		}

		Result Video::Palette::SetCustom(Colors colors,CustomType type) throw()
		{
			return emulator.renderer.LoadCustomPalette( colors, type == EXT_PALETTE );
		}

		uint Video::Palette::GetCustom(uchar (*colors)[3],CustomType type) const throw()
		{
			return emulator.renderer.SaveCustomPalette( colors, type == EXT_PALETTE );
		}

		void Video::Palette::ResetCustom() throw()
		{
			return emulator.renderer.ResetCustomPalette();
		}

		Video::Palette::CustomType Video::Palette::GetCustomType() const throw()
		{
			return emulator.renderer.HasCustomPaletteEmphasis() ? EXT_PALETTE : STD_PALETTE;
		}

		Video::Palette::Colors Video::Palette::GetColors() const throw()
		{
			return emulator.renderer.GetPalette();
		}

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