// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/

/// @file audio_marker.cpp
/// @see audio_marker.h
/// @ingroup audio_ui
///

#include "audio_marker.h"

#include "include/aegisub/context.h"
#include "options.h"
#include "pen.h"
#include "project.h"
#include "video_controller.h"

#include <algorithm>

class AudioMarkerKeyframe final : public AudioMarker {
	Pen *style;
	int position;
public:
	AudioMarkerKeyframe(Pen *style, int position) : style(style), position(position) { }
	int GetPosition() const override { return position; }
	FeetStyle GetFeet() const override { return Feet_None; }
	wxPen GetStyle() const override { return *style; }
	operator int() const { return position; }
};

AudioMarkerProviderKeyframes::AudioMarkerProviderKeyframes(agi::Context *c, const char *opt_name)
: p(c->project.get())
, keyframe_slot(p->AddKeyframesListener(&AudioMarkerProviderKeyframes::Update, this))
, timecode_slot(p->AddTimecodesListener(&AudioMarkerProviderKeyframes::Update, this))
, enabled_slot(OPT_SUB(opt_name, &AudioMarkerProviderKeyframes::Update, this))
, enabled_opt(OPT_GET(opt_name))
, style(std::make_unique<Pen>("Colour/Audio Display/Keyframe"))
{
	Update();
}

AudioMarkerProviderKeyframes::~AudioMarkerProviderKeyframes() { }

void AudioMarkerProviderKeyframes::Update() {
	auto const& keyframes = p->Keyframes();
	auto const& timecodes = p->Timecodes();

	if (keyframes.empty() || !timecodes.IsLoaded() || !enabled_opt->GetBool()) {
		if (!markers.empty()) {
			markers.clear();
			AnnounceMarkerMoved();
		}
		return;
	}

	markers.clear();
	markers.reserve(keyframes.size());
	for (int frame : keyframes)
		markers.emplace_back(style.get(), timecodes.TimeAtFrame(frame, agi::vfr::START));
	AnnounceMarkerMoved();
}

void AudioMarkerProviderKeyframes::GetMarkers(TimeRange const& range, AudioMarkerVector &out) const {
	// Find first and last keyframes inside the range
	auto a = lower_bound(markers.begin(), markers.end(), range.begin());
	auto b = upper_bound(markers.begin(), markers.end(), range.end());

	// Place pointers to the markers in the output vector
	for (; a != b; ++a)
		out.push_back(&*a);
}

class VideoPositionSnapPoint final : public AudioMarker {
	int position = -1;

public:
	void SetPosition(int new_pos) { position = new_pos; }

	int GetPosition() const override { return position; }
	FeetStyle GetFeet() const override { return Feet_None; }
	wxPen GetStyle() const override { return wxPen{}; }
};

class VideoPositionRange final : public AudioMarker {
	Pen style;
	int position = -1;
	int width = 0;

public:
	VideoPositionRange(bool previous) : style{previous ? "Colour/Audio Display/Previous Frame Range" : "Colour/Audio Display/Current Frame Range"} { }

	void SetPosition(int frame, int nextframe) {
		position = frame + 1;
		width = std::max(1, nextframe - frame);
	}

	int GetPosition() const override { return position; }
	int GetWidth() const override { return width; }
	FeetStyle GetFeet() const override { return Feet_None; }
	wxPen GetStyle() const override { return style; }
};

class VideoPositionMarker final : public AudioMarker {
	Pen style{"Colour/Audio Display/Play Cursor"};
	int position = -1;

public:
	void SetPosition(int new_pos) { position = new_pos; }

	int GetPosition() const override { return position; }
	FeetStyle GetFeet() const override { return Feet_None; }
	wxPen GetStyle() const override { return style; }
};

VideoPositionMarkerProvider::VideoPositionMarkerProvider(agi::Context *c)
: c(c)
, video_seek_slot(c->videoController->AddSeekListener(&VideoPositionMarkerProvider::Update, this))
, enable_opt_changed_slot(OPT_SUB("Audio/Display/Draw/Video Position", &VideoPositionMarkerProvider::OptChanged, this))
{
	OptChanged(*OPT_GET("Audio/Display/Draw/Video Position"));
}

VideoPositionMarkerProvider::~VideoPositionMarkerProvider() { }

void VideoPositionMarkerProvider::SetPositions(int frame_number) {
	auto vc = c->videoController.get();

	range1->SetPosition(vc->TimeAtFrame(frame_number - 1), vc->TimeAtFrame(frame_number));
	range2->SetPosition(vc->TimeAtFrame(frame_number), vc->TimeAtFrame(frame_number + 1));
	marker->SetPosition(vc->TimeAtFrame(frame_number));
	snap1->SetPosition(vc->TimeAtFrame(frame_number, agi::vfr::START));
	snap2->SetPosition(vc->TimeAtFrame(frame_number, agi::vfr::END));
}

void VideoPositionMarkerProvider::Update(int frame_number) {
	SetPositions(frame_number);
	AnnounceMarkerMoved();
}

void VideoPositionMarkerProvider::OptChanged(agi::OptionValue const& opt) {
	if (opt.GetBool()) {
		video_seek_slot.Unblock();
		range1 = std::make_unique<VideoPositionRange>(true);
		range2 = std::make_unique<VideoPositionRange>(false);
		marker = std::make_unique<VideoPositionMarker>();
		snap1 = std::make_unique<VideoPositionSnapPoint>();
		snap2 = std::make_unique<VideoPositionSnapPoint>();
		SetPositions(c->videoController->GetFrameN());
	}
	else {
		video_seek_slot.Block();
		range1.reset();
		range2.reset();
		marker.reset();
		snap1.reset();
		snap2.reset();
	}
}

void VideoPositionMarkerProvider::GetMarkers(const TimeRange &timerange, AudioMarkerVector &out) const {
	if (!c->project->VideoProvider())
		return;

	for (auto range : {range1.get(), range2.get()})
		if (range && timerange.overlaps({range->GetPosition(), range->GetPosition() + range->GetWidth() - 1}))
			out.push_back(range);

	if (marker && timerange.contains(marker->GetPosition()))
		out.push_back(marker.get());

}

void VideoPositionMarkerProvider::GetSnapMarkers(const TimeRange &timerange, AudioMarkerVector &out) const {
	if (!c->project->VideoProvider())
		return;

	for (auto snap : {snap1.get(), snap2.get()})
		if (snap && timerange.contains(snap->GetPosition()))
			out.push_back(snap);
}

SecondsMarkerProvider::SecondsMarkerProvider()
: pen(std::make_unique<Pen>("Colour/Audio Display/Seconds Line", 1, wxPENSTYLE_DOT))
, enabled(OPT_GET("Audio/Display/Draw/Seconds"))
, enabled_opt_changed(OPT_SUB("Audio/Display/Draw/Seconds", &SecondsMarkerProvider::EnabledOptChanged, this))
{
}

void SecondsMarkerProvider::EnabledOptChanged() {
	AnnounceMarkerMoved();
}

void SecondsMarkerProvider::GetMarkers(TimeRange const& range, AudioMarkerVector &out) const {
	if (!enabled->GetBool()) return;

	if ((range.length() + 999) / 1000 > (int)markers.size())
		markers.resize((range.length() + 999) / 1000, Marker(pen.get()));

	size_t i = 0;
	for (int time = ((range.begin() + 999) / 1000) * 1000; time < range.end(); time += 1000) {
		markers[i].position = time;
		out.push_back(&markers[i++]);
	}
}

wxPen SecondsMarkerProvider::Marker::GetStyle() const {
	return *style;
}
