#include "externals.hh"
#include "syncview.hh"
#include "globals.hh"
#include "window.hh"

#define IMGUI_DEFINE_MATH_OPERATORS
#include <imgui_internal.h>

using namespace ebcl;


/*= T_SyncViewImpl_ ============================================================*/
namespace {

struct T_SyncViewImpl_
{
	static constexpr float SeqHeaderHeight = 24;
	static constexpr float BarWidth = 40;

	const uint32_t ColFrame{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , .8 } ) };
	const uint32_t ColHeader{ ImGui::GetColorU32( ImVec4{ .5 , .5 , .5 , .8 } ) };
	const uint32_t ColHeaderText{ ImGui::GetColorU32( ImVec4{ 0 , 0 , 0 , 1 } ) };
	const uint32_t ColMain{ ImGui::GetColorU32( ImVec4{ .4 , .4 , .4 , .8 } ) };

	float zoomLevel{ 0.f };
	float startPos{ 0.f };
	bool followTime{ true };

	bool display( ) noexcept;
	void computeMetrics( float innerWidth ) noexcept;
	void sequencerWidget( ) noexcept;
	void sequencerHeader( ImRect const& bb ) noexcept;
	void sequencerBody( ImRect const& bb ) noexcept;

	// Computed metrics
	float barWidth;
	float cursorPos;
	uint32_t startBar;
	float startBarPos;
	float timePerBar;
};


bool T_SyncViewImpl_::display( ) noexcept
{
	using namespace ImGui;
	auto const& dspSize( GetIO( ).DisplaySize );
	auto& sync( Globals::Sync( ) );

	// Window set-up
	SetNextWindowSize( ImVec2( dspSize.x , 150 ) , ImGuiSetCond_Appearing );
	SetNextWindowPos( ImVec2( 0 , dspSize.y - 150 ) , ImGuiSetCond_Appearing );
	bool displayed{ true };
	Begin( "Sequencer" , &displayed , ImGuiWindowFlags_NoCollapse
				| ImGuiWindowFlags_NoScrollWithMouse );
	if ( !displayed ) {
		End( );
		return false;
	}

	PushItemWidth( 100 );
	SliderFloat( "##zoom" , &zoomLevel , 0 , 1 , "%.2f" );
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Zoom level" );
		EndTooltip( );
	}
	PopItemWidth( );
	SameLine( );
	Checkbox( "##follow" , &followTime );
	if ( IsItemHovered( ) ) {
		BeginTooltip( );
		Text( "Follow cursor" );
		EndTooltip( );
	}
	SameLine( );

	PushID( "playing" );
	if ( Button( sync.playing( ) ? "Stop" : "Play" ) ) {
		sync.playing( ) = !sync.playing( ) && !sync.finished( );
	}
	PopID( );

	const float d( sync.duration( ) );
	float tm( sync.time( ) );
	SameLine( );
	PushID( "sequencer" );
	PushItemWidth( -1 );
	if ( SliderFloat( "" , &tm , 0 , d , "%.1fs" ) ) {
		sync.setTime( tm );
		sync.playing( ) = sync.playing( ) && !sync.finished( );
	}
	PopItemWidth( );
	PopID( );

	PushItemWidth( -1 );
	sequencerWidget( );
	PopItemWidth( );

	End( );
	return true;
}

void T_SyncViewImpl_::sequencerWidget( ) noexcept
{
	using namespace ImGui;

	const auto width{ CalcItemWidth( ) };
	auto* const win( GetCurrentWindow( ) );
	const auto seqId{ win->GetID( "##sequencer" ) };

	const ImVec2 cPos( win->DC.CursorPos );
	const ImVec2 ws( GetWindowContentRegionMax( ) );

	auto& style( ImGui::GetStyle( ) );
	const ImRect bbHeader{ cPos , cPos + ImVec2( width , SeqHeaderHeight ) };
	const ImRect bbDisplay{ ImVec2{ cPos.x , bbHeader.Max.y } ,
		ImVec2{ cPos.x + width , GetWindowPos( ).y + ws.y - style.FramePadding.y } };
	const ImRect bbAll{ bbHeader.Min , bbDisplay.Max };

	ItemSize( bbAll , style.FramePadding.y );
	if ( !ItemAdd( bbAll , seqId ) ) {
		return;
	}
	computeMetrics( std::max( 0.f , width - 2.f ) );

	PushID( seqId );
	BeginGroup( );

	const auto hdrId{ win->GetID( "##header" ) };
	const auto dspId{ win->GetID( "##display" ) };

	if ( ItemAdd( bbHeader , hdrId ) ) {
		PushID( hdrId );
		sequencerHeader( bbHeader );
		PopID( );
	}
	if ( bbDisplay.Min.y < bbDisplay.Max.y && ItemAdd( bbDisplay , dspId ) ) {
		PushID( dspId );
		sequencerBody( bbDisplay );
		PopID( );
	}

	EndGroup( );
	PopID( );
}

void T_SyncViewImpl_::computeMetrics(
		const float innerWidth ) noexcept
{
	auto& sync( Globals::Sync( ) );
	const uint32_t units{ sync.durationUnits( ) };
	zoomLevel = ImSaturate( zoomLevel );
	const float zoom1Pixels{ std::max( units * BarWidth , innerWidth ) };
	const float totalPixels{ zoomLevel * ( zoom1Pixels - innerWidth ) + innerWidth };
	const uint32_t totalBars{ [=](){
			const float b{ std::max( std::min( totalPixels / BarWidth , float( units ) ) , 1.f ) };
			const float mod{ fmod( b , 1.f ) };
			return uint32_t( b + ( mod ? ( 1  - mod ) : 0 ) );
		}() };
	const float unitsPerBar{ float( units ) / totalBars };
	barWidth = totalPixels / totalBars;
	const float absCursorPos{ sync.time( ) * totalPixels / sync.duration( ) };
	if ( followTime ) {
		const float dispUnits{ innerWidth * units / totalPixels };
		const float uSize{ sync.durationUnitSize( ) };
		const float endPos{ std::min( startPos + dispUnits , float( units ) ) };
		startPos = endPos - dispUnits;
		const float spp{ startPos * totalPixels / units };
		const float epp{ endPos * totalPixels / units };
		if ( absCursorPos < spp || absCursorPos > epp ) {
			startPos = std::max( 0.f , sync.time( ) / uSize - unitsPerBar * .5f );
		}
	}
	const float unadjustedStartPixel{ totalPixels * startPos / units };
	if ( unadjustedStartPixel + innerWidth > totalPixels ) {
		startPos = std::max( 0.f , totalPixels - innerWidth );
	}
	const float startPixel{ totalPixels * startPos / units };
	startBar = [=](){
			const float b{ startPixel * totalBars / totalPixels };
			const float mod{ fmod( b , 1.f ) };
			return uint32_t( std::max( 0 , int32_t( b - ( mod ? mod : 1 ) ) ) );
		}();
	startBarPos = startBar * barWidth - startPixel;
	cursorPos = absCursorPos - startPixel;
	timePerBar = unitsPerBar * sync.durationUnitSize( );
	printf( "Z: %f; P@zl1: %f; Pt: %f; Bt: %d; U/B: %f; Bw: %f; Sp: %f; Sb: %d, SbP: %f\n" , zoomLevel , zoom1Pixels , totalPixels , totalBars , unitsPerBar , barWidth , startPixel , startBar , startBarPos );
	assert( startBarPos <= 0 );
	assert( totalPixels >= innerWidth );
}

void T_SyncViewImpl_::sequencerHeader(
		ImRect const& bb ) noexcept
{
	using namespace ImGui;
	auto* const dl( GetWindowDrawList( ) );
	const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } };

	dl->AddRectFilled( inner.Min , inner.Max , ColHeader );
	dl->AddRect( bb.Min , bb.Max , ColFrame );

	if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) {
		auto* const dl( GetWindowDrawList( ) );
		dl->AddLine( inner.Min + ImVec2{ cursorPos , 0 } ,
				ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } ,
				GetColorU32( ImVec4{ 1 , 1 , 1 , .5 } ) );
	}

	PushFont( Globals::Window( ).smallFont( ) );
	PushStyleColor( ImGuiCol_Text , ColHeaderText );
	auto pos{ startBarPos };
	auto bar{ startBar };
	const auto max{ bb.GetWidth( ) + barWidth - 2.f };
	char buffer[ 12 ];
	while ( pos < max ) {
		const ImVec2 taStart{ inner.Min + ImVec2{ pos - barWidth * .5f , 0 } };
		const ImVec2 taEnd{ taStart + ImVec2{ barWidth , inner.Max.y - inner.Min.y } };

		const float time{ bar * timePerBar };
		const float msf{ fmod( time , 1.f ) };
		const float sf{ fmod( time - msf , 60.f ) };
		snprintf( buffer , sizeof( buffer ) , "%02d:%02d.%03d" ,
				uint32_t( ( time - msf - sf ) / 60.f ) ,
				uint32_t( sf ) , uint32_t( msf * 1000.f ) );
		RenderTextClipped( taStart , taEnd , buffer , nullptr , nullptr ,
				ImVec2{ .5f , .2f + ( ( bar % 2 ) ? .6f : 0.f ) } , &inner );
		pos += barWidth;
		bar ++;
	}
	PopStyleColor( );
	PopFont( );
}

void T_SyncViewImpl_::sequencerBody(
		ImRect const& bb ) noexcept
{
	using namespace ImGui;
	auto* const dl( GetWindowDrawList( ) );
	const ImRect inner{ bb.Min + ImVec2{ 1 , 1 } , bb.Max - ImVec2{ 1 , 1 } };

	dl->AddRectFilled( inner.Min , inner.Max , ColMain );
	dl->AddRect( bb.Min , bb.Max , ColFrame );

	auto pos{ startBarPos };
	auto bar{ startBar };
	const auto max{ bb.GetWidth( ) + barWidth - 2.f };
	while ( pos < max ) {
		if ( pos >= 0 && pos < inner.GetWidth( ) ) {
			dl->AddLine( bb.Min + ImVec2{ pos , 0 } ,
					ImVec2{ inner.Min.x + pos , inner.Max.y } ,
					0xff000000 );
		}
		pos += barWidth;
		bar ++;
	}

	if ( cursorPos >= 0 && cursorPos <= inner.GetWidth( ) ) {
		auto* const dl( GetWindowDrawList( ) );
		dl->AddLine( inner.Min + ImVec2{ cursorPos , -1 } ,
				ImVec2{ inner.Min.x + cursorPos , inner.Max.y - 1 } ,
				0xffffffff );
	}
}


} // namespace
/*= T_SyncView =================================================================*/

T_SyncView::T_SyncView( ) noexcept
	: A_PrivateImplementation( new T_SyncViewImpl_( ) )
{ }

bool T_SyncView::display( ) noexcept
{
	return p< T_SyncViewImpl_ >( ).display( );
}