#include "layer_sounds.h"

#include <generated/client_data.h>

#include <game/editor/editor.h>
#include <game/editor/editor_actions.h>

static const float s_SourceVisualSize = 32.0f;

CLayerSounds::CLayerSounds(CEditorMap *pMap) :
	CLayer(pMap, LAYERTYPE_SOUNDS)
{
	m_aName[0] = '\0';
	m_Sound = -1;
}

CLayerSounds::CLayerSounds(const CLayerSounds &Other) :
	CLayer(Other)
{
	m_Sound = Other.m_Sound;
	m_vSources = Other.m_vSources;
}

CLayerSounds::~CLayerSounds() = default;

void CLayerSounds::Render(const CEditorMap *pRenderMap)
{
	// TODO: nice texture
	Graphics()->TextureClear();
	Graphics()->QuadsBegin();

	// draw falloff distance
	Graphics()->SetColor(0.6f, 0.8f, 1.0f, 0.4f);
	for(const auto &Source : m_vSources)
	{
		ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
		pRenderMap->m_EnvelopeEvaluator.EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Offset, 2);
		const vec2 Position = vec2(fx2f(Source.m_Position.x) + Offset.r, fx2f(Source.m_Position.y) + Offset.g);
		const float Falloff = Source.m_Falloff / 255.0f;

		switch(Source.m_Shape.m_Type)
		{
		case CSoundShape::SHAPE_CIRCLE:
		{
			Graphics()->DrawCircle(Position.x, Position.y, Source.m_Shape.m_Circle.m_Radius, 32);
			if(Falloff > 0.0f)
			{
				Graphics()->DrawCircle(Position.x, Position.y, Source.m_Shape.m_Circle.m_Radius * Falloff, 32);
			}
			break;
		}
		case CSoundShape::SHAPE_RECTANGLE:
		{
			const float Width = fx2f(Source.m_Shape.m_Rectangle.m_Width);
			const float Height = fx2f(Source.m_Shape.m_Rectangle.m_Height);
			Graphics()->DrawRectExt(Position.x - Width / 2, Position.y - Height / 2, Width, Height, 0.0f, IGraphics::CORNER_NONE);
			if(Falloff > 0.0f)
			{
				Graphics()->DrawRectExt(Position.x - Falloff * Width / 2, Position.y - Falloff * Height / 2, Width * Falloff, Height * Falloff, 0.0f, IGraphics::CORNER_NONE);
			}
			break;
		}
		}
	}

	Graphics()->QuadsEnd();

	// draw handles
	Graphics()->TextureSet(g_pData->m_aImages[IMAGE_AUDIO_SOURCE].m_Id);
	Graphics()->QuadsBegin();

	Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
	Graphics()->SelectSprite(SPRITE_AUDIO_SOURCE);
	for(const auto &Source : m_vSources)
	{
		ColorRGBA Offset = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
		pRenderMap->m_EnvelopeEvaluator.EnvelopeEval(Source.m_PosEnvOffset, Source.m_PosEnv, Offset, 2);
		const vec2 Position = vec2(fx2f(Source.m_Position.x) + Offset.r, fx2f(Source.m_Position.y) + Offset.g);
		Graphics()->DrawSprite(Position.x, Position.y, Editor()->MapView()->ScaleLength(s_SourceVisualSize));
	}

	Graphics()->QuadsEnd();
}

CSoundSource *CLayerSounds::NewSource(int x, int y)
{
	Map()->OnModify();

	m_vSources.emplace_back();
	CSoundSource *pSource = &m_vSources[m_vSources.size() - 1];

	pSource->m_Position.x = f2fx(x);
	pSource->m_Position.y = f2fx(y);

	pSource->m_Loop = 1;
	pSource->m_Pan = 1;
	pSource->m_TimeDelay = 0;

	pSource->m_PosEnv = -1;
	pSource->m_PosEnvOffset = 0;
	pSource->m_SoundEnv = -1;
	pSource->m_SoundEnvOffset = 0;

	pSource->m_Falloff = 80;

	pSource->m_Shape.m_Type = CSoundShape::SHAPE_CIRCLE;
	pSource->m_Shape.m_Circle.m_Radius = 1500;

	return pSource;
}

void CLayerSounds::BrushSelecting(CUIRect Rect)
{
	Rect.DrawOutline(ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
}

int CLayerSounds::BrushGrab(CLayerGroup *pBrush, CUIRect Rect)
{
	// create new layer
	std::shared_ptr<CLayerSounds> pGrabbed = std::make_shared<CLayerSounds>(pBrush->Map());
	pGrabbed->m_Sound = m_Sound;
	pBrush->AddLayer(pGrabbed);

	for(const auto &Source : m_vSources)
	{
		float SourceX = fx2f(Source.m_Position.x);
		float SourceY = fx2f(Source.m_Position.y);

		if(SourceX > Rect.x && SourceX < Rect.x + Rect.w && SourceY > Rect.y && SourceY < Rect.y + Rect.h)
		{
			CSoundSource NewSource = Source;
			NewSource.m_Position.x -= f2fx(Rect.x);
			NewSource.m_Position.y -= f2fx(Rect.y);

			pGrabbed->m_vSources.push_back(NewSource);
		}
	}

	return pGrabbed->m_vSources.empty() ? 0 : 1;
}

void CLayerSounds::BrushPlace(CLayer *pBrush, vec2 WorldPos)
{
	CLayerSounds *pSoundLayer = static_cast<CLayerSounds *>(pBrush);
	std::vector<CSoundSource> vAddedSources;
	for(const auto &Source : pSoundLayer->m_vSources)
	{
		CSoundSource NewSource = Source;
		NewSource.m_Position.x += f2fx(WorldPos.x);
		NewSource.m_Position.y += f2fx(WorldPos.y);

		m_vSources.push_back(NewSource);
		vAddedSources.push_back(NewSource);
	}
	Map()->m_EditorHistory.RecordAction(std::make_shared<CEditorActionSoundPlace>(Map(), Map()->m_SelectedGroup, Map()->m_vSelectedLayers[0], vAddedSources));
	Map()->OnModify();
}

CUi::EPopupMenuFunctionResult CLayerSounds::RenderProperties(CUIRect *pToolBox)
{
	CProperty aProps[] = {
		{"Sound", m_Sound, PROPTYPE_SOUND, -1, 0},
		{nullptr},
	};

	static int s_aIds[(int)ELayerSoundsProp::NUM_PROPS] = {0};
	int NewVal = 0;
	auto [State, Prop] = Editor()->DoPropertiesWithState<ELayerSoundsProp>(pToolBox, aProps, s_aIds, &NewVal);
	if(Prop != ELayerSoundsProp::NONE && (State == EEditState::END || State == EEditState::ONE_GO))
	{
		Map()->OnModify();
	}

	Map()->m_LayerSoundsPropTracker.Begin(this, Prop, State);

	if(Prop == ELayerSoundsProp::SOUND)
	{
		if(NewVal >= 0)
			m_Sound = NewVal % Map()->m_vpSounds.size();
		else
			m_Sound = -1;
	}

	Map()->m_LayerSoundsPropTracker.End(Prop, State);

	return CUi::POPUP_KEEP_OPEN;
}

bool CLayerSounds::IsEnvelopeUsed(int EnvelopeIndex) const
{
	return std::any_of(m_vSources.begin(), m_vSources.end(), [&](const auto &Source) {
		return Source.m_PosEnv == EnvelopeIndex || Source.m_SoundEnv == EnvelopeIndex;
	});
}

bool CLayerSounds::IsSoundUsed(int SoundIndex) const
{
	return m_Sound == SoundIndex;
}

void CLayerSounds::ModifySoundIndex(const FIndexModifyFunction &IndexModifyFunction)
{
	IndexModifyFunction(&m_Sound);
}

void CLayerSounds::ModifyEnvelopeIndex(const FIndexModifyFunction &IndexModifyFunction)
{
	for(auto &Source : m_vSources)
	{
		IndexModifyFunction(&Source.m_SoundEnv);
		IndexModifyFunction(&Source.m_PosEnv);
	}
}

std::shared_ptr<CLayer> CLayerSounds::Duplicate() const
{
	return std::make_shared<CLayerSounds>(*this);
}

const char *CLayerSounds::TypeName() const
{
	return "sounds";
}
